ES6 快速上手:模块

es6-small

ES6 引入了原生的模块支持。

一个 ES6 模块就是一个 JS 文件,除了以下两个区别:

  • 会自动开启严格模式
  • 可以使用 importexport 关键字

ES6 模块还有一些特点:

  • 模块中声明的变量都是私有的,即使在最外层作用域声明
  • 在模块最外层,this 指向 undefined

模块中声明的变量,默认都是私有的。如果要暴露给其他模块使用,需要 export

// mod1.js
export function foo(a, b, c) {
  ...
}
export class Bar() {
  ...
}

export 可以用在 function, class, var, let, const 前。如果输出的是上例中的 functionclass 声明,则必须有函数名或类名,不能匿名。

如果想要一次性导出多个接口,也可以这样写:

export {foo, bar};

export 语句必须放在最外层,也就是,不能在语句块中 export

import 用来导入模块:

import {foo} from "mod1.js";

或者,导入多个接口:

import {foo, bar} from "mod1.js";

mod1.js会被提前导入并加载。注意导入以后生成的变量 foobar 都是常量,类似 const。所以你不能再声明一个同名变量,或者修改 foobar

你也可以将模块的所有接口整个导入:

import * as myModule from "mod1.js";

这样 mod1.js 的所有接口都被绑定在了 myModule 对象上。你可以在 myModule 上调用 myModule.foo()

不管被导入多少次,一个模块内的代码始终只执行一次:

import { foo } from "mod1.js";
import { bar } from "mod1.js";

重命名接口

导入时,可以给接口重命名:

import {foo as bar} from "mod1.js";

导出时也可以:

export {
  foo as foo1,
  bar as bar2
};

重命名以后,只能使用新的名字。

default

每个 ES6 模块可以有一个默认导出,使用的是 default 关键字:

export default function(a, b) {
  ...
}

export default 关键字后可以跟随任何值。使用 default 时,函数可以是匿名的。

你也可以指定某一个变量为 default

let myObject = {
  foo() {
    return "foo...";
  },
  bar: 66
};
export {myObject as default};

要将一个模块的默认导出 import 进来:

import myModule from "mod1.js";

注意 myModule 没有了花括号。此时在 myModule 上就可以调用 foobar 了。

这种设计是为了与已存在的 CommonJS 和 AMD 模块相兼容。以 jQuery 为例:

import $ from "jquery";

一个模块只能有一个默认导出。但也可以同时存在非默认的导出:

// mod2.js
export let a = 123;
export default function(b, c) {
  ...
}

可以这样把两者一同导入进来:

import foo, { bar } from "mod2.js";

注意非默认的导出总是有一对花括号。默认导出也可以放进括号里,下面语句是同样的作用:

import { default as foo, bar } from "mod2.js";

阅读全文 »

ES6 快速上手:生成器

es6-small

生成器又叫“Generators”,是 ES6 的一个重要的新特性。

生成器是一种特殊的函数,下面就是一个生成器:

function* demo(name) {
  yield "Hi " + name + "!";
  yield "Welcome!";
  yield "Goodbye!";
}

调用以后,它返回一个迭代器。里面的 yield 关键字,用来指定每次调用 .next() 所应该返回的值。调用一下:

var iterator = demo("William");

iterator.next(); //{ value: "Hi William!", done: false }
iterator.next(); //{ value: "Welcome!", done: false }
iterator.next(); //{ value: "Goodbye!", done: false }
iterator.next(); //{ value: undefined, done: true }

可以看到,生成器函数的一个特点,就是每次遇到 yield 关键字,函数的执行都会暂停,yield 后面的表达式的值被返回。再调用 .next() ,函数就往下执行,到下一个 yield

生成器也有表达式语法:

let myGenerator = function *(arg) {
  ...
};

生成器的语法要点就两部分:function*yield* 两边允许空格的存在,只要是在 function 和函数名之间就可以。 yield 后面可以跟任意的表达式。

