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

这些幻灯片也帮了大忙。


当前回答

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

函数的.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] :除空值外的所有值。

其他回答

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

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

原型继承的概念对于许多开发人员来说是最复杂的概念之一。让我们试着理解问题的根源,以便更好地理解原型继承。让我们从一个简单的函数开始。

如果我们在Tree函数上使用一个新运算符,我们将其作为构造函数调用。

每个JavaScript函数都有一个原型。当你记录Tree.prototype时,你会。。。

如果查看上面的console.log()输出,您可以看到Tree.prototype上的构造函数属性和__proto__属性。__proto__表示这个函数所基于的原型,由于这只是一个普通的JavaScript函数,还没有设置继承,所以它引用的是Object原型,它是JavaScript中刚刚内置的东西。。。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

这包括.toString、.toValue、.hasOwnProperty等。。。

__我的mozilla带来的proto__已被弃用,取而代之的是Object.getPrototypeOf方法来获取对象的原型。

Object.getPrototypeOf(Tree.prototype); // Object {} 

让我们向Tree原型添加一个方法。

我们修改了Root并向其添加了一个函数分支。

这意味着当您创建Tree实例时,可以调用它的分支方法。

我们还可以向原型中添加原语或对象。

让我们在树中添加一个子树。

在这里,Child从Tree继承了它的原型,我们在这里做的是使用Object.create()方法根据您传递的内容创建一个新对象,这里是Tree.prototype。在这种情况下,我们正在做的是将Child的原型设置为一个看起来与Tree原型相同的新对象。接下来,我们将Child的构造函数设置为Child,如果不设置,它将指向Tree()。

孩子现在有了自己的原型,它的__proto__指向Tree,Tree的原型指向base Object。

Child  
|
 \
  \
   Tree.prototype
   - branch
   |
   |
    \
     \
      Object.prototype
      -toString
      -valueOf
      -etc., etc.

现在您创建了一个最初在Tree中可用的Child和call分支的实例。我们还没有在Child原型上定义分支。但是,在Child继承的Root原型中。

在JS中,一切都不是对象,一切都可以像对象一样。

Javascript有字符串、数字、布尔值、未定义、null等原语。它们不是对象(即引用类型),但肯定可以充当对象。让我们看一个例子。

在该列表的第一行中,为name指定了一个基本字符串值。第二行将名称视为对象,并使用点表示法调用charAt(0)。

这是幕后发生的事情://JavaScript引擎的作用

String对象在被销毁之前只存在一个语句(一个称为自动装箱的过程)。让我们再次回到我们的原型继承。

Javascript支持基于原型。每个函数都有一个原型属性,该属性引用另一个对象财产/函数是从对象本身或通过原型链(如果不存在)

JS中的原型是一个对象,它使您成为另一个对象的父对象。授权意味着如果你不能做某事,你会告诉别人帮你做。

https://jsfiddle.net/say0tzpL/1/

如果您查找上面的fiddle,dog可以访问toString方法,但它在其中不可用,但可以通过委托给Object.prototype的原型链使用

如果您查看下面的一个,我们正在尝试访问每个函数中可用的调用方法。

https://jsfiddle.net/rknffckc/

如果您查找上面的fiddle,Profile Function可以访问调用方法,但它在其中不可用,但可以通过委托给Function.prototype的原型链使用

注意:prototype是函数构造函数的属性,而__proto__是从函数构造函数构造的对象的属性。每个函数都有一个原型属性,其值为空对象。当我们创建函数的实例时,我们得到一个内部属性[[Prototype]]或__proto__,其引用是function构造函数的原型。

上图看起来有点复杂,但展示了原型链接工作原理的全貌。让我们慢慢来:

有两个实例b1和b2,其构造函数是Bar,父实例是Foo,并且有两个来自原型链的方法通过Bar和Foo识别和说话

https://jsfiddle.net/kbp7jr7n/

如果你查阅上面的代码,我们有Foo构造函数,它有方法identify(),Bar构造函数有speak方法。我们创建了两个Bar实例b1和b2,它们的父类型是Foo。现在,在调用Bar的speak方法时,我们能够通过原型链识别谁在调用speak。

Bar现在拥有Foo的所有方法,这些方法都在其原型中定义。让我们进一步了解Object.prototype和Function.prototype以及它们之间的关系。如果您查找Foo的构造函数,Bar和Object是Function构造函数。

Bar的原型是Foo,Foo的原型是Object,如果仔细观察,Foo原型与Object.prototype相关。

在结束之前,让我们在这里用一小段代码来总结上面的内容。我们在这里使用instanceof运算符来检查对象的原型链中是否包含构造函数的prototype属性,该属性在下面概括了整个大型图。

我希望这是一些信息,我知道这类信息可能很难掌握。。。简单地说,它只是链接到对象的对象!!!!

重要的是要理解对象的原型(可通过object.getPrototypeOf(obj)或通过不推荐的__proto__属性获得)和构造函数的原型属性之间存在区别。前者是每个实例上的属性,后者是构造函数上的属性。也就是说,Object.getPrototypeOf(newFoobar())引用与Foobar.prototype相同的对象。

参考:https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes

每个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。

每个对象都有一个内部属性[[Prototype]],将其链接到另一个对象:

object [[Prototype]] → anotherObject

在传统javascript中,链接对象是函数的原型属性:

object [[Prototype]] → aFunction.prototype

某些环境将[[Prototype]]公开为__proto__:

anObject.__proto__ === anotherObject

创建对象时创建[[Prototype]]链接。

// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject

// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject

// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype

因此,这些陈述是等价的:

var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;

您实际上无法在新语句中看到链接目标(Object.prototype);相反,目标由构造函数(Object)暗示。

记得:

每个对象都有一个链接[[Prototype]],有时公开为__proto__。每个函数都有一个原型属性,最初持有一个空对象。使用new创建的对象链接到其构造函数的原型属性。如果函数从未用作构造函数,则其原型属性将不被使用。如果不需要构造函数,请使用Object.create而不是new。