2131

我不是那么喜欢动态编程语言,但我已经编写了相当一部分的 JavaScript 代码。我从来没有真正理解过这种基于原型的编程,有人知道它是如何工作的吗?

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

我记得前段时间与人们进行了很多讨论(我不确定自己在做什么),但据我所知,没有类的概念。它只是一个对象,这些对象的实例是原始对象的克隆,对吗?

但是 JavaScript 中这个“.prototype”属性的确切用途是什么?它与实例化对象有什么关系?

更新:正确的方法

var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!

function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK

这些幻灯片也确实有很大帮助。

4

26 回答 26

1815

在 Java、C# 或 C++ 等实现经典继承的语言中,您首先创建一个类(对象的蓝图),然后您可以从该类创建新对象,或者您可以扩展该类,定义一个新类来增强原来的班级。

在 JavaScript 中,您首先创建一个对象(没有类的概念),然后您可以扩充自己的对象或从中创建新对象。对于习惯了经典方式的人来说,这并不难,但有点陌生且难以代谢。

例子:

//Define a functional object to hold persons in JavaScript
var Person = function(name) {
  this.name = name;
};

//Add dynamically to the already defined object a new getter
Person.prototype.getName = function() {
  return this.name;
};

//Create a new object of type Person
var john = new Person("John");

//Try the getter
alert(john.getName());

//If now I modify person, also John gets the updates
Person.prototype.sayMyName = function() {
  alert('Hello, my name is ' + this.getName());
};

//Call the new method on john
john.sayMyName();

到目前为止,我一直在扩展基础对象,现在我创建另一个对象,然后从 Person 继承。

//Create a new object of type Customer by defining its constructor. It's not 
//related to Person for now.
var Customer = function(name) {
    this.name = name;
};

//Now I link the objects and to do so, we link the prototype of Customer to 
//a new instance of Person. The prototype is the base that will be used to 
//construct all new instances and also, will modify dynamically all already 
//constructed objects because in JavaScript objects retain a pointer to the 
//prototype
Customer.prototype = new Person();     

//Now I can call the methods of Person on the Customer, let's try, first 
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Let's try:       
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

var Person = function (name) {
    this.name = name;
};
Person.prototype.getName = function () {
    return this.name;
};
var john = new Person("John");
alert(john.getName());
Person.prototype.sayMyName = function () {
    alert('Hello, my name is ' + this.getName());
};
john.sayMyName();
var Customer = function (name) {
    this.name = name;
};
Customer.prototype = new Person();

