跟我學(xué)習(xí)javascript的prototype使用注意事項
一、在prototype上保存方法
不使用prototype進行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)建了多個User類型的實例時,就存在問題了:不僅是name和passwordHash屬性在每個實例上都存在,toString和checkPassword方法在每個實例上都有一份拷貝。就像下圖表示的那樣:
但是,當(dāng)toString和checkPassword被定義在prototype上時,上圖就變成下面這個樣子了:
toString和checkPassword方法現(xiàn)在定義在了User.prototype對象上,也就意味著這兩個方法只存在一份拷貝,并被所有的User實例共享。
也許你會認(rèn)為將方法作為拷貝放在每個實例上,會節(jié)省方法查詢的時間。(當(dāng)方法定義在prototype上時,首先會在實例本身上尋找方法,如果沒有找到才會去prototype上繼續(xù)找)
但是在現(xiàn)代的JavaScript執(zhí)行引擎中,對方法的查詢進行了大量優(yōu)化,所以這個查詢時間幾乎是不需要考慮的,那么將方法放在prototype對象上就節(jié)省了很多內(nèi)存。
二、使用閉包來保存私有數(shù)據(jù)
JavaScript的對象系統(tǒng)從其語法上而言并不鼓勵使用信息隱藏(Information Hiding)。因為當(dāng)使用諸如this.name,this.passwordHash的時候,這些屬性默認(rèn)的訪問級別就是public的,在任何位置都能夠通過obj.name,obj.passwordHash來對這些屬性進行訪問。
在ES5環(huán)境中,也提供了一些方法來更方便的訪問一個對象上所有的屬性,比如Object.keys(),Object.getOwnPropertyNames()。所以,一些開發(fā)人員使用一些規(guī)約來定義JavaScript對象的私有屬性,比如最典型的是使用下劃線作為屬性的前綴來告訴其他開發(fā)人員和用戶這個屬性是不應(yīng)該被直接訪問的。
但是這樣做,并不能從根本上解決問題。其他開發(fā)人員和用戶還是能夠?qū)в邢聞澗€的屬性進行直接訪問。對于確實需要私有屬性的場合,可以使用閉包進行實現(xiàn)。
從某種意義而言,在JavaScript中,閉包對于變量的訪問策略和對象的訪問策略是兩個極端。閉包中的任何變量默認(rèn)都是私有的,只有在函數(shù)內(nèi)部才能訪問這些變量。比如,可以將User類型實現(xiàn)如下:
function User(name, passwordHash) { this.toString = function() { return "[User " + name + "]"; }; this.checkPassword = function(password) { return hash(password) === passwordHash; }; }
此時,name和passwordHash都沒有被保存為實例的屬性,而是通過局部變量進行保存。然后根據(jù)閉包的訪問規(guī)則,實例上的方法可以對它們進行訪問,而在其它地方則不能。
使用這種模式的一個缺點是,利用了局部變量的方法都需要被定義在實例本身上,不能講這些方法定義在prototype對象上。正如在Item34中討論的那樣,這樣做的問題是會增加內(nèi)存的消耗。但是在某些特別的場合下,即使將方法定義在實例上也是可行的。
三、實例狀態(tài)只保存在實例對象上
一個類型的prototype和該類型的實例之間是”一對多“的關(guān)系。那么,需要確保實例相關(guān)的數(shù)據(jù)不會被錯誤地保存在prototype之上。比如,對于一個實現(xiàn)了樹結(jié)構(gòu)的類型而言,將它的子節(jié)點保存在該類型的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上時,所有實例的狀態(tài)都會被集中地保存,在上面這種場景中顯然是不正確的:本來屬于每個實例的狀態(tài)被錯誤地共享了。如下圖所示:
正確的實現(xiàn)應(yīng)該是這樣的:
function Tree(x) { this.value = x; this.children = []; // instance state } Tree.prototype = { addChild: function(x) { this.children.push(x); } };
此時,實例狀態(tài)的存儲如下所示:
可見,當(dāng)本屬于實例的狀態(tài)被共享到prototype上時,也許會產(chǎn)生問題。在需要在prototype上保存狀態(tài)屬性前,一定要確保該屬性是能夠被共享的。
總體而言,當(dāng)一個屬性是不可變(無狀態(tài))的屬性時,就能將它保存在prototype對象上(比如方法能夠被保存在prototype對象上就是因為這一點)。當(dāng)然,有狀態(tài)的屬性也能夠被放在prototype對象上,這要取決于具體的應(yīng)用場景,典型的比如用來記錄一個類型實例數(shù)量的變量。使用Java語言作為類比的話,這類能夠存儲在prototype對象上的變量就是Java中的類變量(使用static關(guān)鍵字修飾)。
四、避免繼承標(biāo)準(zhǔn)類型
ECMAScript標(biāo)準(zhǔn)庫不大,但是提供了一些重要的類型如Array,F(xiàn)unction和Date。在一些場合下,你也許會考慮繼承其中的某個類型來實現(xiàn)特定的功能,但是這種做法并不被鼓勵。
比如為了操作一個目錄,可以讓目錄類型繼承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)對象是真正的Array類型時,length屬性才會起作用。
在ECMAScript標(biāo)準(zhǔn)中,定義了一個不可見的內(nèi)部屬性被稱為 [[class]]。該屬性的值只是一個字符串,所以不要被誤導(dǎo)認(rèn)為JavaScript也實現(xiàn)了自己的類型系統(tǒng)。所以,對于Array類型,這個屬性的值就是“Array”;對于Function類型,這個屬性的值就是“Function”。下表是ECMAScript定義的所有[[class]] 值:
那么當(dāng)對象的類型確實是Array時,length屬性的特別之處就在于:length的值會和該對象中被索引的屬性個數(shù)保持一致。比如對于一個數(shù)組對象arr,arr[0]和arr[1]就表示該對象有兩個被索引的屬性,那么length的值就是2。當(dāng)添加了arr[2]的時候,length的值會被自動同步成3。同樣地,當(dāng)設(shè)置length值為2時,arr[2]會被自動設(shè)置成undefined。
但是當(dāng)繼承Array類型并創(chuàng)建實例時,該實例的 [[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]"
因此,更好的實現(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屬性來實現(xiàn),該屬性的值是一個Array類型對象。
ECMAScript標(biāo)準(zhǔn)庫中,大部分的構(gòu)造函數(shù)都會依賴內(nèi)部屬性值如 [[class]] 來實現(xiàn)正確的行為。對于繼承這些標(biāo)準(zhǔn)類型的子類型,無法保證它們的行為是正確的。因此,不要繼承ECMAScript標(biāo)準(zhǔn)庫中的類型如:
Array, Boolean, Date, Function, Number,RegExp,String
以上就是對使用prototype的幾點注意事項進行總結(jié),希望可以幫助大家正確的使用prototype。
- 數(shù)據(jù)排序誰最快(javascript中的Array.prototype.sort PK 快速排序)
- 比較詳細(xì)的javascript對象的property和prototype是什么一種關(guān)系
- 不錯的一篇關(guān)于javascript-prototype繼承
- javascript prototype的深度探索不是原型繼承那么簡單
- javascript Prototype 對象擴展
- javascript prototype原型操作筆記
- JavaScript isPrototypeOf和hasOwnProperty使用區(qū)別
- JavaScript prototype對象的屬性說明
- JavaScript中的prototype使用說明
- JavaScript為對象原型prototype添加屬性的兩種方式
- javascript中的prototype屬性使用說明(函數(shù)功能擴展)
- JavaScript面向?qū)ο笾甈rototypes和繼承
- 深入了解javascript中的prototype與繼承
- JavaScript prototype 使用介紹
- JavaScript中的prototype.bind()方法介紹
- Javascript中Array.prototype.map()詳解
- JavaScript設(shè)計模式之原型模式(Object.create與prototype)介紹
- JavaScript通過prototype給對象定義屬性用法實例
- 跟我學(xué)習(xí)javascript的prototype,getPrototypeOf和__proto__
- 跟我學(xué)習(xí)javascript的prototype原型和原型鏈
相關(guān)文章
完美兼容各大瀏覽器獲取HTTP_REFERER方法總結(jié)
發(fā)現(xiàn)一個關(guān)于瀏覽器兼容的問題,當(dāng)用JS 執(zhí)行代碼 window.location.href=”http://www.dbjr.com.cn” 來進行跳轉(zhuǎn)的時候,F(xiàn)irefox 可以獲取到到HTTP_REFERER頁面,但是在IE中這一項為空2014-06-06詳解微信小程序?qū)崿F(xiàn)仿微信聊天界面(各種細(xì)節(jié)處理)
這篇文章主要介紹了詳解微信小程序?qū)崿F(xiàn)仿微信聊天界面(各種細(xì)節(jié)處理),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02正則表達(dá)式判斷是否存在中文和全角字符和判斷包含中文字符串長度
對于一些更安全的容錯嚴(yán)重,需要用到2008-09-09JS實現(xiàn)動畫兼容性的transition和transform實例分析
這篇文章主要介紹了JS實現(xiàn)動畫兼容性的transition和transform方法,結(jié)合實例形式分析了transition和transform方法針對手機端瀏覽器兼容性問題上的相關(guān)處理技巧,需要的朋友可以參考下2016-12-12JS創(chuàng)建或填充任意長度數(shù)組的小技巧匯總
在JavaScript 中,我們往往會遇到需要使用某些默認(rèn)值來填充數(shù)組的情況,那么都有哪些方式可以完成這樣的任務(wù)呢?這篇文章主要給大家介紹了關(guān)于JS創(chuàng)建或填充任意長度數(shù)組的小技巧,需要的朋友可以參考下2021-10-10javascript常用方法、屬性集合及NodeList 和 HTMLCollection 的瀏覽器差異
對于 HTMLCollection集合對象 必須要說一說的是 namedItem方法. 看看規(guī)范的解釋.2010-12-12JavaScript實現(xiàn)向setTimeout執(zhí)行代碼傳遞參數(shù)的方法
這篇文章主要介紹了JavaScript實現(xiàn)向setTimeout執(zhí)行代碼傳遞參數(shù)的方法,分析了向setTimeout傳遞參數(shù)的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-04-04