JavaScript—從數組的indexOf方法深入——Object的Property機制。


JavaScript—從數組的indexOf方法深入——Object的Property機制。

在js中,可以說萬物皆對象(object),一個數組也是一個對象(array)。

 

很多對象都有很多很方便的方法 比如數組的push,concat,slice等等,但是如果一些對象,它沒有實現這些方法,我們還是想使用這些功能。那該怎么辦呢?

 

1、很多方法都提供了非常高效的實現, 我們可以仿照它們的實現。

 

比如IE8以下的瀏覽器不支持Array的indexOf方法,為了讓數組支持indexOf,我們可以自己寫一個方法,實現indexOf方法:

(用IE瀏覽器調試 按F12,可以選擇瀏覽器版本到IE5。)

 

復制代碼
 1             var arr = [1, 2, 3, 5];  2 if (Array.prototype.indexOf) {  3 alert("你的瀏覽器支持indexOf方法。");  4 } else {  5 alert("你的瀏覽器不支持indexOf方法。");  6  }  7 if (!Array.prototype.indexOf) {  8 Array.prototype.indexOf = function(item) {  9 for (var i = 0; i < this.length; i++) { 10 if(this[i]==item){ 11 return i; 12  } 13  } 14 return -1; 15  } 16  } 17 alert(arr.indexOf(2)); 18 alert(arr.indexOf(8));
復制代碼

 

 當然這個方法是很垃圾的。在這里具體的實現 我就不獻丑了,提供一個百度上copy的版本:

有興趣的話可以看看v8引擎是怎么實現的:https://github.com/v8/v8/blob/master/src/js/array.js

復制代碼
 1 if (!Array.prototype.indexOf)  2 {  3 Array.prototype.indexOf = function(elt /*, from*/)  4  {  5 var len = this.length >>> 0;  6 var from = Number(arguments[1]) || 0;  7 from = (from < 0)  8 ? Math.ceil(from)  9  : Math.floor(from); 10 if (from < 0) 11 from += len; 12 for (; from < len; from++) 13  { 14 if (from in this && 15 this[from] === elt) 16 return from; 17  } 18 return -1; 19  }; 20 }
復制代碼

 

2、繼承——call和apply方法

如果我們每有一個對象,那每個對象就要自己寫一遍實現是不是很麻煩?

在高級語言中,我們可以用繼承來解決問題,比如下面的java代碼:

復制代碼
 1 public class MyList<E> extends ArrayList<E>  2 {  3  4 public void myAdd(E e){  5 super.add(e);  6 System.out.println("Add:"+e);  7  8  }  9 10 }
復制代碼

但是js中沒有繼承的概念啊,我們可以用call和apply來解決這樣的問題。

上面的代碼就可以改寫為:

