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