1.原型鏈相關知識
正式學習之前,我們先溫習下原型鏈的相關知識:(圖片來源:Javascript的原型鏈圖)
圖片中有待補充的內容包括:每一個new出來的物件或者函式有一個constructor
屬性指向建構函式,例子如下:
var b = new Function();
b.constructor === Function; //true
複製程式碼
先對照圖片對下面的例子做出解釋
var a = {};
console.log(a.prototype); //=> undefined (例項化出來的物件)
var b = function(){};
console.log(b.prototype); //=> {} (對應Foo.prototype)
var c = 'Hello';
console.log(c.prototype); //=> undefined (例項出來的字串)
複製程式碼
補充知識點:
var o1 = new Object();
typeof(o1); //=> 'object'
var o2 = {};
typeof(o2); //=> 'object'
typeof(Object); //=> 'function'
複製程式碼
我們不難得出一個結論,typeof所的到的物件型別和物件的__proto__
型別相同,Function.prototype
除外。
typeof Function.prototype.__proto__ ; //=> 'object'
複製程式碼
2.ES5中的繼承
(相關閱讀:ES5和ES6中的繼承/ECMAScript 繼承機制實現)
2.1 原型鏈方法
將父類例項當做子類建構函式的原型
//先來個父類,帶些屬性
function Super(){
this.flag = true;
}
//為了提高複用性,方法繫結在父類原型屬性上
Super.prototype.getFlag = function(){
return this.flag;
}
//來個子類
function Sub(){
this.subFlag = false;
}
//實現繼承
Sub.prototype = new Super;
//給子類新增子類特有的方法,注意順序要在繼承之後
Sub.prototype.getSubFlag = function(){
return this.subFlag;
}
//構造例項
var es5 = new Sub;
複製程式碼
優點:
- 父類的方法(getName)得到了複用。
缺點:
- 原型中屬性的改變會反應到所有的例項上
- 建立子類的例項時,不能向父類的建構函式傳遞引數
例子如下:
function Super(){
this.flag = true;
}//父類
function Sub(){
this.subFlag = false;
}//子類
Sub.prototype = new Super();//繼承
var obj = new Sub();//子類例項1
obj.flag = flase; //修改之後,由於是原型上的屬性,之後建立的所有例項都會受到影響
var obj_2 = new Sub();//子類例項2
console.log(obj.flag) //false;
複製程式碼
2.2 call和apply方法(和物件冒充類似)
call方法,它的第一個引數用作 this 的物件。其他引數都直接傳遞給函式自身。
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.call(obj, "The color is ", "a very nice color indeed.");
複製程式碼
在這個例子中,函式 sayColor()
在物件外定義,即使它不屬於任何物件,也可以引用關鍵字 this
。物件 obj
的 color
屬性等於blue
。呼叫call()
方法時,第一個引數是 obj
,說明應該賦予sayColor()
函式中的 this 關鍵字值是 obj。第二個和第三個引數是字串。它們與 sayColor(
) 函式中的引數sPrefix
和 sSuffix
匹配,最後生成的訊息 "The color is blue, a very nice color indeed."
將被顯示出來。
優點:
- 子類的每個例項都有自己的屬性(name),不會相互影響。
缺點:
- 父類方法沒有得到複用,方法存在於每個例項當中而不是來自繼承。
2.3 混合方式
function A (color){
this.color = color
}
A.prototype.sayColor = function(){
console.log(this.color)
}
function B(sColor,name){
A.call(this,sColor);
this.name = name;
}
B.prototype = new A();
B.prototype.sayName = function(){
console.log(this.name)
}
var b = new B("red","nik")
複製程式碼
請注意:
在執行 B.prototype = new A();
之後,B.prototype.constructor
指向A
。然而constructor
的定義是要指向原型屬性對應的建構函式的,B.prototype
是B
建構函式的原型,所以應該新增一句糾正:
B.prototype.constructor = B;
複製程式碼
2 ES6中的繼承
相關閱讀(Class的繼承)
2.1 簡介
Class可以通過extends
關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。
class Point {
}
class ColorPoint extends Point {
}
複製程式碼
以上程式碼實際等同於下面的程式碼:
class ColorPoint extends Point {
}
// 等同於
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
複製程式碼
上面程式碼中,constructor
方法和toString
方法之中,都出現了super
關鍵字,它在這裡表示父類的建構函式,用來新建父類的this物件。
子類必須在constructor
方法中呼叫super
方法,否則新建例項時會報錯。這是因為子類沒有自己的this
物件,而是繼承父類的this
物件,然後對其進行加工。如果不呼叫super
方法,子類就得不到this
物件。
**注意:**在子類的建構函式中,只有呼叫super
之後,才可以使用this
關鍵字,否則會報錯。這是因為子類例項的構建,是基於對父類例項加工,只有super
方法才能返回父類例項。
2.2 super 關鍵字
super
這個關鍵字,既可以當作函式使用,也可以當作物件使用。在這兩種情況下,它的用法完全不同。
第一種情況,super
作為函式呼叫時,代表父類的建構函式。ES6 要求,子類的建構函式必須執行一次super
函式。
super
雖然代表了父類A
的建構函式,但是返回的是子類B
的例項,即super
內部的this
指的是B
,因此super()
在這裡相當於A.prototype.constructor.call(this)
。
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
複製程式碼
上面程式碼中,new.target
指向當前正在執行的函式。可以看到,在super()
執行時,它指向的是子類B
的建構函式,而不是父類A
的建構函式。也就是說,super()
內部的this
指向的是B
。
第二種情況,super
作為物件時,在普通方法中,指向父類的原型物件;在靜態方法中,指向父類。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
複製程式碼
上面程式碼中,子類B當中的super.p()
,就是將super
當作一個物件使用。這時,super
在普通方法之中,指向A.prototype
,所以super.p()
就相當於A.prototype.p()
。
由於super
指向父類的原型物件,所以定義在父類例項上的方法或屬性,是無法通過super
呼叫的。
如下:
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
複製程式碼
上面程式碼中,p
是父類A
例項的屬性,super.p
就引用不到它。
如果屬性定義在父類的原型物件上,super
就可以取到。如下:
class A {}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
console.log(super.x) // 2
}
}
let b = new B();
複製程式碼
ES6 規定,通過super
呼叫父類的方法時,方法內部的this
指向子類。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b = new B();
b.m() // 2
複製程式碼
待補充。。。