你真的知道 JavaScript 中的“this”吗?

开发 前端
在前端面试过程中,面试官经常会问一些关于this关键字的问题,即使是工作多年的人也可能知之甚少。因此,本文对this关键字进行了详细分析,以便他人深入了解。

介绍

在前端面试过程中,面试官经常会问一些关于this关键字的问题,即使是工作多年的人也可能知之甚少。因此,本文对this关键字进行了详细分析,以便他人深入了解。

什么是"this"?

调用函数时,将创建一个执行环境,this 在运行时根据函数的执行环境绑定。它允许函数在内部引用上下文中的执行变量,使函数编程更加优雅和简洁。

看看下面的代码,想想为什么不同的调用方法会打印出不同的结果。

var a = 10


const obj = {
a: 20,
foo: function() {
console.log(this.a)


return function() {
console.log(this.a)
}
}
}


obj.foo()() // 20 10
const fn = obj.foo
fn() // 10

其实很简单,因为不同的调用方法的this指向不同的点。为什么这指向不同的函数调用方法?是什么决定了这一点?现在让我们开始带着问题深入了解这个问题!

“this”的约束规则

默认绑定

默认绑定规则下,函数的运行环境为全局环境,this默认指向Window。

默认绑定规则如下:

1、this指向Window的全局函数

在全局函数中直接打印 this 时,可以看到 this 指向 Window。

2、独立函数调用this指向Window

独立的函数调用,即直接调用函数,如 foo()。

function foo() {
console.log(this);
}


foo()

这里的foo默认链接到Window,相当于window.foo()。根据函数的隐式绑定规则,谁调用谁就指向谁。这里的 this 指向 Window。结果如下:

图片

同样,如果嵌套函数中直接调用的函数也是独立的函数调用,那么this也指向Window:

var a = 10
var obj = {
a: 20,
foo: function() {
console.log(this); // {a: 20, foo: ƒ}
console.log(this.a); // 20


function son() {
console.log(this); // Window
console.log(this.a); // 10
}
// Independent function call
son()
}
}


obj.foo()

在上面的代码中,子函数son也嵌套在对象obj的方法foo中。当直接调用子方法时,子里面的this指向Window,所以子函数里面的this.a结果是全局变量a,也就是10。

如果要在子函数中使用 obj 中的变量 a 怎么办?只需将 this 对象分配给另一个变量,并在子方法中引用此变量:

var a = 10
var obj = {
a: 20,
foo: function() {
const that = this


function son() {
console.log(that.a); // 20
}
son()
}
}


obj.foo()

3、对于自执行函数调用,this指向window

自执行函数,顾名思义,就是定义函数后自动调用的函数,自执行函数的this指向如下代码:

// exp1
(function() {
console.log(this); // Window
})()


// exp2
var a = 10
function foo() {
(function son(that) {
console.log(this); // Window
console.log(that); // {a: 20, foo: ƒ}
})(this)
}


var obj = {
a: 20,
foo: foo,
}


obj.foo()

上面代码中的foo函数内部嵌套了一个自执行函数son,而son内部的this指向Window。这里this指向的原理类似于独立函数调用,即先声明一个son方法,然后通过son()执行该函数。如果要获取son中上层对象obj的变量,可以在调用时将this点作为参数传递给自执行函数son。

4、闭包里面的this指向Window

闭包可以理解为一个函数内部定义的函数,可以访问其他函数的内部变量。当我们查看闭包中的 this 点时,我们可以看到 this 指向 Window。

var a = 10


var obj = {
a: 20,
foo: function() {
var sum = this.count + 10


console.log(this.a); // 20
return function() {
console.log(this.a); // 10
return sum
}
}
}


obj.foo()()

上面代码中,foo函数的第一个this.a的this指向obj对象,所以结果是20。return函数调用的this指向Window,结果是10。obj.foo ()() 可以理解为:

const fn = obj.foo()
fn()

fn 是 obj.foo() 返回的函数。fn 函数是独立调用的,this 指向 Window。

隐式绑定

当函数作为方法调用时,this指向函数的直接父对象,称为隐式绑定。

在隐式绑定规则中,认为 this 指向调用函数的人,并将指向函数的直接父对象。比如obj.foo()中foo函数里面的this指向obj,而obj1中的foo函数。obj2.foo() 指向 obj2。

var a = 10
function foo () {
console.log(this.a);
}


var obj = {
a: 20,
foo: foo,
obj2: {
a: 30,
foo: foo
}
}


// exp1
foo() // 10


// exp2
obj.foo() // 20


// exp3
obj.obj2.foo() // 30

上面的代码也是对 foo 函数的调用。调用方法不同,结果不同。

‘exp1’中的foo直接被独立函数调用,所以,this指向Window,结果为10;‘exp2’中的调用方法是obj。foo(),foo函数的this指向上级调用对象obj;结果是 20。'exp3' 中 foo 函数的直接上级对象是 obj2,所以结果是 30。

隐式绑定丢失

隐式绑定丢失意味着隐式绑定的函数丢失了它的绑定对象,所以默认绑定到Window。这种方法在我们的项目中很容易导致错误,但也很常见。

1、隐式绑定的函数被分配为没有 this 指向的变量。

在下面的代码中,obj下的foo值实际上是foo函数的地址信息,并不是真正的foo函数。当 obj.调用 foo() 时, this 的 this 隐式绑定到 obj。当 var fn=obj.foo 为 fn 分配一个函数时。相当于把foo函数的地址赋给fn。这时候fn没有和obj关联,所以这里fn()的运行环境就是全局环境,this指向Window,this的结果a 是 10。

var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a);
}
}
function bar (fn) {
fn()
}


bar(obj.foo) // 10

2、隐式绑定的函数作为参数传给函数,丢失了this点。

