令人惊讶的实例化模式——用于包装函数的 new 关键字

Surprising instantiation pattern - new keyword used on a wrapper function

Paper.js 库上工作时,我在代码库中发现了一种实例化我以前从未使用过的 class 的方法。它用于例如 here 实例化 Shape class.
这个实例化模式可以简化为:给定一个 Ball class 和一个实例化它的 createBall() 方法:

function Ball() {}
function createBall() {return new Ball()}

当然,我们可以通过调用获得球实例:

var ball = createBall();

但更令人惊讶的是,我们还可以通过调用(注意 new 关键字)获得球实例:

var ball = new createBall();

或者更抽象的方式:

var ball = new function() {return new Ball()};

由于 createBall() returns 一个 Ball 实例,它给人的印象是我们正在使用 new 实例化 Ball class它的一个实例上的关键字。

但是正如我们在下面的代码中看到的那样,这是不允许的,如果我们手动执行会抛出错误:

var ball1 = new Ball();
var ball2 = new ball1();
// error: ball1 is not a constructor

谁能给我解释一下这背后的逻辑是什么?

下面是不同实例化方式的对比示例:

// Class
function Ball() {}

// Method creating an instance of the class
function createBall() {
  // Instantiate the class
  var ball = new Ball();
  // Return the instance
  return ball;
}

// Expected: instantiating directly works
var ball1 = new Ball();
console.log('ball1', ball1 instanceof Ball); // outputs true

// Expected: instantiating through the creation method works
var ball2 = createBall();
console.log('ball2', ball2 instanceof Ball); // outputs true

// Unexpected: instantiating like this surprisingly works
var ball3 = new createBall();
console.log('ball3', ball3 instanceof Ball); // outputs true

// Expected: instantiating like this throws an error
var ball4 = new ball1();
// error: ball1 is not a constructor

编辑

看完@robert-zigmond的评论后,我发现了另一个误导案例:

function Dog() {}

function Ball() {
  return new Dog();
}

var instance = new Ball();

console.log('is instance a Ball ?', instance instanceof Ball); // false
console.log('is instance a Dog ?', instance instanceof Dog); // true

ball1 甚至都不是一个函数,所以这就是你在那里得到错误的原因。

Javascript 中的任何函数都可以使用 new 运算符调用 - 本质上是构造一个新对象,该对象被视为函数的 this 引用,returns 它(但前提是该函数还没有 return 一个对象——如果是,构造的对象将被丢弃)。

请参阅此处了解 new 运算符的具体作用:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new#Description

你的 "other misleading case" 起初看起来确实令人困惑,因为 instance 被定义为 new Ball()instance instanceof Ball return 是 false。这是因为,正如我上面所说,"constructor function" 只有 return 是新构造的对象,否则 return 是一个根本不是对象的值。在大多数设计用作构造函数的函数中,实际上没有显式的 return 值 - 所以 return 值隐含地是 undefined,因此新构造的对象是 returned.

但在这个例子中,函数将 return 一个对象,它是 Dog 的一个实例 - 所以这个对象最终成为 instance。虽然是通过 new Ball() 创建的,但有些间接,它 实际上是 通过调用 new Dog() 创建的,因此 Dog.prototype 在其原型链中,并且不是 Ball.prototype。这解释了您观察到的 instanceof 运算符的行为。

new Function 被调用时

  1. 创建了一个新对象,继承自Function.prototype
  2. 调用构造函数并将其绑定到第一步创建的新对象。
  3. 如果没有 returned,这个对象将被 returned 或者你可以 return 像你的例子那样的新对象

function createBall() { console.log('origin this', this); return new Ball(); } 当调用 new createBall() 时,它会创建一个继承自 createFoo.prototype this 的新对象,如果没有 returned,则此对象成为 new createBall() 的结果但是这里你 return 一个 new Ball(),所以 this 对象更改为 new Foo() 和 returned