var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();
Customer.prototype.setAmountDue = function (amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function () {
    return this.amountDue;
};
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

虽然如前所述,我不能在 Person 上调用 setAmountDue()、getAmountDue()。

//The following statement generates an error.
john.setAmountDue(1000);
于 2011-01-24T03:42:35.727 回答
1046

每个 JavaScript 对象都有一个名为的内部“槽”[[Prototype]],其值为nullobject。您可以将插槽视为对象的属性,位于 JavaScript 引擎内部,对您编写的代码隐藏。方括号[[Prototype]]是经过深思熟虑的,是表示内部插槽的 ECMAScript 规范约定。

对象的 指向的值[[Prototype]],俗称“该对象的原型”。

如果您通过点 ( obj.propName) 或方括号 ( obj['propName']) 表示法访问属性,并且对象没有直接具有这样的属性(即自己的属性,可通过 进行检查obj.hasOwnProperty('propName')),则运行时会在引用的对象上查找具有该名称的属性[[Prototype]]相反。如果[[Prototype]] 没有这样的属性,[[Prototype]]则依次检查它,依此类推。以这种方式,原始对象的原型链被遍历,直到找到匹配,或者到达它的末端。原型链的顶端是null价值。

[[Prototype]]现代 JavaScript 实现允许通过以下方式读取和/或写入访问权限:

  1. 运算符(在new构造函数返回的默认对象上配置原型链),
  2. 关键字(使用extends类语法时配置原型链),
  3. Object.create将提供的参数设置为[[Prototype]]结果对象的,
  4. Object.getPrototypeOfObject.setPrototypeOf(获取/设置对象创建[[Prototype]] 后),以及
  5. 命名的标准化访问器(即 getter/setter)属性__proto__(类似于 4.)

Object.getPrototypeOf并且Object.setPrototypeOf优先于,部分原因是当对象具有 的原型时,__proto__的行为o.__proto__ 是不寻常null的。

对象的[[Prototype]]初始设置是在对象创建期间设置的。

如果您通过 创建新对象new Func(),则[[Prototype]]默认情况下,该对象将设置为 引用的对象Func.prototype

因此,请注意,所有可以与运算符一起使用的类和所有函数,除了它们自己的内部槽外new,还有一个名为的属性。.prototype[[Prototype]]“原型”这个词的双重使用是该语言新手无休止的困惑的根源。

使用new构造函数可以让我们模拟 JavaScript 中的经典继承;尽管 JavaScript 的继承系统——正如我们所见——是原型的,而不是基于类的。

在将类语法引入 JavaScript 之前,构造函数是模拟类的唯一方法。我们可以将构造函数的属性所引用的对象的.prototype属性视为共享成员;IE。每个实例都相同的成员。在基于类的系统中,每个实例的方法都以相同的方式实现,因此方法在概念上被添加到.prototype属性中;但是,对象的字段是特定于实例的,因此在构造过程中会添加到对象本身。

如果没有类语法,开发人员必须手动配置原型链以实现与经典继承类似的功能。这导致了实现这一目标的不同方法的优势。

这是一种方法:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

...这是另一种方式:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

ES2015 中引入的类语法通过提供extends“一种真正的方式”来配置原型链以模拟 JavaScript 中的经典继承,从而简化了事情。

因此,类似于上面的代码,如果您使用类语法创建一个新对象,如下所示:

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

...结果对象[[Prototype]]将被设置为 的实例Parent,其[[Prototype]]又是Parent.prototype

最后,如果您通过 创建一个新对象Object.create(foo),则生成的对象[[Prototype]]将设置为foo

于 2009-02-21T13:33:54.193 回答
195

这是一个非常简单的基于原型的对象模型,在解释过程中将被视为示例,尚无评论:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var person = new Person("George");

在了解原型概念之前,我们必须考虑一些关键点。

1- JavaScript 函数的实际工作原理:

为了迈出第一步,我们必须弄清楚 JavaScript 函数实际上是如何工作的,作为一个使用this关键字的类之类的函数,或者只是作为一个带有参数的常规函数​​,它做什么以及它返回什么。

假设我们要创建一个Person对象模型。但在这一步中,我将尝试在不使用and关键字的情况下做同样的事情prototypenew

所以在这一步functions中,objectsthis关键字,是我们所拥有的。

第一个问题是在不使用关键字的情况下如何this使用new关键字

因此,要回答这个问题,假设我们有一个空对象,以及两个函数,例如:

var person = {};
function Person(name){  this.name = name;  }

function getName(){
    console.log(this.name);
}

现在不使用new关键字我们如何使用这些功能。所以 JavaScript 有 3 种不同的方法来做到这一点:

一种。第一种方法是将函数作为常规函数调用:

Person("George");
getName();//would print the "George" in the console

在这种情况下,这将是当前上下文对象,通常是 浏览器windowGLOBAL. Node.js这意味着我们将拥有浏览器中的 window.name 或 Node.js 中的 GLOBAL.name,其值为“George”。

湾。我们可以它们附加到一个对象上,作为它的属性

-最简单的方法是修改空person对象,例如:

person.Person = Person;
person.getName = getName;

这样我们就可以这样称呼它们:

person.Person("George");
person.getName();// -->"George"

现在person对象是这样的:

Object {Person: function, getName: function, name: "George"}

-将属性附加到对象的另一种方法是使用该对象的prototype,该对象可以在任何 JavaScript 对象中找到,名称为__proto__,我试图在摘要部分解释一下。所以我们可以通过这样做得到类似的结果:

person.__proto__.Person = Person;
person.__proto__.getName = getName;

但是这种方式我们实际上正在做的是修改Object.prototype,因为每当我们使用字面量 ( ) 创建一个 JavaScript 对象时{ ... },它都是基于创建的,这意味着它Object.prototype作为一个名为__proto__,正如我们在之前的代码片段中所做的那样,所有的 JavaScript 对象都会被更改,这不是一个好的做法。那么现在更好的做法是什么:

person.__proto__ = {
    Person: Person,
    getName: getName
};

而现在其他的对象都安然无恙,但似乎还是不是一个好的做法。所以我们还有一个解决方案,但是要使用这个解决方案,我们应该回到person创建对象的那一行代码(var person = {};),然后将其更改为:

var propertiesObject = {
    Person: Person,
    getName: getName
};
var person = Object.create(propertiesObject);

它所做的是创建一个新的 JavaScriptObject并将 附加propertiesObject__proto__属性。所以要确保你能做到:

console.log(person.__proto__===propertiesObject); //true

但是这里的棘手点是您可以访问对象__proto__第一级中定义的所有属性person(阅读摘要部分以获取更多详细信息)。


正如您所看到的,使用这两种方式中的任何一种this都会准确地指向person对象。

C。JavaScript 有另一种方式为函数提供this,即使用callapply来调用函数。

apply() 方法使用给定的 this 值和作为数组(或类似数组的对象)提供的参数调用函数。

call() 方法使用给定的 this 值和单独提供的参数调用函数。

这种我最喜欢的方式,我们可以轻松地调用我们的函数,例如:

Person.call(person, "George");

或者

//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);

getName.call(person);   
getName.apply(person);

这 3 种方法是找出 .prototype 功能的重要初始步骤。


new2-关键字如何工作?

这是了解功能的第二步。这是.prototype我用来模拟该过程的:

function Person(name){  this.name = name;  }
my_person_prototype = { getName: function(){ console.log(this.name); } };

在这一部分中,我将尝试执行 JavaScript 所采取的所有步骤,而不使用new关键字 and prototype,当您使用new关键字时。因此,当我们这样做时new Person("George")Person函数充当构造函数,这些是 JavaScript 所做的,一一对应:

一种。首先,它创建一个空对象,基本上是一个空哈希,例如:

var newObject = {};

湾。JavaScript 的下一步是所有原型对象附加到新创建的对象

我们my_person_prototype这里有类似原型的对象。

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

这不是 JavaScript 实际附加原型中定义的属性的方式。实际方式与原型链概念有关。


一种。& b。而不是这两个步骤,您可以通过执行以下操作获得完全相同的结果:

var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"

现在我们可以调用我们的getName函数my_person_prototype

newObject.getName();

C。然后它将该对象提供给构造函数,

我们可以用我们的样本来做到这一点,比如:

Person.call(newObject, "George");

或者

Person.apply(newObject, ["George"]);

然后构造函数可以做它想做的任何事情,因为构造函数内部的this是刚刚创建的对象。

现在是模拟其他步骤之前的最终结果: Object {name: "George"}


概括:

基本上,当您在函数上使用new关键字时,您正在调用该函数并且该函数用作构造函数,所以当您说:

new FunctionName()

JavaScript 在内部创建一个对象,一个空的哈希,然后将该对象提供给构造函数,然后构造函数可以为所欲为,因为构造函数内部的 this 是刚刚创建的对象,然后它当然会为您提供该对象如果您没有在函数中使用 return 语句,或者如果您在return undefined;函数体的末尾放置了 a。

所以当 JavaScript 在一个对象上查找一个属性时,它做的第一件事就是在那个对象上查找它。然后有一个[[prototype]]我们通常拥有的秘密属性,__proto__该属性是 JavaScript 接下来要研究的。当它查看 时__proto__,只要它又是另一个 JavaScript 对象,它就有自己的__proto__属性,它会不断上升,直到到达下一个__proto__为空的点。要点是 JavaScript 中唯一一个__proto__属性为 null 的对象是Object.prototypeobject:

console.log(Object.prototype.__proto__===null);//true

这就是 JavaScript 中继承的工作原理。

原型链

换句话说,当你在一个函数上有一个原型属性并且你在它上面调用了一个 new 时,在 JavaScript 完成了对新创建的属性对象的查看之后,它会去查看函数的.prototype,并且这个对象也可能有它的自己的内部原型。等等。

于 2014-02-13T19:32:12.300 回答
80

原型之七公案

西罗桑在冥思之后下到火狐山上,心神清明平静。

然而,他的手却是不动声色,自己抓起一把毛笔,记下了下面的笔记。


0)两种不同的东西可以称为“原型”:

  • 原型属性,如obj.prototype

  • 原型内部属性,[[Prototype]] 在 ES5中表示。

    它可以通过 ES5 检索Object.getPrototypeOf()

    Firefox 可以通过该__proto__属性作为扩展来访问它。ES6 现在提到__proto__.