生成器的主要用处是两个:作为迭代器,异步编程。

作为迭代器使用

生成器作为一种函数,它可以成为对象的属性。回想一下《for…of 和迭代器》中所讲的可遍历对象,只要对象拥有一个能返回迭代器的 [Symbol.iterator]() 方法,就能成为一个可遍历对象。既然生成器正好就返回一个迭代器,所以,生成器就是 [Symbol.iterator]() 方法的最佳选择。比如:

var o = {
  items: [1, 2, 3],
  [Symbol.iterator]: function *() {
    for(let i = 0; i < this.items.length; i++) {
      yield this.items[i];
    }
  }
};

用 ES6 新的简写语法,还可以这样写:

var o = {
  items: [1, 2, 3],
  *[Symbol.iterator]() {
    for(let i = 0; i < this.items.length; i++) {
      yield this.items[i];
    }
  }
};

生成器里也可以使用 return 来提前结束遍历:

function *foo() {
    yield 1;
    return;
    yield 2;
    yield 3;
}
let iterator = foo();

iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: undefined, done: true }

迭代器的 .next() 方法还可以传参,比如

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2; // 4 + 2
    yield second + 3; // 5 + 3
}

let iterator = createIterator();

iterator.next(); // { value: 1, done: false }
iterator.next(4); // { value: 6, done: false }"
iterator.next(5); // { value: 8, done: false }"
iterator.next(); // { value: undefined, done: true }

传给 .next() 的参数,会变为上一个 yield 的返回值。因此,第一次调用 .next() 时不要传参数,传了也获取不到。

解析一下上例的执行过程:第一次调用 .next() 时,生成器内部的代码开始执行。第一句是一个赋值语句 let first = yield 1; 自右向左计算,所以先 yield 1,暂停了。第二次调用 .next() 并传入了 4,函数继续执行左边部分 let first = ...,因为 4 成为 yield 的返回值,所以 let first = 4。第二行同理,在 yield first + 2 也就是 yield 6 暂停。第三次调用 .next(5)以后,let second = 5,最后 yield second + 3 的结果是 8。

生成器另一大用处是在异步编程中,因为生成器有暂停的功能。执行一个异步函数时,可以 yield 暂停,等到异步任务完成,再调用 .next() 继续。

yield*

生成器还可以委托,说白了就是,在生成器中调用另一个生成器,例如:

阅读全文 »

ES6 快速上手:for…of 和迭代器

es6-small

for ... of 是 ES6 新增的循环语句,可以遍历一个集合(比如数组),用来代替普通的 for 循环或 forEach。一个例子:

var myArray = ["a", "b", "c"];
for (var value of myArray) {
  console.log(value);
}

那用 for ... of 有什么好处呢?显然用 forEach 也能遍历数组元素,但是 forEach 的缺陷是无法响应 break, continue, return 语句。所以,不能自由跳出,只能循环到底。另外有一个小提示,在大多数浏览器中 forEach 遍历都比 for 循环要慢。虽然 forEach 更简洁,但是从执行效率考虑应当使用 for

然后你会想到用 for...in,但 for...in 也存在一个问题,就是 for...in 给出的索引都是字符串,比如:

var myArray = ["a", "b", "c"];
for (var value in myArray) {
  console.log(value); //"0", "1", "2"
}

如果你直接拿这些索引来做运算,就会出错,比如 value + 10,会得到 "010" 而不是 10。而且,for...in 不光遍历数组内的元素,如果你往数组上添加了自定义的属性,也会被遍历出来。还有一点,最糟糕的情况下,for...in 还可能会不按 0、1、2……的顺序进行遍历。

for...of 可以克服以上这些缺点。它既支持 break, continue, return,同时,索引的类型和原来类型保持一致,对于数组,索引就是 Number 类型。

for...of 也可以遍历类数组对象,比如 NodeList 类型。

还能用来遍历字符串,会把它当作是字符序列:

