JavaScript .prototype如何工作?


Answers

在使用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);
Question

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

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

我记得曾经和人们讨论过很多次(我不确定自己在做什么),但据我了解,没有一堂课的概念。 它只是一个对象,这些对象的实例是原始的克隆,对吗?

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

编辑

这些slides真的帮助我们理解这个主题。




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功能。

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







Javascript通常没有继承,但它有原型链。

原型链

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

为原型添加一个函数/变量的好处是,它只能在内存中存储一​​次,而不是每个实例。

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




There's two distinct but related entities here that need explaining:

  • The .prototype property of functions.
  • The [[Prototype]] [1] property of all objects [2] .

These are two different things.

The [[Prototype]] property:

This is a property that exists on all [2] objects.

What's stored here is another object, which, as an object itself, has a [[Prototype]] of its own that points to another object. That other object has a [[Prototype]] of its own. This story continues until you reach the prototypical object that provides methods that are accessible on all objects (like .toString ).

The [[Prototype]] property is part of what forms the [[Prototype]] chain. This chain of [[Prototype]] objects is what is examined when, for example, [[Get]] or [[Set]] operations are performed on an object:

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

The .prototype property:

This is a property that is only found on functions. Using a very simple function:

function Bar(){};

The .prototype property holds an object that will be assigned to b.[[Prototype]] when you do var b = new Bar . You can easily examine this:

// 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

One of the most important .prototype s is that Object.prototype . This prototype holds the prototypical object that all [[Prototype]] chains contain. On it, all the available methods for new objects are defined:

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

Now, since .prototype is an object, it has a [[Prototype]] property. When you don't make any assignments to Function.prototype , the .prototype 's [[Prototype]] points to the prototypical object ( Object.prototype ). This is automatically performed anytime you create a new function.

This way, any time you do new Bar; the prototype chain is set up for you, you get everything defined on Bar.prototype and everything defined on 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)

When you do make assignments to Function.prototype all you are doing is extending the prototype chain to include another object. It's like an insertion in a singly linked list.

This basically alters the [[Prototype]] chain allowing properties that are defined on the object assigned to Function.prototype to be seen by any object created by the function.

[1:这不会混淆任何人; 通过提供__proto__性能在许多实现。
[2]:除了null




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

  • 原型属性,如obj.prototype

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

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

    Firefox使它可以通过__proto__属性作为扩展来访问。 ES6现在提到 __proto__一些可选要求。

1)存在这些概念来回答这个问题:

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

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

2)

  • __proto__用于点. 属性查找在obj.property
  • .prototype 直接用于查找,只是间接的,因为它在用new创建对象时确定__proto__

查找顺序是:

  • 添加了obj.p = ...Object.defineProperty(obj, ...) obj属性
  • obj.__proto__属性
  • obj.__proto__.__proto__属性等等
  • 如果某些__proto__null ,则返回undefined

这就是所谓的原型链

你可以避免.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() {}
var f = new F()

对应于以下图表:

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

该图显示了许多语言预定义的对象节点: nullObjectObject.prototypeFunctionFunction.prototype 。 我们的2行代码只创建了fFF.prototype

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添加了classextends关键字,这些关键字仅仅是用于以前可能的原型操作疯狂的语法糖。

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.
(new C(1)).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://.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

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

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype






The concept of prototypal inheritance is one of the most complicated for many developers. Let's try to understand the root of problem to understand prototypal inheritance better. Let's start with a plain function.

If we use a new operator on the Tree function , we call it as a constructor function.

Every JavaScript function has a prototype . When you log the Tree.prototype , you get...

If you look at the above console.log() output, you could a see a constructor property on Tree.prototype and a __proto__ property too. The __proto__ represents the prototype that this function is based off, and since this is just a plain JavaScript function with no inheritance set up yet, it refers to the Object prototype which is something just built in to JavaScript...

Object.prototype

This has things like .toString, .toValue, .hasOwnProperty etc...

__proto__ which was brought my mozilla is deprecated and is replaced by Object.getPrototypeOf method to get the object's prototype .

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

Let's add a method to our Tree prototype .

We have modified the Root and added a function branch to it.

That means when you create an instance of Tree , you can call it's branch method.

We can also add primitives or objects to our Prototype .

Let's add a child-tree to our Tree .

Here the Child inherits its prototype from Tree, what we are doing here is using Object.create() method to create a new object based off what you pass, here it is Tree.prototype . In this case what we're doing is setting the prototype of Child to a new object that looks identical to the Tree prototype. Next we are setting the Child's constructor to Child , if we don't it would point to Tree() .

