JavaScript .prototype如何工作?
我不是那么喜欢动态编程语言,但是我已经写了我的JavaScript代码。 我从来没有真正理解这个基于原型的编程,有没有人知道这是如何工作的?
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
属性的确切目的是什么? 它与实例化对象有什么关系?
编辑
这些幻灯片对于理解这个主题非常有帮助。
每个JavaScript对象都有一个名为[[Prototype]]的内部属性。 如果通过obj.propName
或obj['propName']
查找属性,并且对象没有这样的属性 - 可以通过obj.hasOwnProperty('propName')
来检查 - 运行时将查找对象中的属性而是由[[Prototype]]引用。 如果原型对象也没有这样的属性,则依次检查原型对象的原型,从而遍历原始对象的原型链,直到找到匹配或达到其结尾。
一些JavaScript实现允许直接访问[[Prototype]]属性,例如通过名为__proto__
的非标准属性。 通常,只有在对象创建期间才能设置对象的原型:如果通过new Func()
创建新对象,则该对象的[[Prototype]]属性将设置为由Func.prototype
引用的对象。
这允许在JavaScript中模拟类,尽管JavaScript的继承系统是 - 正如我们所看到的 - 原型的,而不是基于类的:
只要将构造函数看作是类和原型的属性(即由构造函数的prototype
属性引用的对象)作为共享成员即每个实例相同的成员即可。 在基于类的系统中,方法对于每个实例都以相同的方式实现,所以方法通常添加到原型中,而对象的字段是特定于实例的,因此在构建期间将其添加到对象本身。
在使用经典继承如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();
我扮演一位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
对象模型。 但在这一步中,我会试着做同样的事情,而不使用prototype
和new
关键字 。
所以在这一步中, functions
, objects
和this
关键字就是我们所有的。
第一个问题是如何使用this
关键字而不使用new
关键字 。
所以要回答这个问题,假设我们有一个空对象,并且有两个函数:
var person = {};
function Person(name){ this.name = name; }
function getName(){
console.log(this.name);
}
现在不使用new
关键字我们可以如何使用这些功能。 所以JavaScript有三种不同的方式来做到这一点:
一个。 第一种方法就是将函数作为常规函数调用:
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
,该对象的prototype
可以在名为__proto__
任何JavaScript对象中找到,并且我试图在摘要部分对其进行解释。 所以我们可以通过这样做来得到相似的结果:
person.__proto__.Person = Person;
person.__proto__.getName = getName;
但是这样我们实际上正在修改Object.prototype
,因为每当我们使用文字( { ... }
)创建一个JavaScript对象时,它都是基于Object.prototype
创建的,这意味着它会被附加到新创建的对象作为名为__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
关键字时,不使用new
关键字和prototype
,而是采取JavaScript所需的所有步骤。 所以当我们做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;
的return undefined;
在函数体的末尾。
所以当JavaScript去查找一个对象的属性时,它首先要做的事情就是查看对象。 然后有一个秘密属性[[prototype]]
,我们通常将它放在__proto__
这个属性中,这个属性就是JavaScript接下来要看的东西。 并且,当它通过__proto__
查看时,只要它是另一个JavaScript对象,它就有它自己的__proto__
属性,它会一直向上,直到它到达下一个__proto__
为空的点。 该点是JavaScript中唯一的对象,其__proto__
属性为null是Object.prototype
对象:
console.log(Object.prototype.__proto__===null);//true
这就是继承在JavaScript中的作用。
换句话说,当你在一个函数上有一个prototype属性并且你调用了一个新的属性时,在JavaScript完成查看这个新创建的对象的属性之后,它会查看函数的.prototype
并且这个对象也是可能的有它自己的内部原型。 等等。