我不太喜欢动态编程语言,但我已经编写了相当多的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基本上有两种数据类型

非对象物体

非对象

以下是非对象数据类型

一串数字(包括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 model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5

每个对象都包含一个名为[[prototype]]的内部属性,可以通过object.getPrototypeOf()函数访问该属性。create(model)创建一个新对象,并将其[[prototype]]属性设置为对象模型。因此,当您执行Object.getPrototypeOf(产品)时,您将获得对象模型。

产品中的财产按以下方式处理:

当访问属性以读取其值时,将在范围链中查找该属性。变量的搜索从产品开始,一直到其原型。如果在搜索中找到了这样的变量,搜索将立即停止,并返回值。如果在作用域链中找不到这样的变量,则返回undefined。当写入(更改)属性时,该属性将始终写入产品对象。如果产品还没有这样的属性,则会隐式创建和编写它。

使用原型属性的对象链接称为原型继承。这很简单,同意吗?

每个JavaScript对象都有一个名为[[Prototype]]的内部“槽”,其值要么为null,要么为对象。您可以将槽视为对象的属性,在JavaScript引擎内部,隐藏在编写的代码中。[[Prototype]]周围的方括号是故意的,是表示内部插槽的ECMAScript规范约定。

一个对象的[[Prototype]]所指向的值,通俗地说就是“该对象的原型”

如果您通过点(obj.propName)或括号(obj['propName'])表示法访问属性,并且对象没有直接具有这样的属性(即自己的属性,可通过obj.hasOwnProperty('propName')检查),则运行时会在[[Prototype]]引用的对象上查找具有该名称的属性。如果[[Prototype]]也没有这样的属性,则依次检查其[[Prototype]],依此类推。这样,将遍历原始对象的原型链,直到找到匹配项或到达其终点。原型链的顶部是空值。

现代JavaScript实现允许通过以下方式对[[Prototype]]进行读和/或写访问:

新运算符(在从构造函数返回的默认对象上配置原型链),extends关键字(使用类语法时配置原型链),Object.create将提供的参数设置为结果对象的[[Prototype]],Object.getPrototypeOf和Object.setPrototype Of(在创建对象后获取/设置[[Prototype]]),以及名为__proto__(类似于4)的标准化访问器(即getter/setter)属性

Object.getPrototypeOf和Object.setPrototype of优于__proto__,部分原因是当对象的原型为null时,o.__proto__的行为是不寻常的。

对象的[[Prototype]]最初是在创建对象期间设置的。

如果通过new Func()创建新对象,则默认情况下,对象的[[Prototype]]将设置为Func.Prototype引用的对象。

因此,请注意,所有类和所有可以与新运算符一起使用的函数,除了它们自己的[[prototype]]内部槽之外,还有一个名为.prototype的属性。这种“原型”一词的双重使用是该语言新来者无尽困惑的根源。

在构造函数中使用new允许我们模拟JavaScript中的经典继承;尽管正如我们所见,JavaScript的继承系统是原型的,而不是基于类的。

在JavaScript引入类语法之前,构造函数是模拟类的唯一方法。我们可以将构造函数的.prototype属性引用的对象的财产视为共享成员;即每个实例相同的成员。在基于类的系统中,方法对每个实例的实现方式相同,因此方法在概念上被添加到.prototype属性中;然而,对象的字段是特定于实例的,因此在构造过程中会添加到对象本身。

如果没有类语法,开发人员必须手动配置原型链,以实现与经典继承类似的功能。这导致了实现这一目标的多种方式的优势。

这里有一种方法:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

…还有另一种方法:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

ES2015中引入的类语法简化了事情,通过提供extends作为配置原型链的“一种真正的方式”,以模拟JavaScript中的经典继承。

因此,与上面的代码类似,如果您使用类语法创建一个新对象,如下所示:

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

…结果对象的[[Prototype]]将被设置为Parent的一个实例,其[[Prototype]]又是Parent.Prototype。

最后,如果您通过object.create(foo)创建一个新对象,则生成的对象的[[Prototype]]将设置为foo。

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

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

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,而且这个对象可能有自己的内部原型。等等

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