1)这些概念的存在是为了回答这个问题:

当我这样做obj.property时,JS 在哪里寻找.property

直观地说,经典继承应该会影响属性查找。


2)

  • __proto__用于点.属性查找,如obj.property.
  • .prototype直接用于查找,仅用于间接查找,因为它__proto__在创建对象时使用new.

查找顺序为:

  • objobj.p = ...用or添加的属性Object.defineProperty(obj, ...)
  • 的属性obj.__proto__
  • 的属性obj.__proto__.__proto__,等等
  • 如果有__proto__,请null返回undefined

这就是所谓的原型链

您可以避免使用and进行.查找obj.hasOwnProperty('key')Object.getOwnPropertyNames(f)


3)有两种主要的设置方式obj.__proto__

  • new

    var F = function() {}
    var f = new F()
    

    然后new设置:

    f.__proto__ === F.prototype
    

    .prototype使用的地方。

  • Object.create

     f = Object.create(proto)
    

    套:

    f.__proto__ === proto
    

4)代码:

var F = function(i) { this.i = i }
var f = new F(1)

对应下图(有些Number东西省略了):

(Function)       (  F  )                                      (f)----->(1)
 |  ^             | | ^                                        |   i    |
 |  |             | | |                                        |        |
 |  |             | | +-------------------------+              |        |
 |  |constructor  | |                           |              |        |
 |  |             | +--------------+            |              |        |
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |[[Prototype]]   |[[Prototype]]   |prototype   |constructor   |[[Prototype]]
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |  |             |                | +----------+              |        |
 |  |             |                | |                         |        |
 |  |             |                | | +-----------------------+        |
 |  |             |                | | |                                |
 v  |             v                v | v                                |
(Function.prototype)              (F.prototype)                         |
 |                                 |                                    |
 |                                 |                                    |
 |[[Prototype]]                    |[[Prototype]]          [[Prototype]]|
 |                                 |                                    |
 |                                 |                                    |
 | +-------------------------------+                                    |
 | |                                                                    |
 v v                                                                    v
(Object.prototype)                                       (Number.prototype)
 | | ^
 | | |
 | | +---------------------------+
 | |                             |
 | +--------------+              |
 |                |              |
 |                |              |
 |[[Prototype]]   |constructor   |prototype
 |                |              |
 |                |              |
 |                | -------------+
 |                | |
 v                v |
(null)           (Object)

此图显示了许多语言预定义的对象节点:

  • null
  • Object
  • Object.prototype
  • Function
  • Function.prototype
  • 1
  • Number.prototype(可以找到(1).__proto__,括号必须满足语法)

我们的 2 行代码只创建了以下新对象:

  • f
  • F
  • F.prototype

i现在是的属性,f因为当您这样做时:

var f = new F(1)

它评估F为将返回this的值,new然后将其分配给f.


5) .constructor通常来自于F.prototype查找.

f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor

当我们编写f.constructor时,JavaScript 会进行如下.查找:

  • f不具有.constructor
  • f.__proto__ === F.prototype.constructor === F,拿去吧

结果f.constructor == F直观上是正确的,因为F用于构造f,例如设置字段,就像在经典的 OOP 语言中一样。


6)经典的继承语法可以通过操作原型链来实现。

ES6 添加了classandextends关键字,它们主要是以前可能的原型操作疯狂的语法糖。

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
c = new C(1)
c.inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// http://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

没有所有预定义对象的简化图:

(c)----->(1)
 |   i
 |
 |
 |[[Prototype]]
 |
 |
 v    __proto__
(C)<--------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |[[Prototype]] 
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|[[Prototype]]    (D.prototype)--------> (inc2 function object)
| |                |             inc2
| |                |
| |                |[[Prototype]]
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
|                inc
v
Function.prototype

让我们花点时间研究一下以下是如何工作的:

c = new C(1)
c.inc() === 2

第一行设置c.i1如“4)”中所述。

在第二行,当我们这样做时:

c.inc()
  • .inc通过[[Prototype]]链找到:c-> C-> C.prototype->inc
  • 当我们在 Javascript 中调用函数 as时,JavaScript 会在函数调用内部X.Y()自动设置this为 equal !XY()

完全相同的逻辑也解释了d.incd.inc2

这篇文章https://javascript.info/class#not-just-a-syntax-sugarclass提到了值得了解的进一步影响。class如果没有关键字(TODO check which),其中一些可能无法实现:

于 2014-05-26T20:40:03.213 回答
79

prototype允许你上课。如果你不使用prototype,那么它就会变成一个静态的。

这是一个简短的例子。

var obj = new Object();
obj.test = function() { alert('Hello?'); };

在上述情况下,您有静态函数调用测试。此函数只能由 obj.test 访问,您可以将 obj 想象为一个类。

在下面的代码中

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

obj 已成为现在可以实例化的类。obj 可以存在多个实例,并且它们都具有该test功能。

以上是我的理解。我正在把它变成一个社区维基,所以如果我错了,人们可以纠正我。

于 2009-02-21T12:37:10.837 回答
67

读完这个帖子后,我对 JavaScript Prototype Chain 感到困惑,然后我找到了这些图表

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance *[[prototype]]* 和函数对象的 <code>prototype</code> 属性

这是一个通过原型链显示 JavaScript 继承的清晰图表

http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inheritance/

这个包含一个带有代码的示例和几个漂亮的图表。

原型链最终回退到 Object.prototype。

原型链可以在技术上随心所欲地扩展,每次通过将子类的原型设置为父类的对象。

希望对你理解 JavaScript Prototype Chain 也有帮助。

于 2012-11-07T09:48:11.940 回答
41

每个对象都有一个内部属性[[Prototype]],将它链接到另一个对象:

object [[Prototype]] → anotherObject

在传统的 javascript 中,链接对象是prototype函数的属性:

object [[Prototype]] → aFunction.prototype

一些环境将[[Prototype]]公开为__proto__

anObject.__proto__ === anotherObject

在创建对象时创建[[Prototype]]链接。

// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject

// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject

// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype

所以这些语句是等价的:

var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;

