JavaScript中call,apply,bind的區別與實現
區別
- call、apply、bind相同點:都是改變this的指向,傳入的第一個參數都是綁定this的指向,在非嚴格模式中,如果第一個參數是nul或者undefined,會把全局對象(瀏覽器是window)作為this的值,要註意的是,在嚴格模式中,null 就是 null,undefined 就是 undefined
- call和apply唯一的區別是:call傳入的是參數列表,apply傳入的是數組,也可以是類數組
- bind和call、apply的區別: bind返回的是一個改變瞭this指向的函數,便於稍後調用,不像call和apply會立即調用;bind和call很像,傳入的也是參數列表,但是可以多次傳入,不需要像call,一次傳入
- 值得註意:當 bind 返回的函數 使用new作為構造函數時,綁定的 this 值會失效,this指向實例對象,但傳入的參數依然生效 (new調用的優先級 > bind調用)
call實現
對象context想調用一個它沒有的方法f 怎麼辦呢?f.call(context)
通過call來借用方法f ,怎麼做到的呢?
- 對象context添加f方法
- 對象context執行f方法
Function.prototype.myCall = function(context, ...arg) { // 如果第一個參數傳入的是undefined和null,context為window對象 context = context || window; // 為context對象添加函數bar context.fn = this; // this:bar,this指向調用myCall的bar // context對象執行函數bar,並返回結果 return context.fn(...arg); } // 測試一下 var value = 2; var obj = { value: 1 } function bar(name, age) { console.log(this.value); return { value: this.value, name: name, age: age } } bar.myCall(null); // 2 console.log(bar.myCall(obj, 'kevin', 18)); //1 // Object { // value: 1, // name: 'kevin', // age: 18 // }
apply實現
apply和call唯一的區別是:call傳入的是參數列表,apply傳入的是數組,也可以是類數組
Function.prototype.myApply = function(context, arg) { // 如果第一個參數傳入的是undefined和null,context為window對象 context = context || window; // context對象添加函數bar context.fn = this; // this:bar,this指向調用myCall的函數bar // context對象執行函數bar,並返回結果 let result = null; if (!arg) { // 沒有傳入數組 result = context.fn(); }else{ // 傳入瞭參數數組 result = context.fn(...arg); } return result; } // 測試一下 var value = 2; var obj = { value: 1 } function bar(name, age) { console.log(this.value); return { value: this.value, name: name, age: age } } bar.myApply(null); // 2 console.log(bar.myApply(obj, ['kevin', 18])); // 1
bind實現
- bind和call、apply的區別: bind返回的是一個改變瞭this指向的函數,便於稍後調用,不像call和apply會立即調用;bind和call很像,傳入的也是參數列表,但是可以多次傳入,不需要像call,一次傳入
- 值得註意:當 bind 返回的函數 使用new作為構造函數時,綁定的 this 值會失效,this指向實例對象,但傳入的參數依然生效 (new調用的優先級 > bind調用)
bind實現最為復雜,因為經過bind綁定過的函數,既可以被當作普通函數調用,又可以被當作構造函數調用
- bind 返回的函數 作為普通函數調用
// bind 返回的函數 作為普通函數調用 let bindFun = normalFun.myBind(obj, '我是參數傳進來的name') // this:obj bindFun('我是參數傳進來的age')
- bind 返回的函數 作為構造函數調用,綁定的 this 值obj會失效,this指向實例對象a
// bind 返回的函數 作為構造函數調用 let bindFun = Person.myBind(obj, '我是參數傳進來的name') // this:obj let a = new bindFun('我是參數傳進來的age') // this:a
bind 返回的函數 作為普通函數調用 代碼實現
// bind 返回的函數 作為普通函數調用 Function.prototype.myBind = function (context, ...args){ // 如果第一個參數傳入的是undefined和null,context為window對象 context = context || window; // context對象(obj)添加函數normalFun context.fn = this; // this:normalFun, context.fn === normalFun,下面出現context.fn都可以直接看成normalFun // bind返回的函數 return function (...innerArgs) { // bind 返回的函數 作為普通函數被執行 context.fn(...[...args,...innerArgs]); //相當於normalFun(...[...args,...innerArgs]) } } // 測試 let obj = { objName: '我是obj傳進來的name', objAge: '我是obj傳進來的age' } // 普通函數 function normalFun(name, age) { console.log(name); //'我是第一次參數傳進來的name被args接收' console.log(age); //'我是第二次參數傳進來的age被innerArgs接收' console.log(this === obj); // true,this指向obj console.log(this.objName); //'我是obj傳進來的name' console.log(this.objAge); //'我是obj傳進來的age' } // bind 返回的函數 作為普通函數調用 let bindFun = normalFun.myBind(obj, '我是第一次參數傳進來的name被args接收'); // this指向obj bindFun('我是第二次參數傳進來的age被innerArgs接收');
bind 返回的函數 作為構造函數調用
// bind 返回的函數 再經過new調用 Function.prototype.myBind = function (context, ...args){ // 如果第一個參數傳入的是undefined和null,context為window對象 context = context || window; // context對象添加函數Person context.fn = this; // this:Person,context.fn:Person,_this:Person let _this = this; // bind返回的函數 const result = function (...innerArgs) { if (this instanceof _this ) { // this:a (new出來的實例對象) , _this:Person // 為實例對象a添加Person方法 this.fn = _this; // 實例對象a執行Person方法 this.fn(...[...args,...innerArgs]); } } result.prototype = Object.create(this.prototype); // 為什加這一句?看原型圖下面會解釋 return result; } // 測試 function Person(name, age) { console.log(name); //'我是第一次參數傳進來的name被args接收' console.log(age); //'我是第二次參數傳進來的age被innerArgs接收' console.log(this); //構造函數this指向實例對象 } // 構造函數原型的方法 Person.prototype.say = function() { console.log(123); } let obj = { objName: '我是obj傳進來的name', objAge: '我是obj傳進來的age' } // bind 返回的函數 作為構造函數調用 let bindFun = Person.myBind(obj, '我是第一次參數傳進來的name被args接收') // this:obj let a = new bindFun('我是第二次參數傳進來的age被innerArgs接收') // this:a a.say() //123
畫以下兩條語句的原型圖方便理解
let bindFun = Person.myBind(obj, '我是第一次參數傳進來的name被args接收') // this:obj let a = new bindFun('我是第二次參數傳進來的age被innerArgs接收') // this:a
當執行下面語句時,原型圖如下:
let bindFun = Person.myBind(obj, '我是第一次參數傳進來的name被args接收') // this:obj
當執行下面語句時,bindFun就是result看代碼,原型圖如下:
let a = new bindFun('我是第二次參數傳進來的age被innerArgs接收') // this:a
在這裡實例對象a還需要繼承構造函數Person的原型,所以加上瞭
result.prototype = Object.create(this.prototype);
原型圖最終如下:
bind代碼最終實現
Function.prototype.myBind = function (context, ...args){ // 如果第一個參數傳入的是undefined和null,context為window對象 context = context || window; // context對象添加函數Person context.fn = this; // this:Person,context.fn:Person,_this:Person let _this = this; // bind返回的函數 const result = function (...innerArgs) { if (this instanceof _this ) { // this:a (new出來的實例對象) , _this:Person // 為實例對象a添加Person方法 this.fn = _this; // 實例對象a執行Person方法 this.fn(...[...args,...innerArgs]); }else{ // 普通函數被調用 context.fn(...[...args,...innerArgs]); } } result.prototype = Object.create(this.prototype); // 為什加這一句?看原型圖下面會解釋 return result; } // 測試 // function Person(name, age) { // console.log(name); //'我是第一次參數傳進來的name被args接收' // console.log(age); //'我是第二次參數傳進來的age被innerArgs接收' // console.log(this); //構造函數this指向實例對象 // } // // 構造函數原型的方法 // Person.prototype.say = function() { // console.log(123); // } // let obj = { // objName: '我是obj傳進來的name', // objAge: '我是obj傳進來的age' // } // // bind 返回的函數 作為構造函數調用 // let bindFun = Person.myBind(obj, '我是第一次參數傳進來的name被args接收') // this:obj // let a = new bindFun('我是第二次參數傳進來的age被innerArgs接收') // this:a // a.say() //123 // 測試 let obj = { objName: '我是obj傳進來的name', objAge: '我是obj傳進來的age' } // 普通函數 function normalFun(name, age) { console.log(name); //'我是第一次參數傳進來的name' console.log(age); //'我是第二次參數傳進來的age' console.log(this === obj); // true console.log(this.objName); //'我是obj傳進來的name' console.log(this.objAge); //'我是obj傳進來的age' } // bind 返回的函數 作為普通函數調用 let bindFun = normalFun.myBind(obj, '我是第一次參數傳進來的name被args接收'); // this指向obj bindFun('我是第二次參數傳進來的age被innerArgs接收');
到此這篇關於JavaScript中call,apply,bind的區別與實現的文章就介紹到這瞭,更多相關JS call,apply,bind區別內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 原生js如何實現call,apply以及bind
- JS 函數的 call、apply 及 bind 超詳細方法
- JavaScript函數之call、apply以及bind方法案例詳解
- JavaScript中的this例題實戰總結詳析
- Javascript動手實現call,bind,apply的代碼詳解