【问题标题】:Trying to understand the difference between prototype and constructor in JavaScript试图理解 JavaScript 中原型和构造函数的区别
【发布时间】:2015-04-20 10:27:02
【问题描述】:

我是 JavaScript 新手,为了理解这个概念,我阅读了很多关于原型和构造函数的文章,但无论我走到哪里,我都会感到困惑。

当人们同时谈论构造函数和原型时,就会产生混淆。

在下面的例子中

var employee = function Emp(name) {
    this.name = name;
}
var jack = new employee("Jack Dwain");

employee.constructor //gives Function()

employee.prototype // gives  Emp {}

employee.prototype.constructor //gives Emp(name)

jack.constructor //gives Emp(name)

jack.prototype //gives undefined
  1. prototype 是 JS 实现继承的一种方式,因为Emp(name) 是基函数原型,它引用了同一个函数本身。是这样的吗?

  2. employee.constructoremployee.prototype.constructor 有何不同?

  3. 为什么jack.prototypeundefined,即如果它继承自函数Emp(name),为什么它没有引用该函数?

  4. 如何在不输入控制台的情况下清楚地预测自己的原型或构造函数或prototype.constructor ......产生了什么

【问题讨论】:

  • 我认为构造函数是你调用来创建对象的函数。原型定义了对象可以做什么。所以原型比构造函数大。另见:Constructors in JavaScript.
  • 这一行:var employee = function Emp(name) { ... } 令人困惑。最好写成function Emp(name) { ... } 并且只使用Emp 而不是employee
  • 也许这个答案将解释原型继承在 JavaScript 中的工作原理:stackoverflow.com/a/8096017/783743
  • 我认为观察者__proto__ 而不是prototype 用于研究目的更好。这将解释你的最后一行输出。即jack.prototype //gives undefined
  • 你确定employee.prototype 是一个Emp 对象吗?我认为它是 Object 的一个实例。

标签: javascript prototype


【解决方案1】:

原型只是一个对象, 构造函数是指向创建该对象的函数的指针。

构造函数是一个指针。它指向创建您从中检索构造函数的点的 Function()。 (即构造函数只是对 Function() 的引用,我们可以根据需要多次调用它。)

构造函数的用途之一是帮助您创建对象的复制副本。由于constructor属性是对创建对象的函数的引用,所以只要你有一个对象的副本,它就会一直指向原来的constructor。https://coderwall.com/p/qjzbig/understanding-constructor-and-prototype

使用对象构造函数: 通常,单独创建的对象在许多情况下都是有限的。它只创建一个对象。

有时我们喜欢有一个“对象类型”,它可以用来创建一个类型的多个对象。

创建“对象类型”的标准方法是使用对象构造函数:

function person(first, last, email ) {
  this.first_name = first;
  this.last_name = last;
  this.e_mail = email;
}
var myFather = new person("Ibm", "Muh", "ibm@gmail.com");

上面的函数(person)是一个对象构造函数。一旦有了对象构造函数,就可以创建相同类型的新对象:

var myFather = new person("Sul", "Ahm", "sul@gmail.com");

每个 JavaScript 对象都有一个原型。 原型也是一个对象。

所有 JavaScript 对象都从它们的原型继承它们的属性和方法。

对象是使用两种创建对象的方法来创建的。原型。使用 new Date() 创建的对象继承 Date.prototype。

Object.prototype 位于原型链的顶端。

所有 JavaScript 对象(日期、数组、正则表达式、函数......)都继承自 Object.prototype.https://www.w3schools.com/js/js_object_prototypes.asp

关键字原型是 Function() 对象的属性。

prototype 的值是创建该特定对象的对象构造函数。让我们看几个原型:

Boolean.prototype // returns Object Boolean
String.prototype // returns Object String with methods such as "toUpperCase"
Function.prototype // returns function() {} or function Empty() {}

创建原型:

创建对象原型的标准方法是使用对象构造函数:

function Person(first, last, age, eyecolor) {
  this.firstName = first;
  this.lastName = last;
  this.age = age;
}
var myFather = new Person("John", "Doe", 50);

使用构造函数,您可以使用 new 关键字从同一个原型创建新对象,如上所示:

构造函数是 Person 对象的原型。 使用大写首字母命名构造函数被认为是一种很好的做法。

向原型添加属性

您不能像向现有对象添加新属性一样向原型添加新属性,因为原型不是现有对象。

示例: Person.nationality = "英语";

要将新属性添加到原型,必须将其添加到构造函数:

function Person(first, last, age, eyecolor) {
  this.firstName = first;
  this.lastName = last;
  this.age = age;
  this.eyeColor = eyecolor;
  this.nationality = "English";
}

所有本机和复杂对象都检索到它们的原始构造函数,在这种情况下就是它们自己。唯一的例外是 Function 原型,它返回创建它的 Function() 函数。不要将它与构造函数混淆,因为它不一样。

Function.prototype === Function.constructor // returns false, Function.constructor is function Function(){}