您实际上无法在语句中看到链接目标 ( Object.prototype) ;相反,目标是由构造函数 ( ) 隐含的。Object

记住:

  • 每个对象都有一个链接[[Prototype]],有时暴露为__proto__
  • 每个函数都有一个prototype属性,最初持有一个空对象。
  • 使用new创建的对象链接到prototype其构造函数的属性。
  • 如果一个函数从不用作构造函数,它的prototype属性将不被使用。
  • 如果您不需要构造函数,请使用Object.create而不是new.
于 2013-06-18T19:48:08.097 回答
30

Javascript没有通常意义上的继承,但它有原型链。

原型链

如果在对象中找不到对象的成员,它会在原型链中查找它。链由其他对象组成。可以使用__proto__变量访问给定实例的原型。每个对象都有一个,因为 javascript 中的类和实例之间没有区别。

将函数/变量添加到原型的优点是它必须只在内存中一次,而不是每个实例。

它对于继承也很有用,因为原型链可以包含许多其他对象。

于 2009-02-21T12:41:31.667 回答
29

这篇文章很长。但我相信它会清除您关于 JavaScript 继承的“原型”性质的大部分疑问。甚至更多。请阅读完整的文章。

JavaScript 基本上有两种数据类型

  • 非对象
  • 对象

非对象

以下是非对象数据类型

  • 细绳
  • 数(包括 NaN 和 Infinity)
  • 布尔值(真,假)
  • 不明确的

当您使用typeof运算符时,这些数据类型会返回以下内容

typeof "字符串文字" (或包含字符串文字的变量) === 'string'

typeof 5(或任何数字文字或包含数字文字或NaN 或 Infynity的变量)=== 'number'

typeof true(或false或包含truefalse的变量)=== 'boolean'

typeof undefined (或未定义的变量或包含undefined的变量) === 'undefined'

字符串数字布尔数据类型可以表示为对象非对象。当它们表示为对象时,它们的 typeof 始终为 === 'object' 。一旦我们了解了对象数据类型,我们将回到这一点。

对象

对象数据类型可以进一步分为两种类型

  1. 函数类型对象
  2. 非函数类型对象

Function 类型对象是使用typeof运算符返回字符串'function'的对象。所有用户定义的函数和所有可以使用 new 运算符创建新对象的 JavaScript 内置对象都属于这一类。例如。

  • 目的
  • 细绳
  • 数字
  • 布尔值
  • 大批
  • 类型化数组
  • 正则表达式
  • 功能
  • 所有其他可以使用 new 运算符创建新对象的内置对象
  • function UserDefinedFunction (){ /*用户定义代码 */ }

所以, typeof(Object) === typeof(String) === typeof(Number) === typeof(Boolean) === typeof(Array) === typeof(RegExp) === typeof(Function) == = typeof(UserDefinedFunction) === '函数'

所有的Function 类型对象实际上都是内置 JavaScript 对象Function的实例(包括Function对象,即它是递归定义的)。就好像这些对象已按以下方式定义

var Object= new Function ([native code for object Object])
var String= new Function ([native code for object String])
var Number= new Function ([native code for object Number])
var Boolean= new Function ([native code for object Boolean])
var Array= new Function ([native code for object Array])
var RegExp= new Function ([native code for object RegExp])
var Function= new Function ([native code  for object Function])
var UserDefinedFunction= new Function ("user defined code")

如前所述,Function 类型对象可以使用new 运算符进一步创建新对象。例如,可以使用ObjectStringNumberBooleanArrayRegExpUserDefinedFunction类型的对象创建

var a=new Object() or var a=Object() or var a={} //Create object of type Object
var a=new String() //Create object of type String
var a=new Number() //Create object of type Number
var a=new Boolean() //Create object of type Boolean
var a=new Array() or var a=Array() or var a=[]  //Create object of type Array
var a=new RegExp() or var a=RegExp() //Create object of type RegExp
var a=new UserDefinedFunction() 

这样创建的对象都是非函数类型的对象,并返回它们的typeof === 'object'。在所有这些情况下,对象“a”不能使用 operator new 进一步创建对象。所以以下是错误的

var b=new a() //error. a is not typeof==='function'

内置对象Mathtypeof === 'object'。因此,新运算符不能创建 Math 类型的新对象。

var b=new Math() //error. Math is not typeof==='function'

另请注意,ObjectArrayRegExp函数甚至无需使用operator new即可创建新对象。然而后面的没有。

var a=String() // Create a new Non Object string. returns a typeof==='string' 
var a=Number() // Create a new Non Object Number. returns a typeof==='number'
var a=Boolean() //Create a new Non Object Boolean. returns a typeof==='boolean'

用户定义的函数是特殊情况。

var a=UserDefinedFunction() //may or may not create an object of type UserDefinedFunction() based on how it is defined.

由于Function 类型的对象可以创建新对象,因此它们也称为Constructors

每个构造函数/函数(无论是内置的还是用户定义的)在自动定义时都有一个名为“原型”的属性,其值默认设置为对象。该对象本身有一个名为“constructor”的属性,默认情况下它引用回Constructor/Function

例如当我们定义一个函数时

function UserDefinedFunction()
{
}

以下自动发生

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

“原型”属性仅存在于Function 类型对象 中(而不存在于Non Function 类型对象中)。

这是因为当一个新对象被创建(使用 new 操作符)时,它会从构造函数的当前原型对象继承所有属性和方法,即在新创建的对象中创建一个 内部引用 ,该对象引用构造函数的当前原型对象所引用的对象。

在对象中创建的用于引用继承属性的“内部引用”称为对象的原型(它引用了构造函数的“原型”属性所引用的对象,但与它不同)。对于任何对象(函数或非函数),可以使用Object.getPrototypeOf()方法检索。使用这种方法可以追踪对象的原型链。

此外,创建的每个对象函数类型或非函数类型)都有一个“构造函数”属性,该属性继承自构造函数的原型属性引用的对象。默认情况下,此“构造函数”属性引用创建它的构造函数(如果构造函数的默认“原型”未更改)。

对于所有Function 类型对象,构造函数始终是 function Function(){}

对于非函数类型对象(例如 Javascript Built in Math 对象),构造函数是创建它的函数。对于Math对象,它是function Object(){}

如果没有任何支持代码,上面解释的所有概念可能有点难以理解。请逐行阅读以下代码以了解该概念。尝试执行它以获得更好的理解。

