0%

this-in-javascript

This in javascript

在JavaScript中,this是一个非常重要的概念,它代表了函数或方法执行时所处的上下文,this的值取决于函数的调用方式,而不是定义的位置,所以this是在运行时确定的。(箭头函数另议)

this的值

概括来说,this的值受以下因素影响

函数或方法的调用方式

作为普通函数调用

当函数做为普通函数调用时,this指向全局对象,此时又分为严格模式和非严格模式。

  • 严格模式:this的值为undefined
  • 非严格模式:浏览器中为window对象,Node.js中为global对象。

以下代码,在Browser环境运行时,输出window对象,在Node.js环境运行时,输出global对象。

1
2
3
4
5
function globalFunction() {
console.log(this);
}

globalFunction(); // window or global

而以下代码在浏览器和Node.js中则都输出undefined

1
2
3
4
5
6
'use strict';
function globalFunction() {
console.log(this);
}

globalFunction(); // undefined

作为对象方法调用

当函数作为对象的方法调用时,this指向调用该方法的对象。

1
2
3
4
5
6
7
8
const person = {
name: 'Philip',
sayName() {
console.log(this.name);
}
}

person.sayName(); // Philip

需要注意的是,如果将对象方法赋值给普通函数,那么调用普通函数时,this将指向全局对象。
下面的代码,虽然person.sayName是对象方法,但是它被赋值给了普通函数sayName,所以this指向全局对象。但是全局对象上没有name属性,所以无论是Browser环境还是Node.js环境,都输出undefined

1
2
3
4
5
6
7
8
9
10
11
const person = {
name: 'Philip',
sayName() {
console.log(this.name);
}
}

const sayName = person.sayName;
// or const sayName = person['sayName'];
// or const {sayName} = person;
sayName(); // undefined

作为构造函数调用

当函数作为构造函数调用时,this指向新创建的对象。(除非构造函数返回了另外一个对象),在JS中如果函数的首字母大写,表示这是一个构造函数。

1
2
3
4
5
function Person(name) {
this.name = name;
}
const person = new Person('zdd');
console.log(person.name); // zdd

使用原型链定义的构造函数也一样

1
2
3
4
5
6
7
8
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(this.name);
}
const person = new Person('Philip');
person.sayName(); // 输出:Philip,此时 this 指向 person 实例

需要注意:使用原型链定义函数时,不要用箭头函数,因为箭头没有自己的this绑定,而是继承其parent的this,下面代码中,this指向全局对象,会导致name值为undefined。

1
2
3
4
5
6
Person.prototype.sayName = () => {
console.log(this.name);
};

const john = new Person('Philip');
john.sayName(); // 输出:undefined,此时 this 指向全局对象

如果构造函数返回了一个对象,那么this指向这个返回的对象,而不是新创建的对象。

1
2
3
4
5
6
7
8
function Foo() {
this.name = 'Philip';

return {age: 18};
}

const foo = new Foo();
console.log(foo.name); // undefined

在这里,this指向了返回的对象{age: 18},而不是新创建的对象foo。而新创建的对象上没有name属性,所以输出undefined

函数类型

普通函数

上面已经讲过,不再赘述。

箭头函数

与普通函数不同,箭头函数没有自己的this绑定(注意,不是没有this),箭头函数的this继承自它的父级作用域this(定义时就确定了), 所以箭头函数的this是在定义时确定的,而不是在运行时确定的。
以下代码中,sayName是一个箭头函数,它的this继承自它的父级this,也就是全局对象,而全局对象上没有name属性,所以输出undefined

1
2
3
4
5
6
7
8
const person = {
name: 'Philip',
sayName: () => {
console.log(this.name);
},
};

person.sayName(); // undefined

箭头函数的this继承自它的父级作用域this,这里的父级作用域必须是函数或者全局作用域,不能是对象。比如下面的代码:arrowFunction输出undefined。因为outer是对象,并不是它的父级作用域,所以this指向全局对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const outer = {
name: 'outer',
inner: {
name: 'inner',
regularFunction() {
console.log(this.name); // 'inner'
},
arrowFunction: () => {
console.log(this.name); // undefined
},
},
};

outer.inner.regularFunction(); // inner
outer.inner.arrowFunction(); // undefined

思考题1:下面代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function outer() {
this.name = 'outer';

this.inner = function () {
this.name = 'inner';
return {
printName() {
console.log(this.name);
},
};
};
}

const outerObj = new outer();
outerObj.inner().printName();

答案:undefined, 原因:

  1. inner function最终返回一个对象,对象中的printName是一个对象方法。
  2. 返回的对象上没有name属性,所以输出undefined

思考题2:下面代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
function outer() {
this.name = 'outer';
this.inner = function () {
this.name = 'inner';
return {
printName: () => {
console.log(this.name);
},
};
};
}
const outerObj = new outer();
outerObj.inner().printName();

答案:inner, 因为printName是一个箭头函数,它的this继承自它的父级this,也就是inner函数的this,所以输出inner

你可以使用bind, call, apply来调用箭头函数,但是这些方法无法改变箭头函数的this值,因为箭头函数的this值是在定义时确定的。

1
2
3
4
5
6
7
8
9
10
11
const globalObject = this;
const foo = () => this;
const obj = {name: 'Philip'};
console.log(foo() === globalObject); // true

