自從學會call/apply/bind這三個方法後我就各類場合各類使用各類駕輕就熟至今還沒踩過什麼坑,怎麼用?說直白點就是我本身的對象沒有某個方法但別人有,我就能夠經過call/apply/bind去調用執行別人家的方法,不太懂具體用法的同窗可移至MDN學習一下Function.prototype.call() Function.prototype.apply() Function.prototype.bind() ,本文不講解使用,可是這三個方法並非萬能的,並不必定會執行你想要的那個函數,由於可能要看函數中上下文對象的某些屬性特性值容許不容許改變,今天這個坑我就深深踩了一下,並探索了一番。之因此說並不必定是由於確實有部分類數組能夠傳入call/apply/bind且成功調用,好比arguments(雖然它是[object Arguments]類型的,但__proto__卻指向Object.prototype),感興趣的能夠試試。數組
事件原由:
在學習HTML5與類相關的擴充其中classList屬性時候,我瞭解到全部元素有classList這個屬性,而且這個屬性是新集合DOMTokenList的實例,在DOMTokenList.prototype上有一個add(value)方法,用來將給定字符串值添加到DOMTokenList實例列表中,而且即時反應到文檔頁面。若是在列表中value值已存在,就不添加了。我手賤尋思着本身用JS代碼把這個add方法大概實現一下(確定沒人家JS引擎實現的好,我就實現個思路),但就這一實現才發現的這個坑。
(1).先來看這個add方法JS引擎給它設置的屬性特性值如何,是可寫的:
(2).而後實現咱們的add函數app
Object.defineProperty(DOMTokenList.prototype, 'add', { configureable : true, enumerable : true, writable : true, value : function(value){ if([].indexOf.call(this, value)>=0) return; //加到classList類數組中 [].push.call(this, value); } });
是否是感受沒什麼錯,我就是想把'classA'加入到DOMTokenList實例列表中,然而當測試後控制檯會報錯
分析探討:
這個報錯內容爲"類型錯誤:不能設置改變[object Object]的那個特性值只有getter的length屬性"。意思就是[object Object]就是這裏的DOMTokenList.prototype,你換成childNodes它也一樣會給你提示出錯。
這裏push方法由於會改變數組的長度length,並且不止push,pop,unshift,shift的執行也會改變數組長度,經測試它們會報出一樣的錯。這裏的DOMTokenList.prototype.length獲取它的特性值爲:
length的訪問器屬性中set特性被引擎設置爲undefiend了,怪不得不能將value加到document.body.classList類數組中,由於引擎沒有給你提供set的接口函數啊,只給你提供get特性函數返回length長度。而咱們日常使用的這個不會報錯是由於length屬性的value值是可變的,注意這裏實際上是每一個數組實例都有本身的length屬性。
不過好在DOMTokenList.prototype的length屬性configurable特性是true,意味着我能夠本身寫length的set函數。如今整個過程就是:當實現我本身的add函數,由於add函數中調用到push操做,當執行push操做時會更新DOMTokenList.prototype.length(自動調用set函數),我能夠在set函數中執行相關處理,先拿console測試一下是否是這個流程函數
Object.defineProperty(DOMTokenList.prototype, 'length', { set : function(){console.log('執行了')} })
確實是的,並且如今不報錯了。可是我如今作的只是皮毛,'classA'尚未加到document.body.classList中去呢,看看列表裏還只是"document":
不知道push函數引擎是怎麼實現的,這說明JS引擎好像就沒執行push函數,此方法行不通...不清楚它爲何不執行push操做??
那仍是乖乖轉化爲數組再處理吧。常見的是將類數組轉化爲真正的數組再作相關處理,方法挺多好比Array.from(),Array.prototype.slice.call()等等,不過這會改變原來類數組的類型啊,我就想問怎麼樣處理能給類數組添加項而不改變類數組的類型。數組實例的類型由它的__proto__決定,那就好辦了,不過得先設置classList屬性的一些特性項,JS引擎給的set是undefiend
最後重寫下來爲:學習
Object.defineProperty(DOMTokenList.prototype, 'add', { configureable : true, enumerable : true, writable : true, value : function(value){ if([].indexOf.call(this, value)>=0) return; //加到classList類數組中 var newarr = Array.from(this); newarr.push(value); newarr.__proto__ = DOMTokenList.prototype; document.body.classList = newarr; } }); Object.defineProperty(Element.prototype, 'classList', { set: function(value){ console.log(value); //這裏怎麼處理不一樣元素的classList值是JS引擎的事,我實在是不會了 } })
不過這樣寫有點固定具體元素了繼續重寫爲:測試
Object.defineProperty(DOMTokenList.prototype, 'add', { configureable : true, enumerable : true, writable : true, value : function(value, ele){ if([].indexOf.call(this, value)>=0) return; //加到classList類數組中 var newarr = Array.from(this); newarr.push(value); newarr.__proto__ = DOMTokenList.prototype; ele.classList = newarr; } }); Object.defineProperty(Element.prototype, 'classList', { set: function(value){ console.log(value); //這裏怎麼處理不一樣元素的classList值是JS引擎的事,我實在是不會了 } }); //使用 document.body.classList.add('classA', document.body);//["classA"]
總結:
受限於JS引擎中元素實例的某些屬性是共享於其原型屬性的屬性值的set函數,雖然最後也沒有所有實現add函數的功能,我是實在不知道JS引擎中怎麼實現的set,get函數從而保證不一樣實例共享原型上同一屬性並且還保證不一樣實例的該屬性值不同,這估計得看引擎源碼了,心累...有知道的同窗能夠說一下思路嗎??還有對於我上面的分析部分我有兩點疑問:知道的大神求科普!!
(1).JS引擎好像就沒執行push函數,此方法行不通...不清楚它爲何不執行push操做
(2).怎麼樣處理能給類數組添加項而不改變類數組的類型
this