摘要:日常編碼中被開發者用來實現對象冒充,也即顯示綁定。面試題源碼實現,事實上是對基礎知識的一個綜合考核。原型鏈終端指向,不會有構造函數,也不會有等屬性,這些屬性來自。
call/apply/bind 日常編碼中被開發者用來實現 “對象冒充”,也即 “顯示綁定 this“。
面試題:“call/apply/bind源碼實現”,事實上是對 JavaScript 基礎知識的一個綜合考核。
相關知識點:
作用域;
this 指向;
函數柯里化;
原型與原型鏈;
call/apply/bind 的區別三者都可用于顯示綁定 this;
call/apply 的區別方式在于參數傳遞方式的不同;
fn.call(obj, arg1, arg2, ...), 傳參數列表,以逗號隔開;
fn.call(obj, [arg1, arg2, ...]), 傳參數數組;
bind 返回的是一個待執行函數,是函數柯里化的應用,而 call/apply 則是立即執行函數
思路初探Function.prototype.myCall = function(context) { // 原型中 this 指向的是實例對象,所以這里指向 [Function: bar] console.log(this); // [Function: bar] // 在傳入的上下文對象中,創建一個屬性,值指向方法 bar context.fn = this; // foo.fn = [Function: bar] // 調用這個方法,此時調用者是 foo,this 指向 foo context.fn(); // 執行后刪除它,僅使用一次,避免該屬性被其它地方使用(遍歷) delete context.fn; }; let foo = { value: 2 }; function bar() { console.log(this.value); } // bar 函數的聲明等同于:var bar = new Function("console.log(this.value)"); bar.call(foo); // 2;call 的源碼實現
初步思路有個大概,剩下的就是完善代碼。
// ES6 版本 Function.prototype.myCall = function(context, ...params) { // ES6 函數 Rest 參數,使其可指定一個對象,接收函數的剩余參數,合成數組 if (typeof context === "object") { context = context || window; } else { context = Object.create(null); } // 用 Symbol 來作屬性 key 值,保持唯一性,避免沖突 let fn = Symbol(); context[fn] = this; // 將參數數組展開,作為多個參數傳入 const result = context[fn](...params); // 刪除避免永久存在 delete(context[fn]); // 函數可以有返回值 return result; } // 測試 var mine = { name: "以樂之名" } var person = { name: "無名氏", sayHi: function(msg) { console.log("我的名字:" + this.name + ",", msg); } } person.sayHi.myCall(mine, "很高興認識你!"); // 我的名字:以樂之名,很高興認識你!
知識點補充:
ES6 新的原始數據類型 Symbol,表示獨一無二的值;
Object.create(null) 創建一個空對象
// 創建一個空對象的方式 // eg.A let emptyObj = {}; // eg.B let emptyObj = new Object(); // eg.C let emptyObj = Object.create(null);
使用 Object.create(null) 創建的空對象,不會受到原型鏈的干擾。原型鏈終端指向 null,不會有構造函數,也不會有 toString、 hasOwnProperty、valueOf 等屬性,這些屬性來自 Object.prototype。有原型鏈基礎的伙伴們,應該都知道,所有普通對象的原型鏈都會指向 Object.prototype。
所以 Object.create(null) 創建的空對象比其它兩種方式,更干凈,不會有 Object 原型鏈上的屬性。
ES5 版本:
自行處理參數;
自實現 Symobo
// ES5 版本 // 模擬Symbol function getSymbol(obj) { var uniqAttr = "00" + Math.random(); if (obj.hasOwnProperty(uniqAttr)) { // 如果已存在,則遞歸自調用函數 arguments.callee(obj); } else { return uniqAttr; } } Function.prototype.myCall = function() { var args = arguments; if (!args.length) return; var context = [].shift.apply(args); context = context || window; var fn = getSymbol(context); context[fn] = this; // 無其它參數傳入 if (!arguments.length) { return context[fn]; } var param = args[i]; // 類型判斷,不然 eval 運行會出錯 var paramType = typeof param; switch(paramType) { case "string": param = """ + param + """ break; case "object": param = JSON.stringify(param); break; } fnStr += i == args.length - 1 ? param : param + ","; // 借助 eval 執行 var result = eval(fnStr); delete context[fn]; return result; } // 測試 var mine = { name: "以樂之名" } var person = { name: "無名氏", sayHi: function(msg) { console.log("我的名字:" + this.name + ",", msg); } } person.sayHi.myCall(mine, "很高興認識你!"); // 我的名字:以樂之名,很高興認識!apply 的源碼實現
call 的源碼實現,那么 apply 就簡單,兩者只是傳遞參數方式不同而已。
Function.prototype.myApply = function(context, params) { // apply 與 call 的區別,第二個參數是數組,且不會有第三個參數 if (typeof context === "object") { context = context || window; } else { context = Object.create(null); } let fn = Symbol(); context[fn] = this; const result context[fn](...params); delete context[fn]; return result; }bind 的源碼實現
bind 與 call/apply 的區別就是返回的是一個待執行的函數,而不是函數的執行結果;
bind 返回的函數作為構造函數與 new 一起使用,綁定的 this 需要被忽略;
調用綁定函數時作為this參數傳遞給目標函數的值。 如果使用new運算符構造綁定函數,則忽略該值。 —— MDN
Function.prototype.bind = function(context, ...initArgs) { // bind 調用的方法一定要是一個函數 if (typeof this !== "function") { throw new TypeError("not a function"); } let self = this; let F = function() {}; F.prototype = this.prototype; let bound = function(...finnalyArgs) { // 將前后參數合并傳入 return self.call(this instanceof F ? this : context || this, ...initArgs, ...finnalyArgs); } bound.prototype = new F(); return bound; }
不少伙伴還會遇到這樣的追問,不使用 call/apply,如何實現 bind ?
騷年先別慌,不用 call/apply,不就是相當于把 call/apply 換成對應的自我實現方法,算是偷懶取個巧吧。
本篇 call/apply/bind 源碼實現,算是對之前文章系列知識點的一次加深鞏固。
“心中有碼,前路莫慌。”
參考文檔:
MDN - Function.prototype.bind()
不用call和apply方法模擬實現ES5的bind方法
更多前端基石搭建,盡在 Github,期待 Star!
https://github.com/ZengLingYong/blog
作者:以樂之名
本文原創,有不當的地方歡迎指出。轉載請指明出處。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106634.html
摘要:閉包閉包的概念與詞法域關系緊密。閉包甚至在函數已經返回后也可以獲取其外部函數的變量。一種常見的閉包導致的由立即調用函數表達式解決的例子事實上結果的所有都是,而不是按順序得出的,。 介紹 JavaScript 有一個特征————作用域。理解作用域scope可以使你的代碼脫穎而出,減少錯誤,幫助你用它構造強大的設計模式。 什么是作用域 作用域就是在代碼執行期間變量,函數和對象能被獲取到的特...
摘要:不能應用下的等方法。首先我們可以通過給目標函數指定作用域來簡單實現方法保存,即調用方法的目標函數考慮到函數柯里化的情況,我們可以構建一個更加健壯的這次的方法可以綁定對象,也支持在綁定的時候傳參。原因是,在中,多次是無效的。 bind 是返回對應函數,便于稍后調用;apply 、call 則是立即調用 。 apply、call 在 javascript 中,call 和 apply 都是...
摘要:首先我們可以通過給目標函數指定作用域來簡單實現方法保存,即調用方法的目標函數考慮到函數柯里化的情況,我們可以構建一個更加健壯的這次的方法可以綁定對象,也支持在綁定的時候傳參。原因是,在中,多次是無效的。而則會立即執行函數。 bind 是返回對應函數,便于稍后調用;apply 、call 則是立即調用 。 apply、call 在 javascript 中,call 和 apply 都是...
摘要:文章盡量使用大量實例進行講解,它們的使用場景。在嚴格模式下,函數被調用后,里面的默認是后面通過調用函數上的和方法,該變指向,函數里面的指向。利用,可以傳入外層的上下文。同樣適用的還有,里面的對象,它也是一種類數組對象。 call,apply and bind in JavaScript 在ECMAScript中,每個函數都包含兩個繼承而來的方法:apply() 和 call(),這兩個...
摘要:和區別其實他們的作用是一樣的,只是傳遞的參數不一樣而已。接受個參數,第一個參數指定了函數體內對象的指向,第二個參數為數組或者一個類數組??磦€栗子一個有意思的事在中,多次是無效的。而則會立即執行函數。 背景 前兩天在做小程序的需求的時候用到bind的時候才想起自己對這三的東西的了解比較淺薄,這個時候用的時候就有點怕。時候還是要好好學習下,理解下怎么玩。 正文 先說call 和 apply...
閱讀 2556·2021-11-22 12:05
閱讀 3441·2021-10-14 09:42
閱讀 1675·2021-07-28 00:15
閱讀 1982·2019-08-30 11:08
閱讀 1476·2019-08-29 17:31
閱讀 919·2019-08-29 16:42
閱讀 2328·2019-08-26 11:55
閱讀 2108·2019-08-26 11:49