本文主要讲解实现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指向contextreturn this.apply(context, [...innerArgs,...args]);};// 复制原函数的prototypefn.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的结果赋值给一个变量,然后使用该变量来调用函数。