for (var char of "(⊙ˍ⊙)") {
  alert(char);
}

还支持遍历 MapSet 对象。因为 Map 对象都是键值对,所以,需要使用解构赋值:

for (var [name, age] of persons) {
  console.log(name + "'s age is: " + age);
}

但是 for...of 并不支持遍历普通对象。你可以用 for...in 代替,或者借助 Object.keys

for ( var key of Object.keys(obj) ) {
  console.log(key + ": " + obj[key]);
}

除了上面提到的类型,在其他对象上调用 for...of 会抛出错误,包括nullundefined。为什么?因为 for...of 需要有迭代器的配合。

迭代器(iterators)

“迭代器”:简单来说,迭代器的核心就是一个 .next() 方法,每次调用 .next(),应该返回一个类似下面的对象:

{
  value: 100,
  done: false
}

假设 a[1, 2, 3] 的迭代器,那么每次调用迭代器的 .next(),应该是这样的结果:

a.next(); // { value: 1, done: false }
a.next(); // { value: 2, done: false }
a.next(); // { value: 3, done: false }
a.next(); // { value: undefined, done: true }

可以看出,每次返回的 value 就是从数组中抽出的一个元素,一直遍历到最后,done 表示状态,当它变为 true 时,遍历就结束了。

迭代器的作用就是用来遍历集合,再对里面的数据进行处理,就像我们经常用的 forEach 一样。迭代器的好搭档是 for...of

可遍历对象(iterables)

前面说了,只有在数组、NodeListSetMap、字符串上可以使用for...of,因为它们都是可遍历对象,称为“iterables”。在普通对象上使用 for...of 会抛出错误。

什么是可遍历对象?简单来说,只要对象上拥有一个 [Symbol.iterator]() 方法,该方法返回一个迭代器。那这个对象就是可遍历的,可以用 for...of 进行遍历。

[Symbol.iterator]() 所返回的迭代器又叫默认迭代器(default iterator)。上面所讲的那些类型,都内置了默认迭代器,所以能在 for...of 中使用。

阅读全文 »

ES6 快速上手:Symbol 类型

es6-small

ES5 的时候有六种类型:

  • undefined
  • null
  • Boolean
  • Number
  • String
  • Object

现在,ES6 增加了一种新类型:Symbol。下面是一个创建 symbol 的例子:

var mySymbol = Symbol();
typeof mySymbol; //"symbol"

调用 Symbol 函数就可以创建一个 symbol。因为 symbol 是一个原始类型,并不是对象,所以 new Symbol() 会报错。

symbol 的用处就是作为对象的键。用 symbol 作为对象的键可以避免传统的属性名冲突。比如,用上例的 mySymbol 作为键:

obj[mySymbol] = "ok!"; 

创建 symbol 时,可以传入一个字符串描述:

var sym = Symbol("hi");

即使你每次都传入相同的字符串,返回的 symbol 也互不相等。正因为有这个特性,所以 Symbol 类型可以很好地解决属性名冲突的问题。

什么是冲突呢?当多人合作编码的时候,经常会出现你往对象上加了一个某某属性(比如 $),他人正好也想到了这个名称,当你们同时用了这个名称作为属性,代码之间就会发生冲突,互相覆盖。而用 symbol,即使都用了相同的描述,也不是同一个 symbol。

再列出 Symbol 类型的一些特点:第一,以 symbol 为键的属性,不能用 obj.prop 形式访问,只能用方括号。

第二,for...in 循环、Object.keysObject.getOwnPropertyNames 在遍历一个对象时,都会忽略 symbol 键。

如果你想查看对象上有哪些 symbol 键,可以使用 ES6 的新 API:Object.getOwnPropertySymbols(),返回一个数组,里面都是对象所包含的 symbol。

如果想查看所有属性,用另一个 API:Reflect.ownKeys(obj),会同时返回字符串键和 symbol 键。

第三,in 操作符可以正常工作,比如上例,mySymbol in obj会返回 true。执行 delete obj[mySymbol] 也可以删除这个属性。