当一个隐式绑定的函数直接作为参数传递给另一个函数时,这个绑定会丢失,从而指向全局Window。obj.foo作为参数传给bar函数后,this.a的结果是10。这里bar(obj.foo)等价于var fn=obj.foo; bar(fn)。

var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a);
}
}
function bar (fn) {
fn()
}


bar(obj.foo) // 10

3、内置对象setTimeout和setInterval函数的隐式绑定丢失

内置函数 setTimeout 和 setInterval 的 this 默认指向 Window。

// exp1
setTimeout(function() {
console.log(this); // Window
}, 1000)


// exp2
var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a); // 10
}
}


setTimeout(obj.foo, 1000)

对了,当setTimeout或者setInterval的第一个参数是箭头函数时,this会指向上层的函数执行环境。代码如下:

var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a); // 20


setTimeout(() => {
console.log(this.a); // 20
}, 1000)


setTimeout(function() {
console.log(this.a); // 10
}, 1000);
}
}


obj.foo()

显式绑定

当我们要将函数绑定到指定对象时,可以使用call、apply、bind等方法手动改变this的方向,即显式绑定。

在下面的代码中,将 foo 显式绑定到 p 对象的方法分别使用 call、apply 和 bind 来举例说明。显式绑定 call 和 apply 的方法会在显式绑定后直接调用,而显式绑定 this 到 bind 的方法需要手动调用。

var obj = {
a: 20,
foo: function () {
console.log(this.a);
}
}
var p = {
a: 30,
}


obj.foo() // 20
obj.foo.call(p) // 30
obj.foo.apply(p) // 30


const fn = obj.foo.bind(p)
fn() // 30

关于硬装订

显式绑定可以帮我们把this改成指定对象,但是不能解决隐式绑定缺失的问题,比如:

var a = 10
function foo() {
console.log(this.a);
}


var obj = {
a: 20,
foo: foo
}


var p = {
a: 30
}


function func(fn) {
fn()
}


func.call(p, obj.foo) // 10

在上面的代码中,调用是绑定 this 指向 p 对象,但最终 this 指向的是 Window。此时,我们可以通过硬绑定来解决这个问题。

var a = 10
function foo() {
console.log(this.a);
}


var obj = {
a: 20,
foo: foo
}


var p = {
a: 30
}


function func(fn) {
fn()
}


let bar = function () {
foo.call(p)
}
bar() // 30

“new”绑定

new绑定是我们常用的方法。事实上,我们可以创建一个构造,然后新建一个实例对象。这时候this指向了new出来的实例对象。

当我们彼此认识时,我们主要做以下事情:

  • a、创建一个新对象
  • b、让这一点指向新对象并执行构造体
  • c、将新对象的 proto 属性设置为指向构造的原型对象
  • d、判断构造的返回类型。如果是,则返回新对象。如果它是引用类型,它将返回此类型的对象。

首先,创建了“Person”的构造,然后,通过“new”创建了一个“zhangsan”的实例对象。在“zhangsan”的“foo”函数中,“this”指向“zhangsan”的实例。

function Person(name, age) {
this.name = name
this.age = age
this.foo = function () {
console.log(this.name);
}
}


const zhangsan = new Person('zhangsan', 18)
console.log(zhangsan) // {name: 'zhangsan', age: 18, foo: ƒ}
zhangsan.foo() // zhangsan

在严格模式下,“this”指向一个问题。

1.独立调用函数的内部“this”是“undefined”

function foo() {
"use strict"
console.log(this); undifined
}
foo()

2. “call()”和“apply()”中的this总是他们的第一个参数

var a = 10
var obj = {
a: 20,
foo: function () {
"use strict"
console.log(this);
}
}


// When null | undefined, in non-strict mode, this points to window
obj.foo.call(null) // null
obj.foo.call(undefined) // undefined
obj.foo.apply(null) // null
obj.foo.apply(undefined) // undefined
var fn = obj.foo.bind(null)
fn()

总结

这是一个比较复杂的知识点。当然,如果我们真的理解了this的原理,那么遇到this所指出的问题就很简单了。如果我们明白了这一点,不仅可以为前端面试加分,也有利于我们的发展和学习。让我们总结一下,其约束原则如下:

  • 默认绑定,this指向全局Window。
  • 不要忘记隐藏绑定的丢失。
  • 它显示了绑定。他通过使用 call、apply 和 bind 改变了这个方向。
  • new绑定,构造new的一个实例,this指向new的实例对象。
责任编辑:华轩 来源: web前端开发
相关推荐

2022-09-26 13:10:17

JavaScriptthis

2022-09-19 00:46:18

JavaScrip功能开发

2014-01-03 09:13:39

JavaScriptthis

2013-05-08 10:36:07

JavaScriptJS详解JavaScrip

2020-02-19 14:02:49

JavaScriptthis前端

2016-10-11 20:33:17

JavaScriptThisWeb

2019-12-17 08:16:04

JavaScriptthis编程

2011-05-25 14:23:55

Javascriptthis

2017-05-23 15:47:04

JavaScriptthis解析

2014-05-16 10:04:19

JavaScriptthis原理

2009-09-28 11:34:49

Javascript

2020-02-14 13:50:32

JavaScript前端技术

2017-07-26 17:10:24

2018-03-15 16:45:47

前端JavaScriptthis

2009-07-14 14:12:14

Javascript

2015-10-29 14:29:48

javascriptthis指向

2020-08-04 10:31:53

JavaScriptthis开发

2021-04-23 10:01:19

JavaScript 关键字对象

2022-05-09 09:07:47

JavaScript框架开发

2021-02-17 11:25:33

前端JavaScriptthis
点赞
收藏

51CTO技术栈公众号