还有一个额外的属性,__proto__,它引用实例对象的内部 [[proto]] 属性。与 Function() 对象不同,每个对象都有一个__proto__。 不建议更新实例对象的原型,因为原型并不意味着在运行时更改(您应该能够看到谁是谁的原型,否则您需要花费额外的计算来确保没有循环引用)。

【讨论】:

    【解决方案2】:

    如果你想创建一个 javascript 对象,你可以简单地声明一个新对象并赋予它属性(我选择将自己对象化):

    var myself= {
        name:"Niddro",
        age:32
    };
    

    此方法允许您制作 一个 对象。如果您想要的是一个描述一个人的原型,您可以在其中声明几个具有相同设置的人。要创建原型,您可以使用构造函数,如下所示:

    //Constructor
    function generalNameForObject(param1, param2,...) {
        //Give the object some properties...
    }
    

    我有一个原型(配方),我想调用 person,它应该包含属性名称和年龄,我将使用构造函数来制作它:

    function person(name,age) {
        this.name=name;
        this.age=age;
    }
    

    上面的构造函数描述了我的person对象的原型。

    通过调用构造函数创建一个新人:

    var myself = new person("Niddro",31);
    var OP = new person("rajashekar thirumala",23);
    

    一段时间过去了,我意识到我已经过生日了,所以我需要更改原型的属性:

    myself.age=32;
    

    如果要向构造函数添加属性,则需要手动将其添加到构造函数中:

    function person(name,age,rep) {
        this.name=name;
        this.age=age;
        this.reputation=rep;
    }
    

    相反,您可以通过执行以下操作向原型添加属性(这里“原型”是一个实际命令,而不仅仅是一个名称):

    function person(name,age,rep) {
        this.name=name;
        this.age=age;
    }
    person.prototype.reputation=105;
    

    请注意,这将为创建的所有对象添加 105 的声誉。

    我希望这能让您对构造函数和原型之间的关系有更多的了解。

    【讨论】:

    • 这是一个非常实用的解释,谢谢。我是一个初学者,并且在类似的上下文中也看到了一个叫做__proto__ 的东西。和person.prototype一样吗?
    【解决方案3】:

    构造函数:

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

    Foo 是构造函数。构造函数是一个函数。

    有两种方法可以使用这个构造函数Foo

    "对象是通过在 new 表达式中使用构造函数来创建的;对于 例如,new Date(2009,11) 创建一个新的 Date 对象。调用一个 不使用 new 的构造函数的后果取决于 构造函数。例如, Date() 产生一个字符串表示 当前日期和时间,而不是一个对象。”

    来源ECMA-262

    这意味着如果Foo 返回一些东西(通过return "somevalue";),那么typeof Foo() 就是返回值的类型。

    另一方面,当你打电话时

    var o = new Foo();
    

    JavaScript 实际上就是这样做的

    var o = new Object();
    o.[[Prototype]] = Foo.prototype;
    Foo.call(o);
    

    原型:

    当您调用o.a 时,javascript 首先检查a 是否是对象o 的自有属性。如果不是,javascript 将查找属性链以找到a

    有关属性链的更多信息,请查看mdn

    构造函数的prototype 属性有一个非常强大的功能,它在类中不可用。如果它有用,那就另当别论了。构造函数的prototype 属性可以更改在原型链中链接到该原型的每个实例的属性。

    TL、DR:

    注意:这不是一个确切的定义,总结的目的只是为了让你对构造函数和原型有一个感觉。

    如果您使用带有new 关键字的构造函数,那么构造函数和原型具有相似的用途,即使它们完全不同。构造函数初始化对象的属性,因此它提供属性。原型还通过属性链(基于原型的继承)提供属性。

    【讨论】:

    • 我很喜欢这个解释。
    【解决方案4】:

    如果您习惯于在其他 OOP 语言中轻松扩展对象,那么您将很难集中注意力,但我会尽我所能解释这些的用途以及它们是什么。我假设您熟悉其他 OOP 语言。如果我错了,请纠正我。

    所有函数都有原型 Function()。它们继承了 Function 的所有基本功能,例如 toString() 和 valueOf()。

    然后有一个构造函数。这就是你用来初始化对象的东西。

    p = new Foo();

    所以在这种情况下,我们有两件事。

    • Function 为原型的function Foo (Foo)
    • Function 对象,Foo() 作为构造函数(p)

    (跟我来了吗?)

    Foo() 构造函数可以覆盖Function 构造函数的一些基本功能,但也可以保持原样并充分利用它。

    如果你熟悉 OOP 原理,原型是基类,构造函数是你当前的类。在 OOP 中,上面将是 class Foo extends Function

    您还可以从原型和构造函数的整个设置开始继承,在共享功能的同时制作更复杂的对象。

    例如:

    // make a object initialiser extending Function. in oop `class Foo extends Function`
    
    function Foo(bar) {
        this.baz = bar;
    }
    Foo.prototype.append = function(what) {
        this.baz += " " + what;
    };
    Foo.prototype.get() {
        return this.baz
    }
    

    现在假设我们想要不同的方式让 baz 离开那里。一种用于控制台日志记录,另一种用于将其放在标题栏上。 我们可以为我们的类 Foo 做一件大事,但我们不这样做,因为我们需要对新类做完全不同的事情,但是是为不同的实现而设计的。他们唯一需要分享的是 baz 项目以及 setter 和 getter。

    所以我们需要扩展它以使用 OOP 术语。在 OOp 中,这将是所需的最终结果 class Title extends Foo(){}。那么让我们来看看如何到达那里。

    function Title(what) {
        this.message = what;
    }
    

    此时 Title 函数如下所示:

    • 原型函数
    • 构造函数标题

    因此,要使其扩展 Foo,我们需要更改原型。

    Title.prototype = new Foo();
    
    • 原型 Foo
    • 构造函数 Foo

    这是通过针对原型初始化一个新的 Foo() 对象来完成的。 现在它基本上是一个名为 Title 的 Foo 对象。这不是我们想要的,因为现在我们无法访问 Title 中的消息部分。 我们可以通过将构造函数重置为 Title 来使其正确扩展 Foo()

    Title.prototype.constructor = Title;
    
    • 原型 Foo
    • 构造函数标题

    现在我们又面临一个问题。 Foo 的构造函数没有被初始化,所以我们最终得到一个未定义的this.baz

    要解决这个问题,我们需要调用父级。在 java 中,您可以使用 super(vars) 来执行此操作,在 php 中使用 $parent->__construct($vars)

    在javascript中我们要修改Title类的构造函数来调用父对象的构造函数。

    所以 Title 类的构造函数会变成

    function Title(what) {
        Foo.call(this,what);
        this.message = what;
    }
    

    通过使用 Function 对象属性 Foo 继承,我们可以在 Title 对象中初始化 Foo 对象。

    现在你有了一个正确继承的对象。

    因此,它不像其他 OOP 语言那样使用 extend 这样的关键字,而是使用 prototypeconstructor

    【讨论】:

    • 我讨厌 Foo 和 Bar 作为函数名称的示例类,即使经过 10 年的编程:-)
    • 好吧,戴上太阳镜处理它:-p
    • 我只是在想,当 Foo Bar 本身就足够了那很好,但是如果您需要使用 Title 和 Title.message 作为后续,那么 Foo 和 Bar 应该替换为相关比喻
    • 我不明白“这不是我们想要的,因为现在我们无法访问 Title 中的消息部分”,直到我明白更改某些原型会将其构造函数也更改为新原型。在此处查看第二个答案:stackoverflow.com/questions/8093057/…
    • 是的,但我在脑海中写道,任何阅读本文的人都以 OOP 方式理解继承。从 OOP 的角度来看,这对我来说是合乎逻辑的。但也许我像那样很奇怪;-)
    【解决方案5】:

    但事实是,这种方法在许多情况下可能是错误的。在 Javascript 中,当您将方法绑定到 this 关键字时,您只是将该方法提供给该特定实例,并且它与该构造函数的对象实例实际上没有任何关系,就像静态方法一样。请记住,函数是 Javascript 中的一等公民,我们可以像处理对象一样处理它们,在这种情况下,我们只是向函数对象的实例添加属性。这只是故事的一部分,您还必须知道,通过 this 附加的任何方法都会为我们创建的每个新实例重新声明,如果我们希望创建如此多的实例,这可能会对应用程序的内存使用产生负面影响。

    【讨论】:

      【解决方案6】:

      employee.constructor //给出 Function()

      在 JavaScript 中,函数也是对象,可以使用自己的构造函数 Function 来构造。所以你可以编写下面的代码来获取一个 Function 的实例。

      var employee2 = new Function('a', 'b', 'return a+b');
      

      当您使用函数文字创建函数时也会发生同样的情况,就像您的情况一样。而且这个对象的constructor属性也引用了同一个原生的Function对象/类。

      employee.prototype // 给出 Emp {}

      JavaScript 中的每个对象都有一个与之关联的原型。虽然只有函数对象原型可以通过.prototype 直接访问。当您使用 new 关键字创建对象时,会在其对象原型上复制相同的原型。这种复制主要负责继承/扩展。尽管原型被复制了,但它不像 Function 对象那样直接可访问。它可以通过 .__proto__ 以非标准方式使用。以下代码将返回 true。

      jack.__proto__==employee.prototype
      

      employee.prototype.constructor //给出 Emp(name)

      正如Object.prototype.constructor 的文档中所说。这将返回对创建实例原型的 Object 函数的引用。这里引用的对象是employee.prototype 和not employee。这有点复杂,但是对象employee.prototype 的原型是由函数 Emp(name) 创建的

      jack.constructor //给出 Emp(name)

      如前所述,这个对象原型是在你使用 new Emp() 创建对象时由函数 Emp(name) 创建的,

      jack.prototype //给出未定义的

      jack 不是一个函数对象,所以你不能像那样访问它的原型。您可以访问(非标准方式)jack 的原型,如下所示。

      jack.__proto__
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-08-21
        • 1970-01-01
        • 2019-06-07
        • 2018-11-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-07
        相关资源
        最近更新 更多