javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript关键字this

JavaScript关键字this的使用方法详解

作者:IanLew

与其他语言相比,函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别,本文就给大家讲解一下JavaScript关键字中的this,需要的朋友可以参考下

在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。

涵义

当前执行上下文(global、function 或 eval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。

this 可以用在构造函数之中,表示实例对象。除此之外,this 还可以用在别的场合。但不管是什么场合,this 都有一个共同点:它总是返回一个对象。简单说,this 就是属性或方法“当前”所在的对象。

const p = {
  name: 'jay',
  describe() {
    console.log(`${this.name} is a good man`)
  }
}
p.describe()  // jay is a good man

上面代码中,this.name 表示 name 属性所在的那个对象。由于 this.name 是在 describe 方法中调用,而 describe 方法所在的当前对象是 p,因此 this 指向 pthis.name 就是 p.name

由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即 this 的指向是可变的。

const n = {
  name: 'jj'
}
n.describe = p.describe
n.describe()  // jj is a good man

上面代码中,p.describe 属性被赋给 n,于是 n.describe 就表示 describe 方法所在的当前对象是 n,所以this.name 就指向 n.name

关于 this 的指向改变的理解可以参考之前编译执行的相关文章,这里不做赘述。稍稍重构这个例子,this 的动态指向就能看得更清楚。

function describe () {
  console.log(`${this.name} is a good man`)
}
const p = {
  name: 'jay',
  describe
}
const n = {
  name: 'jj',
  describe
}
p.describe()  // jay is a good man
n.describe()  // jj is a good man

只要函数被赋给另一个变量,this 的指向就会变。

const p = {
  name: 'jay',
  describe: function () {
    console.log(`${this.name} is a good man`)
  }
}
const name = 'jj'
const f = p.describe
f()  // jj is a good man

上面代码中,p.describe 被赋值给变量 f,内部的 this 就会指向 f 运行时所在的对象(本例是顶层对象)。

实质

JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this 就是函数运行时所在的对象(环境)。JavaScript 支持运行环境动态切换,也就是说,this 的指向是动态的,没有办法事先确定到底指向哪个对象。

const o = { n: 1 }

JavaScript 引擎会先在内存里面,生成一个对象 { n: 1 },然后把这个对象的内存地址赋值给变量 o。也就是说,变量 o 是一个地址引用(reference)。后面如果要读取 o.n,引擎先从 o 拿到内存地址,然后再从该地址读出原始的对象,返回它的n 属性。原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。

{
  n: {
    [[value]]: 1
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}

属性的值可能是一个函数。

const o = { n: function () {} }

这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给n 属性的 value 属性。

{
  n: {
    [[value]]: 函数的地址
    ...
  }
}

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

const f = function () {}
const o = { f }
f()  // 在全局对象执行
obj.f()  // 在 obj 对象里面执行

由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this 就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

使用场合

this 主要有以下几个使用场合。

全局上下文

无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象。在浏览器环境下,它指的就是顶层对象 window。可以使用 globalThis 获取全局对象,无论你的代码是否在当前上下文运行。

console.log(this === window)  // true

函数上下文

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

function f () {
  return this
}
// 在浏览器中,全局对象是 window
console.log(f() === window)  // true
//在 Node 中
console.log(f() === globalThis)  // true

然而,在严格模式下,如果进入执行环境时没有设置 this 的值,this 会保持为 undefined

function f () {
  "use strict"
  return this
}
console.log(f() === undefined)  // true

箭头函数

在箭头函数中,this 与封闭词法环境的 this 保持一致。在全局代码中,它将被设置为全局对象。

const f = () => this
console.log(f() === this)  // true

构造函数

当一个函数用作构造函数时(使用new关键字),它的 this 被绑定到正在构造的新对象。

function C() {
  this.a = 37
}
const o = new C()
console.log(o.a)  // 37

DOM 事件处理函数

当函数被用作事件处理函数时,它的 this 指向触发事件的元素(一些浏览器在使用非 addEventListener 的函数动态地添加监听函数时不遵守这个约定)。

function bluify (e) {
  console.log(this === e.currentTarget)  // true
}
const element = document.getElementById('do')
element.addEventListener('click', bluify, false)

内联事件处理函数

当代码被内联 on-event 处理函数调用时,它的 this 指向监听器所在的 DOM 元素。

<button onclick="alert(this.tagName.toLowerCase());">Show this</button>

类上下文

this 在 class 中的表现与在函数中类似,因为类本质上也是函数,但也有一些区别和注意事项。在类的构造函数中,this 是一个常规对象。类中所有非静态的方法都会被添加到 this 的原型中。

class P {
   constructor () {
    const proto = Object.getPrototypeOf(this)
    console.log(Object.getOwnPropertyNames(proto))
   }
   first () {}
   second () {}
   static third () {}
}
new P()  // [ 'constructor', 'first', 'second' ]

不像基类的构造函数,派生类的构造函数没有初始的 this 绑定。派生类不能在调用 super() 之前返回,除非其构造函数返回的是一个对象,或者根本没有构造函数。

class A extends P { }
class B extends P {
  constructor() {
    return { a: 5 }
  }
}
class C extends P {
  constructor() {
    super()
  }
}
class D extends P {
  constructor() { }
}
new A()  // ['constructor']
new B()  // 
new C()  // ['constructor']
new D()  // ReferenceError: Must call super constructor in derived class before

对象的方法

当函数作为对象里的方法被调用时,this 被设置为调用该函数的对象。

const o = {
  prop: 37,
  f: function () {
    return this.prop
  }
}
console.log(o.f())  // 37

请注意,这样的行为完全不会受函数定义方式或位置的影响。如果对象的方法里面包含 thisthis 的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this 的指向。

const o = { prop: 37 }
function independent() {
  return this.prop
}
o.f = independent
console.log(o.f())  // 37

this 的绑定只受最接近的成员引用的影响,如果 this 所在的方法不在对象的第一层,这时 this 只是指向当前一层的对象,而不会继承更上面的层。

a = {
  p: 1,
  b: {
    p: 2,
    m: function() {
      console.log(this, this.p)
    }
  }
}
a.b.m() // { p: 2, m: [Function: m] }, 2

但是,下面这几种用法,都会改变 this 的指向。

var o ={
  n: function () {
    console.log(this)
  }
};
o.n()
// 情况一
(o.n = o.n)()  // window
// => (o.n = function () { console.log(this) })()
// => (function () { console.log(this) })()
// 情况二
(false || o.n)()  // window
// => (false || function () { console.log(this) })()
// 情况三
(1, o.n)()  // window
// => (1, function () { console.log(this) })()

原型链

对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象,就像该方法就在这个对象上一样。

const o = {
  f: function () {
    return this.a + this.b
  }
}
const p = Object.create(o)
p.a = 1
p.b = 4
console.log(p.f())  // 5

getter 与 setter

再次,相同的概念也适用于当函数在一个 getter 或者 setter 中被调用。用作 getter 或 setter 的函数都会把 this 绑定到设置或获取属性的对象。

function sum() {
  return this.a + this.b + this.c
}
const o = {
  a: 1,
  b: 2,
  c: 3,
  get average() {
    return (this.a + this.b + this.c) / 3
  }
}
Object.defineProperty(o, 'sum', {
  get: sum,
  enumerable: true,
  configurable: true
})
console.log(o.average, o.sum)  // 2, 6

使用注意项

this 的动态切换,为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。所以在使用的时候,需要特别注意以下几点。

注意多层this

由于 this 的指向是不确定的,所以切勿在函数中包含多层的 this

var o = {
  f1: function () {
    console.log(this)  // {f1: ƒ}
    var f2 = function () {
      console.log(this)  // Window
    }()
  }
}
o.f1()

f2 在编译的时候提升到全局,所以是 window。如果要使用 o 作为 this,可以在 f1 记录 this 然后使用。使用一个变量固定 this 的值,然后内层函数调用这个变量,是非常常见的做法,请务必掌握。

JavaScript 提供了严格模式,也可以硬性避免这种问题。严格模式下,如果函数内部的 this 指向顶层对象,就会报错。

const counter = { count: 0 }
counter.inc = function () {
  'use strict'
  this.count++
}
const f = counter.inc
f()
// TypeError: Cannot read properties of undefined (reading 'count')

注意数组处理方法中的this

数组的 map 和 foreach 方法,允许提供一个函数作为参数。这个函数内部不应该使用 this

const o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this, `${this.v} ${item}`)
    })
  }
}
o.f()
// window, undefined a1
// window, undefined a2

forEach 方法会调用数组中每一项的 toString 方法,所以 this 指向顶层对象 window。解决办法同样可以记录 fthis,然后使用。另一种方法是将 this 当作 foreach 方法的第二个参数,固定它的运行环境。

注意回调函数中的this

回调函数中的 this 往往会改变指向,最好避免使用。

var o = new Object()
o.f = function () {
  console.log(this)  // $('#button')
}
// jQuery 写法
$('#button').on('click', o.f)

点击按钮以后,此时 this 不再指向 o 对象,而是指向按钮的 DOM 对象,因为f方法是在按钮对象的环境中被调用的。为了解决这个问题,可以采用 callapplybind 方法对 this 进行绑定,也就是使得 this 固定指向某个对象,减少不确定性。

以上就是JavaScript关键字this的使用方法详解的详细内容,更多关于JavaScript关键字this的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文