文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

毫无疑问,JavaScript生态系统变化很快。不仅是新工具和框架引入、发展速度迅速, 语言本身随着es2015的引入 (又名ES6)也发生了很大变化. 这也可以理解为什么许多文章都抱怨最近学习现代JavaScript开发是多么困难。

作者的其他相关文章

在这篇文章中我会向你介绍现代JavaScript. 我们将研究一下语言的最新进展,并概述目前用于编写前端web应用程序的工具和技术。 如果你刚开始学习语言,或者好几年都没碰过这么语言而你想前知道JavaScript的最新发展情况,那么这篇文章很适合你.

第 1 段(可获 1.95 积分)

Node.js 简介

Node.js 是一个允许是使用JavaScript编写服务端程序的运行时库. 当应用程序的前端和后端都是用同一种语言编写时就会拥有一个全栈的JavaScript应用程序. 虽然本文侧重于前端开发, Node.js仍然起着重要的作用.

Node.js的的出现对JavaScript生态系统有着重大的影响,它引入了NPM包管理和推广了CommonJS模块格式. 开发人员开始构建更具创新性的工具和方法使浏览器, 服务器, 和本地应用程序之间的界线变得越来越模糊.

第 2 段(可获 1.3 积分)

JavaScript ES2015+

2015年, 第六版 ECMAScript———定义了JavaScript语言的规范———以 ES2015 (通常被称为 ES6)的名字发布了. 这个新版对这门语言做了大幅度的增加使其可更容易和更可行地创建更加强大的Web应用程序. 但改进并没有止于 ES2015; 每年都会发布一个新版本.

声明变量

JavaScript现在有两个附加的方法来声明变量: let 和 const.

let继承于 var –—— 尽管 var 仍然可用, let 限制了变量的作用域范围 (而不是函数作用域) 他们在块内部内部声明,这样可以减少犯错的余地:

第 3 段(可获 1.33 积分)
// ES5
for (var i = 1; i < 5; i++) {
  console.log(i);
}
// <-- 输出 1 to 4
console.log(i);
// <-- 5 (变量 i 在循环外任然可用)

// ES2015
for (let j = 1; j < 5; j++) {
  console.log(j);
}
console.log(j);
// <-- '引用错误: j 未定义'

使用 const 可以定义一个不能被重新赋值变量. 像字符串和数字等原始值, 这就像定义了一个常数, 一旦它已被声明你就不能再改变其值.

const name = 'Bill';
name = 'Steve';
// <-- '类型错误: 不能修改常量.'

// Gotcha
const person = { name: 'Bill' };
person.name = 'Steve';
// person.name 变为 Steve. 
// 不能将一个对象申明为常量, JavaScript 不报错.
第 4 段(可获 0.5 积分)

箭头函数

箭头函数提供了更简单的方法来声明匿名函数 (Lambda表达式), 放弃了 function 关键字和 return 关键字当函数体只有一行代码时. 这可以让你以一种更优美的方式编写代码.

// ES5
var add = function(a, b) {
  return a + b;
}

// ES2015
const add = (a, b) => a + b;

箭头函数的另一个重要特性是它从定义它的上下文继承了 this :

function Person(){
  this.age = 0;

  // ES5
  setInterval(function() {
    this.age++; // |this| refers to the global object
  }, 1000);

  // ES2015
  setInterval(() => {
    this.age++; // |this| properly refers to the person object
  }, 1000);
}

var p = new Person();
第 5 段(可获 0.78 积分)

改进的类定义语法

如果你是面向对象编程的爱好者, 您可能希望在基于原型的基础之上,将类的语法添加到语言上. 虽然只是语法上的写法但它为开发人员提供了一个更简洁的语法来模仿经典的面向对象的原型.

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

承诺 / 异步函数

JavaScript的异步性质长期以来是一个挑战; 任何较重要的应用程序在处理Ajax请求之类的事情时,都用掉进回调地狱的风险.

第 6 段(可获 1.03 积分)

幸运的是, ES2015 增加 promises的本地支持. Promises 表示现在不存在但稍后会有的值, 使异步函数调用的方式更易于管理,避免了进入层层嵌套的回调.

ES2017 (将于今年发布) 介绍了async functions(有时称早异步/等待这方面做出改进 , 可以使你像处理同步代码一样处理异步代码).

async function doAsyncOp () {
  const result = await asynchronousOperation();     
  console.log(result);
  return result;
};
第 7 段(可获 0.9 积分)

模块

