是什么?
call
,apply
,bind
是JavaScript中用于改变普通函数this指向(无法改变箭头函数this指向)的方法,这三个函数实际上都是绑定在Function构造函数的prototype
上,而每一个函数都是Function的实例,因此每一个函数都可以直接调用call,apply,bind
。
区别
- call、apply直接调用,bind返回一个新函数并不立即执行。
- call、bind接受多个参数,apply只接受两个参数:第一个参数是this要指向的对象,第二个参数是参数列表。
核心思想:借用方法
-
A对象有个方法,B对象因为某种原因也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?
-
当然是借用 A 对象的方法啦,既达到了目的,又节省了内存。
-
这就是call/apply/bind的核心理念:借用方法。
-
借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。
手写call、apply、bind
call实现
Function.prototype.myCall = function (context) {
// 判断调用myCall的是否为函数
if (typeof this !== 'function') {
throw new TypeError("被call调用的对象必须是函数");
}
// 判断 context 是否传入,如果未传入则设置成 window
context = context || window;
// 获取参数,除了context其余都合并为参数列表
let args = [...arguments].slice(1);
// 用Symbol来创建唯一的fn,防止名字冲突
let fn = Symbol("key");
// this是调用myCall的函数,将函数绑定到上下文对象的新属性上
context[fn] = this;
// 传入myCall的多个参数
const result = context[fn](...args);
// 将增加的fn方法删除
delete context[fn];
return result;
}
- 测试
const ddl = {
name: "zyzy",
hello: function () {
console.log(`hello,${this.name}!`);
},
};
const obj = { name: "world" };
ddl.hello.myCall(obj); // hello,world!
ddl.hello.call(obj);// hello,world!
apply实现
Function.prototype.myApply = function (context) {
// 判断调用myApply的是否为函数
if (typeof this !== 'function') {
throw new TypeError('被apply调用的对象必须是函数');
}
// 判断myApply有传入第二个参数则判断其是否是数组
if (arguments[1] && !Array.isArray(arguments[1])) {
throw new TypeError('apply的第二个参数必须是数组');
}
// 判断 context 是否传入,如果未传入则设置成 window
context = context || window;
// 获取参数,本身传入的arguments[1]就是一个参数数组,如果不存在则传空数组
let args = arguments[1] || [];
// 用Symbol来创建唯一的fn,防止名字冲突
let fn = Symbol("key");
// this是调用myAppky的函数,将函数绑定到上下文对象的新属性上
context[fn] = this;
// 传入myApply的多个参数
const result = context[fn](...args);
// 将增加的fn方法删除
delete context[fn];
return result;
}
- 测试
const ddl = {
name: "zyzy",
hello: function (params) {
console.log(`hello,${this.name}!I am ${params}`);
},
};
const obj = { name: "world" };
ddl.hello.myApply(obj,['yqyq']); // hello,world!I am yqyq
ddl.hello.apply(obj,['yqyq']);// hello,world!I am yqyq
bind实现
Function.prototype.myBind = function (context) {
// 判断调用myBind的是否为函数
if (typeof this !== 'function') {
throw new TypeError('被bind调用的不是函数');
}
// 判断 context 是否传入,如果未传入则设置成 window
context = context || window;
// 获取参数,除了context其余都合并为参数列表
let args = [...arguments].slice(1);
// 用Symbol来创建唯一的fn,防止名字冲突
let fn = Symbol("key");
// this是调用myBind的函数,将函数绑定到context对象的新属性上
context[fn] = this;
// 返回一个新的函数
return function func() {
// 判断返回出去的函数有没有被new,this指向中构造器调用的优先级最高
if (this instanceof func) {
return new context[fn](...args, ...arguments);
}
// 作为context对象的方法调用
return context[fn](...args, ...arguments);
}
}
- 测试
const ddl = {
name: "zyzy",
hello: function (a,b,c) {
console.log(`hello,${this.name}!`,a + b + c);
},
};
const obj = { name: "world" };
let hello1 = ddl.hello.myBind(obj, 1);
let hello2 = ddl.hello.bind(obj, 1);
hello1(2, 3);// hello,world! 6
hello2(2, 3);// hello,world! 6
new hello1(4, 5);// hello,undefined! 10
new hello2(4, 5);// hello,undefined! 10
如有问题欢迎在评论区讨论,一起进步!
原文链接:https://juejin.cn/post/7343862045122887732 作者:一lemon逼