1. 程式人生 > >javascript 的原型與原型鏈的理解

javascript 的原型與原型鏈的理解

javascript中一切皆物件,但是由於沒有Class類的概念,所以就無法很好的表達物件與物件之間的關係了。

比如物件A與物件B之間,它們兩個是相對獨立的個體,互不干擾,物件A修改自身的屬性不會影響到物件B。

雖然這很好,但是有一個問題,如果物件A與物件B都有一個方法 run() ,並且程式碼也一樣,那物件A與物件B各自都獨立擁有一份 run() 方法的完整程式碼,這是需要資源去儲存的。

一旦我們程式中應用的物件過多,那這種資源消耗會是巨大的。那有沒有一種方法可以讓物件A與物件B擁有一些公共的屬性和方法,讓它們之前有某種聯絡?

我們設想一下,會不會存在一個 common物件,common物件上儲存著公共的屬性和方法,而物件A與物件B裡面有一個prototype屬性指向這個 common物件,

當然我們呼叫物件A或物件B的屬性和方法時,如果在自身物件中沒有找到,就去prototype這個屬性指向的物件上面去找。

而common物件本身也有一個prototype屬性指向更上一級的common物件,然後一直往上找啊找,直到為null,就停止。

這種不斷的從下往上找的這種路徑,就像鏈條一樣,我們稱它為 原型鏈,而那個common物件,我們稱它為 原型物件。

 

我們來看一個建構函式

function Base(name) {
    this.name = name;
}

let A = new Base('A');
let B = new Base('B');

//每一個函式都有一個prototype屬性,指向該函式的原型物件
console.log(Base.prototype);

//當然原型物件也是一個物件,它也有一個constructor,指向建構函式
console.log(Base.prototype.constructor === Base);

//每一個例項物件的constructor都指向建立它們的建構函式
console.log(A.constructor === Base);
console.log(B.constructor === Base);

//每一個例項物件都有一個__proto__屬性,該屬性指向建構函式的原型物件
console.log(A.__proto__ === Base.prototype);
console.log(B.__proto__ === Base.prototype);

1、每一個函式都有一個prototype屬性,它指向該函式的原型物件。

2、原型物件也是物件,它也有自已的constructor,它指向建構函式Base()。換句話說,其實原型物件也是建構函式Base()的一個例項。只不過比較特殊,用來存放公共屬性和方法的。

3、每一個通過建構函式Base()建立的例項物件,都有一個constructor,指向建立它們的建構函式。

4、每一個物件,都有一個 __proto__ 屬性,指向建構函式Base()的 原型物件。換句話說,__proto__ 是將 原型 串聯起來形成鏈條的關鍵。不然物件A與物件B都無法找到原型物件上的公共屬性和方法。

 

function Base(name) {
    this.name = name;
}
//我們在原型物件上新增公共屬性
Base.prototype.status = '開始';
//我們在原型物件上新增公共方法
Base.prototype.run = function() {
    console.log(this.name + ' run ...');
};

let A = new Base('A');
let B = new Base('B');

A.run();
B.run();

console.log(A.status);
console.log(B.status);

//修改原型上的屬性,則例項物件也會跟著改變
Base.prototype.status = '停止';

console.log(A.status);
console.log(B.status);

通過原型與原型鏈,讓物件與物件之間有了關聯關係。

那如何通過原型與原型鏈,讓一個建構函式繼承於另一個建構函式?

比如,我們要讓建構函式Child 繼承於 建構函式Base,只需要讓 Child 的 prototype 指向 Base的 原型物件,不就可以了?

function Base(name) {

}

Base.prototype.name = 'Base';
Base.prototype.run = function () {
    console.log(this.name + ' run ...');
};

function Child() {

}

Child.prototype = Base.prototype;
//注意這個時候,Child.prototype物件的constructor屬性指向了Base
//這就導致通過建構函式Child建立的例項物件,物件的constructor屬性會指向Base,而不是Child,這會導致混亂。
//所以我們重新設定Child.prototype.constructor指向Child
Child.prototype.constructor = Child;

let c = new Child();

console.log(c.name);
c.run();

這樣有一個問題,Child.prototype 與 Base.prototype 指向同一個原型物件,任何對 Child.prototype 的修改都會反應到 Base.prototype 上面。

這時,Base.prototype.constructor 指向了 Child,這顯然是有問題。

我們只能通過一箇中間的空建構函式,來完成原型的指向。

function Base(name) {

}

Base.prototype.name = 'Base';
Base.prototype.run = function () {
    console.log(this.name + ' run ...');
};

function Child() {

}

//建立一箇中間的空建構函式
function Mid() {

}

//讓該空建構函式的prototype指向Base的原型物件
Mid.prototype = Base.prototype;
//再讓Child的prototype指向該空建構函式的一個例項
Child.prototype = new Mid();
//這樣,當修改Child.prototype.constructor時,Base.prototype就不會受影響了
Child.prototype.constructor = Child;

let c = new Child();

console.log(c.name);
c.run();

//Base.prototype的constructor仍然指向Base,沒有受到影響
console.log(Base.prototype.constructor);

  

那怎麼通過原型與原型鏈,讓你一物件繼承於另一個物件呢?

比如,我們要讓物件B繼承於物件A,無非就是想要拿到物件A的屬性和方法,這麼一想,那通過把物件B的 __proto__  指向 物件A,不就可以實現了?

let A = {
    name: 'A',
    run() {
        console.log(this.name + ' run ...');
    }
};
console.log(A.name);
A.run();

let B = {};
//讓物件B的__proto__指向物件A
B.__proto__ = A;
//當物件B呼叫run()方法時會在自身上找,如果沒找到,則通過__proto__向上找
//由於__proto__指向物件A,所以最終會在物件A中找到run()方法
B.run();

B.__proto__.name = 'B';
console.log(A.name);
console.log(B.name);

這樣有一個問題,當修改 B.__proto__.name = 'B'; 時,物件A也會受到影響。

我們可以通過ES5提供的 Object.create() 來解決此問題,Object.create()可以通過指定的 原型物件 建立一個新物件。

let A = {
    name: 'A',
    run() {
        console.log(this.name + ' run ...');
    }
};
console.log(A.name);
A.run();

let B = {};
//通過Object.create()建立一個以物件A為原型物件的新物件
//讓物件B的__proto__指向該新物件
//這樣再操作B.__proto__中的屬性就與物件A無關了。
B.__proto__ = Object.create(A);

B.run();

B.__proto__.name = 'B';
console.log(A.name);
console.log(B.name);