【问题标题】:What is the reason to use the 'new' keyword at Derived.prototype = new Base在 Derived.prototype = new Base 使用“new”关键字的原因是什么
【发布时间】:2012-09-17 13:24:24
【问题描述】:

下面的代码做了什么:

WeatherWidget.prototype = new Widget;

其中Widget 是一个构造函数,我想用一个新函数WeatherWidget 扩展小部件“类”。

new 关键字在那里有什么作用?如果省略它会发生什么?

【问题讨论】:

    标签: javascript constructor prototype


    【解决方案1】:
    WeatherWidget.prototype = new Widget;
    

    new 关键字调用Widget 作为构造函数,并将返回值分配给prototype 属性。 (如果您省略new,除非您添加参数列表(),否则您不会调用Widget。但是,以这种方式调用Widget 可能是不可能的。它肯定有可能如果它不是严格模式代码并且实现符合 ECMAScript Ed. 5.x 那里,那么破坏全局命名空间,因为那时构造函数中的this 将引用 ECMAScript 的全局对象。)

    但这种方法实际上来自really viralbad example in the old Netscape JavaScript 1.3 Guide(反映在 Oracle,原 Sun)。

    这样,您的WeatherWidget 实例将全部继承自相同 Widget 实例。原型链将是:

    [new WeatherWidget()] → [new Widget()] → [Widget.prototype] → …
    

    这可能很有用,但大多数时候您不希望它发生。除非您希望所有 WeatherWidget 实例在它们之间共享它们从该 Widget 实例继承的 属性值,否则您不应该在此处执行此操作,并且仅 通过它,来自Widget.prototype。另一个问题是您需要以这种方式调用父构造函数,这可能不允许像您那样在没有参数的情况下调用,或者无法正确初始化。它当然与模拟基于类的继承(例如来自 Java)无关。

    在这些基于原型的语言中实现基于类的继承的正确方法是(最初由Lasse Reichstein Nielsen in comp.lang.javascript in 2003, for cloning objects 设计):

    function Dummy () {}
    Dummy.prototype = Widget.prototype;
    WeatherWidget.prototype = new Dummy();
    WeatherWidget.prototype.constructor = WeatherWidget;
    

    constructor 原型属性也应该被修复,这样您的WeatherWidget 实例w 将具有预期的w.constructor === WeatherWidget,而不是w.constructor === Widget。但是,请注意它是可枚举的。

    这样,WeatherWidget 实例将通过原型链继承属性,但不会在它们之间共享属性值,因为它们从 Widget.prototypeDummy 继承,而Dummy 没有自己的属性:

    [new WeatherWidget()] → [new Dummy()] → [Widget.prototype] → …
    

    在 ECMAScript Ed 的实现中。 5 及更高版本,您可以并且应该使用

    WeatherWidget.prototype = Object.create(Widget.prototype, {
      constructor: {value: WeatherWidget}
    });
    

    相反。这具有额外的优势,即生成的 constructor 属性为 not writable, enumerable, or configurable

    只有当你显式调用父构造函数时才会调用它,从WeatherWidget,例如

    function WeatherWidget (…)
    {
      Widget.apply(this, arguments);
    }
    

    请参阅我的JSX:object.js 中的Function.prototype.extend(),了解如何概括这一点。使用该代码,它将变成

    WeatherWidget.extend(Widget);
    

    我的Function.prototype.extend() 带有一个可选的第二个参数,您可以使用它轻松地扩充WeatherWidget 实例的原型:

    WeatherWidget.extend(Widget, {
      foo: 42,
      bar: "baz"
    });
    

    相当于

    WeatherWidget.extend(Widget);
    WeatherWidget.prototype.foo = 42;
    WeatherWidget.prototype.bar = "baz";
    

    不过,您仍然需要在子构造函数中显式调用父构造函数;该部分不能合理地自动化。但是我的Function.prototype.extend()Function 实例添加了一个_super 属性,这使它更容易:

    function WeatherWidget (…)
    {
      WeatherWidget._super.apply(this, arguments);
    }
    

    其他人也实现了类似的扩展。

    【讨论】:

    • +1 表示鄙视坏方法,-1 表示不提Object.create :-)
    • +1,已修复。我会看看Object.create() 是否值得在Function.prototype.extend() 的分支中使用。
    • 这是非常有趣的阅读和学习。在您使用 Dummy 的示例中,您写了 WeatherWidget.prototype = new Dummy();,在我的行中,括号丢失了。这有什么不同?
    • @Kai 谢谢。区别是句法上的,而不是语义上的。认为它是良好的代码风格。当您从版本中省略 new 时,注意到程序是如何变化的——不再调用构造函数了吗?使用参数列表,构造函数作为函数的调用可能仍然会失败,但至少它不会静默失败。
    • 即如果构造函数测试this或者使用严格模式。
    【解决方案2】:

    根据一些奇怪的 Javascript 规则,new Widget 实际上调用了构造函数,而不是返回对构造函数的引用。这个问题实际上回答了var a = new Widget()var a = Widget()之间的区别。

    简单来说,new 关键字告诉 Javascript 调用函数 Widget 的规则集与常规函数调用不同。在我脑海中浮现,我记得的是:

    1. 创建了一个全新的对象
    2. Widget 可以使用 this 关键字来引用该对象。
    3. 如果Widget 没有返回任何内容,则会创建这个新对象。
    4. 此对象将继承一些附加属性,这些属性表明它是由 Widget 创建的,用于跟踪属性链。

    如果没有 new 关键字,对小部件的调用会

    1. 如果在严格模式下,this 将被设置为undefined.
    2. 否则,this 将引用全局对象。 (浏览器调用window。)
    3. 如果函数没有返回任何内容,则返回undefined

    参考: new keyword

    【讨论】:

    • 在 SO 搜索 javascript constructor 将填写更多详细信息。
    • 请注意他的代码没有括号——这只是构造函数的赋值
    • @Bergi -- 在 Javascript 构造函数中,括号是可选的,出于某种我从未弄清楚的原因。 See this discussion
    • 是的,这就是为什么如果没有 new 关键字就不会调用 - 因为 OP 省略了它们
    【解决方案3】:
    WeatherWidget.prototype = new Widget;
    

    确实创建了Widget 构造函数的新实例并将其用作WeatherWidget 的原型对象。使用new keyword 创建新对象,将其继承链设置为Widget.prototype,并在其上应用构造函数(您可以在其中设置单独的属性'n'方法,或创建私有范围的变量)。

    如果没有 new 关键字,则将 Widget 函数分配给 prototype 属性 - 这没有任何意义。如果您添加可选括号(即Widget()),它将正常调用该函数,但不是作为新实例的构造函数,而是使用全局对象作为上下文。另请参阅reference for the this keyword

    请注意,您不应真正使用此代码。如前所述,它通过调用构造函数创建一个新实例。但目的只是创建一个继承自 Widgets 原型对象的 empty 对象,而不是实例化某些东西(这可能会造成一些伤害,具体取决于代码)。相反,您应该使用Object.create(或其popular shim):

    WeatherWidget.prototype = Object.create(Widget.prototype);
    

    另见Javascript basic inheritance vs Crockford prototypical inheritance

    【讨论】:

    • 有趣的方法,但请参阅我的更新答案:您还想修复 constructor 属性。这就是 Object.create() 的“流行 shim”失败的地方——即使更新,必须失败——因为你无法模拟不可枚举性。以后只能hasOwnProperty()-过滤掉。这仍然留下了只写和不可配置,只能部分模拟(取决于实现)。
    • 是的,应该将 {constructor:{value:WeatherWidget}} 作为第二个参数传递,并且您可能需要使用不太流行的 shim :-) 但是,我不确定哪个代码取决于正确设置的 @987654343 @属性?
    • 我的一些构造函数上有一个isInstance() 方法。比单独输入鸭子更精确。
    【解决方案4】:

    用简单的英语来说,你是在扩展一个类和另一个类。原型只能是对象,因此您将WeatherWidget 的原型设置为Widget 的新实例。如果您删除了 new 关键字,您会将原型设置为不做任何事情的文字构造函数。

    var Appendages = function(){
      this.legs = 2
    };
    var Features = function() {
       this.ears = 4;
       this.eyes = 1;
    }
    
    // Extend Features class with Appendages class.
    Features.prototype = new Appendages;
    var sara = new Features();
    sara.legs;
    // Returns 2.
    

    了解原型可以是任何对象,这样的事情也可以:

    var appendages = {
      legs : 2
    };
    var Features = function() {
       this.ears = 4;
       this.eyes = 1;
    }
    
    // Extend Features class with Appendages class.
    Features.prototype = appendages;
    var sara = new Features();
    sara.legs;
    // Returns 2.
    

    在 JavaScript 中,如果在对象上找不到键,它会检查您扩展它的父对象。因此,您可以像这样动态更改父对象上的项目:

    var appendages = {
      legs : 2
    };
    var Features = function() {
       this.ears = 4;
       this.eyes = 1;
    }
    
    // Extend Features class with Appendages class.
    Features.prototype = appendages;
    var sara = new Features();
    sara.legs;
    // Returns 2.
    appendages.hair = true;
    sara.hair;
    // Returns true.
    

    请注意,这一切都发生在实例化过程中,这意味着您不能在创建对象后直接切换原型:

    var foo = {name : 'bob'};
    var bar = {nachos : 'cheese'};
    foo.prototype = bar;
    foo.nachos;
    // undefined
    

    但是,所有现代浏览器都带有这种较新的 __proto__ 方法,它允许您这样做:

    var foo = {name : 'bob'};
    var bar = {nachos : 'cheese'};
    foo.__proto__ = bar;
    foo.nachos
    // "cheese"
    

    阅读更多关于理解 JavaScript 原型的内容here。 这个来自 Pivotal Labs 的article 也很不错。

    【讨论】:

    • Appendages 函数有什么用,为什么需要它的实例(包括原型对象)?
    • Bergi:我刚刚添加了第三个示例来展示这一点。对象可以在链上相互扩展,因此它使您能够重用和构建代码。在一个理论示例中,所有车辆都有相似之处,包括发动机。我们可以将其构建为基类,但并非所有车辆都有四个轮子。所以我们可以在其上构建一个汽车类和一个摩托车类,并重用核心代码。在现实生活中,我使用 Backbone.js 框架。很多视图都是相似的,我需要显示项目列表。通过创建一个基本列表类,我可以对其进行扩展,以创建 20 个不同的列表页面,并且几乎没有膨胀。
    • Appendages 最好是类构造函数而不是对象,以便您可以使用各种选项对其进行初始化。所以它可能是一个“引擎”类,您可以在其中传递引擎的大小,同时仍然使其可重用。
    • 不,实例化原型对象的“类构造函数”通常是个坏主意,否则应该解决类似类的问题。
    • 如果你传递给构造函数的东西把它变成了一个完全不同的对象,那么你是对的,但是对于一个小的设置我不同意。
    【解决方案5】:

    new 对于prototype 继承很重要;即
    用方法创建构造函数

    var Obj = function(){};
    Obj.prototype = {};
    Obj.prototype.foo = function(){console.log('foo');};
    

    创建第二个构造函数来扩展第一个构造函数

    var ExObj = function(){};
    

    现在,如果我们在没有new 的情况下进行原型设计,

    ExObj.prototype = Obj;
    (new ExObj).foo(); // TypeError: Object #<Object> has no method 'foo'
    

    这意味着我们没有继承Obj的原型,但是,如果我们使用new进行原型设计

    ExObj.prototype = new Obj();
    (new ExObj).foo(); // console logs 'foo'
    

    此外,向ExObj 的原型添加新内容不会对其基础Obj 进行任何更改。

    【讨论】:

      【解决方案6】:

      JavaScript 函数是“MULTIPLE(2) PERSONALITIES”!!!

      它们是带有输入和输出的常规函数​​,我们称之为function()

      当我们使用 new 关键字时,它们也是 JS 对象的构造函数。 >>>BUTprototype 属性对象的实例。

      然后在WeatherWidget.prototype = 中,将要继承其属性的对象放入构造函数将创建的对象中,通常是new function() 而不是函数。

      JavaScript 通过使用 instanceof 关键字命名构造函数创建的对象,在编程社区中造成巨大的混乱。
      &gt; function f(){}
      undefined
      &gt; new f() instanceof f
      true

      【讨论】:

      • 好吧,你总是可以做到var o = Object.create(f.prototype); o.constructor(); f.prototype.isPrototypeOf(o) :-)
      猜你喜欢
      • 2010-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-17
      • 2016-10-12
      相关资源
      最近更新 更多