我不太喜欢动态编程语言,但我已经编写了相当多的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

这些幻灯片也帮了大忙。


当前回答

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

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

例子:

//在JavaScript中定义一个函数对象以容纳人员var Person=函数(名称){this.name=名称;};//向已定义的对象动态添加一个新的getterPerson.prototype.getName=函数(){返回this.name;};//创建Person类型的新对象var john=新人(“john”);//试试吸气剂警报(john.getName());//如果现在我修改人,John也会得到更新Person.prototype.sayMyName=函数(){alert('你好,我的名字是'+this.getName());};//对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=函数(名称){this.name=名称;};Person.prototype.getName=函数(){返回this.name;};var john=新人(“john”);警报(john.getName());Person.prototype.sayMyName=函数(){alert('你好,我的名字是'+this.getName());};john.sayMyName();var客户=函数(名称){this.name=名称;};Customer.prototype=新角色();var myCustomer=新客户(“梦想股份有限公司”);myCustomer.sayMyName();Customer.prototype.setAmountDue=函数(amountDue){this.amountDue=amountDue;};Customer.prototype.getAmountDue=函数(){return this.amountDue;};myCustomer.setAmountDue(2000);警报(myCustomer.getAmountDue());

虽然如上所述,我不能调用setAmountDue(),但对Person调用getAmountDu()。

//The following statement generates an error.
john.setAmountDue(1000);

其他回答

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

我总是喜欢用类比来理解这类事情在我看来,原型继承与类低音继承相比非常令人困惑,尽管原型是更简单的范例。事实上,对于原型来说,确实没有继承,因此名称本身就具有误导性,更像是一种“委托”。

想象一下。。。。

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

你坐在你的朋友芬尼乌斯旁边,他可能有一支笔。你问,他环顾了一下他的桌子,但没有成功,但他不是说“我没有笔”,而是一个很好的朋友,他会和他的另一个朋友Derp检查是否有笔。德普确实有一支备用笔,并将其交给芬尼乌斯,后者将其交由你完成测验。德普已将笔交给芬尼乌斯,后者已将笔委托给你使用。

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

这是原型如何工作的一个简化示例,其中搜索数据树以查找您要查找的对象。

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

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

这是两件不同的事情。

[[Prototype]]属性:

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

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

[[Prototype]]属性是形成[[Prototype]]链的一部分。例如,对对象执行[[Get]]或[[Set]]操作时,会检查[[Prototype]]对象链:

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

.prototype属性:

这是一个只能在函数上找到的属性。使用一个非常简单的函数:

function Bar(){};

.prototype属性保存一个对象,当您执行var b=new Bar时,该对象将被分配给b.[[prototype]]。您可以轻松检查:

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

最重要的原型之一是Object函数的原型。此原型包含所有[[prototype]]链包含的原型对象。在上面,定义了新对象的所有可用方法:

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

现在,由于.prototype是一个对象,它有一个[[prototype]]属性。如果您没有对Function.prototype进行任何赋值,.prototype的[[prototype]]将指向原型对象(object.prototype)。这将在您创建新函数时自动执行。

这样,任何时候你做新酒吧;原型链已为您设置,您将获得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] :除空值外的所有值。

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

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

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

原型的七个Koan

当西罗·桑在深度冥想后下了火狐山时,他的头脑清晰而平静。

然而,他的手却坐立不安,自己抓起画笔,记下了下面的笔记。


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

prototype属性,如obj.prototype原型内部属性,在ES5中表示为[[prototype]]。它可以通过ES5 Object.getPrototypeOf()进行检索。Firefox可以通过__proto__属性作为扩展访问它。ES6现在提到了__proto__的一些可选要求。


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

当我执行obj.properties时,JS在哪里查找.properties?

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


2)

__proto__用于点。在obj.properties中查找属性。.prototype不直接用于查找,而是间接用于查找,因为它在使用new创建对象时确定__proto__。

查找顺序为:

obj财产添加了obj.p=。。。或Object.defineProperty(obj,…)obj.__proto的财产__对象proto_.proto__的财产,依此类推如果某些__proto__为空,则返回undefined。

这就是所谓的原型链。

你可以避免。使用obj.hasOwnProperty('key')和Object.getOwnPropertyNames(f)查找


3) 设置对象__proto__的主要方法有两种:

新增:var F=函数(){}var f=新f()则new已设置:f.__proto__==f原型这就是使用原型的地方。对象创建:f=对象.create(proto)集合:f.__proto__==原型


4) 代码:

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

对应于下图(省略了一些数字内容):

(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)

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

无效的对象对象.原型作用功能.原型1.Number.prototype(可以用(1)找到__proto__,括号必须满足语法)

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

fFF.原型

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

var f = new F(1)

它计算F,这是new将返回的值,然后将其分配给F。


5) 构造函数通常通过。查找:

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

当我们编写f.constructor时,JavaScript会执行。查找为:

f没有.构造函数f.__proto__==f.prototype具有构造函数==f,因此接受它

结果f.constructor==f直观上是正确的,因为f用于构造f,例如集合字段,与经典OOP语言中的情况非常相似。


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

ES6添加了类并扩展了关键字,这些关键字主要是语法糖,用于之前可能的原型操作疯狂。

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.i设置为1,如“4)”所述。

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

c.inc()

.inc是通过[[原型]]链找到的:c->c->c.Prototype->inc当我们将Javascript中的函数调用为X.Y()时,Javascript会在Y()函数调用中将其自动设置为等于X!

同样的逻辑也解释了d.inc和d.inc2。

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

[[FunctionKind]]:“classConstructor”,强制使用new调用构造函数:ES6类构造函数不能作为普通函数调用的原因是什么?类方法是不可枚举的。可以使用Object.defineProperty完成。类总是使用strict。可以通过显式使用strict for each函数来完成,这无疑是乏味的。