JavaScript 中的 `new` 有什么作用呢?

IT技术 javascript oop
2021-02-21 19:12:50

我对构造函数在 Javascrpt 中的工作方式感到非常困惑;尽管使用该语言多年(主要就好像它就像 LISP 的半命令式版本),但我想更多地了解对象应该如何在其中工作。

鉴于此代码:

function Foo(x) {
    return {
        bar: function() { return x; }
    };
}

callmyFoo = Foo(5)和 和有myFoo = new Foo(5)什么不一样?或者,换句话说,Javascript 中的构造函数到底做了什么

3个回答

callmyFoo = Foo(5)和 和有myFoo = new Foo(5)什么不一样?

该代码没有区别,因为它返回一个对象,并且规范说:

  • result为调用F[[Call]]内部属性的结果,提供obj作为this值,并提供传入[[Construct]]的参数列表作为 args。
  • 如果Type(result)Object然后返回结果

由于该函数返回的结果是一个对象,因此会使用其结果。如果它没有返回一个对象,或者如果它检查了this你会注意到一个不同,例如,如果你把它改写为:

function Foo(x) {
  if (!(this instanceof Foo)) { return new Foo(x); }
  this.bar = function() { return x; };
}
// Now instanceof works.
alert((new Foo) instanceof Foo);

newJavaScript到底做了什么?

new操作导致要调用的函数与this绑定到新创建的Object,其原型是函数的prototype性质。

对于用户定义的函数,

new f(a, b, c)

相当于

// Create a new instance using f's prototype.
var newInstance = Object.create(f.prototype), result;

// Call the function
result = f.call(newInstance, a, b, c),

// If the result is a non-null object, use it, otherwise use the new instance.
result && typeof result === 'object' ? result : newInstance

请注意,语言规范实际上定义了具有两个操作[[Call]][[Construct]] 的函数,因此在某些极端情况下new会表现得很奇怪。

例如绑定和内置函数:

var g = f.call.bind(f);

应该定义一个函数,当被调用时,只是调用f,所以g应该f在所有方面都一样,但是

new g()

产生

TypeError: function call() { [native code] } is not a constructor

因为内置函数Function.prototype.call支持[[Call]]但不支持[[Construct]]

Function.prototype.bindnew和常规调用中的行为也不同this值在调用时始终是绑定的 thisValue,但在您使用时是一个新构造的实例new

@fluffy,不客气。当您对这样的核心语言功能有疑问时,语言规范是最好的去处。这并不容易阅读,但它具有权威性,并且通常有其他文件没有的答案。
2021-04-20 19:12:50
所以你做到了。我撤回我的反对意见:)
2021-04-24 19:12:50
@benekastah,有两个问题。一个在标题中,一个在正文中。我先回答了标题,现在编辑回答了正文中的那个。
2021-04-30 19:12:50
这是信息性的,但并不能完全回答问题。
2021-05-06 19:12:50
谢谢,这终于是对 Javascript 对象如何工作的直接解释,我在其他任何地方都找不到(包括我们的首席 Javascript 人员,他只是说它是“显而易见的”和“基于原型的”,好像这回答了一切)。+1 并接受。
2021-05-09 19:12:50

在此特定示例中,最终结果没有区别。

这是因为您的Foo函数正在返回一个对象实例

new运算符返回一个新创建的对象,从构造函数的原型继承,只有当函数返回原始值(或它不返回任何东西,这是技术上的undefined值)。

例如:

function Foo () {
  return 5; // or "", or null, or no return statement at all (undefined)
}

var foo = new Foo();
typeof foo; // "object"
foo instanceof Foo; // true
Foo.prototype.isPrototypeOf(foo); // true

当您返回一个对象时,从构造函数的原型继承的新创建的对象将被简单地丢弃:

function Foo () {
  return {};
}

var foo = new Foo();
typeof foo; // "object"
foo instanceof Foo; // false
Foo.prototype.isPrototypeOf(foo); // false

也可以看看:

在这种情况下,当您返回一个新对象时没有区别。可以改写为:

function Foo(x){
   this._x = x;
}

Foo.prototype.bar = function() { 
   return this._x;
}

每次调用此语法时new Foo,都会创建一个属性为 的新对象_x好处是该bar函数将存储一次并重用于 Foo 的多个实例。Foo()多次调用问题中的代码将为每个实例创建一个 bar 函数。因此,将函数附加到原型上而不是将它们直接放在对象上会占用更少的内存。

可以在MDC上找到原型如何工作的完整分解