对 call /apply /bind,当第一个参数为空、null、undefined 时,默认传入全局对象!
1. call
(1)参数:call 可以接受多个参数。
fn.call(thisValue, arg1, arg2, ...)
- 第一个参数是一个对象,也就是 this 要指向的对象,如果
call方法的第一个参数是原始值,那么这个原始值会自动转成对应的包装对象。 - 后面的参数是调用函数时所需的参数。
function get() {
console.log(typeof this);
}
get.call(5);
// 对象 [Number 5]
(2)应用
例如 Object.prototype.toString.call这种用于调用对象的原生方法,避免函数覆盖造成的结果。
(3)源码
对func.call(obj, ...args),call 要实现的是将 「修改 func 中的 this指向到 obj 上」,可以看作是 obj.func(...args),这样 func 里的 this 指向就是 obj。
那么我们一开始要怎么取到 func 这个函数呢?this 就是调用 call 的函数!
// fn.call(obj, ...args) => obj.fn(...args)
Function.prototype.myCall = function(context) { // 函数的原型方法
let obj = context || window; // 传参为空或 undefined 时,指向 window
obj.fn = this; // this 就是调用 myCall 的函数
const args = [...arguments].slice(1); // 使用展开运算符将伪数组转为数组
let res = obj.fn(...args);
delete obj.fn;
return res;
}
测试用例
let p1 = {
name: 'Tom',
say() {
console.log(this.name);
}
};
let p2 = {name: 'Jack'};
p1.say.myCall(p2); // 'Jack'
2. apply
(1)参数:apply 的第2个参数要求是数组。虽然apply第2个参数接收的是数组形式,但给 fn 函数传参时,会将数组展开传入。
fn.apply(thisValue, [arg1, arg2, ...])
(2)应用
将一个类似数组的对象(比如arguments对象)转为真正的数组。
// 1.length等于多少就返回多长的数组 2.返回键值
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
(3)源码
obj.fn(args)。
Function.prototype.myApply = function(context) { // 只有方法名改变
let obj = context || window;
obj.fn = this;
const args = [...arguments].slice(1);
let res = obj.fn(...args);
delete obj.fn;
return res;
}
测试用例同 call,只需改变传参形式即可。
3. bind:借助 fn.apply(context, args)
(1)bind 返回的是一个新的函数,需要调用该函数才能获取结果。
let newFn = fn.bind(thisValue[, arg1[, arg2[,...]]]);
newFn();
(2)bind 的参数是散列形式,可以是两部分拼接,如下
var add = function (x, y) {
return x * this.m + y * this.n;
}
var obj = {
m: 2,
n: 2
}
var newAdd = add.bind(obj, 5); // bind的 5 是 add的第1个参数 x
console.log(newAdd(2)) // 新函数的 2 是 add的第2个参数 y
// 14
(3)源码
获取fn.apply(obj, args)需要的变量。
Function.prototype.myBind = function(context) {
let obj = context || window;
const fn = this; // 函数是单独的
const args = [...arguments].slice(1);
return function() {
return fn.apply(obj, args); // 借助 apply 实现
}
}
测试用例
let p1 = {
name: 'Tom',
say() {
console.log(this.name);
}
};
let p2 = {name: 'Jack'};
let fn = p1.say.myBind(p2); // 改变
fn();
4、三者区别
-
是否立即执行函数:
call、apply会立即执行函数,bind则是返回了一个新函数、需要调用才能执行。 -
参数形式不同:除了第一个参数是要改变的对象,对于其他参数,
call是散列形式,apply是数组形式(call的性能较高,因为不需要解析数组),bind的参数也是散列形式,但它可以是两个函数的参数拼接组成的。 -
this 指向:
call和apply可以通过多次不同的绑定来改变绑定对象,但bind为硬绑定,也就是只能绑定一次 &&多次绑定也以第一次为准。
参考文章