这是一个非常简单的基于原型的对象模型,会在解释过程中作为示例使用,但尚未做出注释:
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
关键字来完成相同的事情。
所以在这一步中,我们只有函数
、对象
和this
关键字可用。
第一个问题是没有使用new
关键字,如何使用this
关键字?
为了回答这个问题,假设我们有一个空对象和两个函数:
var person = {};
function Person(name){ this.name = name; }
function getName(){
console.log(this.name);
}
现在,不使用new
关键字,我们如何使用这些功能。因此,JavaScript 有三种不同的方法:
a.第一种方法就是像普通函数一样调用该函数:
Person("George");
getName();//would print the "George" in the console
在这种情况下,它将成为当前的上下文对象,通常是浏览器中的全局window
对象或者Node.js
中的GLOBAL
。这意味着我们会有window.name在浏览器中或GLOBAL.name在Node.js中,并且它的值为"George"。
b. 我们可以将它们作为对象的属性附加到对象上:
- 最简单的方法是修改空的person
对象,例如:
person.Person = Person
person.getName = getName
这样我们就可以像这样调用它们:
person.Person("George")
person.getName()
现在 person
对象的内容如下:
Object {Person: function, getName: function, name: "George"}
-另一种将属性附加到对象的方法是使用该对象的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)
然而,这里的棘手之处在于您可以访问person
对象的第一级别中定义的__proto__
的所有属性(请阅读摘要部分以获取更多详细信息)。
如您所见,使用这两种方法之一,this
将完全指向person
对象。
c. JavaScript还有另一种方式为函数提供this
,即使用call或apply来调用该函数。
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)
这3种方法是了解 .prototype 功能的重要初始步骤。
2- new
关键字是如何工作的?
这是理解 .prototype 功能的第二步。以下是我用来模拟这个过程的代码:
function Person(name){ this.name = name; }
my_person_prototype = { getName: function(){ console.log(this.name); } };
在这部分中,我将尝试在不使用new
关键字和prototype
的情况下,模拟JavaScript使用new
关键字时所执行的所有步骤。因此,当我们执行new Person("George")
时,Person
函数充当构造函数。以下是JavaScript一步一步执行的操作:
a.首先创建一个空对象,类似于一个空哈希表:
var newObject = {};
b. JavaScript接下来执行的操作是将所有原型对象附加到新创建的对象上。
这里我们有一个类似于原型对象的my_person_prototype
。
for(var key in my_person_prototype){
newObject[key] = my_person_prototype[key];
}
JavaScript实际上附加在原型中定义的属性的方式并不是这样的。实际的方式与原型链概念有关。
a. & b. 可以通过以下方式来达到完全相同的结果,而不是这两个步骤:
var newObject = Object.create(my_person_prototype)
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype)
//and also check if you have access to your desired properties
console.log(typeof newObject.getName)
现在我们可以在my_person_prototype
中调用getName
函数:
newObject.getName()
c. 然后将该对象传递给构造函数,
我们可以用示例进行如下操作:
Person.call(newObject, "George");
或者
Person.apply(newObject, ["George"])
如果构造函数使用了 this 关键字,那么在构造函数内部,this 指向的就是刚刚创建的对象。现在在模拟其他步骤之前,得到的最终结果为:
Object {name: "George"}
摘要:
基本上,当您对一个函数使用 new 关键字时,您正在调用该函数并将其作为构造函数使用,因此当您说:
new FunctionName()
JavaScript内部创建一个空哈希对象,并将该对象传递给构造函数。构造函数内部的
this指向刚刚创建的对象,因此构造函数可以对其进行任何操作,当然前提是您的函数体中没有使用return语句或者在函数末尾使用了
return undefined;
语句。
因此,当JavaScript在对象上查找属性时,首先在该对象上查找,然后查找一个密封属性
[[prototype]]
,通常我们将其命名为
__proto__
。当它在
__proto__
上查找时,如果发现另一个JavaScript对象,则它也有自己的
__proto__
属性,依次类推,直到找到下一个
__proto__
为空的对象。这意味着在JavaScript中,唯一一个
__proto__
属性为空的对象就是
Object.prototype
对象。
console.log(Object.prototype.__proto__===null)
这就是JavaScript中继承的工作原理。
![原型链](https://istack.dev59.com/JnpBV.webp)
换句话说,当你在函数上有一个原型属性,并且你对其调用一个new操作符时,在JavaScript完成查找新创建的对象的属性后,它会去查找该函数的.prototype
,而且这个对象可能有自己的内部原型。依此类推。