原型與繼承
- 原型對象
-
- 函數的原型
- 構造函數與原型對象
- 原型鍊
-
- 建立原型鍊
- 原型檢測
- 屬性周遊
- 借用原型
- 原型工廠
- 對象工廠
- 混合模式
原型對象
每個對象都有一個原型
prototype
對象,通過函數建立的對象也将擁有這個原型對象。原型是一個指向對象的指針。
- 原型類似其他面向對象語言中的父類(基類)
- 所有對象的原型預設是
的執行個體Object
- 原型包含
屬性,指向構造函數,是以可以通過一個執行個體的對象找到其構造函數建立另一同類型對象constructor
- 對象包含
指向他的原型對象,__proto__
不是對象屬性,了解為__proto__
的 getter/setter 實作,他是一個非标準定義prototype
- 多個對象共同繼承一個原型可以共享原型中的成員,實作代碼複用,可以解決建構函數建立對象時複制多個函數造成的記憶體占用問題
-
繼承是為了複用代碼,繼承的本質是将原型指向到另一個對象
預設情況下建立的對象都有原型,下面的代碼展示數組對象的層次,其中包含了變量
的原型arr
的成員:Array
let arr = ["a"];
//dir()可以像檔案目錄一樣列印對象
console.dir(arr);
輸出:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL3IzNwEzN0MTMzEDOwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
以下x、y的原型都為元對象
Object
let a = {};
let b = {};
console.log(a,b);
輸出:
函數的原型
函數比較特殊,
prototype
用于執行個體對象時使用,
__proto__
用于構造函數時使用。因為函數比較特殊,既可以被當做構造函數産生一個對象。也可以本身是
function
建立的一個執行個體對象。
function User(){};
let lisi = new User();
//通過User構造函數執行個體的lisi執行個體對象的__proto__直接指向Object
console.log(lisi.__proto__);
//構造函數的prototype指向Object
console.log(User.prototype);
console.log(lisi.__proto__ == User.prototype);
//構造函數的__proto__指向其父親 function
console.log(User.__proto__);
輸出:
關系圖如下:
構造函數與原型對象
下面是使用構造函數建立對象的原型展現:
- 構造函數擁有原型對象,利用
屬性通路prototype
- 構造函數在建立對象時把原型賦予對象
- 對象原型在配置後可以通過
(指向構造函數)通路構造函數constructor
- 執行個體對象可以直接通過
通路對象原型__proto__
- 可以通過執行個體對象的
通路構造函數,但是constructor
本質上是對象原型的屬性constructor
下面的示例展示利用對象執行個體arr找到其原型對象再利用其構造函數建立一個新的對象【JS學習筆記 08】 對象的原型與繼承原型對象
let arr = new Array([1,2,3,4,5]);
//利用對象執行個體arr找到其原型對象再利用其構造函數建立一個新的對象
let newArr = new arr.__proto__.constructor([6,7,8,9,10]);//new可以省略
console.table(newArr);
輸出:
原型鍊
在前面我們可以了解到,JS中大部分的資料類型其實都是對象類型的。如果仔細看上面代碼中
Array
構造的原型其實就是
Object
類型的一個執行個體對象,可以使用
__proto__
屬性通路到
Object
。
執行
console.log(Array.prototype);
可以在控制台看到
Array
的原型對象中的
__proto__
指向
Obeject
的構造的原型對象
同時也就意味可以直接通過
Array.prototype.__proto__
通路
Object
的原型對象
有如下的關系圖:
在上面的橙色标記的引用鍊中,通過引用類型的原型,繼承另一個引用類型的屬性與方法,這也是實作繼承的步驟。
建立原型鍊
- 使用
可設定對象的原型。下面的例子将建立一條原型鍊,使Object.setPrototypeOf
繼承obj3
,obj2
繼承obj2
。obj1
将同時擁有三者的屬性。obj3
let obj1 = {
prop1 : "obj1"
}
let obj2 = {
prop2 : "obj2"
}
let obj3 = {
prop3 : "obj3"
}
//繼承關系如下 obj3 -> obj 2 -> obj1
Object.setPrototypeOf(obj3,obj2);
Object.setPrototypeOf(obj2,obj1);
console.log(obj3.prop1);//輸出:obj1
console.log(obj3.prop2);//輸出:obj2
console.log(obj3.prop3);//輸出:obj3
- 采用構造函數直接指派的方式也可以建立原型鍊:使
繼承C的原型
,B的原型
繼承B的原型
A的原型
function A(){};
function B(){};
function C(){};
let a = new A();
B.prototype = a;
let b = new B();
C.prototype = b;
let c = new C();
- 使用
建立一個新對象時使用現有對象做為新對象的原型對象Object.create
let a ={};
//使b繼承于a
let b = Object.create(a);
原型檢測
instanceof
關鍵字可以用來檢測構造函數的
prototype
屬性是否出現在某個執行個體對象的原型鍊上,即會向上對比是否為該類型。
function A(){};
function B(){};
function C(){};
let a = new A();
B.prototype = a;
let b = new B();
C.prototype = b;
let c = new C();
console.log(c instanceof B);//true
console.log(c instanceof A);//true
console.log(b instanceof A);//true
使用
isPrototypeOf
可以檢測一個對象是否在另一個對象的原型鍊中,接上面的代碼:
console.log(a.isPrototypeOf(b));//ture
console.log(a.isPrototypeOf(c));//ture
console.log(b.isPrototypeOf(c));//ture
屬性周遊
in
關鍵字會對原型鍊上所有屬性描述
enumerable
為
ture
的屬性進行周遊(向上攀升),使用
hasOwnProperty
可以判斷對象中屬性是否為自有屬性,即非繼承來的屬性。
let a ={
name1:"a"
}
let b = {
name2:"b"
}
let c = {
name3:"c"
}
//繼承關系 c -> b -> a (->:繼承)
Object.setPrototypeOf(c,b);
Object.setPrototypeOf(b,a);
for (const key in c) {
console.log(key);
}
/*輸出:name3 name2 name 1 */
for (const key in c) {
if (c.hasOwnProperty(key)) {
console.log(key)
}
}/*輸出: name 3*/
借用原型
call
和
apply
可以改變函數體内的
this
指針,進而可以借用其他對象的方法來完成功能。根據傳入的對象不同,兩者都可以改變函數體中
this
的值,兩者的差别在于
call
傳入零散的參數,而
apply
傳入一個參數數組。
let exam = {
score: new Map([["C/C++",90],["Java",87],["Js",99]]),
average : function(){
let s = Array.from(this.score.values());
let sum = s.reduce((total,value) => total+=value);
return sum/s.length;
}
}
console.log(exam.average()); // 92
let game = {
score: [100,99,200,123,213]
}
//game對象沒有average方法,但是可以借用exam的完成平均數的計算
console.log(exam.average.call(game)); // 147
原型工廠
原型工廠是将繼承的過程封裝,使用繼承業務簡單化
//使sub構造繼承sup構造
function extend(sub,sup){
//繼承原型
sub.prototype = Object.create(sup);
//定義構造函數,防止構造函數位址丢失
sub.prototype.constructor = sub;
}
對象工廠
在原型對象的基礎上可以拓展到對象工廠,即子類的構造函數的建立。
function A(name,age){
this.name = name;
this.age = age;
}
A.prototype.show = function(){
console.log(this.name + " " + this.age);
}
function B(name,age){
let instance = Object.create(A.prototype);
//複用A的構造函數,執行B的執行個體化
A.call(instance,name,age);
//額外添加屬性
instance.newProp = " B ";
return instance;
}
let b = new B("lisi",20);
b.show();//lisi 20
混合模式
JS
不能實作多繼承,如果要使用多個原型的方法時可以使用mixin混合模式來完成。在JS中,使用
Object.assign
來讓需要繼承的多個對象進行合并,以實作需要使用到多個類的方法的情況。