ES6中必须掌握的一些特性

const,let关键字就不说了,是个前端都知道。下面来讲讲其他一些特性,如Classes、箭头函数、解构赋值、剩余参数、生成器、模板字符串。

一、Classes

构造方法

constructor([arguments]) { … }

一个class中只能有一个指定的“constructor”(构造)方法。如果 class 定义的时候包含多个构造方法,程序将会抛出 SyntaxError 错误。

在构造方法中可以使用 super 关键字来调用父类的构造方法。

如果没有显式指定构造方法,则会添加默认的constructor方法。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Square extends Polygon {
constructor(length) {
// 在这里调用父类的"length",赋值给矩形的"width"和"height"。
super(length, length);
// 注意:子类必须在constructor方法中调用super方法,否则新建实例时会报错。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
set area(value) {
this.area = value;
}
}

extends
extends关键词被用在类声明或者类表达式上,以创建一个类的子类。

1
class ChildClass extends ParentClass { ... }

例子,继承Date:

1
2
3
4
5
6
7
8
9
10
class myDate extends Date {
constructor() {|
super();
}
getFormattedDate() {
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
return this.getDate() + "-" + months[this.getMonth()] + "-" + this.getFullYear();
}
}

static
static关键字为一个类定义一个静态方法。

1
static methodName() { ... }

静态方法可以直接在类上调用,并且不能在类的实例上调用。静态方法经常被用来创建工具函数。

为了在同一个类的另一个静态方法中调用一个静态方法,你可以使用 this 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
class StaticMethodCall {
static staticMethod() {
return 'Static method has been called';
}
static anotherStaticMethod() {
return this.staticMethod() + ' from another static method';
}
}
StaticMethodCall.staticMethod();
// 'Static method has been called'
StaticMethodCall.anotherStaticMethod();
// 'Static method has been called from another static method'

静态方法不能直接在非静态方法中使用 this 关键字来访问。你需要使用类名来调用它们:CLASSNAME.STATIC_METHOD_NAME() 或者将其作为构造函数的属性来调用该方法: this.constructor.STATIC_METHOD_NAME()。

1
2
3
4
5
6
7
8
9
10
11
12
13
class StaticMethodCall {
constructor() {
console.log(StaticMethodCall.staticMethod());
// 'static method has been called.'
console.log(this.constructor.staticMethod());
// 'static method has been called.'
}
static staticMethod() {
return 'static method has been called.';
}
}

下面的例子说明了这几点:

  1. 静态方法是怎么在一个类中被实现的。
  2. 拥有静态成员的类是可以被子类化的 。
  3. 静态方法可以怎样被调用以及怎样不可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Tripple {
static tripple(n) {
n = n || 1;
return n * 3;
}
}
class BiggerTripple extends Tripple {
static tripple(n) {
return super.tripple(n) * super.tripple(n);
}
}
console.log(Tripple.tripple()); // 3
console.log(Tripple.tripple(6)); // 18
console.log(BiggerTripple.tripple(3)); // 81
var tp = new Tripple();
console.log(BiggerTripple.tripple(3)); // 81(不会受父类被实例化的影响)
console.log(tp.tripple()); // 'tp.tripple is not a function'.

二、箭头函数

箭头函数表达式的语法比函数表达式短,并且不绑定自己的 this,arguments,super或 new.target。此外,箭头函数总是匿名的。这些函数表达式最适合非方法函数,它们不能用作构造函数。

基础语法:

(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
// equivalent to: => { return expression; }

// 如果只有一个参数,圆括号是可选的:
(singleParam) => { statements }
singleParam => { statements }

// 无参数的函数需要使用圆括号:
() => { statements }

高级语法:

// 返回对象字面量时应当用圆括号将其包起来:
params => ({foo: bar})

// 支持 Rest parameters 和 default parameters:
(param1, param2, …rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => { statements }

// 参数中支持解构赋值
var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6

更短的函数
在一些函数式编程模式里,更短的函数书写方式很受欢迎。试比较:

1
2
3
4
5
6
7
8
9
10
var a = [
"Hydrogen",
"Helium",
"Lithium",
"Beryl­lium"
];
var a2 = a.map(function(s){ return s.length });
var a3 = a.map( s => s.length );

不绑定this

1
2
3
4
5
6
7
8
9
10
11
function Person() {
// 构造函数 Person() 定义的 `this` 就是新实例对象自己
this.age = 0;
setInterval(function growUp() {
// 在非严格模式下,growUp() 函数定义了其内部的 `this`
// 为全局对象, 不同于构造函数Person()的定义的 `this`
this.age++;
}, 1000);
}
var p = new Person();
1
2
3
4
5
6
7
8
9
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| 正确地指向了 person 对象
}, 1000);
}
var p = new Person();

