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的繼承 § ⇧
constructor
方法中調用super
方法,否則新建實例時會報錯。這是因爲子類沒有自己的this
對象,而是繼承父類的this
對象,然後對其進行加工。如果不調用super
方法,子類就得不到this
對象。this
,然後再將父類的方法添加到this
上面(Parent.apply(this)
)。ES6的繼承機制完全不同,實質是先創造父類的實例對象this
(所以必須先調用super
方法),然後再用子類的構造函數修改this
。另一個需要注意的地方是,在子類的構造函數中,只有調用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
,子類B
的prototype
屬性的__proto__
屬性指向父類A
的prototype
屬性。
這樣的結果是因爲,類的繼承是按照下面的模式實現的。
實例的__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
無法綁定,導致拿不到內部屬性。
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
關鍵字不僅可以用來繼承類,還可以用來繼承原生的構造函數。因此可以在原生數據結構的基礎上,定義自己的數據結構