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

这些幻灯片也帮了大忙。


当前回答

我发现在引用obj_n.prop_X时,将“原型链”解释为递归约定很有帮助:

如果obj_n.prop_X不存在,请检查obj_n+1.prop_X,其中obj_n+1=obj_n。[[原型]]

如果prop_X最终在第k个原型对象中找到,则

obj_1.prop_X=obj_1.[[原型]].[[原型]]..(k次)..[[原型]].prop_X

您可以在此处通过Javascript对象的财产找到它们之间的关系图:

http://jsobjects.org

其他回答

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

原型链

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

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

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

让我告诉你我对原型的理解。我不会将这里的继承与其他语言进行比较。我希望人们不要再比较语言了,而只把语言理解为语言本身。理解原型和原型继承非常简单,我将在下面向您展示。

原型就像一个模型,你可以根据它来创建产品。要理解的关键点是,当您使用另一个对象作为原型创建一个对象时,原型和产品之间的联系是持久的。例如:

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。

当构造函数创建对象时,该对象隐式引用构造函数的“prototype”属性以解析属性引用。构造函数的“prototype”属性可以由程序表达式constructor.prototype引用,添加到对象原型中的财产通过继承由共享原型的所有对象共享。

每个对象都有一个内部属性[[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。