第四,symbol 类型不能被自动转换为字符串:

var sym = Symbol("hello");
alert("my symbol is " + sym); // TypeError

可以通过 String(sym) 或调用 sym.toString()

共享 symbol

由于每次调用 Symbol("hello") 返回的 symbol 都是不同的,但有时会有共享同一个 symbol 的需要,所以 ES6 提供了一个全局的 symbol 注册表。

如果你想要共享一个 symbol,用 Symbol.for() 来代替 Symbol()

let sym = Symbol.for("hello");

调用 Symbol.for() 时,如果传入相同的字符串,那么返回的就是同一个 symbol。如果该 symbol 还不存在,则会创建一个 symbol 返回。

Symbol.for() 创建的 symbol 上,还可以调用 Symbol.keyFor() 来查看它的描述。

let sym = Symbol.for("hello");
let sym2 = Symbol.for("hello");
Symbol.keyFor(sym); //"hello"
Symbol.keyFor(sym2); //"hello"

let sym3 = Symbol("hello");
Symbol.keyFor(sym3); // undefined

你可以看到在一个普通 Symbol() 创建的 symbol 上调用 Symbol.keyFor() 会发生什么。

内置 symbol

ES6 内置了一些 symbol,它们都作为 Symbol 函数的属性存在,比如后面关于迭代器文章里要提到的 Symbol.iterator。但是,调用 Object.getOwnPropertySymbols() 时,这些内置 symbol 不被计算在内。

ES6 快速上手:类

es6-small

ES6 终于引入了类。下面是一个类的例子:

class Person {
  constructor(age, country) {
    this._age = age;
    this._country = country;
  }

  get age() {
    return this._age;
  }

  get country() {
    return this._country;
  }

  toString() {
    return `${this.age} ${this.country}`;
  }
}

在上例中,constructor 代表类的构造函数。constructor 里面定义的属性,都是实例属性。定义类时,可以没有 constructor

agecountrytoString 都是原型属性和方法,相当于定义在了 Person.prototype 上。其中 agecountry 是只读的。

如你所见,属性和属性之间,不再需要分隔符了。你可以继续用逗号,或者用分号也可以。

类的语法

第一种是类的声明:

class MyClass {
 ...
}

语法要点:

  • 注意名字后面不能有括号。
  • 类的声明不会被提升到作用域顶部,所以必须先定义后使用。
  • classlet 一样,不能重复声明。

第二种是类表达式:

var MyClass = class {
  constructor() {
    //一个实例属性
    this.name = "demo";
  }
  method() {
    //一个原型方法
  }
}

这种语法下,class 后面也可以再加一个标识符:

var MyClass = class SameClass {
  ...
}

此时 MyClassSameClass 是一样的,可以互相替换。

创建实例还和以前一样:

var instance = new MyClass();

类的一些特点

如果你用 typeof MyClass,会发现其实它还是一个 function

如果没有 new,直接调用 MyClass 会抛出错误。

声明类的代码会全部在严格模式下执行,相当于自动加了 "use strict";

class 里面定义的所有方法都是不可枚举的。也就是说,不能通过 for...in 遍历到,Object.keys 也获取不到。

类也可以当作函数的参数:

function createObj(MyClass) {
  return new MyClass();
}

let obj = createObj(class {
  foo() {
    console.log("hello");
  }
});
obj.foo(); //"hello"

类也可以立即执行,只要在后面多加个括号:

let singleton = new class {
  constructor(foo) {
    this.foo = foo;
  }
  bar() {
    ...
  }
}("hello");

可以用来创建单例(singleton)。

访问器属性

类也可以添加访问器属性,像对象一样。访问器属性就是一对 getter/setter,语法如下:

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

  get age() {
    return this.age;
  }
  set age(value) {
    this.age = value;
  }
}

如果省略 setter,属性就是只读的。

静态成员

