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

其他回答

这篇文章很长。但我相信这会澄清你的大部分疑问关于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。

希望这有帮助。

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

这里有一个简短的例子。

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

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

另一个显示__proto__、原型和构造函数关系的方案:

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

考虑构造函数:

 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.原型):

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

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

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