欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

如何理解JavaScript中的this关键字

程序员文章站 2022-04-11 08:27:42
前言:王福朋老师的 "JavaScript原型和闭包系列" 文章看了不下三遍了,最为一个初学者,每次看的时候都会有一种 “大彻大悟” 的感觉,而看完之后却总是一脸懵逼。 原型与闭包 可以说是 JavaScirpt 中理解起来最难的部分了,当然,我也只是了解到了一些皮毛,对于 JavaScript O ......

前言:王福朋老师的 javascript原型和闭包系列 文章看了不下三遍了,最为一个初学者,每次看的时候都会有一种 “大彻大悟” 的感觉,而看完之后却总是一脸懵逼。原型与闭包 可以说是 javascirpt 中理解起来最难的部分了,当然,我也只是了解到了一些皮毛,对于 javascript oop 更是缺乏经验。这里我想总结一下 javascript 中的 this 关键字,王福朋老师的在文章里也花了大量的篇幅来讲解 this 关键字的使用,可以说 this 关键字也是个值得注意的。

我也不知道博客园编译出来的markdown文档会这么丑,作为一个完美主义者,有点无法忍受,附上原文链接


我们都知道,每一个 ”代码段“ 都会执行在某一个 上下文环境 当中,而在每一个代码执行之前,都会做一项 “准备工作”,也就是生成相应的 上下文环境,所以每一个 上下文环境 都可能会不一样。

上下文环境 是什么?我们可以去看王福朋老师的文章(链接在文末),讲解的很清楚了,这里不赘述了。

”代码段“ 可以分为三种:

  • 全局代码
  • 函数体
  • eval 代码

与之对应的 上下文环境 就有:

  • 全局上下文
  • 函数上下文

elav 就不讨论了,不推荐使用)

当然,这和 this 又有什么关系呢?this 的值就是在为代码段做 “准备工作” 时赋值的,可以说 this 就是 上下文环境 的一部分,而每一个不同的 上下文环境 可能会有不一样的 this值。

每次在寻找一个问题的解决方案或总结一个问题的时候,我总会去尝试将这个问题进行合适的分类,而从不同的方面去思考问题。

所以,这里我大胆的将 this 关键字的使用分为两种情况:

  1. 全局上下文的 this

  2. 函数上下文的 this

(你也可以选择其他的方式分类。当然,这也不重要了)

全局上下文中的 this

在全局执行上下文中(在任何函数体外部),this 都指向全局对象:

// 在浏览器中, 全局对象是 window
console.log(this === window) // true

let a = 'zavier tang'
console.log(a) // 'zavier tang'
console.log(window.a) // 'zavier tang'
console.log(this.a) // 'zavier tang'

this.b = 18
console.log(a) // 18
console.log(window.a) // 18
console.log(this.a) // 18

// 在 node 环境中,this 指向global
console.log(this === global) // true

函数上下文中的 this

在函数内部,this 的值取决与函数被调用的方式。

this 的值在函数定义的时候是确定不了的,只有函数调用的时候才能确定 this 的指向。实际上 this 的最终指向的是那个调用它的对象。(也不一定正确)

1. 全局函数

对于全局的方法调用,this 指向 window 对象(node下为 global ):

let foo = function () {
  return this
}
// 在浏览器中
foo() === window // true

// 在 node 中
foo() === global //true

但值得注意的是,以上代码是在 非严格模式 下。然而,在 严格模式 下,this 的值将保持它进入执行上下文的值:

let foo = function () {
  "use strict"
  return this
}

f2() // undefined

即在严格模式下,如果 this 没有被执行上下文定义,那它为 undefined

在生成 上下文环境 时:

  • 若方法被 window(或 global )对象调用,即执行 window.foo(),那 this 将会被定义为 window(或 global );
  • 若被普通对象调用,即执行 obj.foo(),那 this 将会被定义为 obj 对象;(在后面会讨论)
  • 但若未被对象调用,即直接执行 foo(),在非严格模式下,this 的值默认指向全局对象 window(或 global ),在严格模式下,this 将保持为 undefined

通过 this 调用全局变量:

let a = 'global this'

