1.手写call
- 首先需要判断调用call方法的是不是function,如果不是则需要抛出错误,因为call方法只存在于函数的原型
- 如果想要改变this指向,可以暂且将this绑定在传入的对象的某一个属性中,当然要确保该属性是独一无二的
- 给对象context新增的属性不能覆盖原有属性,之后再使用context执行函数func,那么this就顺利绑定在context上了
- 得到的函数执行结果需要保存下来为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]);