Javascript F.prototype

2023-02-17 10:52 更新

我們還記得,可以使用諸如 ?new F()? 這樣的構(gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)新對(duì)象。

如果 F.prototype 是一個(gè)對(duì)象,那么 new 操作符會(huì)使用它為新對(duì)象設(shè)置 [[Prototype]]。

請(qǐng)注意:

JavaScript 從一開(kāi)始就有了原型繼承。這是 JavaScript 編程語(yǔ)言的核心特性之一。

但是在過(guò)去,沒(méi)有直接對(duì)其進(jìn)行訪問(wèn)的方式。唯一可靠的方法是本章中會(huì)介紹的構(gòu)造函數(shù)的 "prototype" 屬性。目前仍有許多腳本仍在使用它。

請(qǐng)注意,這里的 F.prototype 指的是 F 的一個(gè)名為 "prototype" 的常規(guī)屬性。這聽(tīng)起來(lái)與“原型”這個(gè)術(shù)語(yǔ)很類似,但這里我們實(shí)際上指的是具有該名字的常規(guī)屬性。

下面是一個(gè)例子:

let animal = {
  eats: true
};

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

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true

設(shè)置 Rabbit.prototype = animal 的字面意思是:“當(dāng)創(chuàng)建了一個(gè) new Rabbit 時(shí),把它的 [[Prototype]] 賦值為 animal”。

這是結(jié)果示意圖:


在上圖中,"prototype" 是一個(gè)水平箭頭,表示一個(gè)常規(guī)屬性,[[Prototype]] 是垂直的,表示 rabbit 繼承自 animal。

?F.prototype? 僅用在 ?new F? 時(shí)

F.prototype 屬性僅在 new F 被調(diào)用時(shí)使用,它為新對(duì)象的 [[Prototype]] 賦值。

如果在創(chuàng)建之后,F.prototype 屬性有了變化(F.prototype = <another object>),那么通過(guò) new F 創(chuàng)建的新對(duì)象也將隨之擁有新的對(duì)象作為 [[Prototype]],但已經(jīng)存在的對(duì)象將保持舊有的值。

默認(rèn)的 F.prototype,構(gòu)造器屬性

每個(gè)函數(shù)都有 "prototype" 屬性,即使我們沒(méi)有提供它。

默認(rèn)的 "prototype" 是一個(gè)只有屬性 constructor 的對(duì)象,屬性 constructor 指向函數(shù)自身。

像這樣:

function Rabbit() {}

/* 默認(rèn)的 prototype
Rabbit.prototype = { constructor: Rabbit };
*/


我們可以檢查一下:

function Rabbit() {}
// 默認(rèn):
// Rabbit.prototype = { constructor: Rabbit }

alert( Rabbit.prototype.constructor == Rabbit ); // true

通常,如果我們什么都不做,constructor 屬性可以通過(guò) [[Prototype]] 給所有 rabbits 使用:

function Rabbit() {}
// 默認(rèn):
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // 繼承自 {constructor: Rabbit}

alert(rabbit.constructor == Rabbit); // true (from prototype)


我們可以使用 constructor 屬性來(lái)創(chuàng)建一個(gè)新對(duì)象,該對(duì)象使用與現(xiàn)有對(duì)象相同的構(gòu)造器。

像這樣:

function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit("White Rabbit");

let rabbit2 = new rabbit.constructor("Black Rabbit");

當(dāng)我們有一個(gè)對(duì)象,但不知道它使用了哪個(gè)構(gòu)造器(例如它來(lái)自第三方庫(kù)),并且我們需要?jiǎng)?chuàng)建另一個(gè)類似的對(duì)象時(shí),用這種方法就很方便。

但是,關(guān)于 "constructor" 最重要的是……

……JavaScript 自身并不能確保正確的 "constructor" 函數(shù)值。

是的,它存在于函數(shù)的默認(rèn) "prototype" 中,但僅此而已。之后會(huì)發(fā)生什么 —— 完全取決于我們。

特別是,如果我們將整個(gè)默認(rèn) prototype 替換掉,那么其中就不會(huì)有 "constructor" 了。

例如:

function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false

因此,為了確保正確的 "constructor",我們可以選擇添加/刪除屬性到默認(rèn) "prototype",而不是將其整個(gè)覆蓋:

function Rabbit() {}

// 不要將 Rabbit.prototype 整個(gè)覆蓋
// 可以向其中添加內(nèi)容
Rabbit.prototype.jumps = true
// 默認(rèn)的 Rabbit.prototype.constructor 被保留了下來(lái)

或者,也可以手動(dòng)重新創(chuàng)建 constructor 屬性:

Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit
};

// 這樣的 constructor 也是正確的,因?yàn)槲覀兪謩?dòng)添加了它

總結(jié)

在本章中,我們簡(jiǎn)要介紹了為通過(guò)構(gòu)造函數(shù)創(chuàng)建的對(duì)象設(shè)置 [[Prototype]] 的方法。稍后我們將看到更多依賴于此的高級(jí)編程模式。

