一、let、const
没有变量提升
暂时性死区,必须引用前声明
块级作用域内才可以使用
不可以重复声明,否则会报错
二、箭头函数
ES6允许使用“箭头”(=>
)定义函数
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return
语句返回。
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
使用注意点
由于箭头函数没有自己的this
,所以当然也就不能用call()
、apply()
、bind()
这些方法去改变this
的指向。箭头函数有几个使用注意点。
(1)函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new
命令,否则会抛出一个错误。
(3)不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
(4)不可以使用yield
命令,因此箭头函数不能用作Generator函数。
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
除了this
,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments
、super
、new.target
。
三、promise
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
1.Promise的立即执行性
Promise对象表示未来某个将要发生的事件,但在创建(new)Promise时,作为Promise参数传入的函数是会被立即执行的,只是其中执行的代码可以是异步代码。有些同学会认为,当Promise对象调用then方法时,Promise接收的函数才会执行,这是错误的。因此,代码中”create a promise”先于”after new Promise”输出。then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行。
promise.Trick>promise函数回调>setTimeout
2.Promise 三种状态
Promise的内部实现是一个状态机。Promise有三种状态:pending,resolved,rejected。当Promise刚创建完成时,处于pending状态;当Promise中的函数参数执行了resolve后,Promise由pending状态变成resolved状态;如果在Promise的函数参数中执行的不是resolve方法,而是reject方法,那么Promise会由pending状态变成rejected状态。
3.Promise 状态的不可逆性
Promise状态的一旦变成resolved或rejected时,Promise的状态和值就固定下来了,不论你后续再怎么调用resolve或reject方法,都不能改变它的状态和值。因此,p1中resolve(“success2”)并不能将p1的值更改为success2,p2中reject(“reject”)也不能将p2的状态由resolved改变为rejected.
4.链式调用
Promise对象的then方法返回一个新的Promise对象,因此可以通过链式调用then方法。then方法接收两个函数作为参数,第一个参数是Promise执行成功时的回调,第二个参数是Promise执行失败时的回调。两个函数只会有一个被调用,函数的返回值将被用作创建then返回的Promise对象。这两个参数的返回值可以是以下三种情况中的一种:
(1)、return 一个同步的值 ,或者 undefined(当没有返回一个有效值时,默认返回undefined),then方法将返回一个resolved状态的Promise对象,Promise对象的值就是这个返回值。
(2)、return 另一个 Promise,then方法将根据这个Promise的状态和值创建一个新的Promise对象返回。
(3)、 throw 一个同步异常,then方法将返回一个rejected状态的Promise, 值是该异常。
根据以上分析,代码中第一个then会返回一个值为2(1*2),状态为resolved的Promise对象,于是第二个then输出的值是2。第二个then中没有返回值,因此将返回默认的undefined,于是在第三个then中输出undefined。第三个then和第四个then中分别返回一个状态是resolved的Promise和一个状态是rejected的Promise,依次由第四个then中成功的回调函数和第五个then中失败的回调函数处理。
5.Promise then() 回调异步性
Promise接收的函数参数是同步执行的,但then方法中的回调函数执行则是异步的,因此,”success”会在后面输出
四、symbol类型
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值,它是JavaScript的第七种数据类型
var s = Symbol();
typeof s
//'symbol'
var sy=Symbol('foo')
\\Symbol(foo)
- 1
- 2
- 3
- 4
- 5
- 6
基本数据类型:number、string、boolean、null、undefined
引用数据类型:Object、function、Date、Array、REGEXP
Symbol函数前不能使用 new;生成Symbol是一个原始类型的值,Symbol值不是对象,所以不能添加基本属性,它类似于字符串的数据类型
Symbol 接受字符串,方便对实例的描述
如果参数为对象就会被 toString 转换为字符串后调用
var sd={}
var sg=Symbol(sd)
//Symbol([object Object])
- 1
- 2
- 3
Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的
var sy1=Symbol();
var sy2=Symbol();
sy1==sy2
//false
sy1===sy2
//false
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Symbol值不能与其他类型的值进行运算,会报错。
var sy1=Symbol();
var str='sada'+sy1
\\TypeError: Cannot convert a Symbol value to a string
- 1
- 2
- 3
- 4
消除魔术字符串
魔术字符串:在代码之中多次出现,与代码形成强耦合的某一个具体的字符串或者数值
Symbol作为属性名不会出现for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回
Object.getOwnPropertySymbols可以返回Symbol作为对象属性
Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和 Symbol 键名
Symbol.for()
var sy1=Symbol.for('sy')
var sy2=Symbol.for('sy')var
sy1===sy2
//true
var sm1=Symbol('ha')
var sm2=Symbol.for('ha')
sm1===sm2
//false
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Symbol.keyFor()
Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key
var sm1=Symbol('ha')
var sm2=Symbol.for('ha')
var key1=Symbol.keyFor(sm1)
//undefined
var key2=Symbol.keyFor(sm2)
//ha
- 1
- 2
- 3
- 4
- 5
- 6
- 7
需要注意的是,Symbol.for为Symbol值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值
内置的Symbol值
Symbol.hasInstance
指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
[1, 2, 3] instanceof new MyClass() // true
五、class定义类
ES6中的类实际就是一个函数,且正如函数的定义方式有函数声明和函数表达式两种方式一样,类的定义也有两种方式,分别为:
- 类声明
- 类表达式
类声明
类声明是定义类的一种方式,使用class关键字后跟一个类名,就可以定义一个类。如下:
class Foo {
constructor() {
// ..
}
}
- 1
- 2
- 3
- 4
- 5
不存在变量提升(hoist)
类声明和函数声明不同的一点是,函数声明存在变量提升现象,而类声明不会。即,类必须先声明,然后才能使用,否则会抛出ReferenceError
异常。
var foo = new Foo(); // Uncaught ReferenceError: Foo is not defined(...)
class Foo {
// ...
}
- 1
- 2
- 3
- 4
这种规定的原因与类的继承有关,必须保证子类在父类之后定义。
let Foo = class {};
class Bar extends Foo {
}
- 1
- 2
- 3
- 4
上面的代码不会报错,因为class Bar
继承Foo
时,Foo
已经有定义了。但是,如果存在Class提升,上面代码就会报错,因为Class Bar
会被提升到代码头部,而表达式式Foo
是不会提升的,所以导致Class Bar
继承Foo
的时候,Foo
还没有定义。
类表达式
类表达式就定义类的另外一种方式,就像函数表达式一样,在类表达式中,类名是可有可无的。若定义的类名,则该类名只有的类的内部才可以访问到。
// 方式一
const MyClass = class {};
// 方式二:给出类名
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
上面方式二定义类的同时给出了类名,此时,Me
类名只可以在Class的内部代码可用,指代当前类。MyClass的name属性值为给出的类名。
let my = new MyClass();
my.getClassName(); // Me
Me.name; // Uncaught ReferenceError: Me is not defined(…)
MyClass.name; // Me
- 1
- 2
- 3
- 4
采用类表达式,可以写出立即执行的Class。如下:
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('Zhang San');
person.sayName(); // Zhang San
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
类体和方法定义
类的成员需要定义在一对大括号内{}
,大括号内的代码的大括号本身组成了类体。类成员包括类构造器和类方法(包括静态方法和实例方法)。
严格模式
类体中的代码都强制在严格模式中执行,即默认”use strict”。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。
构造器(constructor方法)
constructor
方法是一个特殊的类方法,它既不是静态方法也不是实例方法,它仅在实例化的时候被调用。一个类只能拥有一个名为constructor
的方法,否则会抛出SyntaxError
异常。
如果没有定义constructor
方法,这个方法会被默认添加,即,不管有没有显示定义,任何一个类都有constructor
方法。
子类必须在constructor方法中调用super
方法,否则新建实例时会报错。因为子类没有自己的this
对象,而是继承父类的this
对象,然后对其进行加工,如果不调用super
方法,子类就得不到this
对象。
class Point {}
class ColorPoint extends Point {
constructor() {}
}
let cp = new ColorPoint(); // ReferenceError
- 1
- 2
- 3
- 4
- 5
- 6
- 7
上面代码中,ColorPoint
继承了父类Point
,但是它的构造函数没有调用super
方法,导致新建实例时报错。
原型方法
定义类的方法时,方法名前面不需要加上function
关键字。另外,方法之间不需要用逗号分隔,加了会报错。
class Bar {
constructor() {}
doStuff() {}
toString() {}
toValue() {}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
类的所有方法都是定义在类的prototype
属性上的,上面的写法等同于下面:
Bar.prototype = {
doStuff() {},
toString() {},
toValue() {}
};
- 1
- 2
- 3
- 4
- 5
所以,在类的实例上调用方法,实际上就是调用原型上的方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor; // true
- 1
- 2
- 3
- 4
上面代码中,b
是B类的实例,它的constructor
方法就是B类原型的constructor
方法。
由于类的方法都是定义在prototype
上面,所以类的新方法可以添加在prototype
对象上面。Object.assign
方法可以很方便地一次向类添加多个方法。
class Point {
constructor() {
// ...
}
}
Object.assign(Point.prototype, {
toString() {},
toValue() {}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
class Point {
constructor(x, y) {
// ...
}
toString() {
return '(' + x + ', ' + y + ')';
}
}
Object.keys(Point.prototype); // []
Object.getOwnPropertyNames(Point.prototype); // ["constructor", "toString"]
Object.getOwnPropertyDescriptor(Point, 'toString');
// Object {writable: true, enumerable: false, configurable: true}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
静态方法
static
关键字用来定义类的静态方法。静态方法是指那些不需要对类进行实例化,使用类名就可以直接访问的方法。静态方法经常用来作为工具函数。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx*dx + dy*dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
console.log(Point.distance(p1, p2));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
静态方法不可以被实例继承,是通过类名直接调用的。但是,父类的静态方法可以被子类继承。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod(); // "hello"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
静态方法也可以用super
关键字调用。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod(); // "hello too"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
extends关键字
extends
关键字用于实现类之间的继承。子类继承父类,就继承了父类的所有属性和方法。 extends
后面只可以跟一个父类。
super 关键字
super
关键字可以用来调用其父类的构造器或方法。
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
类的Getter和Setter方法
与ES5一样,在类内部可以使用get
和set
关键字,对某个属性设置取值和赋值方法。
class Foo {
constructor() {}
get prop() {
return 'getter';
}
set prop(val) {
console.log('setter: ' + val);
}
}
let foo = new Foo();
foo.prop = 1;
// setter: 1
foo.prop;
// "getter"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
上面代码中,prop
属性有对应 的赋值和取值方法,因此赋值和读取行为都被自定义了。
存值和取值方法是设置在属性的descriptor对象上的。
var descriptor = Object.getOwnPropertyDescriptor(Foo.prototype, 'prop');
"get" in descriptor // true
"set" in descriptor // true
- 1
- 2
- 3
- 4
上面代码中,存值和取值方法是定义在prop
属性的描述对象上的,这与ES5一致。
类的Generator方法
如果类的某个方法名前加上星号(*
),就表示这个方法是一个Generator函数。
class Foo {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x);
}
// hello
// world
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个Generator函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of
循环会自动调用这个遍历器。