一、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
循環會自動調用這個遍歷器。