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,无论箭头函数如何被调用或在哪里被调用,其内部的this2不会改变。 - 适合场景:箭头函数非常适合那些需要访问父作用域
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