ES2015 中增加了另一个突出的特点是原生模块格式, 使模块的定义和使用成为语言的一部分. 加载模块以前只能以第三方库的形式提供. 我们将在下一节更深入地讨论模块.

还有其他特性,我们不会在这里谈论, 但我们已经涵盖了一些主要的差异,这些差异是你学习JavaScript最新进展时应该注意到的. 你可以再 在Babel site 上的 学习 ES2015 界面看到一个样例列表, 这里你你可以看到到到目前为止哪些特性是可用的. 这些特性包括模板字符串, 迭代器, 构造器,新的数据结构 例如 Map 和Set, 等等.

第 8 段(可获 1.58 积分)

如果想更深入地了解 ES2015,请移步高级课程:深入ES2015

代码审查(Linting)

Linter 是这样一种工具,它会分析你的代码并将之与一套规则进行比较,检查语法是否有错误、代码格式是否符合规范,代码是否按照较好的实践编写。建议每个人都使用 Linter,尤其是对刚起步的开发者来说,它非常有用。在编辑器/IDE中正确配置之后你就可以以编写代码时得到实时反馈,确保你不会写出语法错误的代码,还能让你(通过提示)学到新的语言特性。

你可以看看 ESLint,它是最流行的工具之一,而且支持 ES2015+。

第 9 段(可获 1.28 积分)

模块化编写代码

现代 Web 应用程序拥有数千(甚至数十万)行代码。如果没有一个组件化的机制,在这样的规模下几乎无法工作。组件化机制允许针对性的编写相对独立的代码,它们可以根据需要进行复用。这就是模块要干的事情。

CommonJS 模块

一些模块格式已经存在了数年时间,其中最流行的就是 CommonJS。它是 Node.js 默认使用的模块格式,也可以通过模块打包工具的处理之后在客户端代码中使用,稍后我们会对此进行详述。

 

第 10 段(可获 1.28 积分)

它利用一个模块对象从 JavaScript 文件中导出功能,再在需要的地方通过 require() 函数导入这些功能。

// lib/math.js
function sum(x, y) {
  return x + y;
}

const pi = 3.141593

module.exports = {
  sum: sum,
  pi: pi
};


// app.js
const math = require("lib/math");

console.log("2π = " + math.sum(math.pi, math.pi));

ES2015 模块

ES2015 在语言层面引入了一种定义和使用组件的方法,这在以前只能通过第三方库实现。你可以根据需要使用独立的文件来包含功能,只导出要用于应用的那些部分。

 

第 11 段(可获 0.86 积分)

注意: 目前浏览器内建对 ES2015 模块的支持还在开发中,因此你如果要使用这些特性需要一些额外的工具。

这里有个例子:

// lib/math.js

export function sum(x, y) {
  return x + y;
}
export let pi = 3.141593;

我们有一个模块导出了一个函数和一个变量。我们可以在另外的文件中包含这个文件,并使用这些暴露的函数:

// app.js

import * as math from "lib/math";

console.log("2π = " + math.sum(math.pi, math.pi));

或者我们也可以指定或者导入我们所需的,而不是全部:

// otherApp.js

import {sum, pi} from "lib/math";

console.log("2π = " + sum(pi, pi));
第 12 段(可获 0.81 积分)

这些示例来自 Babel 网站。要想深入了解,可以去看看 理解ES6模块

包管理

其它语言早已经有自己的包仓储和管理工具,方便搜索和安装第三方库或组件。Node.js 也有自己的包管理工具和仓库,npm。虽然还存在一些其它的包管理工具,不过 npm 实际上已经成为 JavaScript 包管理工具,而且据称其包注册量是世界之最。

你可以在 npm 仓库中搜寻第三方模块,然后用在项目中,只需要使用一个 npm install <package> 命令,非常简单。下载的包放在本地的 node_modules 目录中,这里存放着所有包及它们所依赖的包。

第 13 段(可获 1.51 积分)

你可以在 package.json 文件中把下载的包注册为自己项目的依赖包,注册信息与项目或模块信息放在一起(它本身就是一个可以发布的 npm 包)。

你可以为开发环境或生产环境定义不同的依赖。生产环境的依赖是要让这个包正常工作必须的,而开发环境的依赖则只在开发这个包的时候需要用到。

Example package.json file

