在最近的JS学习中,我遇到了一个头疼的问题,this关键字,究竟是如何指向的呢,我时常会被它弄得晕头转向。不知道各位是否也会被this搞得头晕。我将在这里分享我最近学习this的心得,如有不对之处还请大佬们指出。
引言
在JavaScript编程中,
this
关键字是一个核心概念,它在函数执行时指代函数的上下文环境。正确理解和使用this
不仅能够提升代码的可读性和可维护性,还能有效减少因上下文传递而导致的冗余代码。本文将深入解析this
的工作原理、使用场景,并通过实际代码示例来阐述其重要性。
在正文开始前请各位分析一段代码
var a = 2
function foo() {
var a = 1
function bar () {
console.log(this.a);
}
bar()
}
foo()
你们觉得它会输出什么呢,1 还是 2呢?。不知道?不用担心,往下看
this
的基础与指向规则
this
的指向并非一成不变,而是根据函数的调用方式和上下文环境动态决定的。以下是this
的几种典型绑定规则:
-
默认绑定:当函数独立调用,或者作为全局函数调用时,非严格模式下
this
指向全局对象(在浏览器中是window
,Node.js中是global
)。 -
隐式绑定:当函数作为某个对象的属性方法被调用时,
this
绑定到该对象上。 -
显式绑定:通过
.call()
,.apply()
, 或.bind()
方法,可以显式地将this
绑定到指定对象。- call 会把foo中的this掰弯到obj中
- apply 也是一样,只不过参数是数组
- bind 会创建一个新的函数,这个函数的this被掰弯到obj中
-
new绑定:使用
new
操作符调用构造函数时,this
会绑定到新创建的实例上。 -
箭头函数:箭头函数不绑定自己的
this
,它会捕获其所在上下文的this
值,即定义时的上下文。
很显然,在前言的例子中,它是属于默认绑定的 其中的bar()
函数就是独立调用,所以它在node环境中会输出undefined,在window中输出的是2.
请大家记住一句话,函数在哪个词法作用域调用,那么this就会指向那个词法作用域
请大家再来看看下面的例子,根据上面那句话来分析:
var a = 1
function foo(){
var a = 2;
function bar(){
var a = 3;
function baz(){
console.log(this.a);
}
baz();
}
bar();
}
foo();
var a = 2
分析: baz在bar中调用,所以baz的this指向bar的this,同理,bar的this指向foo的this,foo的this指向全局。所以baz也是指向全局的,这就是默认绑定
其他的绑定规则会在下面一一展示
箭头函数的特性
箭头函数由于不绑定自己的this
,因此特别适合用于回调函数、事件处理器等场景,减少了因this
改变导致的问题。例如:
const obj = {
a: 1,
handler: () => console.log(this.a), // 这里的this指向定义时的全局对象,而非obj
};
obj.handler(); // 输出:1 (在非严格模式下,取决于全局环境)
this
的优先级
当多种绑定规则同时存在时,它们遵循一定的优先级:
- new绑定
- 显示绑定 (
call
,apply
,bind
) - 箭头函数(无自己的
this
,继承自外层) - 隐式绑定
- 默认绑定
实践示例
示例1:默认绑定与全局作用域
(function(){
var a = 1;
console.log(this.a); // 输出:undefined(严格模式下)或1(非严格模式下,取决于全局环境)
})();
示例2:隐式丢失
var obj = {
a: 1,
foo: foo
}
function foo() {
var a = 2;
console.log(this.a);
}
// obj.foo();
var obj2 = {
obj: obj
}
obj2.obj.foo();
隐式丢失:当一个函数被多个函数链式调用时,this指向最近调用的对象
示例3:显式绑定:
var obj = {
a: 1
}
function foo(x, y) {
console.log(this.a, x + y);
}
foo.call(obj, 1, 2); // call 会把foo中的this掰弯到obj中
foo.apply(obj, [3, 4]); // apply 也是一样,只不过参数是数组
// foo.bind(obj)(5, 6); // bind 会创建一个新的函数,这个函数的this被掰弯到obj中 // 返回的是一个新函数
const bar = foo.bind(obj,1,1);
bar(2);
看完上面的示例,相信各位大佬都能理解this的绑定规则了
手写myCall
方法
首先我们先理解官方的call方法
var obj = {
a: 1
}
function foo() {
console.log(this.a);
}
foo.call(obj);
foo可以调用call说明它被打造在Function.prototype(原型)上,其实它就是借助了隐式绑定规则来修改this的指向。谁调用myCall,他就拿到谁。在obj中再写一份foo(),再让obj触发掉foo,这就触发了隐式绑定,这就把this指向obj了,最后把foo()销毁。就形成如下四步
- 拿到foo :
- 将foo引用到obj上
- 让obj触发foo
- 移除obj上的foo
var obj = {
a: 1
}
function foo() {
console.log(this.a);
}
// foo.call(obj);
Function.prototype.myCall = function(context){
const context = arguments[0];
const args = Array.from(arguments).slice(1); // 使用Array.from将类数组对象转换为数组
context.fn = this;
var result = context.fn(...args);
delete context.fn;
return result;
}
foo.myCall(obj);
通过上述内容,我们可以看到this
在JavaScript中扮演着至关重要的角色,掌握其使用和绑定规则,是成为一名高效JavaScript开发者的关键。无论是通过原生方法还是自定义实现,灵活运用this
能够极大地增强代码的灵活性和可维护性。