您好,登錄后才能下訂單哦!
這篇文章主要介紹javascript的案例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
我們經常在框架級的源碼中看到類似如下的一句代碼,比如:
var toStr1 = Function.prototype.call.bind(Object.prototype.toString);
在這一句代碼中既使用call方法,同時也使用bind方法,乍看之下,有點暈!這到底是想干嘛?
無妨,我們調用看看,傳入不同的類型試試,效果如下:
console.log(toStr1({})); // "[object Object]"console.log(toStr1([])); // "[object Array]"console.log(toStr1(123)); // "[object Number]"console.log(toStr1("abc")); // "[object String]"console.log(toStr1("abc")); // "[object String]"console.log(toStr1(new Date));// "[object Date]"復制代碼
從結果中可以看到該方法的主要功能是用于檢測對象的類型。但通常類型檢測,我們可能更多地看到如下代碼實現:
var toStr2 = obj => Object.prototype.toString.call(obj);console.log(toStr2({})); // "[object Object]"console.log(toStr2([])); // "[object Array]"console.log(toStr2(123)); // "[object Number]"console.log(toStr2("abc")); // "[object String]"console.log(toStr2("abc")); // "[object String]"console.log(toStr2(new Date));// "[object Date]"復制代碼
熟悉bind和call的同學應該知道,兩種方法本質上是相同的,而第二種方法更簡潔,僅僅使用一次call就能獲得我們想要的功能,且代碼邏輯清晰,理解起來更加容易,可在眾多框架中為何更多使用第一種呢?
其實主要的原因是防止原型污染
,比如我們在業務代碼中覆寫了Object.prototype.toString
方法,第二種寫法將得不到正確的結果,而第一種寫法仍然可以。我們用代碼來來試試:
var toStr1 = Function.prototype.call.bind(Object.prototype.toString);var toStr2 = obj => Object.prototype.toString.call(obj);Object.prototype.toString = function(){ return'toString方法被覆蓋!'; }// 接著我們再調用上述方法// toStr1調用結果如下:console.log(toStr1({})); // "[object Object]"console.log(toStr1([])); // "[object Array]"console.log(toStr1(123)); // "[object Number]"console.log(toStr1("abc")); // "[object String]"console.log(toStr1("abc")); // "[object String]"console.log(toStr1(new Date));// "[object Date]"// toStr2調用結果如下:console.log(toStr2({})); // "toString方法被覆蓋!"console.log(toStr2([])); // "toString方法被覆蓋!"console.log(toStr2(123)); // "toString方法被覆蓋!"console.log(toStr2("abc")); // "toString方法被覆蓋!"console.log(toStr2("abc")); // "toString方法被覆蓋!"console.log(toStr2(new Date));// "toString方法被覆蓋!"復制代碼
結果很明顯。第一種方法仍然能正確得到結果,而第二種則不行!那么為什么會這樣呢?我們知道bind函數返回結果是一個函數,這個函數是函數內部的函數,會被延遲執行,那么很自然聯想到這里可能存在閉包!因為閉包可以保持內部函數執行時的上下文狀態
。不過在現代版瀏覽器中call和bind都已經被js引擎內部實現了,我們沒有辦法調試!但是我們可以通過polly-fill提供的近似實現的源碼來理解引擎內部的邏輯,下面是本文調試的demo,大家可以嘗試下:
// 模擬實現call// ES6實現Function.prototype.mycall = function (context) { context = context ? Object(context) : window; var fn = Symbol(); context[fn] = this; let args = [...arguments].slice(1); let result = context[fn](...args); delete context[fn] return result; }// 模擬實現bindFunction.prototype.mybind = function (context) { if (typeof this !== "function") { throw new Error("請使用函數對象調用我,謝謝!"); } var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () { }; var fBound = function () { var bindArgs = Array.prototype.slice.call(arguments); return self.myapply(this instanceof fNOP ? this : context, args.concat(bindArgs)); } fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }// 模擬實現apply// ES6實現Function.prototype.myapply = function (context, arr) { context = context ? Object(context) : window; var fn = Symbol(); context[fn] = this; let result; if (!arr) { result = context[fn](); } else { result = context[fn](...arr); } delete context[fn] return result; }var toStr1 = Function.prototype.mycall.mybind(Object.prototype.toString);console.log(toStr1({})); // "[object Object]"console.log(toStr1([])); // "[object Array]"console.log(toStr1(123)); // "[object Number]"console.log(toStr1("abc")); // "[object String]"console.log(toStr1(new Date));// "[object Date]"復制代碼
上述的實現略去一些健壯性的代碼,僅保留核心邏輯,具體的實現細節這里不做解釋,有興趣的可以自己研究,從devtools我們看到mybind
形成的閉包確實在函數對象toStr1的作用域上!
當然如果你對原型鏈有深刻理解的話,其實這句有趣的代碼還可以寫成如下方式:
var toStr3 = Function.call.bind(Object.prototype.toString);var toStr4 = Function.call.call.bind(Object.prototype.toString);var toStr5 = Function.call.call.call.bind(Object.prototype.toString);// 甚至可以這么寫。。。var toStr6 = (()=>{}).call.bind(Object.prototype.toString);復制代碼
-END-
以上是javascript的案例分析的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。