js中this的用法
阿新 • • 發佈:2018-11-10
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