1、实现 call
call
方法:
call
方法用于调用一个函数,并指定函数内部的this
值。它接受一个函数的上下文对象作为第一个参数,后面可以跟随任意数量的参数。- 使用示例:
func.call(context, arg1, arg2, ...)
手写 call:
js
// 传递参数从一个数组变成逐个传参了,不用...扩展运算符的也可以用arguments代替
Function.prototype.myCall = function (context, ...args) {
// 这里默认不传就是给window,也可以用es6给参数设置默认参数
context = context || window;
args = args ? args : [];
// 给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol();
context[key] = this;
// 通过隐式绑定的方式调用函数
const result = context[key](...args);
// 删除添加的属性
delete context[key];
// 返回函数调用的返回值
return result;
};
2、实现 apply
apply
方法:
apply
方法也用于调用一个函数,并指定函数内部的this
值,但与call
不同,它接受一个数组或类数组对象作为参数列表,其中数组中的每个元素都被传递给函数作为参数。- 使用示例:
func.apply(context, [arg1, arg2, ...])
手写 apply:
js
Function.prototype.myApply = function (context, args) {
// 这里默认不传就是给window,也可以用es6给参数设置默认参数
context = context || window;
args = args ? args : [];
// 给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol();
context[key] = this;
// 通过隐式绑定的方式调用函数
const result = context[key](...args);
// 删除添加的属性
delete context[key];
// 返回函数调用的返回值
return result;
};
3、实现 bind
bind
方法:
bind
方法用于创建一个新函数,其this
值永久地绑定到指定的上下文对象。与call
和apply
不同,bind
不会立即执行函数,而是返回一个新的函数,稍后可以调用。- 使用示例:
var boundFunc = func.bind(context);
手写 bind:
js
Function.prototype.myBind = function (context, ...args) {
// 保存原始函数的引用
const fn = this;
// 如果没有传入参数,则初始化为空数组
args = args ? args : [];
// 返回一个新的函数 newFn,这个函数实际上是用来代理原函数的
return function newFn(...newFnArgs) {
// 如果 newFn 是通过构造函数调用的(使用 new 关键字),则执行原函数,并传递参数
if (this instanceof newFn) {
return new fn(...args, ...newFnArgs);
}
// 如果 newFn 是普通函数调用的,则使用 apply 方法将原函数绑定到指定的上下文 context,并传递参数
return fn.apply(context, [...args, ...newFnArgs]);
};
};
4、区别
-
参数传递方式:
call
和apply
接受不同的参数传递方式。call
接受一系列参数,而apply
接受一个数组。
-
函数执行时机:
call
和apply
立即执行函数,并返回函数的执行结果。bind
不会立即执行函数,而是返回一个新的函数,需要在稍后手动调用。
-
函数绑定:
call
和apply
不会改变原始函数的this
值,它们只是在调用时临时改变上下文。bind
创建一个新函数,该函数永久绑定了指定的上下文对象,不会改变原始函数的this
值。
-
返回值:
call
和apply
直接返回函数的执行结果。bind
返回一个新的函数。
举个例子🌰:
js
var person = {
name: 'John',
greet: function(greeting) {
console.log(greeting + ' ' + this.name);
}
};
person.greet('Hello'); // 输出 "Hello John"
var anotherPerson = {
name: 'Alice'
};
// 使用 call
person.greet.call(anotherPerson, 'Hi'); // 输出 "Hi Alice"
// 使用 apply
person.greet.apply(anotherPerson, ['Hola']); // 输出 "Hola Alice"
// 使用 bind
var greetFunc = person.greet.bind(anotherPerson);
greetFunc('Bonjour'); // 输出 "Bonjour Alice"
5、补充:this 的指向
this
的指向是在函数调用的时候确定下来的,this
的指向大致可以分为五种。
5.1、默认绑定
默认绑定一般发生在回调函数,函数直接调用。
js
function test() {
// 严格模式下是undefined
// 非严格模式下是window
console.log(this);
}
setTimeout(function () {
// setTimeout的比较特殊
// 严格模式和非严格模式下都是window
console.log(this);
});
arr.forEach(function () {
// 严格模式下是undefined
// 非严格模式下是window
console.log(this);
});
5.2、隐式绑定
这个通俗点用一句话概括就是谁调用就是指向谁。
js
const obj = {
name: 'joy',
getName() {
console.log(this); // obj
console.log(this.name); // joy
},
};
obj.getName();
5.3、显示绑定 call、apply、bind
js
const obj1 = {
name: 'joy',
getName() {
console.log(this);
console.log(this.name);
},
};
const obj2 = {
name: 'sam',
};
obj1.getName.call(obj2); // obj2 sam
obj1.getName.apply(obj2); // obj2 sam
const fn = obj1.getName.bind(obj2);
fn(); // obj2 sam
5.4、new 绑定
js
function Vehicle() {
this.a = 2
console.log(this);
}
new Vehicle(); // this指向Vehicle这个new出来的对象
5.5、es6 的箭头函数
es6
的箭头函数比较特殊,箭头函数 this
为父作用域的 this
,不是调用时的 this
。要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的 this
指向是静态的,声明的时候就确定了下来。比较符合 js
的词法作用域吧。
js
window.name = 'win';
const obj = {
name: 'joy',
age: 12,
getName: () => {
console.log(this); // 其父作用域this是window,所以就是window
console.log(this.name); // win
},
getAge: function () {
// 通过obj.getAge调用,这里面this是指向obj
setTimeout(() => {
// 所以这里this也是指向obj,所以结果是12
console.log(this.age);
});
},
};
obj.getName();
obj.getAge();
既然有5种 this
的绑定方式,那么肯定有优先级的先后。
箭头函数 -> new绑定 -> 显示绑定call/bind/apply -> 隐式绑定 -> 默认绑定