摘要:模擬和模擬一樣,現(xiàn)摘抄下面的代碼添加一個返回值對象然后我們定義一個函數(shù),如果執(zhí)行下面的代碼能夠返回和函數(shù)一樣的值,就達到我們的目的。
原文:https://zhehuaxuan.github.io/...目的
作者:zhehuaxuan
本文主要用于理解和掌握call,apply和bind的使用和原理,本文適用于對它們的用法不是很熟悉,或者想搞清楚它們原理的童鞋。
好,那我們開始!
在JavaScript中有三種方式來改變this的作用域call,apply和bind。我們先來看看它們是怎么用的,只有知道怎么用的,我們才能來模擬它。
首先是Function.prototype.call(),不熟的童鞋請猛戳MDN,它是這么說的:call()允許為不同的對象分配和調用屬于一個對象的函數(shù)/方法。也就是說:一個函數(shù),只要調用call()方法,就可以把它分配給不同的對象。
如果還是不明白,不急!跟我往下看,我們先來寫一個call()函數(shù)最簡單的用法:
function source(){ console.log(this.name); //打印 xuan } let destination = { name:"xuan" }; console.log(source.call(destination));
上述代碼會打印出destination的name屬性,也就是說source()函數(shù)通過調用call(),source()函數(shù)中的this對象可以分配到destination對象中。類似于實現(xiàn)destination.source()的效果,當然前提是destination要有一個source屬性
好,現(xiàn)在大家應該明白call()的基本用法,我們再來看下面的例子:
function source(age,gender){ console.log(this.name); console.log(age); console.log(gender); } let destination = { name:"xuan" }; console.log(source.call(destination,18,"male"));
打印效果如下:
我們可以看到可以call()也可以傳參,而且是以參數(shù),參數(shù),...的形式傳入。
上述我們知道call()的兩個作用:
1.改變this的指向2.支持對函數(shù)傳參
我們看到最后還還輸出一個undefined,說明現(xiàn)在調用source.call(…args)沒有返回值。
我們給source函數(shù)添加一個返回值試一下:
function source(age,gender){ console.log(this.name); console.log(age); console.log(gender); //添加一個返回值對象 return { age:age, gender:gender, name:this.name } } let destination = { name:"xuan" }; console.log(source.call(destination,18,"male"));
打印結果:
果不其然!call()函數(shù)的返回值就是source函數(shù)的返回值,那么call()函數(shù)的作用已經很明顯了。
這邊再總結一下:
模擬Function.prototype.call()改變this的指向
支持對函數(shù)傳參
函數(shù)返回什么,call就返回什么。
根據call()函數(shù)的作用,我們下面一步一步的進行模擬。我們先把上面的部分代碼摘抄下來:
function source(age,gender){ console.log(this.name); console.log(age); console.log(gender); //添加一個返回值對象 return { age:age, gender:gender, name:this.name } } let destination = { name:"xuan" };
上面的這部分代碼我們先不變。現(xiàn)在只要實現(xiàn)一個函數(shù)call1()并使用下面方式
console.log(source.call1(destination));
如果得出的結果和call()函數(shù)一樣,那就沒問題了。
現(xiàn)在我們來模擬第一步:改變this的指向。
假設我們destination的結構是這樣的:
let destination = { name:"xuan", source:function(age,gender){ console.log(this.name); console.log(age); console.log(gender); //添加一個返回值對象 return { age:age, gender:gender, name:this.name } } }
我們執(zhí)行destination.source(18,"male");就可以在source()函數(shù)中把正確的結果打印出來并且返回我們想要的值。
現(xiàn)在我們的目的更明確了:給destination對象添加一個source屬性,然后添加參數(shù)執(zhí)行它。
所以我們定義如下:
Function.prototype.call1 = function(ctx){ ctx.fn = this; //ctx為destination this指向source 那么就是destination.fn = source; ctx.fn(); // 執(zhí)行函數(shù) delete ctx.fn; //在刪除這個屬性 } console.log(source.call1(destination,18,"male"));
打印效果如下:
我們發(fā)現(xiàn)this的指向已經改變了,但是我們傳入的參數(shù)還沒有處理。
第二步:支持對函數(shù)傳參。
我們使用ES6語法修改如下:
Function.prototype.call1 =function(ctx,...args){ ctx.fn = this; ctx.fn(...args); delete ctx.fn; } console.log(source.call1(destination,18,"male"));
打印效果如下:
參數(shù)出現(xiàn)了,現(xiàn)在就剩下返回值了,很簡單,我們再修改一下:
Function.prototype.call1 =function(ctx,...args){ ctx.fn = this || window; //防止ctx為null的情況 let res = ctx.fn(...args); delete ctx.fn; return res; } console.log(source.call1(destination,18,"male"));
打印效果如下:
現(xiàn)在我們實現(xiàn)了call的效果!
模擬Function.prototype.apply()apply()函數(shù)的作用和call()函數(shù)一樣,只是傳參的方式不一樣。apply的用法可以查看MDN,MDN這么說的:apply() 方法調用一個具有給定this值的函數(shù),以及作為一個數(shù)組(或類似數(shù)組對象)提供的參數(shù)。
apply()函數(shù)的第二個參數(shù)是一個數(shù)組,數(shù)組是調用apply()的函數(shù)的參數(shù)。
function source(age,gender){ console.log(this.name); console.log(age); console.log(gender); return { age:age, gender:gender, name:this.name } } let destination = { name:"xuan" }; console.log(source.apply(destination,[18,"male"]));
效果和call()是一樣的。既然只是傳參不一樣,我們把模擬call()函數(shù)的代碼稍微改改:
Function.prototype.apply1 =function(ctx,args=[]){ ctx.fn = this || window; let res = ctx.fn(...args); delete ctx.fn; return res; } console.log(source.apply1(destination,[18,"male"]));
執(zhí)行效果如下:
apply()函數(shù)的模擬完成。
Function.prototype.bind()對于bind()函數(shù)的作用,我們引用MDN,bind()方法會創(chuàng)建一個新函數(shù)。當這個新函數(shù)被調用時,bind() 的第一個參數(shù)將作為它運行時的 this對象,之后的一序列參數(shù)將會在傳遞的實參前傳入作為它的參數(shù)。我們看一下代碼:
function source(age,gender){ console.log(this.name); console.log(age); console.log(gender); return { age:age, gender:gender, name:this.name } } let destination = { name:"xuan" }; var res = source.bind(destination,18,"male"); console.log(res()); console.log("==========================") var res1 = source.bind(destination,18); console.log(res1("male")); console.log("==========================") var res2 = source.bind(destination); console.log(res2(18,"male"));
打印效果如下:
我們發(fā)現(xiàn)bind函數(shù)跟apply和call有兩個區(qū)別:
1.bind返回的是函數(shù),雖然也有call和apply的作用,但是需要在調用bind()時生效2.bind中也可以添加參數(shù)
明白了區(qū)別,下面我們來模擬bind函數(shù)。
模擬Function.prototype.bind()和模擬call一樣,現(xiàn)摘抄下面的代碼:
function source(age,gender){ console.log(this.name); console.log(age); console.log(gender); //添加一個返回值對象 return { age:age, gender:gender, name:this.name } } let destination = { name:"xuan" };
然后我們定義一個函數(shù)bind1,如果執(zhí)行下面的代碼能夠返回和bind函數(shù)一樣的值,就達到我們的目的。
var res = source.bind1(destination,18); console.log(res("male"));
首先我們定義一個bind1函數(shù),因為返回值是一個函數(shù),所以我們可以這么寫:
Function.prototype.bind1 = function(ctx,...args){ var that = this;//外層的this指向通過變量傳進去 return function(){ //將外層函數(shù)的參數(shù)和內層函數(shù)的參數(shù)合并 var all_args = [...args].concat([...arguments]); //因為ctx是外層的this指針,在外層我們使用一個變量that引用進來 return that.apply(ctx,all_args); } }
打印效果如下:
這里我們利用閉包,把外層函數(shù)的ctx和參數(shù)args傳到內層函數(shù),再將內外傳遞的參數(shù)合并,然后使用apply()或call()函數(shù),將其返回。
當我們調用res("male")時,因為外層ctx和args還是會存在內存當中,所以調用時,前面的ctx也就是source,args也就是18,再將傳入的"male"跟18合并[18,"male"],執(zhí)行source.apply(destination,[18,"male"]);返回函數(shù)結果即可。bind()的模擬完成!
但是bind除了上述用法,還可以有如下用法:
function source(age,gender){ console.log(this.name); console.log(age); console.log(gender); //添加一個返回值對象 return { age:age, gender:gender, name:this.name } } let destination = { name:"xuan" }; var res = source.bind1(destination,18); var person = new res("male"); console.log(person);
打印效果如下:
我們發(fā)現(xiàn)bind函數(shù)支持new關鍵字,調用的時候this的綁定失效了,那么new之后,this指向哪里呢?我們來試一下,代碼如下:
function source(age,gender){ console.log(this); } let destination = { name:"xuan" }; var res = source.bind(destination,18); console.log(new res("male")); console.log(res("male"));
執(zhí)行new的時候,我們發(fā)現(xiàn)雖然bind的第一個參數(shù)是destination,但是this是指向source的。
不用new的話,this指向destination。
好,現(xiàn)在再來回顧一下我們的bind1實現(xiàn):
Function.prototype.bind1 = function(ctx,...args){ var that = this; return function(){ //將外層函數(shù)的參數(shù)和內層函數(shù)的參數(shù)合并 var all_args = [...args].concat([...arguments]); //因為ctx是外層的this指針,在外層我們使用一個變量that引用進來 return that.apply(ctx,all_args); } }
如果我們使用:
var res = source.bind(destination,18); console.log(new res("male"));
如果執(zhí)行上述代碼,我們的ctx還是destination,也就是說這個時候下面的source函數(shù)中的ctx還是指向destination。而根據Function.prototype.bind的用法,這時this應該是指向source自身。
我們先把部分代碼抄下來:
function source(age,gender){ console.log(this.name); console.log(age); console.log(gender); //添加一個返回值對象 return { age:age, gender:gender, name:this.name } } let destination = { name:"xuan" };
我們改一下bind1函數(shù):
Function.prototype.bind1 = function (ctx, ...args) { var that = this;//that肯定是source //定義了一個函數(shù) let f = function () { //將外層函數(shù)的參數(shù)和內層函數(shù)的參數(shù)合并 var all_args = [...args].concat([...arguments]); //因為ctx是外層的this指針,在外層我們使用一個變量that引用進來 var real_ctx = this instanceof f ? this : ctx; return that.apply(real_ctx, all_args); } //函數(shù)的原型指向source的原型,這樣執(zhí)行new f()的時候this就會通過原型鏈指向source f.prototype = this.prototype; //返回函數(shù) return f; }
我們執(zhí)行
var res = source.bind1(destination,18); console.log(new res("male"));
效果如下:
已經達到我們的效果!
現(xiàn)在分析一下上述實現(xiàn)的代碼:
//調用var res = source.bind1(destination,18)時的代碼分析 Function.prototype.bind1 = function (ctx, ...args) { var that = this;//that肯定是source //定義了一個函數(shù) let f = function () { ... //內部先不管 } //函數(shù)的原型指向source的原型,這樣執(zhí)行new f()的時候this就會指向一個新家的對象,這個對象通過原型鏈指向source,這正是我們上面執(zhí)行apply的時候需要傳入的參數(shù) //f.prototype==>source.prototype f.prototype = this.prototype; //返回函數(shù) return f; }
f()函數(shù)的內部實現(xiàn)分析:
//new res("male")相當于運行new f("male");下面進行函數(shù)的運行態(tài)分析 let f = function () { console.log(this);//這個時候打印this就是一個_proto_指向f.prototype的對象,因為f.prototype==>source.prototype,所以this._proto_==>source.prototype //將外層函數(shù)的參數(shù)和內層函數(shù)的參數(shù)合并 var all_args = [...args].concat([...arguments]); //正常不用new的時候this指向當前調用處的this指針(在全局環(huán)境中執(zhí)行,this就是window對象);使用new的話這個this對象的原型鏈上有一個類型是f的原型對象。 //那么判斷一下,如果this instanceof f,那么real_ctx=this,否則real_ctx=ctx; var real_ctx = this instanceof f ? this : ctx; //現(xiàn)在把真正分配給source函數(shù)的對象傳入 return that.apply(real_ctx, all_args); }
至此bind()函數(shù)的模擬實現(xiàn)完畢!如有不對之處,歡迎拍磚!您的寶貴意見是我寫作的動力,謝謝大家。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/102537.html
摘要:之前文章詳細介紹了的使用,不了解的查看進階期。不同的引擎有不同的限制,核心限制在,有些引擎會拋出異常,有些不拋出異常但丟失多余參數(shù)。存儲的對象能動態(tài)增多和減少,并且可以存儲任何值。這邊采用方法來實現(xiàn),拼成一個函數(shù)。 之前文章詳細介紹了 this 的使用,不了解的查看【進階3-1期】。 call() 和 apply() call() 方法調用一個函數(shù), 其具有一個指定的 this 值和分...
摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對象這種行為就像把原函數(shù)當成構造器,提供的值被忽略,同時調用時的參數(shù)被提供給模擬函數(shù)。 bind() bind() 方法會創(chuàng)建一個新函數(shù),當這個新函數(shù)被調用時,它的 this 值是傳遞給 bind() 的第一個參數(shù),傳入bind方法的第二個以及以后的參數(shù)加上綁定函數(shù)運行時本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來調用原函數(shù)。bind返回的綁定函數(shù)也能使用 n...
摘要:跨域請求詳解從繁至簡前端掘金什么是為什么要用是的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據訪問的問題。異步編程入門道典型的面試題前端掘金在界中,開發(fā)人員的需求量一直居高不下。 jsonp 跨域請求詳解——從繁至簡 - 前端 - 掘金什么是jsonp?為什么要用jsonp?JSONP(JSON with Padding)是JSON的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據訪問的問題...
摘要:引言上一節(jié)介紹了高階函數(shù)的定義,并結合實例說明了使用高階函數(shù)和不使用高階函數(shù)的情況。我們期望函數(shù)輸出,但是實際上調用柯里化函數(shù)時,所以調用時就已經執(zhí)行并輸出了,而不是理想中的返回閉包函數(shù),所以后續(xù)調用將會報錯。引言 上一節(jié)介紹了高階函數(shù)的定義,并結合實例說明了使用高階函數(shù)和不使用高階函數(shù)的情況。后面幾部分將結合實際應用場景介紹高階函數(shù)的應用,本節(jié)先來聊聊函數(shù)柯里化,通過介紹其定義、比較常見的...
摘要:也就是說當返回的函數(shù)作為構造函數(shù)的時候,時指定的值會失效,但傳入的參數(shù)依然生效。構造函數(shù)效果的優(yōu)化實現(xiàn)但是在這個寫法中,我們直接將,我們直接修改的時候,也會直接修改函數(shù)的。 JavaScript深入系列第十一篇,通過bind函數(shù)的模擬實現(xiàn),帶大家真正了解bind的特性 bind 一句話介紹 bind: bind() 方法會創(chuàng)建一個新函數(shù)。當這個新函數(shù)被調用時,bind() 的第一個參數(shù)...
閱讀 2580·2021-08-20 09:38
閱讀 1360·2019-08-30 15:43
閱讀 598·2019-08-29 17:13
閱讀 1609·2019-08-29 14:01
閱讀 1320·2019-08-29 13:29
閱讀 2331·2019-08-23 18:29
閱讀 2052·2019-08-23 17:51
閱讀 1920·2019-08-23 17:16