JavaScript從數(shù)組的indexOf()深入之Object的Property機(jī)制
在JavaScript中,數(shù)組可以使用Array構(gòu)造函數(shù)來(lái)創(chuàng)建,或使用[]快速創(chuàng)建,這也是首選的方法。數(shù)組是繼承自O(shè)bject的原型,并且他對(duì)typeof沒(méi)有特殊的返回值,他只返回'object'。
js中,可以說(shuō)萬(wàn)物皆對(duì)象(object),一個(gè)數(shù)組也是一個(gè)對(duì)象(array)。
很多對(duì)象都有很多很方便的方法 比如數(shù)組的push,concat,slice等等,但是如果一些對(duì)象,它沒(méi)有實(shí)現(xiàn)這些方法,我們還是想使用這些功能。那該怎么辦呢?
1、很多方法都提供了非常高效的實(shí)現(xiàn), 我們可以仿照它們的實(shí)現(xiàn)。
比如IE8以下的瀏覽器不支持Array的indexOf方法,為了讓數(shù)組支持indexOf,我們可以自己寫(xiě)一個(gè)方法,實(shí)現(xiàn)indexOf方法:
(用IE瀏覽器調(diào)試 按F12,可以選擇瀏覽器版本到IE5。)
var arr = [, , , ]; if (Array.prototype.indexOf) { alert("你的瀏覽器支持indexOf方法。"); } else { alert("你的瀏覽器不支持indexOf方法。"); } if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(item) { for (var i = ; i < this.length; i++) { if(this[i]==item){ return i; } } return -; } } alert(arr.indexOf()); alert(arr.indexOf());
當(dāng)然這個(gè)方法是很垃圾的。在這里具體的實(shí)現(xiàn) 我就不獻(xiàn)丑了,提供一個(gè)百度上copy的版本:
有興趣的話可以看看v8引擎是怎么實(shí)現(xiàn)的:https://github.com/v8/v8/blob/master/src/js/array.js
if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(elt /*, from*/) { var len = this.length >>> ; var from = Number(arguments[]) || ; from = (from < ) ? Math.ceil(from) : Math.floor(from); if (from < ) from += len; for (; from < len; from++) { if (from in this && this[from] === elt) return from; } return -; }; }
2、繼承——call和apply方法
如果我們每有一個(gè)對(duì)象,那每個(gè)對(duì)象就要自己寫(xiě)一遍實(shí)現(xiàn)是不是很麻煩?
在高級(jí)語(yǔ)言中,我們可以用繼承來(lái)解決問(wèn)題,比如下面的java代碼:
public class MyList<E> extends ArrayList<E> { public void myAdd(E e){ super.add(e); System.out.println("Add:"+e); } }
但是js中沒(méi)有繼承的概念啊,我們可以用call和apply來(lái)解決這樣的問(wèn)題。
上面的代碼就可以改寫(xiě)為:
var myObject = function(){ } myObject.prototype.add = function(){ Array.prototype.push.call(this,arguments); //輸出arguments for(var i=;i<arguments.length;i++){ console.log("Add:"+arguments[i]); } } var obj = new myObject(); obj.add(,,);
這里可以看到:雖然用高級(jí)語(yǔ)言的繼承方式實(shí)現(xiàn)了myAdd方法,但是現(xiàn)在myAdd方法只能傳一個(gè)參數(shù),如果要傳多個(gè)參數(shù),則需要再寫(xiě)一個(gè)public void myAdd(E[] e)方法,甚至是public void myAdd(List<E> e)方法。而JS用一個(gè)方法就可以搞定,用arguments對(duì)象表示輸入的所有參數(shù),這是高級(jí)語(yǔ)言難以做到的。
(ps,其實(shí)在java中可以寫(xiě)public void myAdd(E... e),這個(gè)是不定參數(shù),用法上public void myAdd(E[] e)是一樣的)
call和apply方法用于改變函數(shù)內(nèi)this指針的指向,call只有兩個(gè)參數(shù),而apply通常是知道參數(shù)個(gè)數(shù)之后才使用的,下面以例子說(shuō)明:
var Obj = function(name){ this.name = name; } Obj.prototype.getName = function(){ return this.name; } var obj1 =new Obj("zou"); var obj2 = {name:'andy'}; var name = obj1.getName.call(obj2); alert(name);
參考是:
apply(object,arg1,arg2,....)
call(object,[arg1,arg2,....])
call后面只能跟一個(gè)“數(shù)組”,包括了所有的參數(shù)。而apply則是一顆語(yǔ)法糖,如果知道參數(shù)的個(gè)數(shù),用apply將很方便。
上面的object也可以是null或者undefined,這樣,這個(gè)object就是global object(window),例如,還是接著上例:
var name = 'goo'; alert(obj1.getName.call(null)); (在嚴(yán)格模式下,由于全局對(duì)象是null,故會(huì)拋出異常:Uncaught TypeError: Cannot read property 'name' of null)
3、Object.defineProperty
(注意:不要在IE8以下使用該類特性)
微軟:將屬性添加到對(duì)象,或修改現(xiàn)有屬性的特性。
getter、setter,
其實(shí)js中對(duì)于對(duì)象的屬性也有g(shù)etter和setter函數(shù),不過(guò)個(gè)人覺(jué)得js中的getter和setter更像C#一些。
例如下面的代碼就定義了一個(gè)getter/setter:
function myobj(){ } Object.defineProperty(myobj.prototype,'length',{ get:function(){ return this.length_; //這里不能是length。 }, set:function(value){ return this.length_=value; } });
注釋的地方不能是length,否則會(huì)無(wú)限遞歸。
也可以去掉set,讓length變量只讀。
Object.defineProperty(myobj.prototype,'length',{ get:function(){ return this.length_; //這里不能是length。 }, /*set:function(value){ return this.length_=value; }*/ }); myobj.length = 3;
這個(gè)代碼會(huì)拋出異常:Uncaught TypeError: Cannot set property length of #<myobj> which has only a getter。
要讓對(duì)象的屬性只讀,還可以用writable:false,
Object.defineProperty(myobj.prototype,'length',{ writable:false });
writable:false不能與get set共存,否則會(huì)拋出Type Error。
configurable:是否能用delete語(yǔ)句刪除,但是configurable屬性好像在嚴(yán)格模式下才有效,這樣的代碼在非嚴(yán)格模式下仍然能執(zhí)行:(嚴(yán)格模式報(bào)錯(cuò))
Object.defineProperty(myobj.prototype,'length',{ configurable:false }); var obj = new myobj(); delete obj.length;
value:指定該對(duì)象的固定值。value:10,表示這個(gè)對(duì)象初始值為10.
在非嚴(yán)格模式下,這樣的代碼不會(huì)報(bào)錯(cuò),嚴(yán)格模式下會(huì)報(bào)錯(cuò):
Object.defineProperty(myobj.prototype,'length',{ writable:false, value:'10' }); var obj = new myobj(); obj.length = 100;
可以用getOwnPropertyDescriptor來(lái)獲取并修改這些值,比如說(shuō),現(xiàn)在我的length屬性是只讀的。
運(yùn)行這樣的代碼,結(jié)果卻報(bào)錯(cuò)了:
Object.defineProperty(myobj.prototype,'length',{ value:, writable:false, }); var descriptor = Object.getOwnPropertyDescriptor(myobj.prototype, "length"); descriptor.writable = true; Object.defineProperty(myobj.prototype,'length',descriptor); Uncaught TypeError: Cannot redefine property: length
這是因?yàn)閏onfigurable的默認(rèn)值是false,在調(diào)用了defineProperty之后,configurable就具有false屬性,這樣就不能逆轉(zhuǎn)了。以后就不能改了。
所以必須使用 configurable:true,這個(gè)對(duì)象屬性才是可以修改的,完整的代碼如下:
Object.defineProperty(myobj.prototype,'length',{ value:, writable:false, configurable:true }); var descriptor = Object.getOwnPropertyDescriptor(myobj.prototype, "length"); descriptor.writable = true; Object.defineProperty(myobj.prototype,'length',descriptor); myobj.prototype.length = ; var obj = new myobj(); alert(obj.length);
可以加上一句descriptor.configurable = false;
表示這個(gè)屬性我修改了,以后你們都不能再修改了
這個(gè)特性在很多時(shí)候也有用,數(shù)組Array的push pop等方法,如果使用call、apply,要求對(duì)象的length可變。如果對(duì)象的length屬性只讀,那么調(diào)用call、apply時(shí),會(huì)拋出異常。
就比如DOMTokenList對(duì)象,它的length就是不可以變的。我拿到了一個(gè)DOM對(duì)象DOMTokenList,
但是它的configurable是true,我們可以修改讓它的length屬性可以變啊:
看見(jiàn)沒(méi),這個(gè)configurable是true,而setter是undefined,我們給它寫(xiě)一個(gè)set方法,不就可以了嗎?
var descriptor = Object.getOwnPropertyDescriptor(DOMTokenList.prototype,'length'); descriptor.set = function(value){ this.length = value; } Object.defineProperty(DOMTokenList.prototype,'length',descriptor);
然后運(yùn)行,
又拋出了一個(gè)異常,Uncaught RangeError: Maximum call stack size exceeded(…)
這是因?yàn)?,我們?cè)趕et this.length時(shí),它會(huì)在我們寫(xiě)的那個(gè)set方法中無(wú)限遞歸。
因此,我們需要使用delete消除length屬性的影響,也就是:
var descriptor = Object.getOwnPropertyDescriptor(DOMTokenList.prototype,'length'); descriptor.set = function(value){ delete DOMTokenList.prototype.length; this.length = value; } Object.defineProperty(DOMTokenList.prototype,'length',descriptor);
這樣,DOMTokenList也就支持了push,pop等等操作了。
Array.prototype.push.call(document.body.classList,'abc')
然后再行封裝
DOMTokenList.prototype.push = function(){ Array.prototype.push.call(document.body.classList,Array.prototype.slice.call(arguments)); }
Array.prototype.slice.call(arguments)方法用于把a(bǔ)rguments對(duì)象轉(zhuǎn)換為數(shù)組。
相關(guān)文章
基于JavaScript實(shí)現(xiàn)類似于百度學(xué)術(shù)高級(jí)檢索功能
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)類似于百度學(xué)術(shù)高級(jí)檢索功能 的相關(guān)資料,需要的朋友可以參考下2016-03-03JavaScript實(shí)現(xiàn)三級(jí)聯(lián)動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)三級(jí)聯(lián)動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07針對(duì)JavaScript中this指向的簡(jiǎn)單理解
這篇文章主要為大家詳細(xì)JavaScript中this指向的簡(jiǎn)單理解,感興趣的小伙伴們可以參考一下2016-08-08JS實(shí)現(xiàn)的車(chē)標(biāo)圖片提示效果代碼
這篇文章主要介紹了JS實(shí)現(xiàn)的車(chē)標(biāo)圖片提示效果代碼,涉及JavaScript鼠標(biāo)事件觸發(fā)頁(yè)面元素遍歷修改的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10JavaScript實(shí)現(xiàn)為事件句柄綁定監(jiān)聽(tīng)函數(shù)的方法分析
這篇文章主要介紹了JavaScript實(shí)現(xiàn)為事件句柄綁定監(jiān)聽(tīng)函數(shù)的方法,結(jié)合實(shí)例形式分析了javascript事件綁定的常用技巧與注意事項(xiàng),需要的朋友可以參考下2017-11-11document.execCommand()的用法小結(jié)
本篇文章主要是對(duì)document.execCommand()的用法進(jìn)行了詳細(xì)的介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01