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

这些幻灯片也帮了大忙。


当前回答

面向对象JavaScript的最终指南-一个非常简洁和清晰的30分钟视频解释(原型继承主题从5:45开始,尽管我更愿意听整个视频)。这段视频的作者还制作了JavaScript对象可视化工具网站http://www.objectplayground.com/.

其他回答

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

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

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

原型链

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

向原型中添加函数/变量的优点在于,它只需在内存中存在一次,而不是针对每个实例。

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

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

JavaScript基本上有两种数据类型

非对象物体

非对象

以下是非对象数据类型

一串数字(包括NaN和Infinity)布尔值(true,false)未定义

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

typeof“string literal”(或包含字符串文本的变量)==“string”

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

typeof true(或false或包含true或false的变量)==“boolean”

typeof undefined(或未定义变量或包含未定义的变量)==“undefineed”

字符串、数字和布尔数据类型既可以表示为对象,也可以表示为非对象。当它们表示为对象时,其类型总是==“object”。一旦我们了解了对象数据类型,我们将回到这一点。

物体

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

函数类型对象非函数类型对象

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

对象一串数字布尔型大堆类型化数组RegExp(RegExp)作用所有其他可以使用new运算符创建新对象的内置对象函数UserDefinedFunction(){/*用户定义的代码*/}

所以typeof(对象)==typeof(字符串)==typeof(数字)==typeof(布尔)==typeof(数组)==type-of(RegExp)==typeoft(函数)==typesof(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运算符进一步创建新对象。例如,object、String、Number、Boolean、Array、RegExp或UserDefinedFunction类型的对象可以使用

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”无法进一步创建对象使用运算符new。所以以下是错误的

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

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

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

还要注意,Object、Array和RegExp函数可以创建一个新对象,而无需使用运算符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.

由于函数类型对象可以创建新对象,因此它们也被称为构造函数。

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

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

function UserDefinedFunction()
{
}

自动发生以下情况

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

此“prototype”属性仅存在于Function类型对象中(并且从不在非函数类型对象中)。

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

在对象中创建的用于引用继承的财产的“内部引用”称为对象的原型(它引用由Constructor的“prototype”属性引用的对象,但与之不同)。对于任何对象(函数或非函数),都可以使用object.getPrototypeOf()方法进行检索。使用此方法可以跟踪对象的原型链。

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

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

对于非函数类型的对象(例如Javascript内置数学对象),构造函数是创建它的函数。对于Math对象,它是函数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简单对象(由新的Object()或{}创建,包括内置的Math对象)->Object.prototype->null使用新的或Object.create创建的对象->一个或多个原型链->Object.prototype->null

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

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

人们可能会认为,将构造函数的prototype属性设置为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

本文摘要如下

有两种类型的对象功能类型和非功能类型只有函数类型的对象才能使用运算符new创建新对象。这样创建的对象是非函数类型的对象。非函数类型对象无法使用运算符new进一步创建对象。默认情况下,所有函数类型对象都具有“prototype”属性。此“prototype”属性引用了一个对象,该对象具有默认引用Function类型对象本身的“构造函数”属性。所有对象(函数类型和非函数类型)都有一个“构造函数”属性,默认情况下该属性引用创建它的函数类型对象/构造函数。内部创建的每个对象都引用创建该对象的构造函数的“prototype”属性。该对象称为创建对象的prototype(与它引用的函数类型对象“prototype“属性不同)。这样,创建的对象可以直接访问Constructor的“prototype”属性(在创建对象时)引用的对象中定义的方法和财产。可以使用object.getPrototypeOf()方法检索对象的原型(以及继承的属性名称)。事实上,这种方法可以用于导航对象的整个原型链。每个对象的原型链最终都会追溯到object.prototype(除非对象是使用object.create(null)创建的,在这种情况下,对象没有原型)。typeof(new Array())==“object”是由语言设计的,而不是Douglas Crockford指出的错误将构造函数的原型属性设置为null(或undefined、number、true、false、string)不会创建具有null原型的对象。在这种情况下,新创建的对象的原型设置为object.prototype,其构造函数设置为函数object。

希望这有帮助。

考虑以下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();

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

数据收到设置删去获取长度

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

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

原型就是这样产生的。原型是财产的“蓝图”,它是继承而不是被实例复制的。因此,这意味着对于一个对象的所有实例,它只在内存中存在一次,并且由所有这些实例共享。

现在,再次考虑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个实例现在都共享这四个方法,而不是每个实例都有自己的副本。