协慌网

登录 贡献 社区

JavaScript .prototype 如何工作?

我不是那种动态编程语言,但是我写了很多 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.propNameobj['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");

在完成原型概念之前,我们必须考虑一些关键点。

1- JavaScript 函数如何实际工作:

要迈出第一步,我们必须弄清楚,JavaScript 函数实际上是如何工作的,作为一个类使用this关键字的函数,或者作为一个常规函数及其参数,它做什么以及返回什么。

假设我们想要创建一个Person对象模型。但是在这一步中,我将尝试在不使用prototypenew关键字的情况下做同样的事情

所以在这一步中, functionsobjectsthis关键字都是我们所拥有的。

第一个问题是如果不使用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对象。

C。 JavaScript 有另一种方式为用户提供功能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 功能的重要初始步骤。


2- 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 = {};

湾 JavaScript 采取的下一步是所有原型对象附加到新创建的对象

我们这里的my_person_prototype类似于原型对象。

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

这不是 JavaScript 实际附加原型中定义的属性的方式。实际的方式与原型链概念有关。


一个。 &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_prototype调用getName函数:

newObject.getName();

C。然后它将该对象赋予构造函数,

我们可以使用以下示例执行此操作:

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