我不是那么喜欢动态编程语言,但我已经编写了相当多的 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 对象都有一个名为的内部“槽”[[Prototype]],其值为null或object。您可以将插槽视为对象的属性,位于 JavaScript 引擎内部,对您编写的代码隐藏。方括号[[Prototype]]是经过深思熟虑的,是表示内部插槽的 ECMAScript 规范约定。
[[Prototype]]
null
object
对象的 指向的值[[Prototype]],俗称“该对象的原型”。
如果您通过点 ( obj.propName) 或方括号 ( obj['propName']) 表示法访问属性,并且对象没有直接具有这样的属性(即自己的属性,可通过 进行检查obj.hasOwnProperty('propName')),则运行时会在引用的对象上查找具有该名称的属性[[Prototype]]相反。如果[[Prototype]] 也没有这样的属性,[[Prototype]]则依次检查它,依此类推。以这种方式,原始对象的原型链被遍历,直到找到匹配,或者到达它的末端。原型链的顶端是null价值。
obj.propName
obj['propName']
obj.hasOwnProperty('propName')
[[Prototype]]现代 JavaScript 实现允许通过以下方式读取和/或写入访问权限:
new
extends
Object.create
Object.getPrototypeOf
Object.setPrototypeOf
__proto__
Object.getPrototypeOf并且Object.setPrototypeOf优先于,部分原因是当对象具有 的原型时,__proto__的行为o.__proto__ 是不寻常null的。
o.__proto__
对象的[[Prototype]]初始设置是在对象创建期间设置的。
如果您通过 创建新对象new Func(),则[[Prototype]]默认情况下,该对象将设置为 引用的对象Func.prototype。
new Func()
Func.prototype
因此,请注意,所有可以与运算符一起使用的类和所有函数,除了它们自己的内部槽外new,还有一个名为的属性。.prototype``[[Prototype]]“原型”这个词的双重使用是该语言新手无休止的困惑的根源。
.prototype``[[Prototype]]
使用new构造函数可以让我们模拟 JavaScript 中的经典继承;尽管 JavaScript 的继承系统——正如我们所见——是原型的,而不是基于类的。
在将类语法引入 JavaScript 之前,构造函数是模拟类的唯一方法。我们可以将构造函数的属性所引用的对象的.prototype属性视为共享成员;IE。每个实例都相同的成员。在基于类的系统中,每个实例的方法都以相同的方式实现,因此方法在概念上被添加到.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。
Parent
Parent.prototype
最后,如果您通过 创建一个新对象Object.create(foo),则生成的对象[[Prototype]]将设置为foo。
Object.create(foo)
foo
在 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);