与 strict mode 的关系
考虑到 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略。

1
2
var f = () => {'use strict'; return this};
f() === window; // 或全局对象

严格模式的其他规则依然不变。

使用 call 或 apply 调用
由于 this 已经在词法层面完成了绑定,通过 call() 或 apply() 方法调用一个函数时,只是传入了参数而已,对 this 并没有什么影响:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var adder = {
base : 1,
add : function(a) {
var f = v => v + this.base;
return f(a);
},
addThruCall: function(a) {
var f = v => v + this.base;
var b = {
base : 2
};
return f.call(b, a);
}
};
console.log(adder.add(1)); // 输出 2
console.log(adder.addThruCall(1)); // 仍然输出 2(而不是3 ——译者注)

不绑定 arguments
箭头函数不会在其内部暴露出 arguments 对象: arguments.length, arguments[0], arguments[1] 等等,都不会指向箭头函数的 arguments,而是指向了箭头函数所在作用域的一个名为 arguments 的值(如果有的话,否则,就是 undefined)。

1
2
3
4
5
6
7
8
9
10
11
var arguments = 42;
var arr = () => arguments;
arr(); // 42
function foo() {
var f = () => arguments[0]; // foo's implicit arguments binding
return f(2);
}
foo(1); // 1

箭头函数没有自己的 arguments 对象,不过在大多数情形下,rest参数可以给出一个解决方案:

1
2
3
4
5
6
function foo() {
var f = (...args) => args[0];
return f(2);
}
foo(1); // 2

像方法一样使用箭头函数

如上所述, 箭头函数表达式对没有方法名的函数是最合适的.让我们看看当我们试着把它们作为方法时发生了什么。

1
2
3
4
5
6
7
8
9
10
'use strict';
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log( this.i, this)
}
}
obj.b(); // prints undefined, Window
obj.c(); // prints 10, Object {...}

箭头函数没有定义this绑定。

1
2
3
4
5
6
7
8
9
10
11
'use strict';
var obj = {
a: 10
};
Object.defineProperty(obj, "b", {
get: () => {
console.log(this.a, typeof this.a, this);
return this.a+10; // represents global object 'Window', therefore 'this.a' returns 'undefined'
}
});

使用 new 操作符
箭头函数不能用作构造器,和 new 一起用就会抛出错误。

使用 yield 关键字
yield 关键字通常不能在箭头函数中使用(except when permitted within functions further nested within it)。因此,箭头函数不能用作Generator函数。

返回对象字面量
要记得用圆括号括起来

1
var func = () => ({ foo: 1 });

换行
箭头函数在参数和箭头之间不能换行哦。

解析顺序

1
2
3
4
5
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {}; // SyntaxError: invalid arrow-function arguments
callback = callback || (() => {}); // ok

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 一个空箭头函数,返回 undefined
let empty = () => {};
(() => "foobar")() // 返回 "foobar"
var simple = a => a > 15 ? 15 : a;
simple(16); // 15
simple(10); // 10
let max = (a, b) => a > b ? a : b;
// Easy array filtering, mapping, ...
var arr = [5, 6, 13, 0, 1, 18, 23];
var sum = arr.reduce((a, b) => a + b); // 66
var even = arr.filter(v => v % 2 == 0); // [6, 0, 18]
var double = arr.map(v => v * 2); // [10, 12, 26, 0, 2, 36, 46]
// 更多简明的promise链
promise.then(a => {
// ...
}).then(b => {
// ...
});

三、解构赋值

解构赋值(destructuring assignment)语法是一个Javascript表达式,它使得从数组或者对象中提取数据赋值给不同的变量成为可能。

语法

var a, b, rest;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

[a, b, …rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]

({a, b} = {a:1, b:2});
console.log(a); // 1
console.log(b); // 2

// ES7 - not implemented in Firefox 47a01
({a, b, …rest} = {a:1, b:2, c:3, d:4});

解构数组

基本变量赋值

1
2
3
4
5
6
var foo = ["one", "two", "three"];
var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"

声明赋值分离

1
2
3
4
5
var a, b;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

默认值
为了防止从数组中取出一个值为undefined的对象,可以为这个对象设置默认值。

1
2
3
4
5
var a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

交换变量
在一个解构表达式中可以交换两个变量的值。没有解构赋值的情况下,交换两个变量需要一个临时变量。

1
2
3
4
5
6
var a = 1;
var b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

解析一个从函数返回的数组

1
2
3
4
5
6
7
8
function f() {
return [1, 2];
}
var a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2

忽略某些返回值

1
2
3
4
5
6
7
function f() {
return [1, 2, 3];
}
var [a, , b] = f();
console.log(a); // 1
console.log(b); // 3

