手写call、apply

126 阅读2分钟

1.手写call

  1. 首先需要判断调用call方法的是不是function,如果不是则需要抛出错误,因为call方法只存在于函数的原型
  2. 如果想要改变this指向,可以暂且将this绑定在传入的对象的某一个属性中,当然要确保该属性是独一无二的
  3. 给对象context新增的属性不能覆盖原有属性,之后再使用context执行函数func,那么this就顺利绑定在context上了
  4. 得到的函数执行结果需要保存下来为result,然后将context身上的新增的属性删除掉,最后返回result即可
let obj = {
    name: "张三",
    fn: "a",
};
​
var name = "李四";
function func(a, b, c, d) {
    console.log(this.name, a, b, c, d);
}
​
Function.prototype.myCall = function (context) {
    // console.log(this); //this指向的是func这个函数
    if (typeof this !== "function") throw new Error("type error");
    context = context || window;
    let args = [...arguments].slice(1);
    let result = null;
    /**
* 这里直接给context绑定fn属性有点不妥,因为如果context身上本身就具有fn属性的话就会被覆盖掉
* 因此可以使用es6的symbol数据类型(每一个数据都是唯一的),symbol不是对象所以不可以用new操作符运算
*/
    //使用symbol定义fn属性,让其成为独一无二的属性防止与context中的属性冲突
    let fn = Symbol();
    context[fn] = this;
    //让context对象调用fn函数,那么this就绑定在context身上了
    result = context[fn](...args);
    //删除该新增属性,不改变原有对象
    delete context[fn];
    return result;
};
//二者得到的结果是一样的
func.myCall(obj, 1, 2, 3, 4);
func.call(obj, 1, 2, 3, 4);

2.手写apply

如果会手写call了,那么apply也不在话下了,只有传参形式不一样而已

let obj = {
    name: "张三",
    fn: "a",
};
​
var name = "李四";
function func(a, b, c, d) {
    console.log(this.name, a, b, c, d);
}
​
Function.prototype.myApply = function (context) {
    if (typeof this !== "function") throw new Error("type error");
    context = context || window;
    let result = null;
    //arguments是一个对象,类似数组但不是数组,它也有length属性和下标
    let args = [...arguments][1];
    let fn = Symbol();
    context[fn] = this;
    //与call思路一样,只是这里需要判断arguments有没有传入第二个参数
    result = arguments.length > 1 ? context[fn](...args) : context[fn]();
    delete context[fn];
    return result;
};
//二者得到的结果是一样的
func.myApply(obj, [1, 2, 3, 4]);
func.apply(obj, [1, 2, 3, 4]);