重写bind,call,apply方法

前言

不能使用ES6的语法糖来实现,比如(解构赋值,symbol)

call方法

想要对call 的重写,前提是需要知道 call 的几个特点:(假设有一个 函数 test)

  1. test 调用 call() ,那么就会执行 text。
  2. test 中的this 指向 call 的第一个参数。
  3. call 的第二个参数开始,都在test函数的参数列表中
1
2
3
4
5
function test() {
console.log(this,arguments);
}

test.call({a:1,b:2},'张三','李四')

call.png

重写call

在重写call前,我需要知道一个知识:

谁调用这个函数,这个函数内的this就指向谁

上述的 test 函数,默认是window调用它的,所以 test函数的this就指向 window

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.myCall = function (context) {
context = context ? Object(context) : window; // 简化了, 这里就直接把数据类型都转换为Object了
let uniqueKey = new Date().getTime().toString(); // 创建一个唯一值,避免方法名冲突
context[uniqueKey] = this;
let args = [];
for (let i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}

const result = eval('context[uniqueKey](' + args + ')');
delete context[uniqueKey];
return result;
};

重写 apply

它和 call 的区别就在于第二个参数是数组.

但还是需要注意一下:

  1. apply 的 第二个参数接收 数组
  2. apply 只取到第二个参数,从第三个参数开始到最后直接被忽略
  3. apply 的 第二个参数,如果是 object/funciton/null/undefined ,那么 arguments.length 直接为 0
  4. apply 的 第二个参数, 如果是 number/string/boolean,会报错

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.myApply = function (context, args) {
context = context ? Object(context) : window;
let uniqueKey = new Date().getTime().toString();
context[uniqueKey] = this;
if (typeof args === 'number' || typeof args === 'string' || typeof args === 'boolean') {
throw new TypeError('CreateListFromArrayLike called on non-object');
}
if (!args || Array.isArray(args) === false) {
return context[uniqueKey]();
}

// 代码到这里了就证明args是一个数组了,
let result = context[uniqueKey](...args);
delete context[uniqueKey];
return result;
};

重写bind

写之前,先看一看bind都有哪些特点:(假设有一个 函数 test)

  1. 使用 bind() 时,test 不会执行,而是返回一个新的函数
  2. bind 的第一个参数和 call,apply 一样的特点
  3. bind 可以分离 text 的参数 (函数柯里化
    • bind 可以接收一部分参数,返回的新函数也可以接收一部分参数
  4. 返回的新函数可以作为构造函数,而this指向的是 test 构造出来的实例
    • 实例应该继承test构造函数上的原型属性

根据以上特点来实现一下

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.myBind = function (context) {
let _this = this,
// bind()传的参数列表
args = [].slice.call(arguments, 1), // 去掉第一个,因为第一个是this指向的东西
_tempFn = function () {}; // 作为原型链继承的中间件函数

let newFn = function () {
// 返回的新函数的参数列表
let newArgs = [].slice.call(arguments);
return _this.apply(this instanceof newFn ? this : context, args.concat(newArgs));
};
// newFn.prototype = this.prototype // 这个共用了一个原型,不太好
_tempFn.prototype = this.prototype;
newFn.prototype = new _tempFn();
return newFn;
};