復制代碼
 1             var myObject = function(){  2  3  4  }  5 myObject.prototype.add = function(){  6 Array.prototype.push.call(this,arguments);  7 //輸出arguments  8 for(var i=0;i<arguments.length;i++){  9 console.log("Add:"+arguments[i]); 10  } 11 12  } 13 var obj = new myObject(); 14 obj.add(1,2,3);
復制代碼

 

這里可以看到:雖然用高級語言的繼承方式實現了myAdd方法,但是現在myAdd方法只能傳一個參數,如果要傳多個參數,則需要再寫一個 public void myAdd(E[] e)方法,甚至是public void myAdd(List<E> e)方法。而JS用一個方法就可以搞定,用arguments對象表示輸入的所有參數,這是高級語言難以做到的。

(ps,其實在java中可以寫public void myAdd(E... e),這個是不定參數,用法上public void myAdd(E[] e)是一樣的)

 

call和apply方法用於改變函數內this指針的指向,call只有兩個參數,而apply通常是知道參數個數之后才使用的,下面以例子說明:

復制代碼
            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后面只能跟一個“數組”,包括了所有的參數。而apply則是一顆語法糖,如果知道參數的個數,用apply將很方便。

上面的object也可以是null或者undefined,這樣,這個object就是global object(window),例如,還是接着上例:

var name = 'goo'; alert(obj1.getName.call(null));

(在嚴格模式下,由於全局對象是null,故會拋出異常:Uncaught TypeError: Cannot read property 'name' of null)

 

 3、Object.defineProperty

(注意:不要在IE8以下使用該類特性)

 

微軟:將屬性添加到對象,或修改現有屬性的特性。

getter、setter,

其實js中對於對象的屬性也有getter和setter函數,不過個人覺得js中的getter和setter更像C#一些。

例如下面的代碼就定義了一個getter/setter:

復制代碼
            function myobj(){  } Object.defineProperty(myobj.prototype,'length',{ get:function(){ return this.length_; //這里不能是length。  }, set:function(value){ return this.length_=value; } });
復制代碼

注釋的地方不能是length,否則會無限遞歸。

 

也可以去掉set,讓length變量只讀。

復制代碼
            Object.defineProperty(myobj.prototype,'length',{
                get:function(){ return this.length_; //這里不能是length。  }, /*set:function(value){ return this.length_=value; }*/ }); myobj.length = 3;
復制代碼

這個代碼會拋出異常:Uncaught TypeError: Cannot set property length of #<myobj> which has only a getter。

 

要讓對象的屬性只讀,還可以用writable:false,

            Object.defineProperty(myobj.prototype,'length',{
                                
                writable:false });

writable:false不能與get set共存,否則會拋出Type Error。

configurable:是否能用delete語句刪除,但是configurable屬性好像在嚴格模式下才有效,這樣的代碼在非嚴格模式下仍然能執行:(嚴格模式報錯)

復制代碼
            Object.defineProperty(myobj.prototype,'length',{
                                
                
                configurable:false }); var obj = new myobj(); delete obj.length;
復制代碼

value:指定該對象的固定值。value:10,表示這個對象初始值為10.

 

在非嚴格模式下,這樣的代碼不會報錯,嚴格模式下會報錯:

復制代碼
            Object.defineProperty(myobj.prototype,'length',{
                                
                writable:false, value:'10' }); var obj = new myobj(); obj.length = 100;
復制代碼

 

可以用getOwnPropertyDescriptor來獲取並修改這些值,比如說,現在我的length屬性是只讀的。

 

運行這樣的代碼,結果卻報錯了:

復制代碼
 1             Object.defineProperty(myobj.prototype,'length',{  2  3 value:10,  4 writable:false,  5  6  7  8  });  9 var descriptor = Object.getOwnPropertyDescriptor(myobj.prototype, 10 "length"); 11 12 descriptor.writable = true; 13 Object.defineProperty(myobj.prototype,'length',descriptor);
復制代碼

Uncaught TypeError: Cannot redefine property: length

 

這是因為configurable的默認值是false,在調用了defineProperty之后,configurable就具有false屬性,這樣就不能逆轉了。以后就不能改了。

所以必須使用 configurable:true,這個對象屬性才是可以修改的,完整的代碼如下:

 

復制代碼
 1             Object.defineProperty(myobj.prototype,'length',{  2  3 value:10,  4 writable:false,  5 configurable:true  6  7  8  });  9 var descriptor = Object.getOwnPropertyDescriptor(myobj.prototype, 10 "length"); 11 12 descriptor.writable = true; 13 Object.defineProperty(myobj.prototype,'length',descriptor); 14 myobj.prototype.length = 100; 15 var obj = new myobj(); 16 alert(obj.length);
復制代碼

可以加上一句descriptor.configurable = false;

表示這個屬性我修改了,以后你們都不能再修改了

 

這個特性在很多時候也有用,數組Array的push pop等方法,如果使用call、apply,要求對象的length可變。如果對象的length屬性只讀,那么調用call、apply時,會拋出異常。

就比如DOMTokenList對象,它的length就是不可以變的。我拿到了一個DOM對象DOMTokenList,

 

 

但是它的configurable是true,我們可以修改讓它的length屬性可以變啊:

 

 

看見沒,這個configurable是true,而setter是undefined,我們給它寫一個set方法,不就可以了嗎?

            var descriptor = Object.getOwnPropertyDescriptor(DOMTokenList.prototype,'length'); descriptor.set = function(value){ this.length = value; } Object.defineProperty(DOMTokenList.prototype,'length',descriptor);

然后運行,

又拋出了一個異常,Uncaught RangeError: Maximum call stack size exceeded(…)

這是因為,我們在set this.length時,它會在我們寫的那個set方法中無限遞歸。

因此,我們需要使用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)方法用於把arguments對象轉換為數組。

 

 

 

參考文檔:

https://msdn.microsoft.com/zh-cn/library/dd548687

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

http://www.tuicool.com/articles/ju26riE


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM