手写 call
原生 call
我们首先来看看 call 方法实现了什么
var foo = {
name: 'chou',
};
function sayName() {
console.log(this.name);
}
sayName.call(foo); // chou
不难看出
- call 改变了 this 的指向,指向到 foo 对象
- sayName 方法执行了
- call 是可以被所有的函数继承的,所以 call 方法应该被定义在 Function.prototype 上
实现
var foo = {
name: 'chou',
}
function sayName() {
console.log(this.name)
}
// ------------------------
// 改造foo对象
var foo = {
name: 'chou',
sayName() {
console.log(this.name)
}
}
foo.sayName() // chou
所以 如果我们
- 将函数设置为对象的属性
- 执行该函数
- 删除该函数
是不是就可以模拟 call 了呢
好啦 动手实现吧
Function.prototype._call = function (context) {
// this 就是 sayName 函数
context.fn = this;
// 执行函数
context.fn();
// 删除删除
delete context.fn;
};
// ------------------------
// test
var foo = {
name: 'chou',
};
function sayName() {
console.log(this.name);
}
sayName._call(foo); // chou
但是 call 的第二个参数可以接收一个变量
同时 如果不传递参数 默认会绑定 window
好啦 我们继续模拟
Function.prototype._call = function (context) {
var args = [];
// 从第二个参数开始 因为第一个存放的是我们的函数
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
// 不传递参数的时候 默认是window
context = context || window;
context.fn = this;
context.fn(...args);
delete context.fn;
};
// ------------------------
// test
var foo = {
name: 'chou',
};
function sayName(...msg) {
console.log(this.name, ...msg);
}
sayName._call(foo, 'yep', '🤨'); // chou yep 🤨
手写 apply
思路
apply 和 call 的区别在于 apply 的参数接收一个数组参数 而 call 则是参数列表
我们可以取 argument 的第一个元素
因为如果正确的传递 则 argument 的第一个元素应该是我们想要的 那个作为参数的数组
我们可以对这个值进行校验
- 如果不是数组 则抛出异常
- 如果不传 则默认只调用该方法
- 如果是数组 则按正确的处理
实现
Function.prototype._apply = function (context) {
// 获取参数
var args = arguments[1];
// 如果不传参数 默认是window调用
context = context || window;
// 绑定方法
context.fn = this;
if (!args) {
// 没有参数 就直接调用该方法
context.fn();
} else {
if (args instanceof Array) {
// 有参数 调用方法 并传递参数
context.fn(...args);
} else {
try {
// 参数不是数组 报错
throw new Error('CreateListFromArrayLike called on non-object');
} catch (e) {
console.log(e);
}
}
}
// 删除方法
delete context.fn;
};
手写 bind
第一版
我们借用 apply 或 call 返回一个新的函数即可
Function.prototype._bind = function (context) {
var self = this;
return function () {
return self.apply(context);
};
};
第二版
我们模拟 bind 调用中可以传递参数的形式
Function.prototype._bind = function (context) {
var self = this;
// 获取第二个参数到最后一个参数 第一个为函数 我们需要传入的变量
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
// 用concat合并两次的参数
return self.apply(context, args.concat(bindArgs));
};
};