1. 程式人生 > >js中this的用法

js中this的用法

nodejs和browser中this的比較

nodejs的全域性物件是global,一個js檔案是一個模組(module),模組內部形成模組作用域(module scope),定義的變數只能在本模組使用。可以使用global物件或module.exports在多個模組間分享變數。

// 模組內部this預設指向module.exports
this === module.exports     // true
this === exports    // true
this    // {}

// x.js匯出
this.a = 1
exports.b = 2
module.exports.c = 3
// y.js const {a, b, c} = require('./x.js')

browser環境的全域性物件是window,相當於nodejs的global。由於不存在模組作用域,會導致this的值在nodejs和browser中不同。

// 全域性環境(global context)下this預設指向window
this === window     // true

變數宣告

var a = 1
let b = 2
const c = 3
d = 4

// nodejs中,a b c都是模組內部變數
global.a    // undefined
global.b    // undefined
global.c // undefined global.d // 4, d沒有使用關鍵字宣告,自動成為global的屬性 // browser中,未宣告的變數和使用var宣告的變數,自動稱為window的屬性,屬於遺留(legacy)問題 this.a // 1 this.d // 4 // es6引入let和const,宣告的變數不再自動成為window的屬性 this.b // undefined this.c // undefined

nodejs和browser中都不允許直接對this賦值。

this = 1    // Invalid left-hand side in assignment

函式上下文(function context)中this的用法

函式上下文中,nodejs和browser中,this的指向的原理基本相同,以下在nodejs中測試。

  • this沒有明確的指向時預設指向全域性物件
function foo() {
  return this
}

// 非嚴格模式下,未設定this的值,預設返回全域性物件
foo() === global    // true in node
foo() === window    // true in browser

function foo() {
  'use strict'
  return this
}

// 嚴格模式下,不允許this指向全域性物件
foo()   // undefined
  • 箭頭函式(arrow function)
    箭頭函式中的this指向會參考函式定義時的上下文(context),形成閉包(closure)。
this.a = 1
let foo = (b) => this.a + b

foo(2)      // 3
foo.call({a: 2}, 2)     // 3,call並沒有改變this的指向

let f1 = foo.bind({a: 3})
f1(2)       // 3,bind也沒有改變this的指向

this.a = 2
foo()       // 4,跟隨改變
  • 物件的方法
    函式作為物件的方法呼叫時,this指向呼叫該方法的物件。
let obj = {
  x: 1,
  f: getX,
  y: {
    x: 2,
    g: getX
  }
}

// getX可以在obj內部定義,也可以在外部定義
function getX() {
  return this.x
}

obj.f()     // 1
getX.call(obj)  // 1
obj.y.g()   // 2
getX.call(obj.y)    // 2
  • 建構函式

new關鍵字預設返回function中的this物件

function Foo() {
  this.a = 1
}

// new返回{a: 1}
let f = new Foo()

如果建構函式返回一個物件(object),則new返回該物件,不返回已定義的this物件

function Foo() {
  this.a = 1
  return {b: 2}    // 返回{b: 2}
  return [1, 2]    // 返回[1, 2]
  return new Number(2)    // 返回數值物件[Number 2]

  return null    // 返回{a: 1}
  return undefined    // 返回{a: 1}
  return    // 返回{a: 1}
  return 1    // 返回{a: 1}
  return 'abc'    // 返回{a: 1}
}
  • 物件的原型上(on the object’s prototype chain)

物件可以呼叫prototype上的方法,this指向當前物件,很像是物件自身的方法,這是js原型鏈繼承的特性。

function Foo(a, b) {
  this.a = a
  this.b = b
}

Foo.prototype.add = function () {
  console.log(this.a + this.b)
}

Foo.prototype.delayAdd = function () {
  setTimeout(this.add, 500)
}

let foo = new Foo(1, 2)
foo.add()       // 3,是預期結果,this會繫結foo

增加一個方法

Foo.prototype.delayAdd = function () {
  setTimeout(this.add, 500)
}

let foo = new Foo(1, 2)
foo.delayAdd()  // undefined,非預期結果

// 分析:delayAdd相當於下面的寫法
Foo.prototype.delayAdd = function () {
  // this指向foo
  setTimeout(function () {
    // 注意:這裡nodejs指向Timeout物件,是setTimeout()的返回值
    // browse裡this的指向window
    return this.a + this.b
  }, 500)
}

// 使用bind修改回撥函式中this的指向
Foo.prototype.delayAdd = function () {
  setTimeout(this.add.bind(this), 500)
}

foo.delayAdd()  // 3,正確

例項分析

主要考慮物件方法、普通函式、箭頭函式中this的指向。通過物件方法呼叫和賦值呼叫可能結果不同。

let o = {
  foo: () => this,
  bar: function () {
    return this
  },
  baz: function () {
    return () => this
  },
  qux: () => {
    return () => this
  },
  one: function () {
    return function () {
      return this
    }
  },
  two: () => {
    return function () {
      return this
    }
  }
}

foo是使用箭頭函式定義的物件方法,上下文中this指向module.exports,形成閉包。

o.foo() === module.exports  // true
o.qux() === module.exports  // true

// 只有箭頭函式巢狀
let foo = () => {
  return () => {
    return () => this
  }
}
foo()()() === exports   // true

bar是使用普通函式定義的物件方法,內部this指向當前物件o。箭頭函式定義時形成閉包,this指向上下文的this。

o.bar() === o       // true
o.baz()() === o     // true

one和two中this沒有直接位於物件方法中,也沒有其他方法改變this的指向,指向global。

o.one()() === global    // true
o.two()() === global    // true

如果是先賦值再呼叫,結果可能不同,主要考慮上下文的改變。

let b = o.baz
b()() === global    // true
// 相當於
let b = function(){
  return () => this
}

參考:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this