手写call,apply和bind(分析三者的用法与区别)
它們有什么用及區(qū)別?
在闡述它們?nèi)绾问褂弥?#xff0c;我們有必要整理清楚this的用法,簡單的說this是JavaScript語言的一個關(guān)鍵字,它是函數(shù)運行時,在函數(shù)體內(nèi)部自動生成的一個對象,只能在函數(shù)體內(nèi)部使用。
那么問題又來了,this的值是什么呢?
因為this是在函數(shù)運行時,函數(shù)內(nèi)部自動生成的一個對象,那么接下來我們通過函數(shù)來對this進行分析。 首先JavaScript中的函數(shù)可以分為兩類:
- 常規(guī)函數(shù):函數(shù)聲明式,函數(shù)表達式,構(gòu)造函數(shù)
- 箭頭函數(shù):(ES6引入使用)
接下來分別分析this在這些函數(shù)中究竟是什么?
理解常規(guī)函數(shù)中的this
1.純粹的函數(shù)調(diào)用
function test(name) {console.log(name)console.log(this) } test('Jerry') //調(diào)用函數(shù) 復制代碼以上函數(shù)調(diào)用的方式是非常常見的,然而這只是一種簡寫的形式,完整的寫法應該如下:
function test(name) {console.log(name)console.log(this) } test.call(undefined, 'Tom') 復制代碼這里面便出現(xiàn)了我們將要學習的call,先不討論它的作用,我們繼續(xù)討論this的用處,call方法接受的第一個參數(shù)就是this,但是我們這里是undefined,按照規(guī)定,如果你傳的context 是 null 或者 undefined,那么 window對象就是默認的context(嚴格模式下默認context 是 undefined)。
2.對象中函數(shù)的調(diào)用
const obj = {name: 'Jerry',greet: function() {console.log(this.name)} } obj.greet() //第一種調(diào)用方法 obj.greet.call(obj) //第二種調(diào)用方法 復制代碼從上面的例子中,我們發(fā)現(xiàn)這次call方法的第一個參數(shù)為obj,此時說明函數(shù)greet內(nèi)部的this指向了obj對象,這顯而易見便知call方法的作用是改變this的指向,又因為上面兩種調(diào)用方式結(jié)果一樣可知函數(shù)的this指向可以理解為誰調(diào)用便指向誰。
3.構(gòu)造函數(shù)中的this
每個構(gòu)造函數(shù)在new之后都會返回一個對象,這個對象就是this,也就是context上下文。
理解箭頭函數(shù)中的this
在使用箭頭函數(shù)的時候,箭頭函數(shù)會默認綁定外層的this值,所以在箭頭函數(shù)中this的值和外層的this是一樣的。因為箭頭函數(shù)沒有this,所以需要通過查找作用域鏈來確定this的值。
這就意味著如果箭頭函數(shù)被非箭頭函數(shù)包含, this綁定的就是最近一層非箭頭函數(shù)的 this。
注意:多層對象件套里面的this是和最外層保持一致的。
因為今天的重點是講解call,apply,bind的用法及實現(xiàn),然而箭頭函數(shù)是沒有這些方法的,所以箭頭函數(shù)的使用僅限于此。
首先說明call,apply是ES5中的語法,bind是ES6新引入的,它們?nèi)叩南嗨浦帪?#xff1a;
- 都是用來改變函數(shù)的this對象的指向
- 第一個參數(shù)都是this要指向的對象
- 都可以利用后續(xù)參數(shù)進行傳參
不同之處使用一個例子進行說明:
const personOne = {name: "張三",age: 12,say: function () {console.log(this.name + ',' + this.age);} }const personTwo = {name: "李四",age: 24 }personOne.say(); //張三,12 復制代碼對于以上的結(jié)果,我們應該都非常清楚,那么問題來了,如果我們想要知道personTwo對象的信息如何實現(xiàn)呢?
分別使用call,apply以及bind方法實現(xiàn),并從中得到它們?nèi)叩膮^(qū)別:
personOne.say.call(personTwo); //李四,24 personOne.say.apply(personTwo); //李四,24 personOne.say.bind(personTwo); //沒有輸出任何東西 復制代碼修改以上代碼對比可知:call和apply都是對函數(shù)的直接調(diào)用,而bind方法返回的仍然是一個函數(shù),因此我們需要執(zhí)行它才會有結(jié)果。
personOne.say.call(personTwo); //李四,24 personOne.say.apply(personTwo); //李四,24 personOne.say.bind(personTwo)(); //李四,24 復制代碼接著繼續(xù)討論其余參數(shù)
const personOne = {name: "張三",age: 12,say: function (gender, phone) {console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);} }const personTwo = {name: "李四",age: 24 }personOne.say("女", "123"); 復制代碼這個例子的區(qū)別于上面的即為say函數(shù)需要傳遞參數(shù),我們分別使用這三種方法實現(xiàn)傳遞參數(shù):
personOne.say.call(personTwo, "女", "123"); //李四,24,女,123 personOne.say.apply(personTwo, ["女", "123"]); //李四,24,女,123 personOne.say.bind(personTwo, "女", "123")(); //李四,24,女,123 復制代碼顯而易見的區(qū)別call和bind除了第一個參數(shù)外,之后的參數(shù)均為一一傳遞,而apply除了第一個參數(shù)外,只有一個參數(shù)即為一個數(shù)組,數(shù)組中的每一項為函數(shù)需要的參數(shù)。
說明它們的用法以及區(qū)別之后,我們就要自己嘗試著剖析它的原理,自己書寫這三個方法啦~~~~~
call實現(xiàn)
在知道了它的使用即原理之后,想必直接看實現(xiàn)方法應該也可以理解的,那么先上代碼:
Function.prototype.myCall = function (obj) {const object = obj || window; //如果第一個參數(shù)為空則默認指向window對象let args = [...arguments].slice(1); //存放參數(shù)的數(shù)組object.func = this;const result = object.func (...args);delete object.func; //記住最后要刪除掉臨時添加的方法,否則obj就無緣無故多了個fnreturn result; } 復制代碼代碼非常簡短,一步步進行說明解釋:
因為call方法是每一個函數(shù)都擁有的,所以我們需要在Function.prototype上定義myCall,傳遞的參數(shù)obj即為call方法的第一個參數(shù),說明this的指向,如果沒有該參數(shù),則指向默認為window對象。
args為一個存放除第一個參數(shù)以外的其余參數(shù)的數(shù)組(arguments為函數(shù)中接收到的多有參數(shù),[...arguments]可以將arguments類數(shù)組轉(zhuǎn)換為真正的數(shù)組,詳細講解可以查看ES6語法)。
解釋object.func=this之前,我們先使用示例使用一下自己定義的myCall函數(shù):
const personOne = {name: "張三",age: 12,say: function (gender, phone) {console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);} }const personTwo = {name: "李四",age: 24 }Function.prototype.myCall = function (obj) {const object = obj || window; //如果第一個參數(shù)為空則默認指向window對象let args = [...arguments].slice(1); //存放參數(shù)的數(shù)組object.func = this;const result = object.func (...args);delete object.func; //記住最后要刪除掉臨時添加的方法,否則object就無緣無故多了個funcreturn result; }personOne.say.myCall(personTwo,"女",18333669807); //李四,24,女,18333669807 復制代碼根據(jù)示例,我們進行解釋,myCall里面的this指的是personOne.say這個方法(因為myCall是一個方法,上面所說的,誰調(diào)用它,它的this便指向誰),object.func=this相當于給object這個對象克隆了一個personOne.say方法,讓object在調(diào)用這個方法,相當于object.personOne.say,達到了call的效果。(記住最后要刪除掉臨時添加的方法,否則object就無緣無故多了個func)
object.func (...args)里面的參數(shù)即為傳入的其余參數(shù)。
apply實現(xiàn)
通過上面的分析,想必大家應該已經(jīng)基本明白了它是如何實現(xiàn)的了,那么接下來實現(xiàn)apply就非常簡單了,因為兩者的區(qū)別主要就是參數(shù)的傳遞方式不同,和上面一樣,先直接看一下代碼:
Function.prototype.myApply = function (obj) {const object = obj || window; //如果第一個參數(shù)為空則默認指向window對象if (arguments.length > 1) {var args = arguments[1]; //存放參數(shù)的數(shù)組} else {var args = []; //存放參數(shù)的數(shù)組}object.func = this;const result = object.func(...args);delete object.func; //記住最后要刪除掉臨時添加的方法,否則obj就無緣無故多了個fnreturn result; }personOne.say.myApply(personTwo, ["女", 24]); 復制代碼主要區(qū)別就是獲取參數(shù)不同,因為apply的帶二個參數(shù)為數(shù)組,數(shù)組中包含函數(shù)需要的各項參數(shù)值,其余內(nèi)容實現(xiàn)myCall相同,此處就不在做解釋。
bind實現(xiàn)
話不多說,依舊是先上代碼
Function.prototype.myBind = function (obj) {const object = obj || window; //如果第一個參數(shù)為空則默認指向window對象let self = this;let args = [...arguments].slice(1); //存放參數(shù)的數(shù)組return function () {let newArgs = [...arguments]return self.apply(object, args.concat(newArgs))} }personOne.say.myBind(personTwo, "女", 24)(); 復制代碼前面的知識不重復說,return function是因為bind返回的是一個函數(shù),并且這個函數(shù)不會執(zhí)行,需要我們再次調(diào)用,那么當我們調(diào)用的時候,我們依舊可以對這個函數(shù)進行傳遞參數(shù),即為支持柯里化形式傳參,所以需要在返回的函數(shù)中聲明一個空的數(shù)組接收調(diào)用bind函數(shù)返回的函數(shù)時傳遞的參數(shù),之后對兩次的參數(shù)使用concat()方法進行連接,調(diào)用ES5中的apply方法。
到此為止,這三種方法的使用,區(qū)別以及實現(xiàn)已經(jīng)都講述完了,希望這篇文章對大家有所幫助~~總結(jié)
以上是生活随笔為你收集整理的手写call,apply和bind(分析三者的用法与区别)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈数组常见遍历方法
- 下一篇: js DOM