JavaScript中this的那些事儿
前言
在js中,this关键字是十分重要但很容易混淆的概念。它的值取决于函数的调用上下文,具体来说,
this
的值在执行时绑定,并且可以指向不同的对象,具体有:默认绑定,隐式绑定,隐式丢失,显示绑定以及new绑定等,废话不多说,直接开始。
THIS
哪里可以写this
- 全局。有以下几种情况:
- 浏览器环境中:全局的
this
通常指向window
对象。这意味着在全局作用域中或者非严格模式下的普通函数中使用this
,它将引用window
对象。例如,在控制台直接输入this
并执行,会输出窗口对象的详细信息。 - Node.js环境中:全局的
this
指向global
对象。global
对象是Node.js中的全局命名空间,类似于浏览器中的window
,包含了所有的全局变量和函数。 - 严格模式('use strict';) :在严格模式下,全局作用域中的
this
不会自动指向全局对象。在非函数体的顶层代码中(比如脚本文件的最外层或模块顶级),this
会是undefined
,而在函数内部的严格模式下,未明确绑定的this
同样是undefined
而不是全局对象。
- 函数体内。这就来到了今天的重点,也是让很多前端小白都头疼的函数内的this指向。
函数内的this绑定
1.默认绑定
非严格模式下的普通函数调用: 当一个函数并非作为对象的方法被调用,也没有使用 new
关键字,且不是箭头函数时,this
会被绑定到全局对象(在非严格模式下)。同样地,与全局中的this类似,在严格模式下,这种情况下的 this
会是 undefined
,而这种情况加上全局中的this指向,就组成了this的默认绑定。
2.隐式绑定
首先来看这段代码
let obj = {
myname:'ace',
age:18,
say:function(){
console.log(this.myname);
}
}
obj.say()
这段代码首先是定义了obj这个对象,其内部有myname
、age
以及一个say
,分别存放了字符串
、数字
以及一个函数体
。最后是调用了obj内的函数体say
,要输出this.myname
,而明显只有同样作为对象属性的myname是正解,而this能找到吗?是可以的:
再来看这段代码:
而这就是this的隐式绑定:当一个函数被某个对象所拥有,或者函数被某个上下文对象调用时,该函数中的this指向该上下文对象。而这是我们“看不到”的,所以称为隐式绑定。
3.隐式丢失
这种严格来说是隐式绑定的特殊情况,this
的隐式丢失是指原本通过隐式绑定绑定到特定对象的 this
,在某些情况下失去了对该对象的绑定,转而指向了其他对象或默认值(在非严格模式下是全局对象,在严格模式下是 undefined
)。这种情况通常发生在以下几种情形中:
- 赋值给另一个变量或作为参数传递: 如果将一个对象的方法赋值给一个变量,然后通过这个变量来调用函数,那么原本的隐式绑定就会丢失。
const obj = {
method: function() {
console.log(this);
}
};
const func = obj.method; // 方法赋值给变量
func(); // 这里 `this` 不再指向 `obj`,而是全局对象(非严格模式)或 `undefined`(严格模式)
- 间接引用方法调用: 当通过一个表达式来间接调用一个对象的方法时,如果该表达式的结果不是一个对象属性引用,
this
的绑定也可能丢失。
const obj = {
method: function() {
console.log(this);
}
};
let funcName = 'method';
obj[funcName](); // 这里 `this` 仍指向 `obj`,因为是间接但正确的对象属性引用
(obj[funcName] = obj[funcName])(); // 但这样调用会导致 `this` 的隐式丢失
- 数组的
.map()
,.forEach()
等迭代方法: 当在这些迭代方法中使用对象的方法时,如果不采取措施,this
可能不会按预期绑定。
const array = [1, 2, 3];
const obj = {
process: function(value) {
console.log(this, value);
}
};
array.forEach(obj.process); // 这里 `this` 不指向 `obj`
4.显示绑定
显示绑定即通过特定的方法明确this的指向,而并不通过js引擎来自动指定,在js中主要有以下几种方法来完成显示绑定:
-
.call()
方法:func.call(context, arg1, arg2, ...)
允许你显式地设置函数执行时的this
值为context
,同时可以传递额外的参数给函数。
-
.apply()
方法:func.apply(context, [argsArray])
与.call()
相似,也是用来改变函数调用的this
上下文,但它接受一个数组或类数组对象作为参数列表传递给被调用的函数。
-
.bind()
方法:var boundFunc = func.bind(context, arg1, arg2, ...)
返回一个新的函数,该函数的this
值永久绑定到context
上。还可以预先传递部分参数给函数。与.call()
和.apply()
立即调用函数不同,.bind()
返回的函数需要你手动调用。
这三种方法都是用来改变this的指向的,但他们的返回值有所不同:
.call()
方法会立即调用函数,并返回该函数的执行结果。这意味着如果被调用的函数有返回值,.call()
就会返回这个值。如果没有明确的返回值(或者函数返回undefined
),.call()
也会返回undefined
。- 类似于
.call()
,.apply()
也会立即调用函数,并返回该函数的执行结果。 - 与前两者不同,
.bind()
方法并不会立即调用函数,而是返回一个新的函数,这个新函数的this
值被永久绑定到了.bind()
的第一个参数所指定的上下文。这意味着.bind()
的直接返回值是一个函数,你可以稍后调用这个新函数。被绑定的函数如果有返回值,将在你调用这个新函数时返回,而不是在.bind()
调用时。
这就是this的显示绑定:通过call,apply,bind,将函数的this掰弯到一个对象中。
5.new绑定
new
绑定是一种特殊的函数调用模式,用于创建并初始化一个由构造函数定义的新对象。当使用new
关键字调用一个函数时,会发生以下几件事,从而形成了this
的new
绑定:
- 新对象创建:首先,JavaScript会创建一个新的空对象(即实例)。
- 原型链链接:接着,这个新对象的[[Prototype]](也就是内部的
__proto__
属性)被链接到构造函数的prototype
属性所指向的对象上。这意味着新对象可以从构造函数的原型链上继承属性和方法。 this
绑定:在构造函数内部,this
被绑定到新创建的对象上。这意味着在构造函数中使用this
时,它指的是新创建的实例。- 执行构造函数:构造函数的代码被执行,其中可以使用
this
来初始化新对象的属性和方法。 - 自动返回:如果构造函数没有显式返回一个对象(或者返回
null
、undefined
),那么new
调用会自动返回新创建的对象。如果构造函数返回一个对象,那么这个对象将替代默认的返回值。
例如:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name);
};
const alice = new Person('Alice');
alice.sayHello(); // 输出 "Hello, my name is Alice"
在这个例子中,new Person('Alice')
使用new
关键字调用了Person
构造函数,创建了一个新的Person
实例,并将name
属性初始化为"Alice"。之后,我们可以通过这个实例调用sayHello
方法。
这就是this的几种绑定,但还有一个例外
例外
箭头函数
箭头函数中没有this这个机制,写在箭头函数中的this那也是外层非箭头函数的。
这意味着箭头函数内部的this
是词法绑定(lexical binding),在定义时就已经确定,不会像普通函数那样在运行时动态改变。具体来说:
- 词法作用域绑定:箭头函数不生成自己的
this
上下文,它没有自己的this
。它会捕获其所在上下文的this
值,并将其作为自己的this
值使用。这个上下文通常是包含箭头函数的最近一层非箭头函数的上下文。 - 不变性:一旦箭头函数捕获了
this
,无论箭头函数如何被调用或在哪里被调用,其内部的this
2不会改变。 - 适合场景:箭头函数非常适合那些需要访问父作用域
this
的场景,比如在事件处理程序、回调函数或定时器中,可以避免使用.bind()
方法或额外的变量来保存this
。 看以下代码:
const obj = {
name: 'Object',
regularMethod: function() {
console.log(this.name); // 输出 "Object"
},
arrowMethod: () => {
console.log(this.name); // 输出 "Window" 或 "global",取决于环境,因为这里的this继承自全局作用域
}
};
obj.regularMethod(); // 正常绑定到obj
obj.arrowMethod(); // 箭头函数的this继承自外部作用域
// 使用箭头函数保持外部this
const obj2 = {
name: 'Another Object',
method: function() {
setTimeout(() => {
console.log(this.name); // 输出 "Another Object",因为箭头函数继承了method的this
}, 100);
}
};
obj2.method();
在这个例子中,regularMethod
内的this
按照常规函数的规则绑定到obj
上,而arrowMethod
内的this
则继承了定义时的上下文,即局对象。在obj2
的method
中,箭头函数保证了在setTimeout
回调里this
依然指向obj2
。
总结
js中的this关键字十分重要,又容易混淆视听,希望我的分享对你有所帮助,我们下次再见!!!
转载自:https://juejin.cn/post/7392194854862372902