本文详细解析JavaScript中this关键字的四种指向场景:对象方法调用、普通函数调用、构造器调用及call/apply调用。重点对比call与apply在参数传递上的差异,并通过代码示例演示如何利用它们改变this指向及借用数组方法,帮助开发者掌握核心机制。
引言与参考资料
学习背景:
在早期的JavaScript开发中,this、call与apply机制常令人困惑,但其应用频率极高。为此,我专门投入一天时间深入研读并厘清这三者的逻辑。
参考资源涵盖《JavaScript设计模式与开发实践》(主)、《JavaScript高级程序设计》及《你不知道的JavaScript》(辅)。这三部著作对构建this、call、apply的知识体系至关重要。
JavaScript中this的指向规则
this关键字解析
本节首先聚焦于this关键字的行为机制。
依据《JavaScript设计模式与开发实践》的观点,理解this的关键在于把握其核心定义。
JavaScript中的this始终指向一个特定的对象。 在实际运行环境中,this的指向行为可归纳为以下四种场景:
- 场景一:作为对象的方法被调用
- 场景二:作为普通函数被调用
- 场景三:作为构造函数被调用
- 场景四:通过apply或call方法调用
下文将逐一剖析前三种场景,第四种场景(apply与call)将在后续章节详细展开。
1. 作为对象的方法调用
规则:当方法属于某个对象时,this指向该对象实例。 示例代码:
/**
* 1.作为对象的方法调用
*
* 作为对象方法调用时,this指向该对象。
*/
var obj = {
a: 1,
getA: function() {
console.log(this === obj);
console.log(this.a);
}
};
obj.getA(); // true , 1
2. 作为普通函数调用
规则:当函数独立运行时,this默认指向全局对象(在浏览器环境中为window)。 示例代码:
/**
* 2.作为普通函数调用
*
* 不作为对象属性调用时,this必须指向一个对象。那就是全局对象。
*/
window.name = 'globalName';
var getName = function() {
console.log(this.name);
};
getName(); // 'globalName'
var myObject = {
name: "ObjectName",
getName: function() {
console.log(this.name)
}
};
myObject.getName(); // 'ObjectName'
// 这里实质上是把function() {console.log(this.name)}
// 这句话赋值给了theName。thisName在全局对象中调用,自然读取的是全局对象的name值
var theName = myObject.getName;
theName(); // 'globalName'
3. 作为构造器调用
规则:当使用new关键字调用构造函数时,this指向新创建的实例对象。 示例代码:
/**
* 3.作为构造器调用
*
* 作为构造器调用时,this指向返回的这个对象。
*/
var myClass = function() {
this.name = "Lxxyx";
};
var obj = new myClass();
console.log(obj.name); // Lxxyx
console.log(obj) // myClass {name: "Lxxyx"}
例外情况:若构造函数内部显式return一个对象,则this的指向失效,返回该对象。 若return非对象类型的数据,则正常返回新实例。
var myClass = function() {
this.name = "Lxxyx";
// 加入return时,则返回的是别的对象。this不起作用。
return {
name:"ReturnOthers"
}
};
var obj = new myClass();
console.log(obj.name); // ReturnOthers
call与apply方法详解
4. Call与Apply机制
Call与Apply功能一致,均用于强制指定函数执行时this的指向。
Call与Apply的差异对比
Call:首个参数指定this指向,后续参数需逐个传入。 Apply:首个参数指定this指向,第二个参数为数组,用于批量传入所有参数。
若首个参数传null或undefined,this将指向调用上下文本身(在非严格模式下指向全局对象)。
1. 改变this指向
说明:这是call和apply最核心的应用场景,用于动态切换函数内部的this绑定。 示例代码:
var name = "GlobalName"
var func = function() {
console.log(this.name)
};
func(); // "GlobalName"
var obj = {
name: "Lxxyx",
getName: function() {
console.log(this.name)
}
};
obj.getName.apply(window) // "GlobalName" 将this指向window
func.apply(obj) // "Lxxyx" 将this指向obj
2. 借用其他对象的方法
此处以立即执行函数(IIFE)作为引入:
(function(a, b) {
console.log(arguments) // 1,2
// 调用Array的原型方法
Array.prototype.push.call(arguments, 3);
console.log(arguments) // 1,2,3
})(1,2)
函数内部存在arguments属性,其类型为类数组对象。 由于类数组对象无法直接调用数组原型方法,需借助call或apply调用Array原型方法。 其原理直观:以push方法为例,在V8引擎源码中实现如下:
function ArrayPush() {
var n = TO_UINT32(this.length); // 被push对象的长度
var m = % _ArgumentsLength(); // push的参数个数
for (var i = 0; i < m; i++) {
this[i + n] = % _Arguments(i); // 复制元素
}
this.length = n + m; //修正length属性
return this.length;
}
该方法仅依赖this指针,因此任何类数组对象均可通过此方式调用相关数组方法进行处理。
此部分逻辑较为复杂,加之作者自身水平限制,建议有条件者购买相关专著,或关注后续博客更新。
学习总结
通过本章节学习,深化了对JavaScript核心机制的理解。最显著的进步在于阅读优秀框架源码时,不再因this、call、apply及bind的交织使用而感到困惑,过程充满成就感。
下一阶段计划:深入探索日常开发中常用的CSS技术。鉴于JavaScript基础已夯实,HTML与CSS的同步提升同样不可或缺。