let foo = function () {
  console.log(this.a)
}
foo() // 'global this'
let a = 'global this'

let foo = function () {
  this.a = 'rename global this' // 修改全局变量 a
  console.log(this.a)
}
foo() // 'rename global this'

所以,对于全局的方法调用,this 指向的是全局对象 window (或global ),即调用方法的对象。(注意严格模式的不同)

2. 作为对象的方法

当函数作为对象的方法调用时,它的 this 值是调用该函数的对象。也就是说,函数的 this 值是在函数被调用时确定的,在定义函数时确定不了(箭头函数除外)。

let obj = {
  name: 'zavier tang',
  foo: function () {
    console.log(this)
    console.log(this.name)
  }
}

obj.foo() // object {name: 'zavier tang', foo: function}    // 'zavier tang'

//foo函数不是作为obj的方法调用
let fn = obj.foo // 这里foo函数并没有执行
fn() // window {...}  // undefined

this 的值同时也只受最靠近的成员引用的影响:

//接上面代码
let o = {
  name: 'zavier tang in object o',
  fn: fn,
  obj: obj
}
o.fn() // object {name: 'zavier tang in object o', fn: fn, obj: obj}  // 'zavier tang in object o'
o.obj.foo() // object {name: 'zavier tang', foo: function}    // 'zavier tang'

3. 作为构造函数

如果函数作为构造函数,那函数当中的 this 便是构造函数即将 new 出来的对象:

let foo = function () {
  this.name = 'zavier tang',
  this.age = 20,
  this.year = 1998,
  console.log(this)
}

let tang = new foo()

console.log(tang.name) // 'zavier tang'
console.log(tang.age) // 20
console.log(tang.year) // 1998

foo 不作为构造函数调用时,this 的指向便是前面讨论的,指向全局变量:

// 接上面代码
foo() // window {...}

4. 函数调用 applycallbind

当一个函数在其主体中使用 this 关键字时,可以通过使用函数继承自function.prototypecallapply 方法将 this 值绑定到调用中的特定对象。即 this 的值就取传入对象的值:

let obj1 = {
  name: 'zavier1'
}

let obj2 = {
  name: 'zavier2'
}

let foo = function () {
  console.log(this)
  console.log(this.name)
}
foo.apply(obj1) // ojbect {name: 'zavier1'}   //'zavier1'
foo.call(obj1) // ojbect {name: 'zavier1'}   //'zavier1'

foo.apply(obj2) // ojbect {name: 'zavier2'}   //'zavier2'
foo.call(obj2) // ojbect {name: 'zavier2'}   //'zavier2'

applycall 不同,使用 bind 会创建一个与 foo 具有相同函数体和作用域的函数。但是,特别要注意的是,在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,无论之后如何调用。

let foo = function () {
  console.log(this.name)
}

let obj1 = {
  name: 'zavier1'
}
let obj2 = {
  name: 'zavier2'
}

let g = foo.bind(obj1)
g() // 'zavier1'

let h = g.bind(ojb2) // bind只生效一次!
h() // 'zavier1'

let o = {
  name: 'zavier tang',
  f:f,
  g:g,
  h:h
}
o.f() // 'zavier tang'
o.g() // 'zavier1'
o.h() // 'zavier1'

5. 箭头函数

在箭头函数中,this 的值与创建箭头函数的上下文的 this 一致。

在全局代码中,this 的值为全局对象:

let foo = (() => this)
//在浏览器中
foo() === window // true
// 在node中
foo() === global // true

作为对象的方法:

let foo = (() => this)

let obj ={
  foo: foo
}
// 作为对象的方法调用
obj.foo() === window // true

// 用apply来设置this
foo.apply(obj) === window // true
// 用bind来设置this
foo = foo.bind(obj)
foo() === window // true

箭头函数 foothis 被设置为创建时的上下文(在上面代码中,也就是全局对象)的this 值,而且无法通过其他调用方式设定 foothis 值。

与普通函数对比,箭头函数的 this 值是在函数创建创建确定的,而且无法通过调用方式重新设置 this 值。普通函数中的 this 值是在调用的时候确定的,可通过不同的调用方式设定 this 值。


原文链接