实现call apply bind函数
在JavaScript中,call、apply和bind是用来改变函数上下文的三个方法,在实现之前先了解下三个函数的区别:
三个函数的使用区别
- 调用效果不同: call与apply改变函数的this指向后调用了该函数,而bind只会返回一个被绑定好this的函数,不会主动执行
- 传入参数不同 call方法接受一个参数列表,apply方法接受一个参数数组
需要留意的情况
call
,apply
,bind
的第一个参数thisArg
传入的是函数需要绑定this的对象, 当thisArg
为undefinnd
或者null
时,this会被绑定到全局上,在浏览器环境下也就是window
,所以需要对thisArg
做边界判断- 如果传入
thisArg
的是基本数据类型,需要对基本数据类型转化成对象, 这里推荐使用Object()
进行转换,该方法不会改变数据的类型,且如果给定值是一个已经存在的对象,则会返回这个已经存在的值(相同地址),,需要注意的是如果设定Symbol()
值为对象属性,该Symbol
属性不可被delete
运算符删除,需要使用Reflect.deleteProperty()
方法删除 - 考虑到绑定this的函数有可能为箭头函数:
箭头函数中this
指向不能被call
,apply
,bind
方法改变,调用这三个函数后箭头函数中的this
依旧指向被定义时的执行上下文
const foo = () => {
console.log(this);
};
const obj = { name: "obj" };
foo.call(obj); // window (this依旧指向该foo定义时的执行上下文)
箭头函数中不存在arguments
,当在函数内没有arguments
会通过作用域链去上层作用域找, 一直找到全局作用域,而浏览器环境下全局作用域没有arguments,可能会报错,所以实现时建议不要使用arguments
来获取函数参数
-
对于this绑定,这里采用也就是隐式绑定,给对象添加一个方法
func
的形式来绑定this
,考虑对象自身可能存在同名属性,为避免影响对象,使用ES6
的Symbol()
方法生成的唯一值代替func
属性。每个从Symbol()
返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的 -
如果绑定this的函数执行后需要返回内容,需要将执行结果拿到并返回出去
call
//fn.call(obj,'1','2')
Function.prototype.myCall = function (thisArg, ...args) {
// 获取函数
const fn = this;
// 获取要绑定this的对象
thisArg =
thisArg !== undefined && thisArg !== null ? Object(thisArg) : window;
// 获取参数
args = Array.isArray(args) ? args : [];
//这里采用Symbol()保证新加的属性不同名,不会污染对象
const func = Symbol("fn");
// 给对象添加要绑定的函数
thisArg[func] = fn;
// 通过(对象.方法)的形式隐式绑定函数的this,并获取函数执行后的返回结果
const result = thisArg[func](...args);
// 调用完成之后删除对象上的该函数
Reflect.deleteProperty(thisArg, func);
// 将函数调用结果返回
return result;
};
apply
// fn.apply(obj,['name','age'])
Function.prototype.myApply = function (thisArg, argArray) {
// 获取函数
const fn = this;
// 获取要绑定this的对象
thisArg =
thisArg !== undefined && thisArg !== null ? Object(thisArg) : window;
// 获取参数
argArray = Array.isArray(argArray) ? argArray : [];
// 这里采用Symbol()保证新加的属性不同名,不会污染对象
const func = Symbol("fn");
// 给对象添加要绑定的函数
thisArg[func] = fn;
// 隐式绑定函数的this,获取函数执行后的返回结果
const result = thisArg[func](...argArray);
// 调用完成之后删除对象上的该函数
Reflect.deleteProperty(thisArg, func);
// 将函数调用结果返回
return result;
};
bind
//fn.bind(obj,1,2)(5,6)
Function.prototype.myBind = function (thisArg, ...argArray) {
// 获取函数
const fn = this;
// 获取要绑定this的对象
thisArg =
thisArg !== undefined && thisArg !== null ? Object(thisArg) : window;
// 获取绑定时的参数
argArray = Array.isArray(argArray) ? argArray : [];
// 定义需要返回的代理函数
function proxyFn(...args) {
// 获取两次传参的总参数
const arg = [...argArray, ...args];
// 这里采用Symbol()保证新加的属性不同名,不会污染对象
const func = Symbol("fn");
// 给对象添加要绑定的函数
thisArg[func] = fn;
// 隐式绑定函数的this,获取函数执行后的返回结果
const result = thisArg[func](...arg);
// 删除绑定的函数属性
Reflect.deleteProperty(thisArg, func);
// 返回函数的执行结果
return result;
}
// 最后返回未执行的函数
return proxyFn;
};
检验
const obj = { name: "obj" };
function foo() {
console.log(this, this.name);
}
foo.myCall(obj)
console.log(obj, "输出");
可以看到this已被绑定到obj对象上,执行时可拿到obj上绑定的函数,执行结束后obj依旧保持原样