JavaScript 中关于 prototype 的概念总是颇具争议。在本文中,我将尝试解释 JavaScript 中的 .prototype 是如何工作的。
原文链接:JavaScript 中的 .prototype 的工作原理
JavaScript 中关于 prototype 的概念总是颇具争议。在本文中,我将尝试解释 JavaScript 中的 .prototype 是如何工作的。
这是一个非常简单的基于对象模型的 prototype,它将会成本文讨论的一个样例。
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
console.log(this.name);
}
var person = new Person(“George”);
在彻底理解 prototype 的概念之前,还有一些很重要的地方需要我们考虑。
JavaScript 中的函数是如何工作的
为了迈出第一步,我们必须弄清楚,JavaScript 中的函数是如何工作的,一个类是如何像函数一样使用 this 关键字或一个普通的带有参数函数是如何调用并返回的。
比如说,我们希望创建一个 Person 对象模型。但是在这一步中,我将要尝试做一些相同的事,但是不使用 prototype 和 new 关键字。
所以在这一步中,我使用的只有 function、object 和 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
在这个情况中,this 是目前环境的对象,在浏览器中是 window 对象,在 Node.js 中是 GLOBAL。它意味着,如果在浏览器中,window.name 的值会是 “George”,在 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,我们可以在任何 JavaScript 对象中找到名为 proto 的属性,这个我会在稍后解释。我们先来看一下结果:
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 对象。
使用 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);
上述介绍的三类方法是理解 .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 = {};
下一步,JavaScript 将所有 prototype 对象附加到新创建的对象中
我们在这里用与 prototype 对象相似的 my_person_prototype 模拟:
for(var key in my_person_prototype){
newObject[key] = my_person_prototype[key];
}
实际上,JavaScript 并没有将所有定义在 prototype 中的属性附加到新对象中,而是用了一种与原型链概念相关的方法。
除了上述两部之外,你也可以用下面的代码得到相同结果
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”]);
然后,构造函数就可以去做它想做的事了,因为构造函数中的 this 就是刚刚创建的对象。
现在,最后的结果是:
Object {name: “George”}
总结
基本上,当你对一个函数使用 new 关键字时,你就会调用这个函数,并且这个函数是作为构造函数被调用的,因此,当你写到:
new FunctionName()
JavaScript 在内部会创建一个对象,一个空哈希,并会赋给这个对象一个构造函数,然后构造函数可以做一切他想做的事情,因为这个构造函数中的 this 就是刚刚创建的那个对象,并且如果你没有在函数中使用返回语句或在函数体的结尾使用了 return defined;,你也可以得到指向那个对象的 this。
因此,当 JavaScript 去查找一个对象的属性时,要做的第一件事就是查找那个对象本身的属性。然后,里面有一个隐秘的属性 [[prototype]],该属性和我们经常使用的 proto 很像,这个属性就是 JavaScript 要查找的下一个地方。JavaScript 会通过 proto 属性来到另一个对象中,然后这个对象又有 proto 属性,JavaScript 就会顺着 proto 属性继续查找下去,知道找到需要的属性或方法,或者说最后一个对象的 proto 属性是 null。在 JavaScript 中,proto 是 null 的对象就是 Object.prototype。
console.log(Object.prototype.proto===null);//true
JavaScript 的内部工作如下所示: