js中this指向详解。附call、new设置this及方法重写(4)

160 阅读4分钟

这篇文章中的末尾有一道练习题。其中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/…