JavaScript中改变this指向的三种方式总结
本文详细解析JavaScript中改变this指向的三种核心方法:call、apply和bind。通过对比它们的使用场景与参数差异,深入剖析其底层实现原理,并逐步演示如何手动实现myCall、myApply以及处理new关键字的完整版myBind,帮助开发者彻底掌握函数绑定机制。
前言
在之前的文章《可能是你看过最完整的this指向总结!》中,我们探讨了this在不同执行环境下的指向规律,其中特别指出:
this的绑定值并非固定不变,而是可以通过特定手段进行手动调整,常用的手段包括call、bind和apply这三个内置方法。
本文将重点讲解这三个方法的具体用法,并进一步探讨如何从零开始实现自定义的myCall、myBind和myApply方法。
一、call
1. 使用方法
function person(a,b,c){
console.log(this);
console.log(this.name,this.age)
}
person(); // 1.直接调用,this指向window
// 打印 window
// 打印 undefined,undefined
const obj = {name:"owl",age:18}
person.call(obj) // 2.传入指定的this值
// 打印 {name: 'owl', age: 18}
// owl 18
function person(a,b,c){
console.log(a,b,c)
console.log(this);
console.log(this.name,this.age)
}
person.call(obj,1,2,3) // 3.给person函数传入指定的this值和实参值
// 打印 1 2 3
// {name: 'owl', age: 18}
// owl 18
2. 实现myCall方法
在了解call()的基本用法后,我们尝试手动实现一个myCall方法,具体的代码逻辑及注释说明如下:
Function.prototype.myCall = function (obj) {
if (typeof this !== "function") {
throw new Error(
"Function.prototype.myCall - what is trying to be bound is not callable"
);
}
const ctx = obj || window; // 1.定义一个ctx变量获取传入的对象obj,如果没有则取window
ctx.func = this; // 2.在ctx对象上新增一个属性func,并且给他赋值为this
// this就是调用myCall函数的函数,在本例中就是person()方法
const args = Array.from(arguments).slice(1); // 3.处理传入的参数,第一个参数为对象obj,
// 所以从第二个开始参数截取
const result = arguments.length > 1 ? ctx.func(...args) : ctx.func();
// 4. 如果传入参数,我们就把实参带入到func函数中执行,如果没有,则直接执行。
delete ctx.func; // 5. 执行完函数以后,记得删除掉这个“中间变量”属性 ctx
return result; // 6. 返回result
};
在上述代码中,func变量实际上指向的是person函数,而ctx变量则是我们传入用于指定this指向的目标对象,即 {name: 'owl', age: 18}。
当我们执行第四步,使用ctx.func()或者ctx.func(...args)来调用func()时,这一过程正好契合了前文中提到的调用对象的函数方法时,被调用函数中的this永远指向这个对象这一规则。
因此,通过这种机制,我们成功实现了手动控制this指向的目标。
var obj = {
name: "owllai",
};
function testCall(a, b, c) {
console.log(this.name, a, b, c);
}
testCall.myCall(obj,1,2,3)
apply
- apply方法与call方法的主要区别在于,其接收的第二个参数必须是一个类数组对象。
- 除了参数传递形式的差异外,两者的核心逻辑几乎一致。因此,在编写自定义apply方法时,只需对涉及参数处理的部分进行相应调整即可。
1.使用方法
function person(a,b,c){
console.log(this);
console.log(this.name,this.age)
}
person(); // 1.直接调用,this指向window
// 打印 window
// 打印 undefined,undefined
const obj = {name:"owl",age:18}
person.apply(obj) // 2.传入指定的this值
// 打印 {name: 'owl', age: 18}
// owl 18
function person(a,b,c){
console.log(a,b,c)
console.log(this);
console.log(this.name,this.age)
}
person.apply(obj,[1,2,3]) // 3.给person函数传入指定的this值和实参值(类数组对象)
// 打印 1 2 3
// {name: 'owl', age: 18}
// owl 182.实现myApply方法
Function.prototype.myApply = function (obj) {
if (typeof this !== "function") {
throw new Error(
"Function.prototype.myApply - what is trying to be bound is not callable"
);
}
const ctx = obj || window; // 1.定义一个ctx变量获取传入的对象obj,如果没有则取window
ctx.func = this; // 2.在ctx对象上新增一个属性func,并且给他赋值为this
// this就是调用myApply函数的函数,在本例中就是person()方法
const args = arguments[1]; // 3.处理传入的参数,第一个参数为对象obj,
// 第二个参数为数组实参
const result = arguments[1] ? ctx.func(...arguments[1]) : ctx.func();
//第四步: 调用方法,获得结果。
delete ctx.func;
return result;
};在实现myApply方法时,我们主要需要修改两处逻辑:
- 第三步获取参数时,直接提取arguments数组中的第二项
- 第四步调用函数时,将提取到的arguments数组第二项作为参数传入
bind
1.使用方法
- bind方法的参数传递形式与call类似:第一个参数用于指定this的指向值,从第二个参数开始,依次是执行函数所需的形参。
- 与call和apply不同,bind方法在调用后不会立即执行函数,而是返回一个新的函数供后续调用。
语法:
bind(thisArg) bind(thisArg, arg1) bind(thisArg, arg1, arg2) bind(thisArg, arg1, arg2, /* …, */ argN)
function person(a, b, c) {
console.log(this);
console.log(this.name, this.age);
}
const obj = { name: "owl", age: 18 };
let newPerson = person.bind(obj);
console.log(newPerson); // ƒ person(a, b, c) {
// console.log(this);
// console.log(this.name, this.age);
// }
newPerson(); // {name: 'owl', age: 18}
// owl 18
function person(a, b, c) {
console.log(a,b,c)
console.log(this);
console.log(this.name, this.age);
}
let newPersonWithArgs = person.bind(obj,1,2,3) ;
newPersonWithArgs(); // 1 2 3
// {name: 'owl', age: 18}
// owl 182.实现myBind方法
简单版本
function person(a, b, c) {
console.log(a, b, c);
console.log(this);
console.log(this.name, this.age);
}
let obj = {
name: "owllai",
age: 18,
};
Function.prototype.myBind = function (obj) {
if (typeof this !== "function") {
throw new Error(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
var self = this; // 1. 这个this代表调用myBind方法的函数,在本例中也就是person
var args = Array.prototype.slice.call(arguments, 1);
// 2. 获取传入的其他参数,从arguments数组的第二项开始截取
var fn = function () { // 3.定义一个要返回的函数
//4. 返回的新函数也是可以接收参数的,所以我们要一起获取
var newArgs = Array.prototype.slice.call(arguments);
//5. 将obj和参数一起传入,使用apply来执行
return self.apply(obj, args.concat(newArgs));
};
//6. 最后返回结果函数
return fn;
};
let newBind = person.myBind(obj, 1, 2, 3);
newBind();虽然myBind的简单版本已经实现,但仍存在一个问题:既然返回的是一个新函数,除了直接调用newBind()外,该函数还可以作为构造函数,通过new关键字进行实例化。
例如以下的实例化场景:
let newBind = person.myBind(obj, 1, 2, 3);
let n = new newBind();- 通常情况下,变量n应当是newBind函数通过new关键字创建的实例化对象,此时构造函数的this应指向该实例化对象。
- 然而在
return self.apply(obj, args.concat(newArgs));这一行代码中,我们硬编码了this指向为obj,这显然不符合预期行为。 - 因此,我们必须考虑new关键字的情况,并对简单版本的myBind代码进行改造。
完整版本
Function.prototype.myBind = function (obj) {
if (typeof this !== "function") {
throw new Error(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
var self = this; // 这个this代表调用myBind方法的函数,在本例中也就是person函数
var args = Array.prototype.slice.call(arguments, 1);
var fn = function () {
var newArgs = Array.prototype.slice.call(arguments);
return self.apply(
//如果没有进行判断,永远写死obj作为apply的第一个参数,那么如果对fn这个返回函数进行new时,这个fn函数的this指向永远是外部传过来的obj
//这样是不正确的,如果作为new关键字使用这个fn函数,this指向必须是指向new出来的实例对象
//怎么判断是不是用new关键字来调用呢?
// 我们可以用 instanceof 来判断返回函数的原型是否在实例的原型链上
// 如果返回函数是被new了,那这个返回函数的实例对象的this就指向了person函数
this instanceof fn ? this : obj,
args.concat(newArgs)
);
};
// 创建一个空函数
var tmpFn = function () {};
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
// 可以直接使用 fn.prototype = this.prototype (this代表fn)
// fn.prototype = this.prototype;
// 也就是让返回函数的原型对象和person函数的原型对象映射
// 至于为什么使用一个空函数 tmpFn 作为中介,把 fn.prototype 赋值为空对象的实例(原型式继承),
// 这是因为直接 fn.prototype = this.prototype 有一个缺点,修改 fn.prototype 的时候,也会直接修改 this.prototype ;
// tmpFn空函数的原型指向绑定函数的原型
tmpFn.prototype = this.prototype; //(this代表person函数)
// 空对象的实例赋值给 fn.prototype
fn.prototype = new tmpFn();
return fn;
};至此,关于JavaScript中改变this指向的三种方式及其源码实现的总结介绍完毕。如需了解更多JavaScript改变this指向的相关内容,欢迎查阅本站过往文章或继续浏览后续文章,感谢大家的支持!
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
