调整字号

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 后面的圆括号。

let 定义变量的特点:

第一,let 不能重复定义同一个变量:

let foo = 1;
let foo = 2;

如果用 var,这样是可行的。用 let 则会报错。

第二,全局作用域中定义的 let 变量不会成为 window 的属性。

第三,用在循环语句中时,let 会有一些微妙的地方。先举一个例子,假设让你向数组 a 中填充 5 个函数,这 5 个函数分别返回从 1 到 5 这五个数字,你可能这样写:

var a = [];
for(var i = 1; i < 6; i ++) {
  a.push(function() {
    return i;
  });
}

如果你试一下结果,就发现上面这种写法错了,a 中的每个函数都返回 6。

因为 a 中其实有 5 个闭包,每个闭包都引用了外层函数中的变量 i,这 5 个函数其实引用的都是同一个 i。而 i 的值,在 for 循环结束时就是 6。为了改这个问题,在 ES5 中你得这样写:

var a = [];
for(var i = 1; i < 6; i ++) {
  (function(i) {
    a.push(function() {
      return i;
    });  
  })(i);
}

现在,5 个闭包引用的不再是同一个 i,换句话说有了各自的 i,所以结果正确了。如果换做用 let 的话,直接用第一种写法,结果就是正确的:

var a = [];
for(let i = 1; i < 6; i ++) {
  a.push(function() {
    return i;
  });
}

因为在循环语句中,每一次遍历,里面的闭包都会获得一个 let 变量的拷贝,也就是说每个闭包都有各自的 i 变量,不再是外层函数那个 i。与此同时,外层 i 的自增并不影响闭包里的 i。这就是 let 和循环语句结合时发生的事情。循环语句包括:for, for...infor...of

const

const 用来定义常量。例如:

const PI = 3.14;

constlet 一样也是只在局部作用域中有效。而且,也不能重复声明,不管之前是用 let 还是 const 声明过:

let foo = 1;
const bar = 2;
const foo = 2; //error
const bar = 3; //error

const 声明变量时,必须同时赋值:

const foo; //SyntaxError

之所以叫常量,也就是说只能被赋值一次,再次尝试赋值会发生错误。但并不是说不能更改常量的值,只是不能更改常量的指向而已。

const foo = 123;
foo = "abc"; //错误,不能将foo指向另一个值

const bar = {name: "Ethan"};
bar.name = "Jackie"; //这是可以的

如果 const 指向一个对象,你仍然可以修改对象的属性。所以不要认为 const 就是一个不可以更改的变量。

flight