class Person {
  static walk(a, b) {
     ...
  }
}

静态方法的调用:Person.walk(),其实等同于在 Person 上创建了 walk() 方法。

静态成员默认也是不可枚举的。

阅读全文 »

ES6 快速上手:解构赋值

es6-small

这个特性又叫做“destructuring”。其语法的一般形式为:

[ variable1, variable2, ..., variableN ] = array;

这是数组的解构赋值。后文还有对象的解构赋值。

假设有数组 value = [1, 2, 3],要把三个值分别赋给三个变量 a, b, c,在ES5中要这样写:

var a = value[0];
var b = value[1];
var c = value[0];

ES6 中使用解构赋值:

[a, b, c] = value;

如果需要同时声明变量:

let [a, b, c] = value;

当然换成 var, const 也同样可以。结合“rest”使用:

var [head, ...tail] = [1, 2, 3, 4];
console.log(tail); // [2, 3, 4]

也可以嵌套:

var value = [1, 2, [3, 4, 5]];
var [a, b, [c, d]] = value; //a,b,c,d分别为1,2,3,4

在指定位置省略:

var value = [1, 2, 3, 4, 5];
var [a, , c, , e] = value; //1,3,5

如果出现越界访问的情况,值是 undefined

var value = [1];
var [a, b] = value; //1,undefined

这时候可以指定默认值:

var value = [1];
var [a = 100, b = 2] = value; //1,2

注意的是默认值只对 undefined 值起作用:

var value = [null, null];
var [a = 1, b = 2] = value; //a,b还是null

用处:可以用来交换两个变量的值(没有比这更简单的了):

[el1, el2] = [el2, el1];

或者,从正则表达式的结果里取出分组:

var [, firstName, lastName] = "John Doe".match(/^(w+) (w+)$/);

对象的解构赋值:

var personA = { name: "Paul" };
var personB = { name: "Lily" };

var { name: nameA } = personA;
var { name: nameB } = personB;

console.log(nameA); // "Paul"
console.log(nameB); // "Lily"

对象的 name 属性分别赋给了 nameAnameB。如果用下面这种简洁的写法:

var personA = { name: "Paul" };
var { name } = personA;

那么会把 name 属性赋给同名的变量 name

深层嵌套的对象:

var person = {
  name: {
    firstName: "John", 
    lastName: "Doe"
  }
};
var {name: {firstName, lastName}} = person; //得到两个变量firstName,lastName,注意name未定义

对象里嵌套数组:

var person = {
  dateOfBirth: [1, 1, 1980]
};
var {dateOfBirth: [day, month, year]} = person; //得到三个变量day, month, year

发生嵌套时,一般后面跟了冒号的变量都是未定义的,如上例的 dateOfBirth。这点要注意。

如果要解构的属性不存在,得到的是 undefined。对象解构赋值也可以使用默认值:

var {firstName = "John", lastName: userLastName = "Doe"} = {};

默认值对于 null 同样不起作用。

注意对于下面的写法,会抛出错误:

{ blowUp } = { blowUp: 10 }; // SyntaxError 

