es6中"類"--class的方式創建對象和es5中正常用法比較

JavaScript語言的傳統方法是通過構造函數,定義並生成新對象。下面是一個例子。(可以是工廠模式,構造函數模式,組合模式優缺點自己可以在網上查找)

function Point(x,y){

this.x=x;

this.y = y;

}

Point.prototype.toString = function(){

return '('+this.x+','+this.y+')';

}

var p= new Point(1,2);

在es6中使用類的方式定義:

//定義類
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
哈哈哈,1.類Point中方法之間不用,號隔開,方法不用function進行定義,

構造函數的prototype屬性,在ES6的“類”上面繼續存在。事實上,類的所有方法都定義在類的prototype屬性上面。

class Point {
  constructor(){
    // ...
  }

  toString(){
    // ...
  }

  toValue(){
    // ...
  }
}

// 等同於

Point.prototype = {
  toString(){},
  toValue(){}
};
2.類的內部所有定義的方法,都是不可枚舉的(但是在es5中prototype的方法是可以進行枚舉的)

3.每一個類中都有一個constructor方法該方法返回實例對象

4.

類的構造函數,不使用new是沒法調用的,會報錯。這是它跟普通構造函數的一個主要區別,後者不用new也可以執行。

二、用類進行實例和用普通的構造函數進行實例:

1、用類進行實例的必須使用new否則就會報錯

2、與ES5一樣,實例的屬性除非顯式定義在其本身(即定義在this對象上),否則都是定義在原型上(即定義在class上)。

3、與ES5一樣,類的所有實例共享一個原型對象。

4、Class不存在變量提升(hoist),這一點與ES5完全不同

new Foo(); // ReferenceError
class Foo {}

上面代碼中,Foo類使用在前,定義在後,這樣會報錯,因爲ES6不會把類的聲明提升到代碼頭部。這種規定的原因與下文要提到的繼承有關,必須保證子類在父類之後定義。

{
  let Foo = class {};
  class Bar extends Foo {
  }
}

上面的代碼不會報錯,因爲Bar繼承Foo的時候,Foo已經有定義了。但是,如果存在class的提升,上面代碼就會報錯,因爲class會被提升到代碼頭部,而let命令是不提升的,所以導致Bar繼承Foo的時候,Foo還沒有定義。

2、Class的繼承 § 

class中的繼承使用extend
1、子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因爲子類沒有自己的this對象,而是繼承父類的this對象,然後對其進行加工。如果不調用super方法,子類就得不到this對象。
2、ES5的繼承,實質是先創造子類的實例對象this,然後再將父類的方法添加到this上面(Parent.apply(this))。ES6的繼承機制完全不同,實質是先創造父類的實例對象this(所以必須先調用super方法),然後再用子類的構造函數修改this
3、

另一個需要注意的地方是,在子類的構造函數中,只有調用super之後,纔可以使用this關鍵字,否則會報錯。這是因爲子類實例的構建,是基於對父類實例加工,只有super方法才能返回父類實例。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正確
  }
}

上面代碼中,子類的constructor方法沒有調用super之前,就使用this關鍵字,結果報錯,而放在super方法之後就是正確的。

super.print.call(this)

類的prototype屬性和__proto__屬性 § 

大多數瀏覽器的ES5實現之中,每一個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class作爲構造函數的語法糖,同時有prototype屬性和__proto__屬性,因此同時存在兩條繼承鏈。

(1)子類的__proto__屬性,表示構造函數的繼承,總是指向父類。

(2)子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。

class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

上面代碼中,子類B__proto__屬性指向父類A,子類Bprototype屬性的__proto__屬性指向父類Aprototype屬性。

這樣的結果是因爲,類的繼承是按照下面的模式實現的。

實例的__proto__屬性

子類實例的__proto__屬性的__proto__屬性,指向父類實例的__proto__屬性。也就是說,子類的原型的原型,是父類的原型。

原生構造函數的繼承

原生構造函數是指語言內置的構造函數,通常用來生成數據結構。ECMAScript的原生構造函數大致有下面這些。

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

以前,這些原生構造函數是無法繼承的,比如,不能自己定義一個Array的子類。

function MyArray() {
  Array.apply(this, arguments);
}

MyArray.prototype = Object.create(Array.prototype, {
  constructor: {
    value: MyArray,
    writable: true,
    configurable: true,
    enumerable: true
  }
});

上面代碼定義了一個繼承Array的MyArray類。但是,這個類的行爲與Array完全不一致。

var colors = new MyArray();
colors[0] = "red";
colors.length  // 0

colors.length = 0;
colors[0]  // "red"

之所以會發生這種情況,是因爲子類無法獲得原生構造函數的內部屬性,通過Array.apply()或者分配給原型對象都不行。原生構造函數會忽略apply方法傳入的this,也就是說,原生構造函數的this無法綁定,導致拿不到內部屬性。

ES5是先新建子類的實例對象this,再將父類的屬性添加到子類上,由於父類的內部屬性無法獲取,導致無法繼承原生的構造函數。比如,Array構造函數有一個內部屬性[[DefineOwnProperty]],用來定義新屬性時,更新length屬性,這個內部屬性無法在子類獲取,導致子類的length屬性行爲不正常。

ES6允許繼承原生構造函數定義子類,因爲ES6是先新建父類的實例對象this,然後再用子類的構造函數修飾this,使得父類的所有行爲都可以繼承。下面是一個繼承Array的例子。

class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined

上面代碼定義了一個MyArray類,繼承了Array構造函數,因此就可以從MyArray生成數組的實例。這意味着,ES6可以自定義原生數據結構(比如Array、String等)的子類,這是ES5無法做到的。

上面這個例子也說明,extends關鍵字不僅可以用來繼承類,還可以用來繼承原生的構造函數。因此可以在原生數據結構的基礎上,定義自己的數據結構




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章