call, apply, bind的模拟实现
程序员文章站
2022-07-14 14:27:52
...
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方法,也可以参考该博文
上一篇: JS基础之call、apply、bind
下一篇: this指向的深入解析