本文详细解析JavaScript中call、apply和bind三个方法的区别与联系。三者均用于改变函数执行时的this指向,但调用时机与参数传递方式不同:call和apply立即执行,区别在于参数形式;bind返回新函数以便延迟调用。文章还深入探讨了bind在作为构造函数使用时的this失效问题,并提供了三者的完整源码实现与原型链分析。
区别
- call、apply和bind的共同点在于它们都能改变函数执行时的this指向,且第一个参数都用于指定this的值。在非严格模式下,如果第一个参数传入null或undefined,this将默认指向全局对象(浏览器环境中为window)。需要注意的是,在严格模式下,null和undefined会保持原值,不会指向全局对象。
- call和apply的主要区别在于参数传递方式:call接受的是参数列表,而apply接收的是一个数组或类数组对象作为参数集合。
- bind与call、apply的核心区别在于执行时机:bind不会立即调用函数,而是返回一个已经绑定了this的新函数,供后续调用。虽然bind和call都接受参数列表,但bind允许分多次传入参数,而call必须一次性传入所有参数。
- 值得注意的是,当使用bind返回的函数并通过new关键字作为构造函数调用时,原本绑定的this值会失效,this将指向新创建的实例对象。不过,通过bind预先传入的参数依然有效。这是因为new调用的优先级高于bind调用。
call实现
当对象context想要调用一个它自身没有的方法f时,该如何操作?f.call(context) 可以通过call来借用方法f,具体实现方式如下。
- 在对象context上添加f方法。
- 执行对象context上的f方法。
Function.prototype.myCall = function(context, ...arg) {
// 如果第一个参数传入的是undefined和null,context为window对象
context = context || window;
// 为context对象添加函数bar
context.fn = this; // this:bar,this指向调用myCall的bar
// context对象执行函数bar,并返回结果
return context.fn(...arg);
}
// 测试一下
var value = 2;
var obj = {
value: 1
}
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
bar.myCall(null); // 2
console.log(bar.myCall(obj, 'kevin', 18)); //1
// Object {
// value: 1,
// name: 'kevin',
// age: 18
// }apply实现
apply和call的唯一区别在于参数传递形式:call传入的是参数列表,而apply传入的是数组或类数组对象。
Function.prototype.myApply = function(context, arg) {
// 如果第一个参数传入的是undefined和null,context为window对象
context = context || window;
// context对象添加函数bar
context.fn = this; // this:bar,this指向调用myCall的函数bar
// context对象执行函数bar,并返回结果
let result = null;
if (!arg) { // 没有传入数组
result = context.fn();
}else{ // 传入了参数数组
result = context.fn(...arg);
}
return result;
}
// 测试一下
var value = 2;
var obj = {
value: 1
}
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
bar.myApply(null); // 2
console.log(bar.myApply(obj, ['kevin', 18])); // 1bind实现
- bind与call、apply的区别在于:bind返回的是一个改变了this指向的函数,便于稍后调用,不像call和apply会立即执行;bind和call在参数传递上相似,都接受参数列表,但bind允许分多次传入参数,无需像call那样一次性传入。
- 需要注意:当bind返回的函数使用new作为构造函数调用时,绑定的this值会失效,this将指向新创建的实例对象,但通过bind预先传入的参数依然生效(new调用的优先级高于bind调用)。
bind的实现最为复杂,因为经过bind绑定后的函数,既可以作为普通函数调用,也可以作为构造函数调用。
- bind返回的函数作为普通函数调用。
// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是参数传进来的name') // this:obj
bindFun('我是参数传进来的age')- bind返回的函数作为构造函数调用时,原本绑定的this值obj会失效,this将指向新创建的实例对象a。
// bind 返回的函数 作为构造函数调用
let bindFun = Person.myBind(obj, '我是参数传进来的name') // this:obj
let a = new bindFun('我是参数传进来的age') // this:abind返回的函数作为普通函数调用的代码实现。
// bind 返回的函数 作为普通函数调用
Function.prototype.myBind = function (context, ...args){
// 如果第一个参数传入的是undefined和null,context为window对象
context = context || window;
// context对象(obj)添加函数normalFun
context.fn = this; // this:normalFun, context.fn === normalFun,下面出现context.fn都可以直接看成normalFun
// bind返回的函数
return function (...innerArgs) {
// bind 返回的函数 作为普通函数被执行
context.fn(...[...args,...innerArgs]); //相当于normalFun(...[...args,...innerArgs])
}
}
// 测试
let obj = {
objName: '我是obj传进来的name',
objAge: '我是obj传进来的age'
}
// 普通函数
function normalFun(name, age) {
console.log(name); //'我是第一次参数传进来的name被args接收'
console.log(age); //'我是第二次参数传进来的age被innerArgs接收'
console.log(this === obj); // true,this指向obj
console.log(this.objName); //'我是obj传进来的name'
console.log(this.objAge); //'我是obj传进来的age'
}
// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是第一次参数传进来的name被args接收'); // this指向obj
bindFun('我是第二次参数传进来的age被innerArgs接收'); bind返回的函数作为构造函数调用。
// bind 返回的函数 再经过new调用
Function.prototype.myBind = function (context, ...args){
// 如果第一个参数传入的是undefined和null,context为window对象
context = context || window;
// context对象添加函数Person
context.fn = this; // this:Person,context.fn:Person,_this:Person
let _this = this;
// bind返回的函数
const result = function (...innerArgs) {
if (this instanceof _this ) { // this:a (new出来的实例对象) , _this:Person
// 为实例对象a添加Person方法
this.fn = _this;
// 实例对象a执行Person方法
this.fn(...[...args,...innerArgs]);
}
}
result.prototype = Object.create(this.prototype); // 为什加这一句?看原型图下面会解释
return result;
}
// 测试
function Person(name, age) {
console.log(name); //'我是第一次参数传进来的name被args接收'
console.log(age); //'我是第二次参数传进来的age被innerArgs接收'
console.log(this); //构造函数this指向实例对象
}
// 构造函数原型的方法
Person.prototype.say = function() {
console.log(123);
}
let obj = {
objName: '我是obj传进来的name',
objAge: '我是obj传进来的age'
}
// bind 返回的函数 作为构造函数调用
let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
let a = new bindFun('我是第二次参数传进来的age被innerArgs接收') // this:a
a.say() //123绘制以下两条语句的原型图以便理解。
let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
let a = new bindFun('我是第二次参数传进来的age被innerArgs接收') // this:a当执行下面语句时,原型图如下。
let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
当执行下面语句时,bindFun即为result(参见代码),原型图如下。
let a = new bindFun('我是第二次参数传进来的age被innerArgs接收') // this:a
此时实例对象a还需要继承构造函数Person的原型,因此原型链中增加了相关连接。
result.prototype = Object.create(this.prototype);最终的原型图结构如下。