Child now has its own prototype , its __proto__ points to Tree and Tree's prototype points to base Object .

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

Now you create an instance of Child and call branch which is originally available in Tree . We haven't actually defined our branch on the Child prototype . BUT, in the Root prototype which Child inherits from.

In JS everything is not an object, everything can act like an object.

Javascript has primitives like strings, number, booleans, undefined, null. They are not object(ie reference types) , but certainly can act like an object . Let's look at an example here.

In the first line of this listing, a primitive string value is assigned to name. The second line treats name like an object and calls charAt(0) using dot notation.

This is what happens behind the scenes: // what the JavaScript engine does

The String object exists only for one statement before it's destroyed (a process called autoboxing ). Let's again get back to our prototypal inheritance .

  • Javascript supports inheritance via delegation based on prototypes .
  • Each Function has a prototype property, which refers to another object.
  • properties/functions are looked from the object itself or via prototype chain if it does not exist

A prototype in JS is an object which yields you to the parent of another object . [ie.. delegation] Delegation means that if you are unable to do something, you'll tell someone else to do it for you.

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

If you look up the above fiddle, dog has access to toString method, but its not available in it, but available via the prototype chain which delegates to Object.prototype

If you look at the below one, we are trying to access the call method which is available in every function .

https://jsfiddle.net/rknffckc/

If you look up the above fiddle, Profile Function has access to call method, but its not available in it, but available via the prototype chain which delegates to Function.prototype

Note: prototype is a property of the function constructor, whereas __proto__ is a property of the objects constructed from the function constructor. Every function comes with a prototype property whose value is an empty object . When we create an instance of the function, we get an internal property [[Prototype]] or __proto__ whose reference is the prototype of the Function constructor .

The above diagram looks bit complicated, but brings out the whole picture on how prototype chaining works. Let's walk through this slowly:

There are two instance b1 and b2 , whose constructor is Bar and parent is Foo and has two methods from prototype chain identify and speak via Bar and Foo

https://jsfiddle.net/kbp7jr7n/

If you look up the code above, we have Foo constructor who has the method identify() and Bar constructor which has speak method. We create two Bar instance b1 and b2 whose parent type is Foo . Now while calling speak method of Bar , we are able to identify the who is calling the speak via prototype chain.

Bar now has all the methods of Foo which are defined in its prototype . Let's dig further in understanding the Object.prototype and Function.prototype and how they are related. If you look up the constructor of Foo , Bar and Object are Function constructor .

The prototype of Bar is Foo , prototype of Foo is Object and if you look closely the prototype of Foo is related to Object.prototype .

Before we close this down, let's just wrap with a small piece of code here to summarize everything above . We are using instanceof operator here to check whether an object has in its prototype chain the prototype property of a constructor which below summarizes the entire big diagram.

I hope this add's some information, I know this kinda could be big to grasp... in simple words its it's just objects linked to objects!!!!




Let me tell you my understanding of prototypes. I am not going to compare the inheritance here with other languages. I wish people would stop comparing languages, and just understand the language as itself. Understanding prototypes and prototypal inheritance is so simple, as I will show you below.

Prototype is like a model, based on which you create a product. The crucial point to understand is that when you create an object using another object as it's prototype, the link between the prototype and the product is ever-lasting. 例如:

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

Every object contains an internal property called the [[prototype]], which can be accessed by the Object.getPrototypeOf() function. Object.create(model) creates a new object and sets it's [[prototype]] property to the object model . Hence when you do Object.getPrototypeOf(product) , you will get the object model .

Properties in the product are handled in the following way:

  • When a property is accessed to just read it's value, its looked up in the scope chain. The search for the variable starts from the product upwards to it's prototype. If such a variable is found in the search, the search is stopped right there, and the value is returned. If such a variable cannot be found in the scope chain, undefined is returned.
  • When a property is written(altered), then the property is always written on the product object. If the product does not have such a property already, it is implicitly created and written.

Such a linking of objects using the prototype property is called prototypal inheritance. There, it is so simple, agree?




I found it helpful to explain the "prototype chain" as recursive convention when obj_n.prop_X is being referenced:

if obj_n.prop_X doesn't exist, check obj_n+1.prop_X where obj_n+1 = obj_n.[[prototype]]

If the prop_X is finally found in the k-th prototype object then

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

You can find a graph of the relation of Javascript objects by their properties here:

http://jsobjects.org






Related