当构造函数内部拥有返回值时会发生什么

534 阅读3分钟

现象

我们都知道,在 JavaScript 里面生成一个对象有很多种方法,其中一种便是使用构造函数。首先,定义一个构造器,在构造器内部定义对象的属性,再在构造器的原型上定义对象的方法,如下所示:

const Person = function (name) {
  this.name = name;
}
Person.prototype.sayName = function () {
  console.log(this.name)
}

于是,当我们对 Person 调用 new 操作符,并传入一个 name 的时候,便会生成一个新的对象, 并且该对象会继承 Person 原型上所定义的属性或方法。

然而,当我们的构造器拥有一个返回值的时候,会发生什么呢?

const Person = function (name) {
  this.name = name;
  return { name:  'Jason' }
}

Person.prototype.sayName = function () {
  console.log(this.name)
}

const person = new Person('Tony')

person.name
// Jason
person.sayName
// undefined
person instanceof Person
// false

可以看到,当我们在构造函数中返回一个对象时,对构造函数调用 new 操作符,最后得到的将会是我们返回的对象,而当我们返回一个非对象的值的时候,得到的则是在构造函数中初始化的 this

注:这里的对象表示非原始值:

The ECMAScript language types are Undefined, Null, Boolean, String, Symbol, Number, and Object.

那么,造成这种现象的原因是什么呢?这个时候就需要了解当我们对一个构造函数调用 new 操作符的时候,到底发生了什么。

概念

ECMAScript 规范中,定义了函数对象这一概念,一个函数对象内部包含了以下几个属性(简单摘抄,完整属性列表参见前面链接):

  • FunctionKind ("normal", "classConstructor", "generator", "async")
  • ConstructorKind("base", "derived")

作为构造器的函数对象还含有内部方法[[Construct]]

流程

对构造函数调用 new

当我们对一个函数调用 new 操作符的时候,会执行EvaluateNew(constructExpr, arguments)方法。

  1. 方法内部会对传入的参数进行一系列的校验,并通过 constructExpr 获取相应的 constructor
  2. 当解析后得到的 constructor 不是构造器(如箭头函数)的时候,会抛出一个 TypeError
  3. 执行语句 Return ? Construct(constructor, argList)
  4. 调用 constructorconstruct 方法 —— Return ? F.[[Construct]](argumentsList, newTarget)

Construct

  1. 先进行一系列的断言
  2. 判断 F.ConstructorKind 是否为 basebase 表示基类),如果是 base,则初始化函数内部的 thisObject.create(F.prototype)
  3. 判断 F.ConstructorKind 是否为 basebase 表示基类),如果是,则执行OrdinaryCallBindThis(F, calleeContext, thisArgument)
  4. 执行 OrdinaryCallEvaluateBody(F, argumentsList),得到结果 result
  5. 如果 result 的值为一个对象,则直接返回该对象
  6. 如果 F.ConstructorKindbase,则返回上面初始化的 this
  7. 如果 result 的值不是 undefined,则抛出一个TypeError
    注:这种场景,比如在继承后的 classconstructor 中返回了一个字符串
    class A {}
    class B extends A {
      constructor() {
        return ''
      }
    }
    // Uncaught TypeError: Derived constructors may only return object or undefined
    new B() 
    

总结

在实际的使用过程中, 我们往往很少会在构造函数中返回一个值,最常见的场景大概是 return this 以实现链式调用。在某次突发奇想,对此感到好奇并且尝试之后,一路刨根问底,才了解到简单的调用背后,包含了这么多复杂的步骤。

参考