W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
?"prototype"
? 屬性在 JavaScript 自身的核心部分中被廣泛地應用。所有的內(nèi)建構造函數(shù)都用到了它。
首先,我們將看看原生原型的詳細信息,然后學習如何使用它為內(nèi)建對象添加新功能。
假如我們輸出一個空對象:
let obj = {};
alert( obj ); // "[object Object]" ?
生成字符串 "[object Object]"
的代碼在哪里?那就是一個內(nèi)建的 toString
方法,但是它在哪里呢?obj
是空的!
……然而簡短的表達式 obj = {}
和 obj = new Object()
是一個意思,其中 Object
就是一個內(nèi)建的對象構造函數(shù),其自身的 prototype
指向一個帶有 toString
和其他方法的一個巨大的對象。
就像這樣:
當 new Object()
被調(diào)用(或一個字面量對象 {...}
被創(chuàng)建),按照前面章節(jié)中我們學習過的規(guī)則,這個對象的 [[Prototype]]
屬性被設置為 Object.prototype
:
所以,之后當 obj.toString()
被調(diào)用時,這個方法是從 Object.prototype
中獲取的。
我們可以這樣驗證它:
let obj = {};
alert(obj.__proto__ === Object.prototype); // true
alert(obj.toString === obj.__proto__.toString); //true
alert(obj.toString === Object.prototype.toString); //true
請注意在 Object.prototype
上方的鏈中沒有更多的 [[Prototype]]
:
alert(Object.prototype.__proto__); // null
其他內(nèi)建對象,像 Array
、Date
、Function
及其他,都在 prototype 上掛載了方法。
例如,當我們創(chuàng)建一個數(shù)組 [1, 2, 3]
,在內(nèi)部會默認使用 new Array()
構造器。因此 Array.prototype
變成了這個數(shù)組的 prototype,并為這個數(shù)組提供數(shù)組的操作方法。這樣內(nèi)存的存儲效率是很高的。
按照規(guī)范,所有的內(nèi)建原型頂端都是 Object.prototype
。這就是為什么有人說“一切都從對象繼承而來”。
下面是完整的示意圖(3 個內(nèi)建對象):
讓我們手動驗證原型:
let arr = [1, 2, 3];
// 它繼承自 Array.prototype?
alert( arr.__proto__ === Array.prototype ); // true
// 接下來繼承自 Object.prototype?
alert( arr.__proto__.__proto__ === Object.prototype ); // true
// 原型鏈的頂端為 null。
alert( arr.__proto__.__proto__.__proto__ ); // null
一些方法在原型上可能會發(fā)生重疊,例如,Array.prototype
有自己的 toString
方法來列舉出來數(shù)組的所有元素并用逗號分隔每一個元素。
let arr = [1, 2, 3]
alert(arr); // 1,2,3 <-- Array.prototype.toString 的結(jié)果
正如我們之前看到的那樣,Object.prototype
也有 toString
方法,但是 Array.prototype
在原型鏈上更近,所以數(shù)組對象原型上的方法會被使用。
瀏覽器內(nèi)的工具,像 Chrome 開發(fā)者控制臺也會顯示繼承性(可能需要對內(nèi)建對象使用 console.dir
):
其他內(nèi)建對象也以同樣的方式運行。即使是函數(shù) —— 它們是內(nèi)建構造器 Function
的對象,并且它們的方法(call
/apply
及其他)都取自 Function.prototype
。函數(shù)也有自己的 toString
方法。
function f() {}
alert(f.__proto__ == Function.prototype); // true
alert(f.__proto__.__proto__ == Object.prototype); // true,繼承自 Object
最復雜的事情發(fā)生在字符串、數(shù)字和布爾值上。
正如我們記憶中的那樣,它們并不是對象。但是如果我們試圖訪問它們的屬性,那么臨時包裝器對象將會通過內(nèi)建的構造器 String
、Number
和 Boolean
被創(chuàng)建。它們提供給我們操作字符串、數(shù)字和布爾值的方法然后消失。
這些對象對我們來說是無形地創(chuàng)建出來的。大多數(shù)引擎都會對其進行優(yōu)化,但是規(guī)范中描述的就是通過這種方式。這些對象的方法也駐留在它們的 prototype 中,可以通過 String.prototype
、Number.prototype
和 Boolean.prototype
進行獲取。
值 ?
null
? 和 ?undefined
? 沒有對象包裝器特殊值
null
和undefined
比較特殊。它們沒有對象包裝器,所以它們沒有方法和屬性。并且它們也沒有相應的原型。
原生的原型是可以被修改的。例如,我們向 String.prototype
中添加一個方法,這個方法將對所有的字符串都是可用的:
String.prototype.show = function() {
alert(this);
};
"BOOM!".show(); // BOOM!
在開發(fā)的過程中,我們可能會想要一些新的內(nèi)建方法,并且想把它們添加到原生原型中。但這通常是一個很不好的想法。
重要:
原型是全局的,所以很容易造成沖突。如果有兩個庫都添加了
String.prototype.show
方法,那么其中的一個方法將被另一個覆蓋。
所以,通常來說,修改原生原型被認為是一個很不好的想法。
在現(xiàn)代編程中,只有一種情況下允許修改原生原型。那就是 polyfilling。
Polyfilling 是一個術語,表示某個方法在 JavaScript 規(guī)范中已存在,但是特定的 JavaScript 引擎尚不支持該方法,那么我們可以通過手動實現(xiàn)它,并用以填充內(nèi)建原型。
例如:
if (!String.prototype.repeat) { // 如果這兒沒有這個方法
// 那就在 prototype 中添加它
String.prototype.repeat = function(n) {
// 重復傳入的字符串 n 次
// 實際上,實現(xiàn)代碼比這個要復雜一些(完整的方法可以在規(guī)范中找到)
// 但即使是不夠完美的 polyfill 也常常被認為是足夠好的
return new Array(n + 1).join(this);
};
}
alert( "La".repeat(3) ); // LaLaLa
在 裝飾器模式和轉(zhuǎn)發(fā),call/apply 一章中,我們討論了方法借用。
那是指我們從一個對象獲取一個方法,并將其復制到另一個對象。
一些原生原型的方法通常會被借用。
例如,如果我們要創(chuàng)建類數(shù)組對象,則可能需要向其中復制一些 ?Array
? 方法。
例如:
let obj = {
0: "Hello",
1: "world!",
length: 2,
};
obj.join = Array.prototype.join;
alert( obj.join(',') ); // Hello,world!
上面這段代碼有效,是因為內(nèi)建的方法 join
的內(nèi)部算法只關心正確的索引和 length
屬性。它不會檢查這個對象是否是真正的數(shù)組。許多內(nèi)建方法就是這樣。
另一種方式是通過將 obj.__proto__
設置為 Array.prototype
,這樣 Array
中的所有方法都自動地可以在 obj
中使用了。
但是如果 obj
已經(jīng)從另一個對象進行了繼承,那么這種方法就不可行了(譯注:因為這樣會覆蓋掉已有的繼承。此處 obj
其實已經(jīng)從 Object
進行了繼承,但是 Array
也繼承自 Object
,所以此處的方法借用不會影響 obj
對原有繼承的繼承,因為 obj
通過原型鏈依舊繼承了 Object
)。請記住,我們一次只能繼承一個對象。
方法借用很靈活,它允許在需要時混合來自不同對象的方法。
Array.prototype
?、?Object.prototype
?、?Date.prototype
? 等)。Number.prototype
?、?String.prototype
? 和 ?Boolean.prototype
?。只有 ?undefined
? 和 ?null
? 沒有包裝器對象。在所有函數(shù)的原型中添加 ?defer(ms)
? 方法,該方法將在 ?ms
? 毫秒后運行該函數(shù)。
當你完成添加后,下面的代碼應該是可執(zhí)行的:
function f() {
alert("Hello!");
}
f.defer(1000); // 1 秒后顯示 "Hello!"
Function.prototype.defer = function(ms) {
setTimeout(this, ms);
};
function f() {
alert("Hello!");
}
f.defer(1000); // 1 秒后顯示 "Hello!"
在所有函數(shù)的原型中添加 ?defer(ms)
? 方法,該方法返回一個包裝器,將函數(shù)調(diào)用延遲 ?ms
? 毫秒。
下面是它應該如何執(zhí)行的例子:
function f(a, b) {
alert( a + b );
}
f.defer(1000)(1, 2); // 1 秒后顯示 3
請注意,參數(shù)應該被傳給原始函數(shù)。
Function.prototype.defer = function(ms) {
let f = this;
return function(...args) {
setTimeout(() => f.apply(this, args), ms);
}
};
// check it
function f(a, b) {
alert( a + b );
}
f.defer(1000)(1, 2); // 1 秒后顯示 3
請注意:我們在 f.apply
中使用 this
以使裝飾器適用于對象方法。
因此,如果將包裝器函數(shù)作為對象方法調(diào)用,那么 this
將會被傳遞給原始方法 f
。
Function.prototype.defer = function(ms) {
let f = this;
return function(...args) {
setTimeout(() => f.apply(this, args), ms);
}
};
let user = {
name: "John",
sayHi() {
alert(this.name);
}
}
user.sayHi = user.sayHi.defer(1000);
user.sayHi();
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: