如何实现call,apply,bind方法?
程序员文章站
2022-07-14 14:27:34
...
很久前写过一篇call,apply,bind的作用,最近学习的过程中看了一篇手写这三个方法的文章。才想起这么多年其实一直知其然不知所以然,还是要更深入原理才好,接下来整理一下我自己的写法。
实现call
首先我们看call的用法,比如
fn.call(obj,a,b,...)
。从中可以看书,call是函数的一个方法,所以我们把它加在Function.prototype
上,传入上下文context,首先判断this是否是function,不是就提示错误。另外当context不传时默认为window。然后再处理传入的参数即可,代码如下:
Function.prototype.myCall = (context) {
if(typeOf this !== 'function') {
throw new TypeError('Error');
}
context = context || window;
// 我们需要暂时将this挂载在context上
// 为了确保不因为属性名冲突而污染context对象
// 我们使用Symble来获取独一无二的值fn
const fn = Symble('fn');
// 然后挂载
context[fn] = this;
// 获取可能存在的后边的参数,传入fn执行
const args = [...arguments].slice(1);
const result = context.fn(...args);
// 执行完删掉fn,保证context跟原来一样
delete context[fn];
// 返回结果
return result;
}
实现apply
实现了call方法之后,apply方法就很简单了,他俩的区别只在于参数而已,代码如下:
Function.prototype.myApply = (context) {
if(typeOf this !== 'function') {
throw new TypeError('Error');
}
context = context || window;
const fn = Symble('fn');
context[fn] = this;
// 获取可能存在的后边的参数,传入fn执行
const args = arguments[1];
let result;
if(args) {
result = context.fn(...args);
} else {
result = context.fn();
}
delete context[fn];
return result;
}
实现bind
bind和之前两者的区别在于,他不会立即执行,只是绑定了上下文
- 前几步和之前的实现大相径庭,就不赘述了
- bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式,我们先来说直接调用的方式
- 对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind 可以实现类似这样的代码
f.bind(obj, 1)(2)
,所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments)
- 最后来说通过 new 的方式,在之前的章节中我们学习过如何判断 this,对于 new 的情况来说,不会被任何方式改变 this,所以对于这种情况我们需要忽略传入的 this
所以代码如下:
Function.prototype.myBind = (context) {
if(typeOf this !== 'function') {
throw new TypeError('Error');
}
const fn = this
const args = [...arguments].slice(1);
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
// F在this的原型链上,所以是new出来的
// new 出来的新函数,他的this定在new的时候,此时忽略这个context
return new fn(...args, ...arguments);
}
// 正常调用bind的时候,再给他apply返回
return fn.apply(context, args.concat(...arguments));
}
}