跟我學(xué)習(xí)javascript的prototype使用注意事項(xiàng)
一、在prototype上保存方法
不使用prototype進(jìn)行JavaScript的編碼是完全可行的,例如:
function User(name, passwordHash) {
this.name = name;
this.passwordHash = passwordHash;
this.toString = function() {
return "[User " + this.name + "]";
};
this.checkPassword = function(password) {
return hash(password) === this.passwordHash;
};
}
var u1 = new User(/* ... */);
var u2 = new User(/* ... */);
var u3 = new User(/* ... */);
當(dāng)創(chuàng)建了多個(gè)User類型的實(shí)例時(shí),就存在問(wèn)題了:不僅是name和passwordHash屬性在每個(gè)實(shí)例上都存在,toString和checkPassword方法在每個(gè)實(shí)例上都有一份拷貝。就像下圖表示的那樣:

但是,當(dāng)toString和checkPassword被定義在prototype上時(shí),上圖就變成下面這個(gè)樣子了:

toString和checkPassword方法現(xiàn)在定義在了User.prototype對(duì)象上,也就意味著這兩個(gè)方法只存在一份拷貝,并被所有的User實(shí)例共享。
也許你會(huì)認(rèn)為將方法作為拷貝放在每個(gè)實(shí)例上,會(huì)節(jié)省方法查詢的時(shí)間。(當(dāng)方法定義在prototype上時(shí),首先會(huì)在實(shí)例本身上尋找方法,如果沒(méi)有找到才會(huì)去prototype上繼續(xù)找)
但是在現(xiàn)代的JavaScript執(zhí)行引擎中,對(duì)方法的查詢進(jìn)行了大量?jī)?yōu)化,所以這個(gè)查詢時(shí)間幾乎是不需要考慮的,那么將方法放在prototype對(duì)象上就節(jié)省了很多內(nèi)存。
二、使用閉包來(lái)保存私有數(shù)據(jù)
JavaScript的對(duì)象系統(tǒng)從其語(yǔ)法上而言并不鼓勵(lì)使用信息隱藏(Information Hiding)。因?yàn)楫?dāng)使用諸如this.name,this.passwordHash的時(shí)候,這些屬性默認(rèn)的訪問(wèn)級(jí)別就是public的,在任何位置都能夠通過(guò)obj.name,obj.passwordHash來(lái)對(duì)這些屬性進(jìn)行訪問(wèn)。
在ES5環(huán)境中,也提供了一些方法來(lái)更方便的訪問(wèn)一個(gè)對(duì)象上所有的屬性,比如Object.keys(),Object.getOwnPropertyNames()。所以,一些開(kāi)發(fā)人員使用一些規(guī)約來(lái)定義JavaScript對(duì)象的私有屬性,比如最典型的是使用下劃線作為屬性的前綴來(lái)告訴其他開(kāi)發(fā)人員和用戶這個(gè)屬性是不應(yīng)該被直接訪問(wèn)的。
但是這樣做,并不能從根本上解決問(wèn)題。其他開(kāi)發(fā)人員和用戶還是能夠?qū)в邢聞澗€的屬性進(jìn)行直接訪問(wèn)。對(duì)于確實(shí)需要私有屬性的場(chǎng)合,可以使用閉包進(jìn)行實(shí)現(xiàn)。
從某種意義而言,在JavaScript中,閉包對(duì)于變量的訪問(wèn)策略和對(duì)象的訪問(wèn)策略是兩個(gè)極端。閉包中的任何變量默認(rèn)都是私有的,只有在函數(shù)內(nèi)部才能訪問(wèn)這些變量。比如,可以將User類型實(shí)現(xiàn)如下:
function User(name, passwordHash) {
this.toString = function() {
return "[User " + name + "]";
};
this.checkPassword = function(password) {
return hash(password) === passwordHash;
};
}
此時(shí),name和passwordHash都沒(méi)有被保存為實(shí)例的屬性,而是通過(guò)局部變量進(jìn)行保存。然后根據(jù)閉包的訪問(wèn)規(guī)則,實(shí)例上的方法可以對(duì)它們進(jìn)行訪問(wèn),而在其它地方則不能。
使用這種模式的一個(gè)缺點(diǎn)是,利用了局部變量的方法都需要被定義在實(shí)例本身上,不能講這些方法定義在prototype對(duì)象上。正如在Item34中討論的那樣,這樣做的問(wèn)題是會(huì)增加內(nèi)存的消耗。但是在某些特別的場(chǎng)合下,即使將方法定義在實(shí)例上也是可行的。
三、實(shí)例狀態(tài)只保存在實(shí)例對(duì)象上
一個(gè)類型的prototype和該類型的實(shí)例之間是”一對(duì)多“的關(guān)系。那么,需要確保實(shí)例相關(guān)的數(shù)據(jù)不會(huì)被錯(cuò)誤地保存在prototype之上。比如,對(duì)于一個(gè)實(shí)現(xiàn)了樹(shù)結(jié)構(gòu)的類型而言,將它的子節(jié)點(diǎn)保存在該類型的prototype上就是不正確的:
function Tree(x) {
this.value = x;
}
Tree.prototype = {
children: [], // should be instance state!
addChild: function(x) {
this.children.push(x);
}
};
var left = new Tree(2);
left.addChild(1);
left.addChild(3);
var right = new Tree(6);
right.addChild(5);
right.addChild(7);
var top = new Tree(4);
top.addChild(left);
top.addChild(right);
top.children; // [1, 3, 5, 7, left, right]
當(dāng)狀態(tài)被保存到了prototype上時(shí),所有實(shí)例的狀態(tài)都會(huì)被集中地保存,在上面這種場(chǎng)景中顯然是不正確的:本來(lái)屬于每個(gè)實(shí)例的狀態(tài)被錯(cuò)誤地共享了。如下圖所示:

