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