因为JS中任何以 { 开始的语句都被解析为一个块语句。解决方案是将整个表达式用一对小括号包裹:

({ blowUp } = { blowUp: 10 });

用处:第一,函数参数的解构:

function myFunc(options) {
  if (options.a) ...
  if (options.b) ...
}

这种语句经常出现在我们的代码中。用 ES6 就看上去更简洁:

function myFunc( {a, b} ) {
  if (a) ...
  if (b) ...
}

阅读全文 »

ES6 快速上手:模板字符串

es6-small

ES6 引入了一种新型的字符串字面量语法,称为模板字符串(template strings)。使用反撇号(`)来代替普通字符串的引号。下面是一个例子:

let greeting = `Hello Jackie`!

有什么用呢?模板字符串为 JavaScript 提供了简单的插值功能(string interpolation)。例:

let name = "Jackie";
let greeting = `Hello ${name}`!

${name} 称为占位符(placeholder)。模板占位符可以是任意 JavaScript 表达式,比如函数调用、算数运算等,都可以作为占位符使用,甚至可以嵌套另一个模板字符串。

如果占位符计算出的值不是字符串,那么会调用 toString 方法。

如果要书写反撇号(`),或者 $,或者 {,都需要转义(加反斜杠)。

与普通字符串的一大区别是,模板字符串可以多行书写:

$("#warning").html(`
  <h1>你好!</h1>
  <p>我叫${name}。这是一个多行
  模板字符串的
  示例。</p>
`);

所有的空格、新行、缩进,都会原样输出在生成的字符串中。

但是,模板字符串并不会自动转义 HTML 字符(比如 < > 等),而且,不支持条件语句、遍历,比其他的模板库还是弱了些。为了突破这些限制,ES6 提供了标签模板(tagged templates)。只需要在起始的反撇号前加上一个额外的标签,如下面的 htmlspecial

var message = htmlspecial`<p>${htmlStr}</p>`;

上面的代码等效于

var message = htmlspecial(templateData, htmlStr);

templateData 是一个不可变数组,存储着模板所有的字符串部分,也就是除去占位符以后剩下的部分,比如上例中 ${htmlStr} 将模板分割为两部分,所以 templateData 数组内就是两个元素,形如 ["<p>", "</p>"]。同时这个数组是不能更改的,相当于用了 Object.freeze

htmlspecial 的一种实现(用来将 < > 等特殊字符转义为实体引用):

function htmlspecial(templateData) {
  var s = templateData[0];
  for (var i = 1; i < arguments.length; i++) {
    var arg = String(arguments[i]);

    // 转义HTML特殊字符。
    s += arg.replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");

    // 拼接
    s += templateData[i];
  }
  return s;
}

函数返回的值就会当作模板字符串的输出。

ES6 快速上手:箭头函数

es6-small

ES6 添加了新的函数语法:箭头函数。语法像下面这样:

([param, param, ...]) => {
  函数体
}

几个例子:

() => { ... } //无参数
x => { ... } //一个参数
(x, y) => { ... } //多个参数
(x, y) => {
  if(x > 1) {
    ...
  }
}

可以这样用:

let foo = x => { return x + 10; }

等同于下面:

function foo(x) {
  return x + 10;
}

如果只有一个 return 语句的话,还可以简写成:

let foo = x => x + 10; // x + 10的值会被返回

当然,如果简写的话你需要注意,如果返回的是一个对象,比如一个空对象:

let foo = x => {}; //报错

这时需要外加一层小括号:

let foo = x => ({}); //OK
//或者老老实实写成:
let foo = x => {
  return {};
};

立即执行的函数(IIFE)在 ES5 中是这样:

(function(name) {
  var greetings = "Hello " + name;
  return greetings;
})("Ethan");

同样可以换成箭头函数:

(name => {
  let greeting = "Hello " + name;
  return greeting;
})("Ethan");

箭头函数有几个特性,首先,是 this 值固定。this 值取决于箭头函数定义时的上下文,与如何调用无关:

var person = {
  name: "Bob",
  sayHello: () => "Hello " + this.name;
};

var hello = person.sayHello;
hello(); //"Hello Bob"

并且,this 的值是无法改变的。一旦创建,this 值就已绑定。

第二,箭头函数不能用作构造函数,当和 new 一起使用时会报错。

第三,箭头函数没有自己的 arguments 对象。所以,在箭头函数内访问 arguments,这时访问的其实是外层函数的 arguments 对象。你可以使用 ES6 的新特性来代替 arguments

let foo = (...args) => {
  console.log(args); //和arguments一样
};

最后,箭头函数和普通函数一样,也会在脚本执行前被提升到作用域顶部。

ES6 快速上手:初探

es6-small

最新版的 JavaScript 标准已经发布了,正式的名称叫做 ECMAScript 2015,也就是我们常说的 ES6,或者还有个更老的名字叫 ES Harmony。因为制订标准的是 ECMA 组织所以叫做 ECMAScript 。 JavaScript 其实就是对 ECMAScript 的实现,或者说是 ECMAScript 的一种方言。

所以,前端领域的新知识真是源源不断,层出不穷……时时刻刻都不能停止学习啊。准备写成一个系列,这些内容都是我自己整理的。

第一篇还是先讲一些比较简短的特性吧。

参数默认值

在 ES6 中,函数参数可以设置默认值了。上例子:

function increment(n = 1) {
  return n + 100;
}

在以前我们需要这样:

function increment(n) {
  n = n || 1; // foo的默认值设为1
  return n + 100;
}

如果没有传入 n,那么 n 的值就是 1。注意,如果你传入 undefinedn 的值还会是 1

可以只给某一个参数设置默认值:

function sum(a, b = 2, c) {
  return a + b + c;
}

可以执行函数来得到默认值:

function foo( bar = getBar() ) {
  return foo + 10;
}

剩余参数

这个特性也叫做“rest”,操作符是三个点 ...,用来包含剩余参数。例子:

function foo(x, ...y) {
  console.log(y);
}
foo(1, "hello", true); // ["hello", true]

可以看到,除了 x 之外剩下的所有参数都被放入数组中传给了 y,这就是“rest”的作用。

y 总是一个数组,里面包含了剩余的所有参数。上例中,如果只传入一个参数,那么 y 就是空数组。

注意这个特性只能用在最后一个参数上,下面是错误的:

// 错误
function foo(x, ...y, z) {
  console.log(y);
}

数组拆分

又叫“spread”,也是三个点,但是用在数组之前,例如 ...[a, b, c]

作用是可以把数组拆分成 a, b, c 的形式。拆分后,有两个用处:第一,是给函数传参:

function sum(a, b, c) {
  return a + b + c;
}
sum(...[1, 2, 3]); // 等于sum(1,2,3)

数组被拆成了三个参数传入了 sum 函数。在 ES5 中,我们就需要这样写:

sum.apply(undefined, [1, 2, 3]);

第二个用处,是用在数组字面量里:

var foo = ['b', 'c'];
var combined = ['a', ...foo]; // 等于['a', 'b', 'c']

对象字面量扩展

ES6 还对对象字面量进行了一些扩展,比如一些简写,下面是简写前的代码:

var obj = {
  foo: foo,
  toString: function() {
    return this.foo;
  }
}

使用 ES6 可以简写成为:

var obj = {
  // 两个foo简写成一个foo
  foo,
  // 省略function关键字
  toString() {
    return this.foo;
  }
}

还有一些扩展:

var obj = {
  //设置原型对象
  __proto__: theProtoObj,

  //属性名可以动态计算
  [ "prop_" + 42 ]: 42
}

动态计算的属性名,使用中括号,里面可以放入任何表达式,包括调用函数。

let

JavaScript 一直都没有局部作用域,这是跟其他语言相比一个奇葩的地方。比如,想要在条件语句中声明变量:

if(isBlack) {
  var black = true;
} else {
  var white = true;
}

你希望在 isBlack=true 时才定义变量 black,否则就定义一个变量 white。结果在 JavaScript 中并不是这样,两个变量都会被定义。只不过其中一个是 undefined

现在 ES6 中引入了 let 以后就不一样了。let 的作用和 var 一样,都是用来定义变量,但一个重要区别是,let 定义的变量只在局部作用域中有效。比如下面:

if(a === 1) {
  let b = 2;
}

for(let c = 0; c < 3; c++) {
  ...
}

function foo() {
  let d = 4;
}

b, c, d 变量都只在相应的代码块中有效,在其他地方,这些变量都是未声明的。如果访问,就抛出 ReferenceError。也就是变量的有效范围仅限于花括号内。当然了,也包括 for 后面的圆括号。
阅读全文 »

flight