正確的實(shí)現(xiàn)應(yīng)該是這樣的:
function Tree(x) {
this.value = x;
this.children = []; // instance state
}
Tree.prototype = {
addChild: function(x) {
this.children.push(x);
}
};
此時(shí),實(shí)例狀態(tài)的存儲(chǔ)如下所示:

可見(jiàn),當(dāng)本屬于實(shí)例的狀態(tài)被共享到prototype上時(shí),也許會(huì)產(chǎn)生問(wèn)題。在需要在prototype上保存狀態(tài)屬性前,一定要確保該屬性是能夠被共享的。
總體而言,當(dāng)一個(gè)屬性是不可變(無(wú)狀態(tài))的屬性時(shí),就能將它保存在prototype對(duì)象上(比如方法能夠被保存在prototype對(duì)象上就是因?yàn)檫@一點(diǎn))。當(dāng)然,有狀態(tài)的屬性也能夠被放在prototype對(duì)象上,這要取決于具體的應(yīng)用場(chǎng)景,典型的比如用來(lái)記錄一個(gè)類型實(shí)例數(shù)量的變量。使用Java語(yǔ)言作為類比的話,這類能夠存儲(chǔ)在prototype對(duì)象上的變量就是Java中的類變量(使用static關(guān)鍵字修飾)。
四、避免繼承標(biāo)準(zhǔn)類型
ECMAScript標(biāo)準(zhǔn)庫(kù)不大,但是提供了一些重要的類型如Array,F(xiàn)unction和Date。在一些場(chǎng)合下,你也許會(huì)考慮繼承其中的某個(gè)類型來(lái)實(shí)現(xiàn)特定的功能,但是這種做法并不被鼓勵(lì)。
比如為了操作一個(gè)目錄,可以讓目錄類型繼承Array類型如下:
function Dir(path, entries) {
this.path = path;
for (var i = 0, n = entries.length; i < n; i++) {
this[i] = entries[i];
}
}
Dir.prototype = Object.create(Array.prototype);
// extends Array
var dir = new Dir("/tmp/mysite", ["index.html", "script.js", "style.css"]);
dir.length; // 0
但是可以發(fā)現(xiàn),dir.length的值是0,而不是期待中的3。
發(fā)生這種現(xiàn)象的原因在于:只有當(dāng)對(duì)象是真正的Array類型時(shí),length屬性才會(huì)起作用。
在ECMAScript標(biāo)準(zhǔn)中,定義了一個(gè)不可見(jiàn)的內(nèi)部屬性被稱為 [[class]]。該屬性的值只是一個(gè)字符串,所以不要被誤導(dǎo)認(rèn)為JavaScript也實(shí)現(xiàn)了自己的類型系統(tǒng)。所以,對(duì)于Array類型,這個(gè)屬性的值就是“Array”;對(duì)于Function類型,這個(gè)屬性的值就是“Function”。下表是ECMAScript定義的所有[[class]] 值:
那么當(dāng)對(duì)象的類型確實(shí)是Array時(shí),length屬性的特別之處就在于:length的值會(huì)和該對(duì)象中被索引的屬性個(gè)數(shù)保持一致。比如對(duì)于一個(gè)數(shù)組對(duì)象arr,arr[0]和arr[1]就表示該對(duì)象有兩個(gè)被索引的屬性,那么length的值就是2。當(dāng)添加了arr[2]的時(shí)候,length的值會(huì)被自動(dòng)同步成3。同樣地,當(dāng)設(shè)置length值為2時(shí),arr[2]會(huì)被自動(dòng)設(shè)置成undefined。
但是當(dāng)繼承Array類型并創(chuàng)建實(shí)例時(shí),該實(shí)例的 [[class]] 屬性并不是Array,而是Object。因此length屬性不能正確的工作。
在JavaScript中,也提供了用于查詢 [[class]] 屬性的方法,即使用Object.prototype.toString方法:
var dir = new Dir("/", []);
Object.prototype.toString.call(dir); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
因此,更好的實(shí)現(xiàn)方法是使用組合而不是繼承:
function Dir(path, entries) {
this.path = path;
this.entries = entries; // array property
}
Dir.prototype.forEach = function(f, thisArg) {
if (typeof thisArg === "undefined") {
thisArg = this;
}
this.entries.forEach(f, thisArg);
};
以上代碼將不再使用繼承,而是將一部分功能代理給內(nèi)部的entries屬性來(lái)實(shí)現(xiàn),該屬性的值是一個(gè)Array類型對(duì)象。
ECMAScript標(biāo)準(zhǔn)庫(kù)中,大部分的構(gòu)造函數(shù)都會(huì)依賴內(nèi)部屬性值如 [[class]] 來(lái)實(shí)現(xiàn)正確的行為。對(duì)于繼承這些標(biāo)準(zhǔn)類型的子類型,無(wú)法保證它們的行為是正確的。因此,不要繼承ECMAScript標(biāo)準(zhǔn)庫(kù)中的類型如:
Array, Boolean, Date, Function, Number,RegExp,String
以上就是對(duì)使用prototype的幾點(diǎn)注意事項(xiàng)進(jìn)行總結(jié),希望可以幫助大家正確的使用prototype。
- 數(shù)據(jù)排序誰(shuí)最快(javascript中的Array.prototype.sort PK 快速排序)
- 比較詳細(xì)的javascript對(duì)象的property和prototype是什么一種關(guān)系
- 不錯(cuò)的一篇關(guān)于javascript-prototype繼承
- javascript prototype的深度探索不是原型繼承那么簡(jiǎn)單
- javascript Prototype 對(duì)象擴(kuò)展
- javascript prototype原型操作筆記
- JavaScript isPrototypeOf和hasOwnProperty使用區(qū)別
- JavaScript prototype對(duì)象的屬性說(shuō)明
- JavaScript中的prototype使用說(shuō)明
- JavaScript為對(duì)象原型prototype添加屬性的兩種方式
- javascript中的prototype屬性使用說(shuō)明(函數(shù)功能擴(kuò)展)
- JavaScript面向?qū)ο笾甈rototypes和繼承
- 深入了解javascript中的prototype與繼承
- JavaScript prototype 使用介紹
- JavaScript中的prototype.bind()方法介紹
- Javascript中Array.prototype.map()詳解
- JavaScript設(shè)計(jì)模式之原型模式(Object.create與prototype)介紹
- JavaScript通過(guò)prototype給對(duì)象定義屬性用法實(shí)例
- 跟我學(xué)習(xí)javascript的prototype,getPrototypeOf和__proto__
- 跟我學(xué)習(xí)javascript的prototype原型和原型鏈
相關(guān)文章
Javascript 實(shí)現(xiàn)計(jì)算器時(shí)間功能詳解及實(shí)例(二)
這篇文章主要介紹了Javascript 實(shí)現(xiàn)計(jì)算器時(shí)間功能詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-01-01
JavaScript數(shù)組去重算法實(shí)例小結(jié)
這篇文章主要介紹了JavaScript數(shù)組去重算法,結(jié)合實(shí)例形式總結(jié)分析了JavaScript數(shù)組去重相關(guān)的讀寫(xiě)、遍歷、比較、排序等操作及算法改進(jìn)相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-05-05
js/jQuery簡(jiǎn)單實(shí)現(xiàn)選項(xiàng)卡功能
本篇文章主要是對(duì)js/jQuery簡(jiǎn)單實(shí)現(xiàn)選項(xiàng)卡功能的示例代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01
關(guān)于JavaScript中parseInt()的一個(gè)怪異行為解決
parseInt()是內(nèi)置的?JS?函數(shù),用于解析數(shù)字字符串中的整數(shù),下面這篇文章主要給大家介紹了關(guān)于JavaScript中parseInt()的一個(gè)怪異行為解決,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12
js取兩個(gè)數(shù)組的交集|差集|并集|補(bǔ)集|去重示例代碼
求兩個(gè)集合的補(bǔ)集、交集、差集、并集等等在實(shí)際應(yīng)用中經(jīng)常會(huì)使用到,下面與大家分享下具體的實(shí)現(xiàn)代碼,感興趣的朋友可以參考下,希望對(duì)大家有所幫助2013-08-08
JavaScript中find()和?filter()方法的區(qū)別小結(jié)
js中find和filter方法大家在工作中會(huì)經(jīng)常遇到,那么他們有什么區(qū)別呢?這篇文章主要給大家介紹了關(guān)于JavaScript中find()和?filter()方法區(qū)別的相關(guān)資料,需要的朋友可以參考下2021-12-12
JavaScript中鏈?zhǔn)秸{(diào)用之研習(xí)
方法鏈一般適合對(duì)一個(gè)對(duì)象進(jìn)行連續(xù)操作(集中在一句代碼)。一定程度上可以減少代碼量,缺點(diǎn)是它占用了函數(shù)的返回值。2011-04-04

