一尘不染

JavaScript .prototype 是如何工作的?

javascript

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

这些幻灯片也确实有很大帮助。


阅读 152

收藏
2022-02-11

共2个答案

一尘不染

每个 JavaScript 对象都有一个名为的内部“槽”[[Prototype]],其值为nullobject。您可以将插槽视为对象的属性,位于 JavaScript 引擎内部,对您编写的代码隐藏。方括号[[Prototype]]是经过深思熟虑的,是表示内部插槽的 ECMAScript 规范约定。

对象的 指向的值[[Prototype]],俗称“该对象的原型”。

如果您通过点 ( obj.propName) 或方括号 ( obj['propName']) 表示法访问属性,并且对象没有直接具有这样的属性(即自己的属性,可通过 进行检查obj.hasOwnProperty('propName')),则运行时会在引用的对象上查找具有该名称的属性[[Prototype]]相反。如果[[Prototype]] 没有这样的属性,[[Prototype]]则依次检查它,依此类推。以这种方式,原始对象的原型链被遍历,直到找到匹配,或者到达它的末端。原型链的顶端是null价值。

[[Prototype]]现代 JavaScript 实现允许通过以下方式读取和/或写入访问权限:

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

Object.getPrototypeOf并且Object.setPrototypeOf优先于,部分原因是当对象具有 的原型时,__proto__的行为o.__proto__ 是不寻常null的。

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

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

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

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

在将类语法引入 JavaScript 之前,构造函数是模拟类的唯一方法。我们可以将构造函数的属性所引用的对象的.prototype属性视为共享成员;IE。每个实例都相同的成员。在基于类的系统中,每个实例的方法都以相同的方式实现,因此方法在概念上被添加到.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

2022-02-11
一尘不染

在 Java、C# 或 C++ 等实现经典继承的语言中,您首先创建一个类(对象的蓝图),然后您可以从该类创建新对象,或者您可以扩展该类,定义一个新类来增强原来的班级。

在 JavaScript 中,您首先创建一个对象(没有类的概念),然后您可以扩充自己的对象或从中创建新对象。对于习惯了经典方式的人来说,这并不难,但有点陌生且难以代谢。

例子:

//Define a functional object to hold persons in JavaScript
var Person = function(name) {
  this.name = name;
};

//Add dynamically to the already defined object a new getter
Person.prototype.getName = function() {
  return this.name;
};

//Create a new object of type Person
var john = new Person("John");

//Try the getter
alert(john.getName());

//If now I modify person, also John gets the updates
Person.prototype.sayMyName = function() {
  alert('Hello, my name is ' + this.getName());
};

//Call the new method on john
john.sayMyName();

到目前为止,我一直在扩展基础对象,现在我创建另一个对象,然后从 Person 继承。

//Create a new object of type Customer by defining its constructor. It's not 
//related to Person for now.
var Customer = function(name) {
    this.name = name;
};

//Now I link the objects and to do so, we link the prototype of Customer to 
//a new instance of Person. The prototype is the base that will be used to 
//construct all new instances and also, will modify dynamically all already 
//constructed objects because in JavaScript objects retain a pointer to the 
//prototype
Customer.prototype = new Person();     

//Now I can call the methods of Person on the Customer, let's try, first 
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Let's try:       
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());
var Person = function (name) {
    this.name = name;
};
Person.prototype.getName = function () {
    return this.name;
};
var john = new Person("John");
alert(john.getName());
Person.prototype.sayMyName = function () {
    alert('Hello, my name is ' + this.getName());
};
john.sayMyName();
var Customer = function (name) {
    this.name = name;
};
Customer.prototype = new Person();

var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();
Customer.prototype.setAmountDue = function (amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function () {
    return this.amountDue;
};
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

虽然如前所述,我不能在 Person 上调用 setAmountDue()、getAmountDue()。

//The following statement generates an error.
john.setAmountDue(1000);
2022-02-11