//bind
const boundFoo = foo.bind(obj);
console.log(boundFoo() === globalObject); // true

console.log(foo.call(obj) === globalObject); // true
console.log(foo.apply(obj) === globalObject); // true

Arrow functions create a closure over the this value of its surrounding scope, which means arrow functions behave if they are “auto-bound” — no matter how it’s invoked, this is bound to what it was when the function was created (the example above, the global object). The same applies to arrow functions created inside other functions: their thremains that of the enclosing lexical context.

箭头函数与普通函数this的区别

函数类型 this指向 this确定时机 是否可以更改this
普通函数 取决于调用情况 运行时确定 可以
箭头函数 取决于父级this 定义时确定 不可以

运行环境

由上面的代码可知,Browser环境与Node.js环境,this的值也不同。

模块类型

CommonJS模块

在CommonJS模块中,top-level this的值为空对象{}。以下代码在输出{}。(可以在Node.js中新建test.js文件,然后运行如下代码)

1
console.log(this);

ES6模块

在ES6模块中,top level this的值为undefined。以下代码在输出undefined。(可以在Node.js中新建test.mjs文件,然后运行如下代码)

1
console.log(this); // undefined

注意,下面这个this不是top level this, 因为它位于函数内部,所以它指向全局对象global.

1
2
3
4
5
function foo() {
console.log(this); // global this.
}

foo(); // undefined

Strict mode

我们以上的讨论都是基于非strict mode下,在严格模式下,全局this的值为undefined。注意strict mode并不影响对象方法及构造函数调用,只影响普通函数调用。

1
2
3
4
5
6
7
'use strict';

function test() {
console.log(this); // undefined
}

test();

globalThis

无论在哪个环境,都可以使用globalThis来获取全局对象。

1
console.log(globalThis);

global this vs globalThis

In Node.js, the this keyword inside a module (a file) refers to the current module's exports, not the global object. This is because each file in Node.js is treated as a separate module and has its own scope.
On the other hand, globalThis is a standard built-in object in JavaScript that always refers to the global object, regardless of where it is called. In the case of Node.js, globalThis refers to the global object which is global
看以下代码

1
console.log(this === globalThis); // true in browser, false in Node.js
1
2
console.log(this); // window in browser, {}(empty object) in Node.js, undefined in ES module
console.log(globalThis); // window in browser, global object in Node.js

DOM中的this

DOM事件处理函数中的this指向绑定事件的元素。以下示例以Chrome浏览器为准。
输出myButton,原因是this指向了绑定事件的元素。

1
2
3
<body>
<button id="myButton" onclick="alert(this.id)">OK</button>
</body>

下面的代码同样输出myButton,原因是this指向了绑定事件的元素。

1
2
3
4
5
6
7
<button id="myButton">OK</button>
<script>
const myButton = document.getElementById("myButton");
myButton.addEventListener("click", function () {
alert(this.id);
});
</script>

下面的代码输出undefined. 因为onButtonClick是普通函数,所以this指向了全局对象。

1
2
3
4
5
6
7
8
<body>
<button id="myButton" onclick="onButtonClick()">OK</button>
<script>
function onButtonClick() {
alert(this.id);
}
</script>
</body>

在实际代码中,我们一般不这样使用this,而是通过event.target来获取元素。

1
2
3
4
5
6
<button id="myButton">OK</button>
<script>
const myButton = document.getElementById("myButton");
myButton.addEventListener("click", function (event) {
alert(event.target.id);
});

也可以使用箭头函数:

1
2
3
4
5
6
7
8
9
<body>
<button id="myButton">OK</button>
<script>
const myButton = document.getElementById("myButton");
myButton.addEventListener("click", (event) => {
alert(event.target.id);
});
</script>
</body>

class中的this,注意super。

手动改变this的值

JS中call, apply, bind这三个函数都可以改变this的值。详情请看这里

总结:

  1. 普通函数调用,this指向全局对象,在浏览器环境下,严格模式this是undefined, 非严格模式下,是 window对象。

  2. 通过对象调用函数,this指向调用函数的对象,如果是链式调用,则指向离函数最近的对象。(如果将对象方法赋值给普通函数,那么遵循第一条)

  3. 调用构造函数时,this指向新创建的对象。

  4. 通过apply/call/bind调用的函数,this指向apply/call/bind绑定的对象,也即第一个参数。

    注意,bind 与apply/call的区别,bind会重新创建一个函数。

  5. 箭头函数中的this,指向箭头函数所在的上下文环境,比如包含箭头函数的函数或者全局上下文。

Global this

  • global context - Outside of any functions or classes(may be defined as an block or arrow functions in global scope)
  • global scope - In a programming environment, the global scope is the scope that contains, and is visible in, all other scopes.
    In client-side JavaScript, the global scope is generally the web page inside which all the code is being executed.
  • global object - A global object is an object that exists in global scope.
    • Browser - window
    • Node.js - global
    • Worker - self
  • globalThis - The global property allow one to access the global object regardless of the current environment.

总结

最后我们用一个表格总结一下本文的内容:

  1. strict mode只影响普通函数调用,不影响对象方法及构造函数调用。

    Mode non-strict mode strict mode
    Environment Browser Node.js Browser Node.js
    this in regular function window global object undefined undefined
  2. top level this

    Environment Browser Node.js ES6 Module
    Top level this window {} undefined