ES6

一、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,以下三個變量在箭頭函數之中也是不存在的,指向外層函數的對應變量:argumentssupernew.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一樣,在類內部可以使用getset關鍵字,對某個屬性設置取值和賦值方法。

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


















發佈了54 篇原創文章 · 獲贊 14 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章