我不是那种动态编程语言,但是我写了很多 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
这些幻灯片也非常有帮助。
在实现 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);
每个 JavaScript 对象都有一个名为[[Prototype]]的内部属性。如果你通过obj.propName
或obj['propName']
查找属性,并且该对象没有这样的属性 - 可以通过obj.hasOwnProperty('propName')
检查 - 运行时查找对象中的属性由 [[Prototype]] 引用。如果 prototype-object 也没有这样的属性,则依次检查其原型,从而遍历原始对象的原型链,直到找到匹配或达到其结束。
一些 JavaScript 实现允许直接访问 [[Prototype]] 属性,例如通过名为__proto__
的非标准属性。通常,只能在对象创建期间设置对象的原型:如果通过new Func()
创建新对象,则对象的 [[Prototype]] 属性将设置为Func.prototype
引用的对象。
这允许在 JavaScript 中模拟类,尽管 JavaScript 的继承系统 - 如我们所见 - 原型,而不是基于类:
只需将构造函数作为类和原型的属性(即构造函数的prototype
属性引用的对象)视为共享成员,即每个实例的成员相同。在基于类的系统中,方法以相同的方式为每个实例实现,因此通常将方法添加到原型中,而对象的字段是特定于实例的,因此在构造期间添加到对象本身。
我扮演 JavaScript 老师的角色,原型概念在我教授时一直是一个有争议的话题。我花了一些时间来提出一个澄清这个概念的好方法,现在在本文中我将试图解释 JavaScript .prototype 是如何工作的。
这是一个非常简单的基于原型的对象模型,在解释过程中将被视为一个样本,尚无评论:
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
console.log(this.name);
}
var person = new Person("George");
在完成原型概念之前,我们必须考虑一些关键点。
要迈出第一步,我们必须弄清楚,JavaScript 函数实际上是如何工作的,作为一个类使用this
关键字的函数,或者作为一个常规函数及其参数,它做什么以及返回什么。
假设我们想要创建一个Person
对象模型。但是在这一步中,我将尝试在不使用prototype
和new
关键字的情况下做同样的事情 。
所以在这一步中, functions
, objects
和this
关键字都是我们所拥有的。
第一个问题是如果不使用new
关键字, this
关键字将如何有用 。
所以回答一下,假设我们有一个空对象,有两个函数,比如:
var person = {};
function Person(name){ this.name = name; }
function getName(){
console.log(this.name);
}
现在不使用new
关键字我们如何使用这些功能。所以 JavaScript 有 3 种不同的方法:
Person("George");
getName();//would print the "George" in the console
在这种情况下,这将是当前的上下文对象,它通常是浏览器中的全局window
对象或Node.js
GLOBAL
。这意味着我们将在浏览器中使用 window.name 或在 Node.js 中使用 GLOBAL.name,并使用 “George” 作为其值。
- 最简单的方法是修改空person
对象,如:
person.Person = Person;
person.getName = getName;
通过这种方式我们可以称之为:
person.Person("George");
person.getName();// -->"George"
现在person
对象就像:
Object {Person: function, getName: function, name: "George"}
- 将属性附加到对象的另一种方法是使用该对象的prototype
,该对象可以在名称为__proto__
任何 JavaScript 对象中找到,我试图在摘要部分稍微解释一下。所以我们可以通过这样做得到类似的结果:
person.__proto__.Person = Person;
person.__proto__.getName = getName;
但是这样我们实际上正在做的是修改Object.prototype
,因为每当我们使用文字( { ... }
)创建一个 JavaScript 对象时,它就会基于Object.prototype
创建,这意味着它会附加到新创建的 object 作为一个名为__proto__
的属性,所以如果我们改变它,就像我们在前面的代码片段中所做的那样,所有 JavaScript 对象都会被改变,这不是一个好习惯。那么现在更好的做法是什么:
person.__proto__ = {
Person: Person,
getName: getName
};
现在其他物品都很平静,但它似乎仍然不是一个好习惯。所以我们还有一个解决方案,但是要使用这个解决方案,我们应该回到创建person
对象的那行代码( var person = {};
)然后改变它:
var propertiesObject = {
Person: Person,
getName: getName
};
var person = Object.create(propertiesObject);
它的作用是创建一个新的 JavaScript Object
并将propertiesObject
附加到__proto__
属性。所以要确保你能做到:
console.log(person.__proto__===propertiesObject); //true
但这里棘手的一点是你可以访问person
对象第一层__proto__
定义的所有属性(阅读摘要部分以获取更多细节)。
当你看到使用这两种方式中的任何一种时, this
将完全指向person
对象。
this
,这是使用电话或应用调用该函数。 apply()方法调用一个给定此值的函数,并将参数作为数组(或类数组对象)提供。
和
call()方法调用一个具有给定值的函数和单独提供的参数。
这种方式是我最喜欢的,我们可以很容易地调用我们的功能:
Person.call(person, "George");
要么
//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);
getName.call(person);
getName.apply(person);
这三种方法是确定. prototype 功能的重要初始步骤。
new
关键字如何运作? 这是理解.prototype
功能的第二步。这是我用来模拟过程的:
function Person(name){ this.name = name; }
my_person_prototype = { getName: function(){ console.log(this.name); } };
在这部分中,当你使用new
关键字时,我将尝试采用 JavaScript 所采取的所有步骤,而不使用new
关键字和prototype
。所以当我们做new Person("George")
, Person
函数作为构造函数,这些是 JavaScript 所做的,一个接一个:
var newObject = {};
我们这里的my_person_prototype
类似于原型对象。
for(var key in my_person_prototype){
newObject[key] = my_person_prototype[key];
}
这不是 JavaScript 实际附加原型中定义的属性的方式。实际的方式与原型链概念有关。
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_prototype
调用getName
函数:
newObject.getName();
我们可以使用以下示例执行此操作:
Person.call(newObject, "George");
要么
Person.apply(newObject, ["George"]);
然后构造函数可以做任何想做的事情,因为这个构造函数内部是刚刚创建的对象。
现在是模拟其他步骤之前的最终结果:Object {name:“George”}
基本上,当你在函数上使用new关键字时,你正在调用它,并且该函数用作构造函数,所以当你说:
new FunctionName()
JavaScript 在内部创建一个对象,一个空哈希,然后它将该对象提供给构造函数,然后构造函数可以做任何想做的事情,因为这个构造函数内部是刚刚创建的对象,然后它给了你那个对象当然如果你没有在你的函数中使用 return 语句或者你没有使用return undefined;
在函数体的末尾。
因此,当 JavaScript 在一个对象上查找一个属性时,它所做的第一件事就是它在该对象上查找它。然后有一个秘密属性[[prototype]]
我们通常会像__proto__
那样拥有它,而这个属性就是 JavaScript 接下来要看的内容。当它查看__proto__
,只要它是另一个 JavaScript 对象,它有自己的__proto__
属性,它会一直向上和向上,直到它到达下一个__proto__
为空的点。关键是 JavaScript 中__proto__
属性为 null 的Object.prototype
对象是Object.prototype
对象:
console.log(Object.prototype.__proto__===null);//true
这就是继承在 JavaScript 中的工作方式。
换句话说,当你在一个函数上有一个 prototype 属性并且你在它上面调用 new 时,在 JavaScript 完成查找新创建的属性对象之后,它将查看函数的.prototype
并且也可能是这个对象有自己的内部原型。等等。