将剩余数组赋值给一个变量

1
2
3
var [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]

用正则表达式匹配提取值

1
2
3
4
5
6
7
8
var url = "https://developer.mozilla.org/en-US/Web/JavaScript";
var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
console.log(parsedURL); // ["https://developer.mozilla.org/en-US/Web/JavaScript", "https", "developer.mozilla.org", "en-US/Web/JavaScript"]
var [, protocol, fullhost, fullpath] = parsedURL;
console.log(protocol); // "https"

解构对象
简单示例

1
2
3
4
5
6
7
8
9
10
11
var o = {p: 42, q: true};
var {p, q} = o;
console.log(p); // 42
console.log(q); // true
// 用新变量名赋值
var {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true

无声明赋值

1
2
var a, b;
({a, b} = {a: 1, b: 2});

给新的变量名赋值

1
2
3
4
5
var o = {p: 42, q: true};
var {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true

默认值

1
2
3
4
var {a = 10, b = 5} = {a: 3};
console.log(a); // 3
console.log(b); // 5

函数参数默认值

1
2
3
4
5
6
7
8
9
10
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {})
{
console.log(size, cords, radius);
// do some chart drawing
}
drawES6Chart({
cords: { x: 18, y: 30 },
radius: 30
});

加载模块
解构赋值可以帮助加载一个模块的特定子集

1
const { Loader, main } = require('toolkit/loader');

解构嵌套对象和数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var metadata = {
title: "Scratchpad",
translations: [
{
locale: "de",
localization_tags: [ ],
last_edit: "2014-04-14T08:43:37",
url: "/de/docs/Tools/Scratchpad",
title: "JavaScript-Umgebung"
}
],
url: "/en-US/docs/Tools/Scratchpad"
};
var { title: englishTitle, translations: [{ title: localeTitle }] } = metadata;
console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"

For of 迭代和解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var people = [
{
name: "Mike Smith",
family: {
mother: "Jane Smith",
father: "Harry Smith",
sister: "Samantha Smith"
},
age: 35
},
{
name: "Tom Jones",
family: {
mother: "Norah Jones",
father: "Richard Jones",
brother: "Howard Jones"
},
age: 25
}
];
for (var {name: n, family: { father: f } } of people) {
console.log("Name: " + n + ", Father: " + f);
}
// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"

从作为函数实参的对象中提取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function userId({id}) {
return id;
}
function whois({displayName: displayName, fullName: {firstName: name}}){
console.log(displayName + " is " + name);
}
var user = {
id: 42,
displayName: "jdoe",
fullName: {
firstName: "John",
lastName: "Doe"
}
};
console.log("userId: " + userId(user)); // "userId: 42"
whois(user); // "jdoe is John"

对象属性计算名和解构

1
2
3
4
let key = "z";
let { [key]: foo } = { z: "bar" };
console.log(foo); // "bar"

四、剩余参数

语法:

function(a, b, …theArgs) {
// …
}

如果一个函数的最后一个形参是以 … 为前缀的,则在函数被调用时,该形参会成为一个数组,数组中的元素都是传递给该函数的多出来的实参的值。

在上例中,theArgs 会包含传递给函数的从第三个实参开始到最后所有的实参 (第一个实参映射到 a, 第二个实参映射到 b)。

剩余参数和 arguments 对象的区别

  1. 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
  2. arguments 对象不是一个真实的数组,而剩余参数是真实的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach,pop。
  3. arguments 对象对象还有一些附加的属性 (比如callee属性)。

使用剩余参数可以避免将arguments转为数组的麻烦

1
2
3
4
5
6
7
8
9
10
// 下面的代码模拟了剩余数组
function f(a, b){
var args = Array.prototype.slice.call(arguments, f.length);
// ...
}
// 现在代码可以简化为这样了
function(a, b, ...args) {
}

示例

1
2
3
4
5
6
7
function fun1(...theArgs) {
alert(theArgs.length);
}
fun1(); // 弹出 "0", 因为theArgs没有元素
fun1(5); // 弹出 "1", 因为theArgs只有一个元素
fun1(5, 6, 7); // 弹出 "3", 因为theArgs有三个元素

五、生成器

语法:

function* gen() {
yield 1;
yield 2;
yield 3;
}

var g = gen(); // “Generator { }”

方法
Generator.prototype.next()
返回 yield 表达式产生的值.

Generator.prototype.return()
返回给定的值并结束生成器.

Generator.prototype.throw()
向生成器抛出错误.

六、模板字符串

语法:

`string text`

`string text line 1
string text line 2`

`string text ${expression} string text`

tag `string text ${expression} string text`

参考:
MDN JavaScript参考文档

分享到