function UserDefinedFunction()
{ 

} 

/* creating the above function automatically does the following as mentioned earlier

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

*/


var newObj_1=new UserDefinedFunction()

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays true

alert(newObj_1.constructor) //Displays function UserDefinedFunction

//Create a new property in UserDefinedFunction.prototype object

UserDefinedFunction.prototype.TestProperty="test"

alert(newObj_1.TestProperty) //Displays "test"

alert(Object.getPrototypeOf(newObj_1).TestProperty)// Displays "test"

//Create a new Object

var objA = {
        property1 : "Property1",
        constructor:Array

}


//assign a new object to UserDefinedFunction.prototype
UserDefinedFunction.prototype=objA

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays false. The object referenced by UserDefinedFunction.prototype has changed

//The internal reference does not change
alert(newObj_1.constructor) // This shall still Display function UserDefinedFunction

alert(newObj_1.TestProperty) //This shall still Display "test" 

alert(Object.getPrototypeOf(newObj_1).TestProperty) //This shall still Display "test"


//Create another object of type UserDefinedFunction
var newObj_2= new UserDefinedFunction();

alert(Object.getPrototypeOf(newObj_2)===objA) //Displays true.

alert(newObj_2.constructor) //Displays function Array()

alert(newObj_2.property1) //Displays "Property1"

alert(Object.getPrototypeOf(newObj_2).property1) //Displays "Property1"

//Create a new property in objA
objA.property2="property2"

alert(objA.property2) //Displays "Property2"

alert(UserDefinedFunction.prototype.property2) //Displays "Property2"

alert(newObj_2.property2) // Displays Property2

alert(Object.getPrototypeOf(newObj_2).property2) //Displays  "Property2"

每个对象的原型链最终都追溯到 Object.prototype(它本身没有任何原型对象)。以下代码可用于跟踪对象的原型链

var o=Starting object;

do {
    alert(o + "\n" + Object.getOwnPropertyNames(o))

}while(o=Object.getPrototypeOf(o))

各种对象的原型链如下所示。

  • 每个 Function 对象(包括内置的 Function 对象)-> Function.prototype -> Object.prototype -> null
  • 简单对象(由 new Object() 或 {} 创建,包括内置的 Math 对象)-> Object.prototype -> null
  • 使用 new 或 Object.create 创建的对象 -> 一个或多个原型链 -> Object.prototype -> null

要创建没有任何原型的对象,请使用以下命令:

var o=Object.create(null)
alert(Object.getPrototypeOf(o)) //Displays null

有人可能认为将 Constructor 的原型属性设置为 null 会创建一个具有 null 原型的对象。然而,在这种情况下,新创建的对象的原型设置为 Object.prototype,其构造函数设置为函数 Object。下面的代码证明了这一点

function UserDefinedFunction(){}
UserDefinedFunction.prototype=null// Can be set to any non object value (number,string,undefined etc.)

var o=new UserDefinedFunction()
alert(Object.getPrototypeOf(o)==Object.prototype)   //Displays true
alert(o.constructor)    //Displays Function Object

以下是本文的摘要

  • 对象有函数类型非函数类型两种
  • 只有Function 类型的对象可以使用操作符 new创建一个新对象。这样创建的对象是非函数类型的对象。Non Function 类型的对象不能使用operator new进一步创建对象。

  • 默认情况下,所有函数类型对象都有一个“原型”属性。这个“原型”属性引用了一个具有“构造函数”属性的对象,该属性默认引用了函数类型对象本身。

  • 所有对象(函数类型非函数类型)都有一个“构造函数”属性,默认情况下引用创建它的函数类型对象/构造函数。

  • 在内部创建的每个对象都引用由 创建它的构造函数的“原型”属性引用的对象。这个对象被称为创建对象的原型(它不同于它所引用的函数类型对象的“原型”属性)。这样创建的对象可以直接访问构造函数的“原型”属性(在对象创建时)引用的对象中定义的方法和属性。

  • 可以使用Object.getPrototypeOf() 方法检索对象的原型(以及继承的属性名称) 。事实上,这种方法可以用于导航对象的整个原型链。

  • 每个对象的原型链最终都追溯到 Object.prototype(除非对象是使用 Object.create(null) 创建的,在这种情况下对象没有原型)。

  • typeof(new Array())==='object'是语言设计的,而不是Douglas Crockford指出的错误

  • 将构造函数的原型属性设置为 null(或 undefined,number,true,false,string)不应创建具有 null 原型的对象。在这种情况下,新创建的对象的原型设置为 Object.prototype,其构造函数设置为函数 Object。

希望这可以帮助。

于 2015-11-04T14:08:57.437 回答
25

对于许多开发人员来说,继承的概念prototypal是最复杂的概念之一。让我们尝试了解问题的根源以便prototypal inheritance更好地理解。让我们从一个plain函数开始。

在此处输入图像描述

如果我们在 上使用new运算符Tree function,我们将其称为constructor函数。

在此处输入图像描述

每个JavaScript函数都有一个prototype. 当您登录时Tree.prototype,您会得到...

在此处输入图像描述

如果您查看上面的console.log()输出,您可以看到一个构造函数属性Tree.prototype和一个__proto__属性。__proto__表示这是基于的prototypefunction因为这只是一个还JavaScript function没有inheritance设置的普通,它指的Object prototype是刚刚内置在JavaScript中的东西......

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

这有诸如此类的东西.toString, .toValue, .hasOwnProperty...

__proto__我的 mozilla 已弃用,取而代之的是Object.getPrototypeOf获取object's prototype.

在此处输入图像描述

Object.getPrototypeOf(Tree.prototype); // Object {} 

让我们添加一个方法到我们的Tree prototype.

在此处输入图像描述

我们已经修改Root并添加了一个function分支。

在此处输入图像描述

这意味着当您创建instanceof 时Tree,您可以调用它的branch方法。

在此处输入图像描述

我们也可以将primitives或添加objects到我们的Prototype.

在此处输入图像描述

让我们child-tree在我们的Tree.

在此处输入图像描述

这里Child继承prototype自 Tree,我们在这里所做的是使用Object.create()方法根据您传递的内容创建一个新对象,这里是Tree.prototype. 在这种情况下,我们所做的是将 Child 的原型设置为一个看起来与Tree原型相同的新对象。接下来我们设置Child's constructor to Child,如果我们不这样做,它将指向Tree()

在此处输入图像描述

Child现在有它自己的prototype,它的__proto__分到TreeTree's prototype分到基地Object

Child  
|
 \
  \
   Tree.prototype
   - branch
   |
   |
    \
     \
      Object.prototype
      -toString
      -valueOf
      -etc., etc.

现在您创建一个instanceofChild并调用branch它最初在Tree. 我们实际上还没有branchChild prototype. 但是,在Root prototype其中 Child 继承自。

在此处输入图像描述

在 JS 中,一切都不是对象,一切都可以像对象一样。

Javascript有原语strings, number, booleans, undefined, null.他们不是object(i.e reference types),但肯定可以像一个object. 让我们看一个例子。

在此处输入图像描述

在此清单的第一行中,primitive为 name 分配了一个字符串值。第二行将 name 视为 an并使用点表示法进行object调用。charAt(0)

这就是幕后发生的事情: //JavaScript引擎做了什么

在此处输入图像描述

String object仅在一个语句被销毁之前存在(一个称为 的过程)autoboxing。让我们再次回到我们的prototypal inheritance.

  • Javascript支持通过delegation基于 prototypes.
  • 每个Function都有一个prototype属性,它引用另一个对象。
  • properties/functions如果不存在,则从object自身或通过 链查找prototype

prototypeJS 中的A是yields您对另一个object. [即..委派] Delegation意味着如果你不能做某事,你会告诉别人为你做。

在此处输入图像描述

https://jsfiddle.net/say0tzpL/1/

如果您查看上面的小提琴,dog 可以访问toString方法,但它在其中不可用,但可以通过委托给的原型链获得Object.prototype

在此处输入图像描述

如果您查看下面的内容,我们正在尝试访问call每个function.

在此处输入图像描述

https://jsfiddle.net/rknffckc/

如果您查看上面的小提琴,ProfileFunction 可以访问call方法,但它在其中不可用,但可以通过委托给的原型链获得Function.prototype

在此处输入图像描述

注意: prototype是函数构造函数的属性,而__proto__是从函数构造函数构造的对象的属性。每个函数都带有一个prototype值为空的属性object。当我们创建函数的实例时,我们会得到一个内部属性[[Prototype]],或者__proto__它的引用是 Function 的原型constructor

在此处输入图像描述

上图看起来有点复杂,但可以全面了解其prototype chaining工作原理。让我们慢慢来看看:

有两个实例b1and b2,其构造函数是Barand parent 是 Foo 并且有来自原型链identifyspeakvia Barand的两个方法Foo

在此处输入图像描述

https://jsfiddle.net/kbp7jr7n/

如果您查看上面的代码,我们有Foo具有方法的构造函数identify()Bar具有speak方法的构造函数。我们创建了两个Bar实例b1b2其父类型为Foo. 现在在调用speak的方法时Bar,我们可以通过prototype链识别谁在调用说话。

在此处输入图像描述

Bar现在拥有Foo在其prototype. 让我们进一步深入了解它们之间的关系Object.prototype以及Function.prototype它们之间的关系。如果您查找 , 的构造函数FooBar并且ObjectFunction constructor

在此处输入图像描述

prototypeof Baris , of isFoo和如果你仔细观察 the of是相关的。prototypeFooObjectprototypeFooObject.prototype

在此处输入图像描述

在我们关闭它之前,让我们在这里用一小段代码来总结上面的所有内容。我们在instanceof这里使用运算符来检查链中是否object具有a 的属性,下面总结了整个大图。prototypeprototypeconstructor

在此处输入图像描述

我希望这个添加的一些信息,我知道这可能很容易掌握......简而言之,它只是链接到对象的对象!!!!

于 2016-07-03T21:44:50.487 回答
25

将原型链分为两类可能会有所帮助。

考虑构造函数:

 function Person() {}

的值Object.getPrototypeOf(Person)是一个函数。事实上,它是Function.prototype。由于Person是作为函数创建的,因此它与所有函数共享相同的原型函数对象。它与 相同Person.__proto__,但不应使用该属性。无论如何,随着Object.getPrototypeOf(Person)你有效地走上所谓的原型链的阶梯。

向上的链条如下所示:

    PersonFunction.prototypeObject.prototype(终点)

Person重要的是这个原型链与可以构造的对象关系不大。这些构造的对象有自己的原型链,并且这个链可能没有与上面提到的相同的近亲。

以这个对象为例:

var p = new Person();

pPerson没有直接的原型链关系。他们的关系是不同的。对象p有自己的原型链。使用Object.getPrototypeOf,您会发现链条如下:

    pPerson.prototypeObject.prototype(终点)

此链中没有函数对象(尽管可能如此)。

所以Person似乎与两种锁链有关,它们过着自己的生活。要从一条链“跳转”到另一条链,请使用:

  1. .prototype:从构造函数的链跳转到创建对象的链。因此,此属性仅针对函数对象定义(因为new只能在函数上使用)。

  2. .constructor:从创建对象的链跳转到构造函数的链。

这是所涉及的两个原型链的可视化演示,以列表示:

在此处输入图像描述

总结一下:

prototype属性不提供主体原型链的信息,而是提供主体创建的对象的信息。

毫不奇怪,物业的名称prototype会导致混淆。prototypeOfConstructedInstances如果这个属性被命名或类似的东西,它可能会更清楚。

你可以在两个原型链之间来回跳转:

Person.prototype.constructor === Person

这种对称性可以通过显式地为prototype属性分配不同的对象来打破(稍后会详细介绍)。

创建一个函数,获取两个对象

Person.prototype是在创建函数的Person同时创建的对象。它具有Person构造函数,即使该构造函数实际上还没有执行。所以同时创建了两个对象:

  1. 函数Person本身
  2. 当函数被作为构造函数调用时将作为原型的对象

两者都是对象,但它们有不同的作用:函数对象构造,而另一个对象代表函数将构造的任何对象的原型。原型对象将成为其原型链中构造对象的父对象。

由于函数也是一个对象,它在自己的原型链中也有自己的父级,但请记住,这两个链是关于不同事物的。

这里有一些可以帮助理解问题的等式——所有这些都打印出来true

function Person() {};

// This is prototype chain info for the constructor (the function object):
console.log(Object.getPrototypeOf(Person) === Function.prototype);
// Step further up in the same hierarchy:
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
console.log(Person.__proto__ === Function.prototype);
// Here we swap lanes, and look at the constructor of the constructor
console.log(Person.constructor === Function);
console.log(Person instanceof Function);

// Person.prototype was created by Person (at the time of its creation)
// Here we swap lanes back and forth:
console.log(Person.prototype.constructor === Person);
// Although it is not an instance of it:
console.log(!(Person.prototype instanceof Person));
// Instances are objects created by the constructor:
var p = new Person();
// Similarly to what was shown for the constructor, here we have
// the same for the object created by the constructor:
console.log(Object.getPrototypeOf(p) === Person.prototype);
console.log(p.__proto__ === Person.prototype);
// Here we swap lanes, and look at the constructor
console.log(p.constructor === Person);
console.log(p instanceof Person);

向原型链添加关卡

尽管在创建构造函数时会创建原型对象,但您可以忽略该对象,并分配另一个对象,该对象应用作该构造函数创建的任何后续实例的原型。

例如:

function Thief() { }
var p = new Person();
Thief.prototype = p; // this determines the prototype for any new Thief objects:
var t = new Thief();

现在t的原型链比p的原型链长一步:

    tpPerson.prototypeObject.prototype(终点)

另一个原型链不再:Thief并且Person兄弟姐妹在其原型链中共享相同的父级:

    Person}
    Thief  } → Function.prototypeObject.prototype(终点)

然后可以将先前呈现的图形扩展到此(Thief.prototype省略原始图形):

在此处输入图像描述

蓝线代表原型链,其他彩色线代表其他关系:

  • 对象与其构造函数之间
  • 在构造函数和将用于构造对象的原型对象之间
于 2017-03-18T22:06:12.783 回答
22

这个“.prototype”属性的确切目的是什么?

标准类的接口变得可扩展。例如,您正在使用Array该类,并且您还需要为所有数组对象添加自定义序列化程序。您会花时间编写子类,还是使用组合或......原型属性通过让用户控制类可用的成员/方法的确切集合来解决这个问题。

将原型视为一个额外的 vtable 指针。当原始类中缺少某些成员时,会在运行时查找原型。

于 2009-02-21T12:37:38.370 回答
20

The Definitive Guide to Object-Oriented JavaScript - 一个非常简洁明了的约 30 分钟视频解释了所提问题(原型继承主题从5:45开始,尽管我宁愿听整个视频)。该视频的作者还制作了 JavaScript 对象可视化网站http://www.objectplayground.com/在此处输入图像描述 在此处输入图像描述

于 2016-02-18T15:17:09.760 回答
16

obj_n.prop_X我发现在被引用时将“原型链”解释为递归约定很有帮助:

如果obj_n.prop_X不存在,请检查obj_n+1.prop_X在哪里obj_n+1 = obj_n.[[prototype]]

如果prop_X最终在第 k 个原型对象中找到

obj_1.prop_X = obj_1.[[prototype]].[[prototype]]..(k-times)..[[prototype]].prop_X

您可以在此处通过属性找到 Javascript 对象的关系图:

js对象图

http://jsobjects.org

于 2013-10-20T22:46:17.100 回答
14

当构造函数创建一个对象时,该对象隐式引用构造函数的“原型”属性以解析属性引用。构造函数的“原型”属性可以被程序表达式constructor.prototype引用,并且添加到对象原型的属性通过继承被共享原型的所有对象共享。

于 2010-02-05T18:42:10.047 回答
12

这里有两个不同但相关的实体需要解释:

  • 函数的.prototype属性。
  • 所有对象[2][[Prototype]][1]属性。

这是两个不同的东西。

物业[[Prototype]]

这是一个存在于所有[2]对象上的属性。

这里存储的是另一个对象,作为一个对象本身,它有一个[[Prototype]]指向另一个对象的自己的对象。另一个对象有[[Prototype]]它自己的一个。这个故事一直持续到您到达提供可在所有对象上访问的方法的原型对象(如.toString)。

[[Prototype]]财产是构成[[Prototype]]链条的一部分。[[Prototype]]例如,当对对象执行操作[[Get]][[Set]]操作时,会检查此对象链:

var obj = {}
obj.a         // [[Get]] consults prototype chain
obj.b = 20    // [[Set]] consults prototype chain

物业.prototype

这是一个仅在函数上才有的属性。使用一个非常简单的函数:

function Bar(){};

.prototype属性包含一个对象b.[[Prototype]],当您执行此操作时将分配给该对象var b = new Bar。你可以很容易地检查这个:

// Both assign Bar.prototype to b1/b2[[Prototype]]
var b = new Bar;
// Object.getPrototypeOf grabs the objects [[Prototype]]
console.log(Object.getPrototypeOf(b) === Bar.prototype) // true

最重要.prototype的 s之一是Objectfunction[[Prototype]]这个原型包含所有链包含的原型对象。在它上面,定义了新对象的所有可用方法:

// Get properties that are defined on this object
console.log(Object.getOwnPropertyDescriptors(Object.prototype))

现在,既然.prototype是一个对象,它就有一个[[Prototype]]属性。当您不对 进行任何分配时Function.prototype.prototype'[[Prototype]]指向原型对象 ( Object.prototype)。每当您创建新函数时,都会自动执行此操作。

这样,每当您new Bar;为您设置原型链时,您都会得到定义的所有内容Bar.prototype和定义的所有内容Object.prototype

var b = new Bar;
// Get all Bar.prototype properties
console.log(b.__proto__ === Bar.prototype)
// Get all Object.prototype properties
console.log(b.__proto__.__proto__ === Object.prototype)

当您对所做的一切进行分配时,Function.prototype您所做的就是扩展原型链以包含另一个对象。这就像在单链表中的插入。

这基本上改变了[[Prototype]]链,允许在分配给的对象上定义的属性被Function.prototype函数创建的任何对象看到。


