天天看點

【JS學習筆記 08】 對象的原型與繼承原型對象

原型與繼承

  • 原型對象
    • 函數的原型
    • 構造函數與原型對象
    • 原型鍊
      • 建立原型鍊
      • 原型檢測
    • 屬性周遊
    • 借用原型
    • 原型工廠
    • 對象工廠
    • 混合模式

原型對象

每個對象都有一個原型

prototype

對象,通過函數建立的對象也将擁有這個原型對象。原型是一個指向對象的指針。

  • 原型類似其他面向對象語言中的父類(基類)
  • 所有對象的原型預設是

    Object

    的執行個體
  • 原型包含

    constructor

    屬性,指向構造函數,是以可以通過一個執行個體的對象找到其構造函數建立另一同類型對象
  • 對象包含

    __proto__

    指向他的原型對象,

    __proto__

    不是對象屬性,了解為

    prototype

    的 getter/setter 實作,他是一個非标準定義
  • 多個對象共同繼承一個原型可以共享原型中的成員,實作代碼複用,可以解決建構函數建立對象時複制多個函數造成的記憶體占用問題
  • 繼承是為了複用代碼,繼承的本質是将原型指向到另一個對象

    預設情況下建立的對象都有原型,下面的代碼展示數組對象的層次,其中包含了變量

    arr

    的原型

    Array

    的成員:
let arr = ["a"];
//dir()可以像檔案目錄一樣列印對象
console.dir(arr);
           

輸出:

【JS學習筆記 08】 對象的原型與繼承原型對象

以下x、y的原型都為元對象

Object

let a = {};
let b = {};
console.log(a,b);
           

輸出:

【JS學習筆記 08】 對象的原型與繼承原型對象

函數的原型

函數比較特殊,

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__);
           

輸出:

【JS學習筆記 08】 對象的原型與繼承原型對象

關系圖如下:

【JS學習筆記 08】 對象的原型與繼承原型對象

構造函數與原型對象

下面是使用構造函數建立對象的原型展現:

  • 構造函數擁有原型對象,利用

    prototype

    屬性通路
  • 構造函數在建立對象時把原型賦予對象
  • 對象原型在配置後可以通過

    constructor

    (指向構造函數)通路構造函數
  • 執行個體對象可以直接通過

    __proto__

    通路對象原型
  • 可以通過執行個體對象的

    constructor

    通路構造函數,但是

    constructor

    本質上是對象原型的屬性
    【JS學習筆記 08】 對象的原型與繼承原型對象
    下面的示例展示利用對象執行個體arr找到其原型對象再利用其構造函數建立一個新的對象
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學習筆記 08】 對象的原型與繼承原型對象

原型鍊

在前面我們可以了解到,JS中大部分的資料類型其實都是對象類型的。如果仔細看上面代碼中

Array

構造的原型其實就是

Object

類型的一個執行個體對象,可以使用

__proto__

屬性通路到

Object

執行

console.log(Array.prototype);

可以在控制台看到

Array

的原型對象中的

__proto__

指向

Obeject

的構造的原型對象

【JS學習筆記 08】 對象的原型與繼承原型對象

同時也就意味可以直接通過

Array.prototype.__proto__

通路

Object

的原型對象

有如下的關系圖:

【JS學習筆記 08】 對象的原型與繼承原型對象

在上面的橙色标記的引用鍊中,通過引用類型的原型,繼承另一個引用類型的屬性與方法,這也是實作繼承的步驟。

建立原型鍊

  • 使用

    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

來讓需要繼承的多個對象進行合并,以實作需要使用到多個類的方法的情況。

繼續閱讀