原型鏈javascript
基本思想:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。前端
原型對象:每一個構造函數在建立時都會有一個prototype
屬性指向這個函數的原型對象,而原型對象會得到一個constructor
屬性指向構造函數。當調用構造函數建立實例後,實例都包含一個指向構造函數的原型對象(不是指向構造函數)的內部指針,Firefox、Safiri、Chrome用__proto__
表示這個指針。java
原型鏈:若是一個實例的原型對象等於另外一個類型的實例,而那個實例的原型對象又等於其餘類型的實例,如此層層遞進,就構成了實例與原型的鏈條,這就是所謂的原型鏈。es6
function A() {}
function B() {}
B.prototype = new A();
var c = new B();
複製代碼
原型搜索機制:讀取一個實例屬性時,首先會在事例中搜索該屬性。若是沒有找到,則會繼續搜索實例的原型。在經過原型鏈實現繼承的狀況下,搜索過程得以沿着原型鏈繼續向上。在找不到屬性或方法的狀況下,搜索過程老是要一環一環地前行到原型鏈末端(Object.prototype
)纔會停下來。瀏覽器
肯定原型與實例之間的關係app
instanceof
frontend
c instanceof B // true
c instanceof A // true
c instanceof Object // true
複製代碼
isPrototypeOf()
函數
B.prototype.isPrototypeOf(c) // true
A.prototype.isPrototypeOf(c) // true
Object.prototype.isPrototypeOf(c) // true
複製代碼
謹慎定義方法ui
原型鏈的問題:this
function A() {}
function B() {
A.call(this);
}
var c = new B();
複製代碼
A.call(this, name, sex, ...)
function A() {}
function B() {
// 繼承屬性
A.call(this, name, ...);
}
// 繼承方法
B.prototype = new A();
B.prototype.constructor = B;
複製代碼
instanceof
和isPrototypeOf()
識別基於組合繼承建立的對象。JavaScript
中最經常使用的繼承模式。function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var a = {};
var b = object(a);
複製代碼
object()
對傳入其中的對象執行了一次淺複製。Object.create()
方法規範了原型式繼承:
function A(original) {
var clone = object(original);
clone.sayHi = function() {
alert('Hi');
};
return clone;
}
var a = {};
var b = A(a);
複製代碼
組合繼承的不足:不管什麼狀況下,都會調用兩次超類型構造函數,一次是建立子類型原型的時候,另外一次是子類型構造函數內部。
寄生組合式繼承的基本模式:
function inherite(subType, superType) {
var f = object(superType.prototype); // 建立superType的實例f
f.constructor = subType;
subType.prototype = f;
}
function SuperType() {}
function SubType() {
SuperType.call(this);
}
inherite(SubType, SuperType);
複製代碼
基本思想:經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。其實是在組合繼承的基礎上,用超類型原型的副本代替調用超類型的構造函數給子類型指定原型。
本質上,是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。
優勢:只調用了一次超類型的構造函數,而且所以避免了在子類型的原型上建立沒必要要的、多餘的屬性。與此同時,原型鏈還能保持不變。所以,還可以正常使用instanceof
和isPrototypeOf()
。廣泛認爲寄生組合式繼承是引用類型最理想的繼承方式。
function create() {
let obj = {}; // 建立一個新對象
let Con = [].shift.call(arguments); // 獲取構造函數
obj.__proto__ = Con.prototype; // 連接到原型
let result = Con.apply(obj, arguments); // 綁定this,執行構造函數
return typeof result === 'object' ? result : obj; // 確保new出來的是個對象
}
// 調用
function F() {}
var f = create(F);
複製代碼
function Foo() {
return this;
}
Foo.getName = function() {
console.log(1);
}
Foo.prototype.getName = function() {
console.log(2);
}
複製代碼
代碼 | 執行結果 | 執行過程 |
---|---|---|
new Foo new Foo() |
Foo {} |
|
new Foo.getName new (Foo.getName) new Foo.getName() new (Foo.getName)() |
1 Foo.getName {} |
1. 執行var f = Foo.getName 2. 執行 new f 或new f() |
(new Foo).getName (new Foo()).getName new Foo().getName |
f() { console.log(2); } |
1. 執行var f = new Foo() 2. 訪問 f.getName |
(new Foo).getName() (new Foo()).getName() new Foo().getName() |
2 |
1. 執行var f = new Foo() 2. 調用 f.getName() |
優先級:.
與()
相等,大於new
操做符的優先級
Class可看做構造函數的另外一種寫法
prototype
屬性上面
__proto__
屬性爲類添加方法,可是不推薦使用,會影響到全部實例class Point {
constructor() {}
toString() {}
}
typeof Point // "function"
Point === Point.prototype.constructor // true
var b = new Point(); // 使用new操做符建立實例
b.constructor === Point.prototype.constructor // true
b.constructor === b.__proto__.constructor // true
Object.keys(Point.prototype) // []
Object.getOwnPropertyNames(Point.prototype) // ["constructor", "toString"]
var Spot = function() {} // 使用ES5改寫Class
Spot.prototype.toString = function() {}
Object.keys(Spot.prototype) // ["toString"]
Object.getOwnPropertyNames(Spot.prototype) // ["constructor", "toString"]
複製代碼
注意點
this
,它默認指向類的實例
this
會指向該方法運行時所在的環境 => 能夠在構造函數中使用bind
或箭頭函數讓this
指向實例對象其餘特性
static
修飾,表示該方法不會被實例繼承,而是直接經過類來調用。
this
指的是類,不是實例。constructor()
方法裏的this
上面,也能夠定義在類的最頂層。new.target
:用在構造函數或Class
中,返回new
命令做用於的那個構造函數或Class
。
new
命令或Reflect.construct()
調用的,new.target
會返回undefined
。new.target
會報錯。使用extends
實現繼承,子類繼承了父類的全部屬性和方法。
constructor
方法中調用super
方法,不然新建實例時會報錯。super
以後,才能使用this
關鍵字。與ES5繼承機制的區別:
ES5的繼承,實質是先創造子類的實例對象this
,而後再將父類的方法添加到this
上面。
Parent.apply(this)
複製代碼
ES6的繼承,實質是先經過super()
方法將父類實例對象的屬性和方法,加到this
上面,而後再用子類的構造函數修改this
。
super
關鍵字:既能夠當作函數使用,也能夠看成對象使用
super
做爲函數調用時,表明父類的構造函數,返回子類的實例。只能用在子類的構造函數中,用在其餘地方會報錯。
class A {}
class B extends A {
constructor() {
super(); // 至關於 A.prototype.constructor.call(this)
}
}
複製代碼
super
做爲對象使用時:
(1) 在子類的普通方法中經過super
調用父類的方法時,super
指向父類的原型對象
super
調用this
指向當前的子類實例super
對某個屬性賦值時,super
就是this
,賦值的屬性會變成子類實例的屬性super
訪問某個屬性時,super
依舊指向父類的原型對象(2) 在子類的靜態方法中經過super
調用父類的方法時,super
指向父類
this
指向當前的子類使用super
的時候,必須顯示指定是做爲函數仍是做爲對象使用,不然會報錯,如:
console.log(super); // 報錯
複製代碼
因爲對象老是繼承自其餘對象,因此能夠在任何一個對象中,使用super
關鍵字
Class的prototype
屬性和__proto__
屬性
大多數瀏覽器的ES5實現中,每個對象都有__proto__
屬性,指向對應的構造函數的prototype
屬性。
ES6的Class中,同時有prototype
屬性和__proto__
屬性,所以同時存在兩條繼承鏈:
__proto__
屬性指向父類prototype
屬性的__proto__
屬性指向父類的prototype
屬性class A {}
class B extends A {}
B.__proto__ = A // true
B.prototype.__proto__ = A.prototype // true
複製代碼
緣由,類的繼承是按照下面的模式實現的:
class A {}
class B {}
// B的實例繼承A的實例
Object.setPrototypeOf(B.protottype, A.prototype);
// let a = new A();
// let b = new B();
// B.prototype === b.__proto__
// A.prototype === a.__proto__
// B繼承A的靜態屬性
Object.setPrototypeOf(B, A);
// Object.setPrototypeOf的實現
Object.setPrototypeOf = function(obj, proto) {
obj.__proto__ = proto;
return obj;
}
複製代碼
原生構造函數的繼承
this
,再將父類的屬性添加到子類上,因爲父類的內部屬性沒法獲取,致使沒法繼承原生的構造函數。this
,而後再用子類的構造函數修飾this
,使得父類的全部行爲均可以繼承。參考: