call,apply,bind 是面试比较常见的一个考点,原来看一个 up 主封装自己的 jquery 的时候就用到了 call 方法来改变 this 的指向。除此以外 babel 在 class 的语法转换也是用到了 call 方法,本文将实现手写 call,apply 和 bind。
浅显介绍
在 js 里面实现改变 this 指向几乎只能依靠 call,apply 和 bind 和三种方式,所以一旦涉及到改变 this 指向的问题就想到 call,虽然是手写但还是浅谈一下这三者的区别,call 和 apply 都是传递参数后立即执行,区别在于传递参数的方式 call 以参数列表的形式依次传递,而 apply 则是以数组的形式,bind 返回一个永久改变了 this 指向的函数。函数自身的并不影响。
const obj = { name: "张三" };
function foo(name) {
this.name = name;
console.log(this);
}
foo.call(obj, "李四"); //打印张三
foo.apply(obj, ["李四"]); //同上
const f = foo.bind(obj, "李四");
f(); //同上
言归正传
不管是call还是apply,如果想实现this指向某个对象,只需要把方法挂载到该对象身上执行一遍,那方法的this不就顺理成章地改变指向了?(全文最关键) 因为对象身上的方法通过对象调用,this永远是指向该对象的,如果把这句话想清楚了,那么接下来的代码都是在围绕者怎么把方法挂载到对象上去执行,其它的不过是点缀。我想针对于代码稍微看看注释就理解了。实现 call
(均为 node 环境执行,浏览器环境全局对象为 window)
Function.prototype.myCall = function (obj, ...args) {
//对obj做一个包装(装箱),都装箱为对象类型
obj = obj == undefined || obj == null ? window : obj;
const foo = this;
// 利用属性描述符将方法挂载到对象身上
Object.defineProperty(obj, "foo", {
//也可以直接obj.foo = foo;但是删除在执行之后,在node环境会打印出foo,
value: this,
writable: false,
enumerable: false,
configurable: true,
});
obj.foo(); //方法挂载到目标对象上执行一遍
delete obj.foo; //用完之后删除即可
};
function test01() {
console.log(this);
}
const obj = {
name: "zxy",
age: 18,
};
test01.myCall(obj);
/* 打印(obj): {name:'zxy',age:'18'} , */
实现 apply
Function.prototype.myapply = function (obj, args = []) {
obj = obj == undefined || obj == null ? globalThis : Object(obj);
let foo = this;
// 利用属性描述符将方法挂载到对象身上
Object.defineProperty(obj, "foo", {
//也可以直接obj.foo = foo;但是删除在执行之后,在node环境会打印出foo,
value: this,
writable: false,
enumerable: false,
configurable: true,
});
obj.foo(...args);
delete obj.foo; //用完之后删除
};
function foo(name, age) {
this.name = name;
this.age = age;
console.log(this); //打印{ name: 'zs', age: 20 }
}
let obj = {
name: "zxy",
age: 18,
};
foo.myapply(obj, ["zs", 20]);
实现 bind
Function.prototype.mybind = function (obj, ...args) {
obj = obj == undefined || obj == null ? globalThis : Object(obj);
// 利用属性描述符将方法挂载到对象身上
Object.defineProperty(obj, "foo", {
//也可以直接obj.foo = foo;但是删除在执行之后,在node环境会打印出foo,
value: this,
writable: false,
enumerable: false,
configurable: true,
});
return function () {
obj.foo(...args);
}; //返回一个改变了this指向的新函数
};
const obj = {
name: "zxy",
};
function foo(name) {
this.name = name;
console.log(this);
}
const foo2 = foo.mybind(obj, "张三");
foo2(); //打印{ name: '张三' }
应用场景
call 等方式主要是改变 this 指向,这样说属实晦涩难懂,或者说有点神经病其它语言都没这种需求, 换句话说可以充分利用起来函数的 this,可以让两个毫不相干的函数和对象产生关系,比如 (只是方便理解,不是说 vue 是这么实现)
const d = {
data: {
name: "zxy",
},
method: {
getname() {
console.log(this.name);
},
},
};
d.method.getname(); //this指向method,打印undefined
d.method.getname.apply(d.data); //打印'zxy'
虽然不太恰当,但是成功把 data 和 getname 之间绑定到一起,这样访问起来只需要在 method 中书写 this 就可以直接访问到 data 的属性,再加工屏蔽一下调用细节,method 用起来就很舒服。 除此以外还有一些技巧性地用法,
- 比如准确的判断类型
Object.prototype.toString.call(obj);
- 查找一段数的最大或最小值(利用了 apply 的传参特性)s
Math.max.apply(null, [1, 2, 3, 4]);
除此以外自己模拟实现 new 和 class 继承都可以使用 call 来建立函数和对象间的关系。文章还正在写。
总结 所有文章都是出自于我个人在 js 使用过程中的一些实战总结,如果有理解不对的地方欢迎大家指正。我是小鱼干,一个妄想做好每一件事的大学生。