{
  "name": "demo",
  "version": "1.0.0",
  "description": "Demo package.json",
  "main": "main.js",
  "dependencies": {
    "mkdirp": "^0.5.1",
    "underscore": "^1.8.3"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Sitepoint",
  "license": "ISC"
}
第 14 段(可获 0.91 积分)

构建工具

在开发现代 Web 应用的过程中,我们在开发时写的代码往往与用于生产环境的代码不同。我们使用较新版本的 JavaScript 来编写代码,而某些浏览器可能并不支持这些版本。我们大量使用下载到 node_modules 中的第三方包,而它们还都有自己的依赖项,我们可能还会使用静态分析工具或压缩工具等。构建工具的存在可以帮助我们处理所的有这些情况,产生更高效同时还能被浏览器理解的东西。

模块构建

第 15 段(可获 1.2 积分)

使用 ES2015/CommonJS 模块编写干净、可复用的代码时,我们需要想些办法来加载这些模块 (至少在浏览器原生支持 ES2015 模块加载之前)。在 HTML 中包含一堆 script 标记并不是一个理想的方案,因为这样很快就会让应用程序变得笨重,所有那些 HTTP 请求都会影响性能。

我们可以在 ES2015 中在需要某个模块的时候用 import 语句来包含它(或在 CommonJS 中使用 require),然后使用一个打包工具将所有东西打包成一个或几个文件(包)。然后我们会把这些文件上传到服务器,并包含在 HTML 中。它包含了所有导入的模块及其依赖模块。

 

第 16 段(可获 1.54 积分)

当前很几个流行的工具可供选择,最流行的有 WebpackBrowserify 和 Rollup.js。你可以根据需要选择其一。

如果你想了解更多关于模块打包及其如何融入应用开发蓝图的知识,我推荐你去阅读理解 JavaScript 模块:打包 & 转译

转译

虽然存在对 ES2015 支持得不错的现代浏览器,但你的用户可能还在使用老旧的浏览器或设备,只支持部分新特性,甚至完全不支持。

为了让我们使用的 JavaScript 新特性能正常工作,我们需要把我们编写的代码翻译成等价的早期版本(通常是 ES5)。做这个事情的标准工具是 Babel,它能编译你的代码,使其能在大多数浏览器中运行。使用个方法之后,我们不再需要等待供应商实现新功能就能开始使用新的 JS 特性。

 

第 17 段(可获 1.9 积分)

有一些特性并不是语法翻译就能实现的。Babel 包含一个 Polyfill 来模拟一些复杂特性的需要的组件,比如 Promise。

构建系统 & 任务运行工具

模块打包和转译只是我们项目的构建过程中的其中两个。除此之外还有一些其它事情,比如压缩(减少文件尺寸)、使用工具进行分析等,还可能存在与 JavaScript 无关的任务,比如优化图片或对 CSS/HTML 进行预处理。

管理任务会变成一件费劲的事情,所以我们需要想办法来自动处理这些事情,只需要简单的命令就可以进行所有这些事情。最流行的两个工具是 Grunt.js 和 Gulp.js,它会能分组组织你的任务,并按一定顺序执行。

 

第 18 段(可获 1.75 积分)

比如,你可以使用 gulp build 命令来选择代码检查、Babel 转译并使用 Browserify 打包。这样就不再需要记住按顺序执行的三个不同命令及其相关的参数,只需要执行一个命令就会自动处理完整个过程。

无论何时你发现在项目中需要手工组织处理过程,都可以考虑使用任务运行工具来自动完成。

相关阅读:了解 Gulp.js

应用程序架构

Web 应用与网站有着不同的需求。加载一个博客页面当然会与加载像 Google Docs 这样的应用不同。你的应用应该尽可能接近桌面应用,否则用户体验就不会太好。

 

第 19 段(可获 1.58 积分)

旧式 Web 应用通常会从 Web 服务器发送多个页面出来,当需要大量动态特性的时候,就根据用户操作,通过 Ajax 加载内容出来替换掉相应的 HTML。虽然这朝着动态 Web 迈进了一大步,但仍然有其复杂之处。为每个用户操作发送一段 HTML 甚至整个页面看起来会浪费资源,尤其是从用户的角度来看,会浪费时间。用户体验仍然与桌面应用有一定的差距。

为了改进这些东西,我们创造了新的方法来构建 Web 应用,通过这些方法我们向用户展示出客户端和服务器端的通信。虽然这大大增加了应用中的 JavaScript 代码,但结果不错,现在的应用更接近本地应用了。在我们点击按钮的时候不再需要加载页面或者大量的等待时间。

 

第 20 段(可获 1.95 积分)

单页应用 (SPA)

Web 应用中最常见的高阶架构称为 SPA,即单页应用。SPA 是 JavaScript 的一大亮点,它包含了一个应用程序正确运行的所有内容。客户端会直接渲染 UI,所以不需要重新加载。只一需要改变的是应用程序内部的数据,这些数据通常通过 Ajax 从远程 API 获取,或者来自其它异步方法通信。

这种方法也有缺点,即应用程序第一次加载的时间过长。不过一旦加载完成,视图(页面)之间的切换就会快很多,因为在客户端和服务器之间只需要发送纯粹的数据。

 

第 21 段(可获 1.48 积分)

通用 / 同构应用

虽然 SPA 有着优秀的用户体验,但于由需求不同,它仍然可能不是最好的解决方案。特别是更快的初次响应时间或者需要让搜索引擎添加索引的情况下。

 

最近有一种解决这类问题的方法,称为同构(或通用) JavaScript 应用程序。在这个架构中,多数代码可以同时运行于服务端和客户端。你可以选择由服务端渲染来提高初次加载页面的速度,之后在用户交互的过程中由客户端接管渲染。因为页面初次是在服务端渲染,搜索引擎可以很好的对其建立索引。

 

第 22 段(可获 1.48 积分)

部署

使用现代的 JavaScript 应用,你写的代码会与在生产环境部署的代码不同,你只需要部署构建结果。其工作流程可能根据项目的大小、开发人员数量和使用的工具/库不同而各不相同。

比如,你正在进行一个简单的项目,每次准备部署的时候你都可以运行构建再将构建结果文件上传到 Web 服务器。注意你只需要上传构建(转译、模块打包、压缩等)的结果,它可能只是一个包含整个应用及其依赖的 .js 文件。

 

第 23 段(可获 1.6 积分)

这个目录结构可能像这样:

├── dist
│   ├── app.js
│   └── index.html
├── node_modules
├── src
│   ├── lib
│   │   ├── login.js
│   │   └── user.js
│   ├── app.js
│   └── index.html
├── gulpfile.js
├── package.json
└── README

应用程序所有源文件都在 src 目录,它们按照 ES2015 编写,导入由 npm 安装的包或者放在 lib 目录下你自己的模块。

然后你可以运行 Gulp 来构建项目。Gulp 的配置写在 gulpfile.js 中。构建过程将所有模块打包到一个文件(包含 npm 安装的那些),将 ES2015+ 转译为 ES5,对其结果进行压缩,等等。然后你可以配置输出到 dist 目录。

 

第 24 段(可获 1.04 积分)

注意:如果你有一些文件并不需要处理,你可以简单的把它们从 src 拷贝到 dist 目录。这可以通过在构建系统中配置一个任务来完成。

现在你可以把 dist 目录中的文件上传到 Web 服务器,不用担心其它文件,那些文件只在开发时有用。

团队开发

如果你和其它开发者一起工作,很可能你们会使用同一个代码库来保存项目,比如 GitHub。这样的话,你可以在提交之前先运行构建过程,并将结果与其它文件一起保存在 Git 库中,以便它可以被下载到生产服务器上。

 

第 25 段(可获 1.51 积分)

不过,多个开发者协同工作的情况下,把构建结果文件保存到库很容易导致问题,你可能更希望构建结构中的各项都保持整洁。幸好还有更好的方法可以处理这个问题:你可以部署像 JenkinsTravis CICircleCI 这样的服务。在处理过程中,它们可以在每次有代码提交并推送到代码库后自动构建你的项目。开发者们只需要关注推送代码变化,而不需要每次都去构建项目,因此代码库会保持整洁(没有自动生成的文件)。当然最后你仍然会有用于部署的已经构建出来的文件。

 

第 26 段(可获 1.38 积分)

小结

如果你在最近几年并未从事 Web 开发,要从简单的 Web 页面过滤到现代的 JavaScript 应用程序可能会显得有些困难,但是我希望本文能成为你新的起点。我已提到的每个主题都尽可能的用更深入一些的文章来讲述,所以你可以进一步探索。

记住,某些时间,在寻找所有可用的选择之后,一切似乎都是颠覆性而混乱的。只要记住 KISS 原则,只使用你认为需要的,而不是所有可用的。在每天回顾的时候,最重要的是解决问题,而不是保持在前沿。

在学习 JavaScript 开发的过程中你得到了什么样的经验?是否有这里我没有提及但你希望看到的东西?我非常愿意看到你的评论!

 

第 27 段(可获 1.83 积分)

文章评论