bind代码最终实现。
Function.prototype.myBind = function (context, ...args){
// 如果第一个参数传入的是undefined和null,context为window对象
context = context || window;
// context对象添加函数Person
context.fn = this; // this:Person,context.fn:Person,_this:Person
let _this = this;
// bind返回的函数
const result = function (...innerArgs) {
if (this instanceof _this ) { // this:a (new出来的实例对象) , _this:Person
// 为实例对象a添加Person方法
this.fn = _this;
// 实例对象a执行Person方法
this.fn(...[...args,...innerArgs]);
}else{
// 普通函数被调用
context.fn(...[...args,...innerArgs]);
}
}
result.prototype = Object.create(this.prototype); // 为什加这一句?看原型图下面会解释
return result;
}
// 测试
// function Person(name, age) {
// console.log(name); //'我是第一次参数传进来的name被args接收'
// console.log(age); //'我是第二次参数传进来的age被innerArgs接收'
// console.log(this); //构造函数this指向实例对象
// }
// // 构造函数原型的方法
// Person.prototype.say = function() {
// console.log(123);
// }
// let obj = {
// objName: '我是obj传进来的name',
// objAge: '我是obj传进来的age'
// }
// // bind 返回的函数 作为构造函数调用
// let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
// let a = new bindFun('我是第二次参数传进来的age被innerArgs接收') // this:a
// a.say() //123
// 测试
let obj = {
objName: '我是obj传进来的name',
objAge: '我是obj传进来的age'
}
// 普通函数
function normalFun(name, age) {
console.log(name); //'我是第一次参数传进来的name'
console.log(age); //'我是第二次参数传进来的age'
console.log(this === obj); // true
console.log(this.objName); //'我是obj传进来的name'
console.log(this.objAge); //'我是obj传进来的age'
}
// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是第一次参数传进来的name被args接收'); // this指向obj
bindFun('我是第二次参数传进来的age被innerArgs接收'); 到此这篇关于JavaScript中call,apply,bind的区别与实现的文章就介绍到这了,更多相关JS call,apply,bind区别内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!