本文主要讲解实现call()、apply()、bind()函数及使用场景举例。在JavaScript中,call()
, apply()
, 和 bind()
是函数对象的方法,它们允许你以不同的方式调用函数,并可以设置函数体内 this
的值。this相关与这三个函数介绍:文章第二部分
1. 实现 call()
实现 call()
方法时,需要创建一个新的函数上下文(通常是通过创建一个新的函数并立即执行它),以便在该上下文中调用原始函数,并将 this
指向设置为提供的参数。
实现步骤:
(1)判断调用对象是否为函数
(2)判断传入上下文对象是否存在。如果存入的参数为null或undefined,则使用全局对象(在浏览器中为window)
(3)将调用函数(this)赋值给参数的某个属性
(4)调用参数的新函数,并传入参数
(5)清理环境,删除在参数上临时创建的属性
(6)返回结果
示例:
// 实现call函数
Function.prototype.myCall = function(context, ...args) {
// 判断函数是否存在
if (typeof this!== 'function') {
throw new TypeError('Error');
}
// 如果context为null或undefined,则使用全局对象(在浏览器中为window)
context = context || window;
// 将调用函数(this)赋值给context的某个属性
context.fn=this;
// 调用context上的新函数,并传入参数
const result = context.fn(...args);
// 清理环境,删除在context上临时创建的属性
delete context.fn;
// 返回结果
return result;
};
// 示例
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const personOne = {
name: 'Alice'
};
greet.myCall(personOne, 'Hello', '!'); // 输出: Hello, Alice!
// 注意:在非严格模式下,如果context为null或undefined,this将指向全局对象(在浏览器中为window)
greet.myCall(null, 'Hello', '!'); // 输出: Hello, undefined!(如果全局对象中没有name属性)
// 在严格模式下,如果context为null或undefined,则this保持为null或undefined
2. 实现 apply()
apply()
方法调用一个函数,其具有一个指定的 this
值,以及作为一个数组(或类数组对象)提供的参数。
实现步骤:
(1)判断调用对象是否为函数
(2)判断传入上下文对象是否存在。如果存入的参数为null或undefined,则使用全局对象(在浏览器中为window)
(3)将调用函数(this)赋值给参数的某个属性
(4)调用参数的新函数,并传入参数
(5)清理环境,删除在参数上临时创建的属性
(6)返回结果
示例:
// 实现apply函数
Function.prototype.myApply = function(context) {
// 判断函数是否存在
if (typeof this!== 'function') {
throw new TypeError('Error');
}
// 如果context为null或undefined,则使用全局对象(在浏览器中为window)
context = context || window;
// 将调用函数(this)赋值给context的某个属性
context.fn=this;
// 调用context上的新函数,并传入参数
let result;
if(arguments[1]){
result = context.fn(...arguments[1]);
}else{
result = context.fn();
}
// 清理环境,删除在context上临时创建的属性
delete context.fn;
// 返回结果
return result;
};
// 示例
function sum(a, b, c) {
return a + b + c;
}
console.log(sum.myApply(null, [1, 2, 3])); // 输出: 6
注意:call()方法接收一个this参数和一系列单独的参数。apply()方法接收一个this参数和一个包含所有参数的数组或类数组对象。
3. 实现 bind()
实现bind
函数主要是创建一个新的函数,这个新函数在被调用时,this
值会被预设为bind
的第一个参数,其余参数将作为新函数的参数,供调用时使用。
实现步骤:
(1)检查调用对象是否为函数
(2)创建并返回一个新函数
(3)处理新函数被用作构造函数的情况
(4)处理普通函数调用
(5)在新函数内部使用apply
或call
调用原始函数
(6)返回新函数
示例:
// 实现bind函数
Function.prototype.myBind = function(context,...args) {
// 判断函数是否存在
if (typeof this!== 'function') {
throw new TypeError('Error');
}
// 创建一个新函数
const fn = (...innerArgs) => {
// 如果新函数被作为构造函数使用,则this指向新创建的对象
if (this instanceof fn) {
return new this(...innerArgs,...args);
}
// 否则,this指向context
return this.apply(context, [...innerArgs,...args]);
};
// 复制原函数的prototype
fn.prototype = Object.create(this.prototype);
// 返回新函数
return fn;
};
// 示例
const personTwo = {
name: 'Bob'
};
const lizi = greet.myBind(personTwo, 'Hello');
lizi('!'); // !, BobHello
4.call()|apply()|bind()使用场景
call、apply和bind方法在JavaScript中主要用于改变函数执行时的上下文环境(即改变函数内部this
的指向)。但是还有一些其他用处, 以下是这些方法的使用场景:
改变函数执行时的上下文环境:当函数被作为对象的方法调用时,this
指向调用该方法的对象。需要控制函数内部的this
指向其他对象,可以使用call
、apply
或bind
方法来达到目的。
function greet() {
console.log('Hello, ' + this.name + '!');
}
const person = {
name: 'Alice'
};
// 使用call
greet.call(person); // 输出: Hello, Alice!
// 使用apply(与call类似,但第二个参数是数组)
greet.apply(person); // 输出: Hello, Alice!
实现继承:可以使用这些方法来实现继承。例通过call
方法调用父构造函数来实现子类继承父类的属性和方法。
function Parent(name) {
this.name = name;
this.sayHello = function() {
console.log('Hello, ' + this.name);
};
}
function Child(name, age) {
Parent.call(this, name); // 继承Parent的name属性
this.age = age;
}
const child = new Child('Bob', 10);
child.sayHello(); // 输出: Hello, Bob
与事件处理相关:在处理DOM事件时,经常需要改变事件处理函数的上下文环境,以便正确地访问事件对象或其他相关数据。可以使用bind
方法来创建一个新的函数。
const button = document.getElementById('myButton');
function showMessage() {
console.log('Button clicked by ' + this.name);
}
const person = {
name: 'Alice'
};
// 使用bind将showMessage的this绑定到person对象
button.addEventListener('click', showMessage.bind(person));
与回调函数相关:如果需要在回调函数内部访问外部作用域的变量或对象,并希望回调函数内部的this
指向特定的对象,可以使用bind
方法来实现。
function fetchData(callback) {
// 假设这是从服务器获取的数据
const data = 'Some data';
// 调用回调函数,但确保回调函数内部的this指向正确的对象
const boundCallback = callback.bind(this);
boundCallback(data);
}
const obj = {
name: 'Alice',
processData: function(data) {
console.log('Processing data for ' + this.name);
console.log(data);
}
};
fetchData(obj.processData.bind(obj));
与异步编程相关:在异步编程中,如使用Promise或async/await时,可能需要控制回调函数的上下文环境。可以使用bind
方法来确保回调函数内部的this
指向正确的上下文。
function fetchUser(userId, callback) {
setTimeout(() => {
// 假设是从服务器获取的用户信息
const user = { id: userId, name: 'Alice' };
// 确保回调函数内部的this指向正确
// callback.bind(this)(user);
const boundCallback = callback.bind(this);
boundCallback(user);
}, 1000);
}
const context = {
logUser: function(user) {
console.log('User:', user.name);
},
};
// 更好的做法是将bind的结果作为参数传递
fetchUser(1, context.logUser.bind(context));
注意:在异步编程中直接使用bind
然后立即调用不是最佳实践。因为bind
返回的是一个新函数,如果直接调用(如callback.bind(this)(user);
),则不会按预期工作。
通常会将bind
的结果赋值给一个变量,然后使用该变量来调用函数。