手写js call、apply、bind实现
- 写这篇文章的初衷是自己对这几个api的理解,另外出去面试的时候可以回忆复习下思路
- 我想分三个模块直接附上代码片段
- 并且会注释上我的思路分析
- 也希望对有需要的小伙伴一点帮助
- 最难的是你理解这些东西,甚至在平时coding时候也知道怎么回事,就是不愿意去思考这背后的点点滴滴
- 其实当你坚持写博文的时候才发现开始很快,但坚持每个礼拜甚至坚持两个礼拜整理点东西,分享出去需要足够的耐心
- 我想着这也许也可以为你面试这份工作提供一点亮点
- call实现
这里举个例子方便后续理解
1. 例如有一个对象这样
var name = 'xiaohong'
const obj = {
name: 'xiaoming',
sayName(age) {
console.log(this.name + age)
}
}
const obj2 = {
name: 'xiaowang'
}
obj.sayName(); //这里输出xiaoming 指向自己出生的对象
obj.sayName.call(obj2) //这里输出xiaowang //指向obj2
obj.sayName.call(null) // 这里输出xiaohong 指向window
//obj.sayName.call(_this, arg1,arg2,.....)
看到这里我想你茅塞顿开了 直接上代码
- call 实现1 ----> ES5
// 挂载到构造函数的原型对象上 /** * params * 参数1 改变this指向的对象 * 参数2~ 传递的参数集合 */ Function.prototype.createCall = function() { // 截取第一个参数 const thisArgs = Array.prototype.shift.call(arguments) || window; // 可以理解为在指定对象上绑定一个函数指向调用函数 thisArgs.fn = this; //创建一个数组收集剩余参数 const args = []; for (let i = 0, len = arguments.length; i < len; i++) { //为了后面eval函数执行,需要将参数包一层字符串 args.push('arguments[' + i + ']'); } //执行函数体 const result = eval('thisArgs.fn(' + args + ')'); //删除额外的属性 delete thisArgs.fn; //返回结果 return result; }
- call实现2 ------> ES6
Function.prototype.createCallTwo = function () { const [thisArgs, ...args] = [...arguments]; //es6 解构参数 if (!thisArgs) { thisArgs = typeof window == undefined ? global : window; } //可以理解为在指定对象上绑定一个函数指向调用函数 thisArgs.func = this; const result = thisArgs.func(...args); delete thisArgs.func; return result; }
- apply实现
用法fn.apply(_this, [arg1, arg2, ....])
Function.prototype._apply = function (context, arr) {
var context = Object.create(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else if(Object.prototype.toString.call(arr) !== '[object Array]') {
throw new Error('第二个参数必须是数组!!')
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
console.log(args, '====args');
result = eval('context.fn(' + args + ')');
}
delete context.fn;
return result;
}
- bind实现
实现思路
- bind函数首先可以改变this指向,然后不会立即执行函数体,而是返回一个新的函数
- 参数可以透传
let obj = {
name: 'hello,bind',
say(a, b, c) {
console.log(this.name, a, b, c);
}
};
let obj2 = {
name: 'good luck'
};
// context 这里接受的bind指向对象
Function.prototype.createBind = function (context) {
//将调用函数别名fn保存
const fn = this;
//将剩余参数缓存起来
const args = Array.prototype.slice.call(arguments, 1);
//返回一个函数
return function () {
//绑定this指向,及参数接收
return fn.apply(
context,
args.concat(Array.prototype.slice.call(arguments))
);
};
};
const sayName = obj.say.bind(obj2, 4); //原版
const sayName2 = obj.say.createBind(obj2, 5); // 自定义
sayName(1, 2, 3); // good luck 4 1 2
sayName2(1, 2, 3); // good luck 5 1 2 👌