call, apply, bind的模擬實現
阿新 • • 發佈:2019-01-09
call,apply,bind都是用於改變this的指向的,call和apply的區別在於,他們傳遞的引數不一樣,後者是用陣列傳參,bind和call, apply兩者的區別是,bind返回的是一個函式。
在模擬他們實現之前,我們需要先做些相容處理,如下
Function.prototype.bind = Function.prototype.bind || function(context){
}
call的實現
call的模擬實現的方法是
- 把函式掛載在物件上
- 然後執行該函式
- 刪除該函式
Function.prototype.call = Function.prototype.call || function(context){
// 當call的第一個引數是null的時候,this的預設指向是window
var context = context || window;
// 把該函式掛載到物件上
context.fn = this;
// 用於儲存call後面的引數
var args = [];
var len = arguments.length;
// 這裡是為了將一個函式的引數傳入到另外一個函式執行
for(var i = 1; i < len; i++){
args.push('arguments[' + i + ']');
}
// 這裡的args預設是會呼叫Array.toString()方法的
var result = eval('context.fn(' + args + ')');
// 刪除掛載在物件裡的該函式
delete context.fn;
// 因為函式可能有放回值,所以把結果也返回出去給他們
return result;
}
var data = {
name: 'mr3x'
}
function Person(age){
console. log(this.name, age);
return {
name: this.name,
age: age,
};
}
Person.call(data, 21); //mr3x 21
apply的實現
apply的模擬實現和call的模擬實現大同小異
Function.prototype.apply = Function.prototype.apply || function(context, arr){
context.fn = this;
var result;
var args = [];
var arr = arr || [];
var len = arr.length;
if(!Array.isArray(arr)) throw new Error('apply的第二個引數必須是陣列')
for(var i = 0; i < len; i++){
args.push('arr[' + i +']');
}
result = eval('context.fn('+ args + ')');
delete context.fn;
return result;
}
var data = {
name: 'mr3x'
}
function Person(age){
console.log(this.name, age);
return {
name: this.name,
age: age,
};
}
Person.apply(data, [21]); //mr3x 21
bind的實現
bind的實現有兩個難點,第一個難點是bind的引數問題,因為bind返回的是一個函式(也叫繫結函式),它不僅可以在bind裡面帶引數,還可以在繫結函式裡面帶引數,如下
var data = {
name: 'mr3x'
}
function Person(name, age){
console.log(this.name, name, age);
}
var bindPerson = Person.bind(data, 'bill');
bindPerson(21); //mr3x bill 21
第二個難點是返回的繫結函式中可以使用new來建立物件(本質是把原函式當成是建構函式),但是這使後的修改的this的指向就失效了,因為用new來建立物件,本質是呼叫了apply,apply也是會修改this的指向的,所以之前bind繫結的就失效了,如下
var data = {
name: 'mr3x'
}
function Person(name, age){
console.log(this.name, name, age);
}
var bindPerson = Person.bind(data, 'bill');
var man = new bindPerson(21); //undefined bill 21
程式碼實現
Function.prototype.bind = Function.prototype.bind || function(context){
var self = this;
// 用於解決呼叫的物件非函式
if(typeof self !== 'function'){
throw new Error('非函式無法呼叫bind');
}
// 外部引數的處理
var args = Array.prototype.slice.call(arguments, 1);
// 用於解決引用型別的傳值問題
var index = function(){}
var fBound = function(){
// 用於處理繫結函式裡的引數問題
var bindArgs = Array.prototype.slice.call(arguments);
// 當是普通函式的時候,this指向window,
//但是建構函式的時候,this指向例項,
return self.apply(this instanceof index ? this : context, args.concat(bindArgs));
}
index.prototype = this.prototype;
fBound.prototype = new index();
return fBound;
}
上述程式碼有一個問題,它只是勉強算是模擬了bind的實現,有一個缺陷就是,原生的bind所返回的繫結函式是沒有prototype屬性的,最高階版本就去看ES5-shim的原始碼是如何實現bind方法,也可以參考該博文