在这篇文章中的末尾有一道练习题。其中obj声明的pringName函数中使用的变量不是obj中的name属性。基于这点,js有一套this机制。
1. 关于this
我们已经知道,在执行上下文中包含语法环境、词法环境和outer指向全局执行上下文。此外,还有一个this。
this和执行上下文进行绑定。也就是说,this的值取决于它出现的上下文:全局、函数或类。
那看各种执行上下文中this的指向吧!
2. 全局执行上下文中的this
你可以在控制台中输出this,看其值。
输出的为Window对象。这也是this和作用域链的唯一交点,作用域链最底端包含了window对象。作用域链和this是两套不同的机制。
3. 函数指向上下文中的this
在函数中,this的值取决于被如何调用,在函数体被执行时创建其绑定。(调用时确定,与定义无关)
上代码:
function getThis(){
console.log(this)
}
getThis()
这段代码中,this输出什么?
也是window对象。这段代码说明:在全局执行上下文中直接调用一个函数,其this指向的是window对象(非严格模式下,严格模式下,指向undefined)。 那我们通过几种方法来手动改变一下this指向。
3.1 通过对象调用方法设置
上代码:
const obj = {
name: "obj",
getThis() {
console.log(this)
}
}
obj.getThis()
const cc = { name: "李三岁" }
cc.getThis = obj.getThis
cc.getThis()
const tmp = obj.getThis
tmp()
执行一下可以看到:
- obj调用getThis方法时,输出的this指向obj对象,为{name: 'obj', getThis: ƒ}
- cc调用getThis方法时,输出的this指向cc对象,为{name: '李三岁', getThis: ƒ}
- tmp常量被赋值为函数后,执行,输出this为window
对于tmp,我个人理解为,js中的执行代码中的函数,若未显式调用,则默认通过window对象进行调用,因此输出window。 因此:
- 对于典型的函数,使用对象调用其内部的方法时,该方法的this指向对象本身。
- 在全局环境中调用函数时,this指向window。
3.2 call方法设置
上代码:
function getThis() {
this.name = "李三岁"
}
let person = {
name: "李逍遥",
sex: "男"
}
getThis.call(person)
console.log(person)
最终输出的person中,name修改为了“李三岁”。我们会发现getThis函数中的this指向了person对象。 除了call方法之外,还有bind和apply方法。
此处的call方法重写的简单逻辑为:
Function.prototype.customCall = function (pointThis, ...args) {
// 非函数不能调用此方法
if (typeof this != 'function') {
throw new Error(this + 'is not a function')
}
// 若为空置为全局对象
pointThis = pointThis === null || undefined ? globalThis : Object(pointThis)
// 重定向this(把函数赋给新的对象,后续调用访问此新对象的属性)
let uniField = Symbol('customThis')
pointThis[uniField] = this
// 函数调用
const result = pointThis[uniField](...args)
// 删除赋予对象的属性,避免后续使用对象被污染
delete pointThis[uniField]
// 返回函数调用的结果
return result
}
let person = {
age: '18'
}
function testFunc(a, b) {
console.log(this, a + b)
console.log(this.age)
return a + b
}
testFunc.customCall(person, 1, 2)
置换了调用方。
3.3 通过构造函数设置
通常通过构造函数创建对象时,例如下述方法:
function testFunc() {
this.age = '17'
}
const f1 = new testFunc()
那testFunc中的this指向谁呢?这时候就拆解一下new。 当new一个对象的时候,逻辑如下:
- 首先,创建一个空对象{},称之为newInstance。
- 绑定原型链:将newInstance的[[Prototype]]属性指向构造函数的prototype属性。(如果构造函数的prototype属性是对象的话,否则,指向Object.prototype。这实现了原型链的继承,意味着如果在newInstance上找不到属性或方法的话,会沿着原型链往上查找)
- 执行构造函数逻辑:使用入参执行构造函数,并将newInstance绑定为this的上下文。此时testFunc函数的执行上下文中的this指向newInstance。(调用testFunc的call方法,并将newInstance作为call方法的第一个参数)
- 最后,返回newInstance。(如果执行构造函数返回的为非原始值-非Object类型的值-的话,否则返回构造函数的返回值)
代码说明为,obj即为newInstance:
function _new(constructorFn, ...args) {
let obj = {}
Object.setPrototypeOf(obj, constructorFn.prototype)
const result = constructorFn.call(obj, ...args)
if (result != null && typeof result === 'object') {
return result
}
return obj
}
使用此方法new一个对象的话是:
function testFunc(name) {
this.age = '17'
this.name = name
}
const f1 = _new(testFunc, 'twob')
这样我们就创建了一个17岁的twob。
4. 回调、嵌套函数中的this
4.1 回调函数
当一个函数作为回调函数传递时,this的值取决于如何调用回调函数。严格模式下,this的值为undefined。非严格模式下,为globalThis。
部分函数可以设置this的值:
4.2 嵌套函数
上代码:
let obj = {
age: "18",
showThis: function () {
console.log(this, '1')
function inner() {
console.log(this, '2')
}
inner()
}
}
obj.showThis()
嵌套函数中的this不会继承外层函数中的this,会指向window(非严格模式)
解决方案:
- 在外层函数中,把this保存到临时变量中;
- 使用箭头函数。
4.3 箭头函数
箭头函数不会创建其自身的执行上下文,其this值取决于外部函数。
文章参考:time.geekbang.org/column/intr… developer.mozilla.org/zh-CN/docs/… developer.mozilla.org/zh-CN/docs/…