[1:这不会让任何人感到困惑;在许多实现中通过__proto__属性可用。
[2]:除null.

于 2017-12-13T10:10:21.180 回答
10

让我告诉你我对原型的理解。我不打算将这里的继承与其他语言进行比较。我希望人们停止比较语言,而只理解语言本身。了解原型和原型继承是如此简单,我将在下面向您展示。

原型就像一个模型,您可以根据它创建产品。要理解的关键点是,当您使用另一个对象作为原型创建对象时,原型和产品之间的联系是永恒的。例如:

var model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5

每个对象都包含一个称为 [[prototype]] 的内部属性,该属性可以被Object.getPrototypeOf()函数访问。Object.create(model)创建一个新对象并将其 [[prototype]] 属性设置为对象模型。因此,当您这样做时Object.getPrototypeOf(product),您将获得对象模型

产品中的属性按以下方式处理:

  • 当访问一个属性以读取它的值时,它会在作用域链中查找。对变量的搜索从产品向上开始到它的原型。如果在搜索中找到这样的变量,则搜索将在此处停止,并返回该值。如果在作用域链中找不到这样的变量,则返回 undefined。
  • 写入(更改)属性时,始终将属性写入产品对象。如果产品还没有这样的属性,它会被隐式创建和写入。

这种使用原型属性的对象链接称为原型继承。就这么简单,同意吗?

于 2014-11-09T15:47:38.523 回答
10

另一种尝试用更好的图片来解释JavaScript 基于原型的继承

简单对象继承

于 2015-10-24T21:21:40.537 回答
10

考虑以下keyValueStore对象:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
        this.get = function(key) { return this.data[key]; };
        this.set = function(key, value) { this.data[key] = value; };
        this.delete = function(key) { delete this.data[key]; };
        this.getLength = function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

我可以通过这样做来创建这个对象的一个​​新实例:

kvs = keyValueStore.create();

此对象的每个实例都将具有以下公共属性:

  • data
  • get
  • set
  • delete
  • getLength

现在,假设我们创建了这个keyValueStore对象的 100 个实例。尽管get, set, delete,getLength将对这 100 个实例中的每一个执行完全相同的操作,但每个实例都有自己的此函数副本。

现在,想象一下,如果您只有一个get、和副本set,并且每个实例都将引用相同的函数。这对性能会更好,并且需要更少的内存。deletegetLength

这就是原型的用武之地。原型是继承但不被实例复制的属性的“蓝图”。所以这意味着对于一个对象的所有实例,它在内存中只存在一次,并且由所有这些实例共享。

现在,keyValueStore再次考虑对象。我可以这样重写它:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
    };

    kvs.prototype = {
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  {
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

这与之前版本的keyValueStore对象完全相同,只是它的所有方法现在都放在原型中。这意味着,所有 100 个实例现在都共享这四种方法,而不是每个都有自己的副本。

于 2016-03-11T19:24:36.867 回答
9

概括:

  • 函数是 javascript 中的对象,因此可以具有属性
  • (构造函数)函数始终具有原型属性
  • 当一个函数被用作带有new关键字的构造函数时,对象得到原型。可以__proto__在新创建的对象的属性上找到对此原型的引用。
  • __proto__属性是指prototype构造函数的属性。

例子:

function Person (name) {
  this.name = name;
}

let me = new Person('willem');

console.log(Person.prototype) // Person has a prototype property

console.log(Person.prototype === me.__proto__) // the __proto__ property of the instance refers to prototype property of the function.

为什么这很有用:

Javascript 在查找对象的属性时有一种称为“原型继承”的机制,基本上是这样的:

  • 首先检查属性是否位于对象本身上。如果是这样,则返回此属性。
  • 如果该属性不在对象本身上,它将“爬上原型链”。它基本上着眼于由proto属性引用的对象。它在那里检查该属性是否在proto引用的对象上可用
  • 如果属性不在proto对象上,它将沿着proto链一直向上爬到 Object 对象。
  • 如果它在对象及其原型链上找不到属性,它将返回未定义。

例如:

function Person(name) {
  this.name = name;
}

let mySelf = new Person('Willem');

console.log(mySelf.__proto__ === Person.prototype);

console.log(mySelf.__proto__.__proto__ === Object.prototype);

更新:

__proto__属性已被弃用,尽管它在大多数现代浏览器中实现,但获取原型对象引用的更好方法是:

Object.getPrototypeOf()

于 2018-08-18T10:24:20.757 回答
7

在理解这类东西时,我总是喜欢类比。在我看来,与 class bass 继承相比,“原型继承”相当令人困惑,尽管原型是更简单的范例。事实上,原型确实没有继承,所以这个名字本身就具有误导性,它更像是一种“委托”。

想象一下这个......

你在上高中,你在课堂上,有一个今天到期的测验,但你没有笔来填写你的答案。嗬!

你坐在你的朋友 Finnius 旁边,他可能有一支笔。你问,他没有成功地环顾他的办公桌,但他没有说“我没有笔”,而是和他的另一个朋友 Derp 一起检查他是否有笔。Derp 确实有一支备用笔并将其传回给 Finnius,后者将其传递给您以完成您的测验。Derp 将笔委托给 Finnius,Finnius 将笔委托给您使用。

这里重要的是 Derp 不会把笔给你,因为你和他没有直接的关系

这是原型如何工作的简化示例,在其中搜索数据树以查找您要查找的内容。

于 2017-05-26T10:24:07.890 回答
3

另一个显示__proto__原型构造函数关系的方案: 在此处输入图像描述

于 2018-08-02T06:16:03.780 回答
1

只是你已经有了一个对象,Object.new但是在使用构造函数语法时你仍然没有一个对象。

于 2018-02-01T09:33:06.533 回答
1

重要的是要了解对象的原型(可通过Object.getPrototypeOf(obj)或通过已弃用__proto__的属性获得)和prototype构造函数的属性之间的区别。前者是每个实例的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())指的是与 相同的对象Foobar.prototype

参考:https ://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes

于 2019-01-21T08:45:21.703 回答
0

Prototype通过克隆现有对象来创建对象。所以当我们考虑原型时,我们真的可以考虑克隆或复制某些东西,而不是虚构它。

于 2018-12-25T07:24:12.527 回答
0

如果您想从基础知识中了解原型和基于原型的继承的概念,请查看官方MDN文档,他们对此进行了很好的解释。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

在继承方面,JavaScript 只有一种构造:对象。每个对象都有一个私有属性,该属性保存到另一个对象的链接,称为其原型。该原型对象有自己的原型,依此类推,直到到达一个对象,其原型为 null。根据定义,null 没有原型,是这个原型链中的最后一环。

此外,这是另一个很好的资源,它使用简单的示例进行解释 - https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes

于 2020-09-23T16:16:33.533 回答