「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
call
MDN上说:call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数;
解释一下,call是一个函数,一个可以指定this值的函数,一个可以接受一个或者多个参数调用放入函数。
证明一下:
function Person(name, price) {
this.name = name;
this.price = price;
}
function Man(name, price) {
Person.call(this, name, price);
}
const obj = new Man('张三', '男');
console.log(obj);
console.log('obj', obj);// { name: '张三', price: '男' }
上述代码和结果证明确实,Man函数存在了自己没有的属性和属性值,为什么会这样?call如何做到的?
- 首先call函数回接受最少1个参数 context + 一个可能的数组参数 args
- context需要判断是否为空,如果为空,将 context 指向全局对象,浏览器下 windos,node 环境 global
- 此时的 context 已经是全局对象或者是传入的 this ,总之一定不为空且是一个对象
- 将 call 函数中的 this 存放在 context 中,为了存放this的属性名与已有的属性名冲突 context 属性名最好使用 Symbol
- call 函数中的 this 此时被谁调用就会指向谁
- 将 context 中的 this 执行,并且将参数 args 传入 this
- 返回 this 执行结果;此时 调用call函数被执行,执行时this被指向到使用call的this;
上述逻辑比较绕,请看下面代码,逐行注释为你解惑
Function.prototype.myCall = function (context, ...args) {
// context 表示Man的this,如果为空,将 context 指向全局对象,浏览器下windos,node环境global
if (typeof context === 'undefined' || context === null) {
context = window;
}
// 此处使用Symbol,防止context当前命名的属性值key与context已有的属性名冲突
let key = Symbol();
// 在man对象上挂载this,这里的this应为被Person调用,所以此处的this指向的是Person
// context[key] = this;
// 此处用不可枚举属性
Object.defineProperty(context, key, {
value: this,
//是否为枚举属性
enumerable: false,
})
// 执行 Person 函数,因为此处是context调用,context又是Man的this;
// 所以 Person 函数执行的时候,Person中this就指向了context,指向了Man
let fn = context[key](...args);
// 删除对象上的函数
delete context[key];
// Person函数的返回值。若没有返回值,则返回 `undefined`
return fn;
};
function Person(name, price) {
this.name = name;
this.price = price;
}
function Man(name, price) {
// 将Person中属性挂载到Man函数
Person.myCall(this, name, price);
this.category = 'food';
}
const obj = new Man('张三', '男');
console.log('obj', obj);
apply
apply和call逻辑类似,不同点在于call的参数是一个一个的,apply的参数是数组形式的;所以二者除了对参数的获取形式不同,其他逻辑基本一致
Function.prototype.myApply = function (context, args) {
// 此处args是数组,已经是数组,不需要使用扩展符展开;此处可以增加args是否为数组的判断
// context 表示Man的this,如果为空,将 context 指向全局对象,浏览器下windos,node环境global
if (typeof context === 'undefined' || context === null) {
context = window;
}
// 此处使用Symbol,防止context为window是,命名的key与window已有的属性名冲突
let key = Symbol();
// 在man对象上挂载this,这里的this应为被Person调用,所以此处的this指向的是Person
context[key] = this;
// 执行 Person 函数,因为此处是context调用,context又是Man的this;
// 所以 Person 函数执行的时候,Person中this就指向了context,指向了Man
let fn = context[key](...args);
// 删除对象上的函数
delete context[key];
// Person函数的返回值。若没有返回值,则返回 `undefined`
return fn;
};
bind
bind与call、apply又不同;bind是绑定,this绑定之后返回一个函数,在执行函数的时候this才会改变
Function.prototype.myBind = function (context) {
// 判空
if (typeof context === 'undefined' || context === null) {
context = window;
}
// 此时 context 是 Man 的this
self = this;
// 此时 self 是 Person 的this
return function (...args) {
// 此处可以理解为 Person 使用 apply 修改 Man的 this 值
return self.apply(context, args);
};
};
function Person(name, price) {
this.name = name;
this.price = price;
}
function Man(name, price) {
Person.call(this, name, price);
console.log(this);
}
function Man2(name, price) {
Person.myBind(this)(name, price);
}
const obj2 = new Man2('张三2', '男2');
console.log(obj2.name);
结语
虽然是逐行理解,但是技术水平有限,不一定能理解透彻。如有问题希望读者不吝赐教。感激之至