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

这些幻灯片也帮了大忙。


当前回答

只是您已经有一个带有object.new的对象,但在使用构造函数语法时仍然没有对象。

其他回答

原型允许您创建类。如果不使用原型,那么它将成为静态的。

这里有一个简短的例子。

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实例,它们都具有测试功能。

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

原型的七个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函数来完成,这无疑是乏味的。

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

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

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

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

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

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

1-JavaScript函数的实际工作方式:

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

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

因此,在这一步中,函数、对象和关键字都是我们所拥有的。

第一个问题是,在不使用新关键字的情况下,该关键字如何有用。

为了回答这个问题,假设我们有一个空对象和两个函数,比如:

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

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

现在不使用新关键字,我们如何使用这些函数。所以JavaScript有三种不同的方法来实现这一点:

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

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

在本例中,这将是当前上下文对象,通常是浏览器中的全局窗口对象或Node.js中的global。这意味着我们将具有浏览器中的window.name或Node.js中的global.name,其值为“George”。

b.我们可以将它们附加到对象上,作为其财产

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

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

这样我们可以这样称呼他们:

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

现在person对象是这样的:

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

-另一种将属性附加到对象的方法是使用该对象的原型,该原型可以在任何名为__proto__的JavaScript对象中找到,我已经尝试在总结部分对此进行了解释。因此,我们可以通过以下操作获得类似的结果:

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

但这样我们实际上要做的是修改Object.prototype,因为每当我们使用文字({…})创建JavaScript对象时,它都是基于Object.prototype创建的,这意味着它会作为名为__proto__的属性附加到新创建的对象上,因此如果我们像前面的代码片段那样更改它,所有JavaScript对象都会更改,这不是一个好的做法。那么,现在最好的做法是:

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

现在其他物体都处于和平状态,但这似乎仍然不是一个好的做法。所以我们还有一个解决方案,但要使用这个解决方案,我们应该回到创建person对象的代码行(varperson={};),然后将其更改为:

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

它所做的是创建一个新的JavaScript对象,并将propertiesObject附加到__proto__属性。为了确保您能够做到:

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

但这里需要注意的是,您可以在person对象的第一层访问__proto__中定义的所有财产(有关详细信息,请阅读摘要部分)。


正如您所看到的,使用这两种方法中的任何一种,这都将精确地指向person对象。

c.JavaScript还有另一种方法来为函数提供此功能,即使用call或apply来调用函数。

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

and

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

这种方式是我最喜欢的,我们可以很容易地调用我们的函数,比如:

Person.call(person, "George");

or

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

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

这三种方法是确定原型功能的重要初始步骤。


2-新关键字是如何工作的?

这是理解原型功能的第二步。这是我用来模拟过程的步骤:

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

在这一部分中,我将尝试采取JavaScript所采取的所有步骤,当您使用新关键字时,不使用新关键字和原型。所以,当我们创建新的Person(“George”)时,Person函数充当构造函数,这是JavaScript所做的,一个接一个:

首先,它生成一个空对象,基本上是一个空散列,如:

var newObject = {};

b.JavaScript的下一步是将所有原型对象附加到新创建的对象

这里的mypersontotype类似于原型对象。

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

JavaScript实际上并不是以这种方式附加原型中定义的财产。实际方式与原型链概念有关。


a.和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"

现在我们可以在my_person_totype中调用getName函数:

newObject.getName();

c.然后将该对象交给构造函数,

我们可以用我们的样本来做这件事,比如:

Person.call(newObject, "George");

or

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

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

现在是模拟其他步骤之前的最终结果:对象{name:“George”}


摘要:

基本上,当您在函数上使用new关键字时,您正在调用该关键字,而该函数充当构造函数,因此当您说:

new FunctionName()

JavaScript在内部生成一个对象,一个空散列,然后它将该对象提供给构造函数,然后构造函数可以做它想要做的任何事情,因为该构造函数的内部是刚刚创建的对象,然后它会给你该对象,当然,如果你没有在函数中使用return语句,或者你没有定义return;在功能体的末端。

因此,当JavaScript在对象上查找属性时,它首先要做的就是在该对象上查找它。然后有一个秘密属性[[prototype]],我们通常将其命名为__proto__,这个属性就是JavaScript接下来要看的。当它查看__proto__时,只要它再次是另一个JavaScript对象,它就有自己的__proto__属性,它会不断上升,直到下一个__proto__为空。点是JavaScript中唯一一个__proto__属性为空的对象。它是object.prototype对象:

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

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

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

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

考虑构造函数:

 function Person() {}

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

向上的链条如下所示:

人员→ 功能.原型→ Object.prototype(终点)

重要的是,这个原型链与Person可以构造的对象关系不大。这些构造的对象有自己的原型链,而这个链可能和上面提到的对象没有共同的祖先。

以该对象为例:

var p = new Person();

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

p型→ 人物原型→ Object.prototype(终点)

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

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

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

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

总结如下:

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

属性原型的名称会导致混淆,这并不奇怪。如果将此属性命名为prototypeOfConstructedInstance或类似的名称,可能会更清楚。

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

Person.prototype.constructor === Person

通过将不同的对象显式指定给原型属性,可以打破这种对称性(稍后将详细介绍)。

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

Person.prototype是在创建函数Person的同时创建的对象。它具有Person作为构造函数,即使该构造函数尚未实际执行。因此,同时创建了两个对象:

功能Person本身将函数作为构造函数调用时充当原型的对象

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

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

以下是有助于把握问题的一些等式——所有这些都是正确的:

函数Person(){};//这是构造函数(函数对象)的原型链信息:console.log(Object.getPrototypeOf(Person)==Function.prototype);//在同一层次结构中更进一步:console.log(Object.getPrototypeOf(Function.prototype)==Object.proto原型);console.log(Object.getPrototypeOf(Object.prototype)==null);console.log(Person.__proto__==Function.prototype);//这里我们交换通道,并查看构造函数的构造函数console.log(Person.constructor==函数);console.log(函数的个人实例);//Person.prototype由Person创建(创建时)//这里我们来回交换车道:console.log(Person.prototype.constructor==人员);//虽然它不是它的一个实例:console.log(!(Person.prototype instanceof Person));//实例是由构造函数创建的对象:var p=新角色();//类似于为构造函数显示的内容,这里我们有//构造函数创建的对象也是如此:console.log(Object.getPrototypeOf(p)==Person.prototype);console.log(p.__proto__==Person.prototype);//在这里,我们交换通道,并查看构造函数console.log(p.constructor==人员);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长一步:

吨→ p→ 人物原型→ Object.prototype(终点)

另一个原型链不再长:Thief和Person是兄弟姐妹,在原型链中共享同一个父级:

个人}小偷}→ 功能.原型→ Object.prototype(终点)

之前展示的图形可以扩展到这一点(省略了最初的Thief.原型):

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

在对象及其构造函数之间在构造函数和将用于构造对象的原型对象之间