一切都很簡(jiǎn)單,只需要記住幾條重點(diǎn)就可以清晰地掌握了:

  • ?F.prototype? 屬性(不要把它與 ?[[Prototype]]? 弄混了)在 ?new F? 被調(diào)用時(shí)為新對(duì)象的 ?[[Prototype]]? 賦值。
  • ?F.prototype? 的值要么是一個(gè)對(duì)象,要么就是 ?null?:其他值都不起作用。
  • ?"prototype"? 屬性僅當(dāng)設(shè)置在一個(gè)構(gòu)造函數(shù)上,并通過(guò) ?new? 調(diào)用時(shí),才具有這種特殊的影響。

在常規(guī)對(duì)象上,prototype 沒(méi)什么特別的:

let user = {
  name: "John",
  prototype: "Bla-bla" // 這里只是普通的屬性
};

默認(rèn)情況下,所有函數(shù)都有 F.prototype = {constructor:F},所以我們可以通過(guò)訪問(wèn)它的 "constructor" 屬性來(lái)獲取一個(gè)對(duì)象的構(gòu)造器。

任務(wù)


修改 "prototype"

重要程度: 5

在下面的代碼中,我們創(chuàng)建了 new Rabbit,然后嘗試修改它的 prototype。

最初,我們有以下代碼:

function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

alert( rabbit.eats ); // true
  1. 我們?cè)黾恿艘恍写a(* 處)?,F(xiàn)在 alert 會(huì)顯示什么?
  2. function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype = {}; // *
    
    alert( rabbit.eats ); // ?
  3. ……如果代碼是這樣的(修改了一行)?
  4. function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype.eats = false; // *
    
    alert( rabbit.eats ); // ?
  5. 像這樣呢(修改了一行)?
  6. function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete rabbit.eats; // *
    
    alert( rabbit.eats ); // ?
  7. 最后一種變體:
  8. function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete Rabbit.prototype.eats; // *
    
    alert( rabbit.eats ); // ?

解決方案

  1. ?true?
  2. Rabbit.prototype 的賦值操作為新對(duì)象設(shè)置了 [[Prototype]],但它不影響已有的對(duì)象。

  3. ?false?
  4. 對(duì)象通過(guò)引用被賦值。來(lái)自 Rabbit.prototype 的對(duì)象并沒(méi)有被賦值,它仍然是被 Rabbit.prototype 和 rabbit 的 [[Prototype]] 引用的單個(gè)對(duì)象。

    所以當(dāng)我們通過(guò)一個(gè)引用更改其內(nèi)容時(shí),它對(duì)其他引用也是可見(jiàn)的。

  5. ?true?
  6. 所有 delete 操作都直接應(yīng)用于對(duì)象。這里的 delete rabbit.eats 試圖從 rabbit 中刪除 eats 屬性,但 rabbit 對(duì)象并沒(méi)有 eats 屬性。所以這個(gè)操作不會(huì)有任何影響。

  7. ?undefined?
  8. 屬性 eats 被從 prototype 中刪除,prototype 中就沒(méi)有這個(gè)屬性了。


使用相同的構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象

重要程度: 5

想象一下,我們有一個(gè)由構(gòu)造函數(shù)創(chuàng)建的對(duì)象 ?obj? —— 我們不知道使用的是哪個(gè)構(gòu)造函數(shù),但是我們想使用它創(chuàng)建一個(gè)新對(duì)象。

我們可以這樣做嗎?

let obj2 = new obj.constructor();

請(qǐng)給出一個(gè)可以使這樣的代碼正常工作的 obj 的構(gòu)造函數(shù)的例子。再給出會(huì)導(dǎo)致這樣的代碼無(wú)法正確工作的例子。


解決方案

如果我們確信 "constructor" 屬性具有正確的值,那么就可以使用這種方法。

例如,如果我們不觸碰默認(rèn)的 "prototype",那么這段代碼肯定可以正常運(yùn)行:

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

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // Pete (worked!)

它起作用了,因?yàn)?nbsp;User.prototype.constructor == User。

……但是如果有人,重寫(xiě)了 User.prototype,并忘記可重新創(chuàng)建 constructor 以引用 User,那么上面這段代碼就會(huì)運(yùn)行失敗。

例如:

function User(name) {
  this.name = name;
}
User.prototype = {}; // (*)

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // undefined

為什么 user2.name 是 undefined?

這是 new user.constructor('Pete') 的工作流程:

  1. 首先,它在 ?user? 中尋找 ?constructor?。沒(méi)找到。
  2. 然后它追溯原型鏈。?user? 的原型是 ?User.prototype?,它也沒(méi)有 ?constructor?(因?yàn)槲覀儭巴洝痹谟覀?cè)設(shè)定它了)。
  3. 再向上追溯,?User.prototype? 是一個(gè)普通對(duì)象 ?{}?,其原型是 ?Object.prototype?。
  4. 最終,對(duì)于內(nèi)建的 ?Object.prototype?,有一個(gè)內(nèi)建的 ?Object.prototype.constructor == Object?。所以就用它了。

所以,最終我們得到了 let user2 = new Object('Pete')。

可能這不是我們想要的。我們想創(chuàng)建 new User 而不是 new Object。這就是缺少 constructor 的結(jié)果。

(以防你好奇,new Object(...) 調(diào)用會(huì)將其參數(shù)轉(zhuǎn)換為對(duì)象。這是理論上的,在實(shí)際中沒(méi)有人會(huì)調(diào)用 new Object 并傳入一個(gè)值,通常我們也不會(huì)使用 new Object 來(lái)創(chuàng)建對(duì)象)。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)