词语连线
让我们先来玩一个游戏,根据 ES5,ES2015, ES2016,ES6,ES2017 这几个词的关系,进行连线。
如果你有点“蒙圈”,不妨先来看看 Javascript 的发展历史。
- 2011 年,
标准化组织 ECMA发布ECMAScript 5.1 版 - 发布后,开始着手制定
ECMAScript 6.0 版本,即ES6 - 为了让标准的升级成为常规流程,决定在每年的 6 月份正式发布一次,作为当年的正式版本:
- 2015 年 6 月发布
《ECMAScript 2015 标准》(简称ES2015),它是 ES6 的第一个版本: - 2016 年 6 月发布
《ECMAScript 2016 标准》(简称ES2016),进行了小幅修订 - 2017 年 6 月发布
ES2017标准
- 2015 年 6 月发布
那么上面游戏的答案是:
ES6 与 ES5
作为一个在 JS 高程《JavaScript 高级程序设计》 指引下走进前端的“老”同学,我常常被 ES5 和 ES6 的写法“困扰”,担心写出来的代码不够“新”。时间与经验的沉淀促进语言的“进化”,那么观察一个特性/语法的变迁,或许是一件有意思的事情。
今天我们就来看看,ES6 中一些语法如果用 ES5 该如何实现。
let
ES5 只有全局作用域和函数作用域,ES6 通过 let 语法增加了块级作用域,增强了一些场景下的合理性。
ES6
for (let i = 0; i < 3; i++) {
//
}
console.log(i); // ReferenceError: i is not defined
ES5
(function() {
for (var i = 0; i < 3; i++) {
//
}
})();
console.log(i); // ReferenceError: i is not defined
const
ES5 中是没有常量的,通常用大写的命名规范来“约定”常量,却不能保证不被更改。ES6 中新增了 const。
ES6
const a = '123';
try {
a = '456';
} catch(err) {
// err: TypeError: Assignment to constant variable
console.log(a); // '123'
}
ES5
Object.defineProperties(window, {
a: {
value: '123',
writable: false
}
});
a = '456';
console.log(a); // '123'
Class
与其他新增的语法特性不同,ES6 中的 Class 实质上是 JavaScript 现有的基于原型的继承的语法糖。
声明一个动物类
ES6
class Animal {
constructor(type, name) {
this.type = type;
this.name = name;
}
static eat() {
}
speak() {
console.log(this.name + ' say:');
}
}
ES5
function Animal(type, name) {
this.type = type;
this.name = name;
}
Animal.eat = function () {
}
Animal.prototype.speak = function () {
console.log(this.name + ' say');
}
实例化一个小明同学
var a = new Animal('people', 'xiaoming');
a.speak();
声明猫猫类(继承动物类)
ES6
class Cat extends Animal {
constructor(name) {
super('cat', name);
}
speak() {
super.speak();
console.log('meow');
}
}
ES5
function Cat(name) {
Animal.call(this, 'cat', name);
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
// 静态方法
Cat.eat = Animal.eat;
Cat.prototype.speak = function () {
Animal.prototype.speak.call(this);
console.log('meow');
};
实例化一只名为“卷卷”的喵,然后喵一声
var juanjuan = new Cat('juanjuan');
juanjuan.speak(); // juanjuan say: meow
对比一下,ES6 更优美吧~
Module
ES6
import/export
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
// 引用
import { pi, sum } from 'lib/math';
console.log('2π = ' + math.sum(math.pi, math.pi));
ES5
require/exports
// lib/math.js
exports.sum = sum;
function sum(x, y) {
return x + y;
}
var pi = exports.pi = 3.141593;
// 引用
var math = require('lib/math');
console.log('2π = ' + math.sum(math.pi, math.pi));
但是,上面这两种写法并不“等价”。这要从 javascript 的模块体系说起。
javascript 一直没有模块体系,于是社区制定了一些模块加载方案,比较通用的是 CommonJS 和 AMD 两种, 前者用于服务器,后者用于浏览器。(为了兼容还引入了UMD)。
CommonJS 和 AMD 模块都是在运行时确定模块关系。即整体加载模块(模块下的所有方法),生成一个对象,然后在从这个对象上读取方法。这种加载称为“运行时加载”。
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
然而 ES6 模块不是对象。
// ES6模块
import { stat, exists, readFile } from 'fs';
上面代码的实质是从 fs 模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
关于模块这部分,其实值得展开研究。(我还没整太明白)
结尾
语法的背后,是语言本身的“特性”,他们的出现是为了解决一些场景的问题。追本溯源是一个很好的学习方式。我们,下期再见~