W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
我們還記得,可以使用諸如 ?new F()
? 這樣的構造函數(shù)來創(chuàng)建一個新對象。
如果 F.prototype
是一個對象,那么 new
操作符會使用它為新對象設置 [[Prototype]]
。
請注意:
JavaScript 從一開始就有了原型繼承。這是 JavaScript 編程語言的核心特性之一。
但是在過去,沒有直接對其進行訪問的方式。唯一可靠的方法是本章中會介紹的構造函數(shù)的
"prototype"
屬性。目前仍有許多腳本仍在使用它。
請注意,這里的 F.prototype
指的是 F
的一個名為 "prototype"
的常規(guī)屬性。這聽起來與“原型”這個術語很類似,但這里我們實際上指的是具有該名字的常規(guī)屬性。
下面是一個例子:
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
設置 Rabbit.prototype = animal
的字面意思是:“當創(chuàng)建了一個 new Rabbit
時,把它的 [[Prototype]]
賦值為 animal
”。
這是結果示意圖:
在上圖中,"prototype"
是一個水平箭頭,表示一個常規(guī)屬性,[[Prototype]]
是垂直的,表示 rabbit
繼承自 animal
。
?
F.prototype
? 僅用在 ?new F
? 時
F.prototype
屬性僅在new F
被調用時使用,它為新對象的[[Prototype]]
賦值。
如果在創(chuàng)建之后,
F.prototype
屬性有了變化(F.prototype = <another object>
),那么通過new F
創(chuàng)建的新對象也將隨之擁有新的對象作為[[Prototype]]
,但已經(jīng)存在的對象將保持舊有的值。
每個函數(shù)都有 "prototype"
屬性,即使我們沒有提供它。
默認的 "prototype"
是一個只有屬性 constructor
的對象,屬性 constructor
指向函數(shù)自身。
像這樣:
function Rabbit() {}
/* 默認的 prototype
Rabbit.prototype = { constructor: Rabbit };
*/
我們可以檢查一下:
function Rabbit() {}
// 默認:
// Rabbit.prototype = { constructor: Rabbit }
alert( Rabbit.prototype.constructor == Rabbit ); // true
通常,如果我們什么都不做,constructor
屬性可以通過 [[Prototype]]
給所有 rabbits 使用:
function Rabbit() {}
// 默認:
// Rabbit.prototype = { constructor: Rabbit }
let rabbit = new Rabbit(); // 繼承自 {constructor: Rabbit}
alert(rabbit.constructor == Rabbit); // true (from prototype)
我們可以使用 constructor
屬性來創(chuàng)建一個新對象,該對象使用與現(xiàn)有對象相同的構造器。
像這樣:
function Rabbit(name) {
this.name = name;
alert(name);
}
let rabbit = new Rabbit("White Rabbit");
let rabbit2 = new rabbit.constructor("Black Rabbit");
當我們有一個對象,但不知道它使用了哪個構造器(例如它來自第三方庫),并且我們需要創(chuàng)建另一個類似的對象時,用這種方法就很方便。
但是,關于 "constructor"
最重要的是……
……JavaScript 自身并不能確保正確的 "constructor"
函數(shù)值。
是的,它存在于函數(shù)的默認 "prototype"
中,但僅此而已。之后會發(fā)生什么 —— 完全取決于我們。
特別是,如果我們將整個默認 prototype 替換掉,那么其中就不會有 "constructor"
了。
例如:
function Rabbit() {}
Rabbit.prototype = {
jumps: true
};
let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false
因此,為了確保正確的 "constructor"
,我們可以選擇添加/刪除屬性到默認 "prototype"
,而不是將其整個覆蓋:
function Rabbit() {}
// 不要將 Rabbit.prototype 整個覆蓋
// 可以向其中添加內容
Rabbit.prototype.jumps = true
// 默認的 Rabbit.prototype.constructor 被保留了下來
或者,也可以手動重新創(chuàng)建 constructor
屬性:
Rabbit.prototype = {
jumps: true,
constructor: Rabbit
};
// 這樣的 constructor 也是正確的,因為我們手動添加了它
在本章中,我們簡要介紹了為通過構造函數(shù)創(chuàng)建的對象設置 [[Prototype]]
的方法。稍后我們將看到更多依賴于此的高級編程模式。
一切都很簡單,只需要記住幾條重點就可以清晰地掌握了:
F.prototype
? 屬性(不要把它與 ?[[Prototype]]
? 弄混了)在 ?new F
? 被調用時為新對象的 ?[[Prototype]]
? 賦值。F.prototype
? 的值要么是一個對象,要么就是 ?null
?:其他值都不起作用。"prototype"
? 屬性僅當設置在一個構造函數(shù)上,并通過 ?new
? 調用時,才具有這種特殊的影響。在常規(guī)對象上,prototype
沒什么特別的:
let user = {
name: "John",
prototype: "Bla-bla" // 這里只是普通的屬性
};
默認情況下,所有函數(shù)都有 F.prototype = {constructor:F}
,所以我們可以通過訪問它的 "constructor"
屬性來獲取一個對象的構造器。
在下面的代碼中,我們創(chuàng)建了 new Rabbit
,然后嘗試修改它的 prototype。
最初,我們有以下代碼:
function Rabbit() {}
Rabbit.prototype = {
eats: true
};
let rabbit = new Rabbit();
alert( rabbit.eats ); // true
alert
會顯示什么?function Rabbit() {}
Rabbit.prototype = {
eats: true
};
let rabbit = new Rabbit();
Rabbit.prototype = {}; // *
alert( rabbit.eats ); // ?
function Rabbit() {}
Rabbit.prototype = {
eats: true
};
let rabbit = new Rabbit();
Rabbit.prototype.eats = false; // *
alert( rabbit.eats ); // ?
function Rabbit() {}
Rabbit.prototype = {
eats: true
};
let rabbit = new Rabbit();
delete rabbit.eats; // *
alert( rabbit.eats ); // ?
function Rabbit() {}
Rabbit.prototype = {
eats: true
};
let rabbit = new Rabbit();
delete Rabbit.prototype.eats; // *
alert( rabbit.eats ); // ?
true
?Rabbit.prototype
的賦值操作為新對象設置了 [[Prototype]]
,但它不影響已有的對象。
false
?對象通過引用被賦值。來自 Rabbit.prototype
的對象并沒有被賦值,它仍然是被 Rabbit.prototype
和 rabbit
的 [[Prototype]]
引用的單個對象。
所以當我們通過一個引用更改其內容時,它對其他引用也是可見的。
true
?所有 delete
操作都直接應用于對象。這里的 delete rabbit.eats
試圖從 rabbit
中刪除 eats
屬性,但 rabbit
對象并沒有 eats
屬性。所以這個操作不會有任何影響。
undefined
?屬性 eats
被從 prototype 中刪除,prototype 中就沒有這個屬性了。
重要程度: 5
想象一下,我們有一個由構造函數(shù)創(chuàng)建的對象 ?obj
? —— 我們不知道使用的是哪個構造函數(shù),但是我們想使用它創(chuàng)建一個新對象。
我們可以這樣做嗎?
let obj2 = new obj.constructor();
請給出一個可以使這樣的代碼正常工作的 obj
的構造函數(shù)的例子。再給出會導致這樣的代碼無法正確工作的例子。
如果我們確信 "constructor"
屬性具有正確的值,那么就可以使用這種方法。
例如,如果我們不觸碰默認的 "prototype"
,那么這段代碼肯定可以正常運行:
function User(name) {
this.name = name;
}
let user = new User('John');
let user2 = new user.constructor('Pete');
alert( user2.name ); // Pete (worked!)
它起作用了,因為 User.prototype.constructor == User
。
……但是如果有人,重寫了 User.prototype
,并忘記可重新創(chuàng)建 constructor
以引用 User
,那么上面這段代碼就會運行失敗。
例如:
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')
的工作流程:
user
? 中尋找 ?constructor
?。沒找到。user
? 的原型是 ?User.prototype
?,它也沒有 ?constructor
?(因為我們“忘記”在右側設定它了)。User.prototype
? 是一個普通對象 ?{}
?,其原型是 ?Object.prototype
?。Object.prototype
?,有一個內建的 ?Object.prototype.constructor == Object
?。所以就用它了。所以,最終我們得到了 let user2 = new Object('Pete')
。
可能這不是我們想要的。我們想創(chuàng)建 new User
而不是 new Object
。這就是缺少 constructor
的結果。
(以防你好奇,new Object(...)
調用會將其參數(shù)轉換為對象。這是理論上的,在實際中沒有人會調用 new Object
并傳入一個值,通常我們也不會使用 new Object
來創(chuàng)建對象)。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: