通俗易懂的语言描述JavaScript原型

时间:2023-03-08 17:25:12

这是一个翻译。原文地址http://javascriptissexy.com/javascript-prototype-in-plain-detailed-language/#

原型(prototype)是每一个JavaScript开发者必须理解的基本概念,本文的目标是通俗而又具体地解释JavaScript的原型。假设你读完这篇博文以后还是不理解JavaScript的原型,请将你的问题写在以下的评论里,我本人会回答全部的问题。

为了理解JavaScript中的原型,你必须理解JavaScript的对象。假设你对对象还不熟悉,你须要阅读我的文章JavaScript Objects in Detail译文:具体解释JavaScript对象)。

并且你要知道属性就是函数中定义的变量。

在JavaScript中有两个互相之间有关联的原型的概念:

1.首先。每个JavaScript函数有一个原型属性,当你须要实现继承的时候你就给这个原型属性附加属性和方法。注意这个原型属性是不能够枚举的:它在for/in循环中是不可获取的。可是FireFox和大多数版本号的Safari和Chrome浏览器有一个__proto__“伪”属性(一种选择方式)同意你訪问对象原型的属性。你可能从来没实用过这个_proto__伪属性,但你得知道它的存在而且它是在某些浏览器中訪问对象的原型属性的一种简单的方法。

对象原型主要用于继承:你为对象的原型属性添加方法和属性,使这些原型和属性存在于该函数的实例。

下面是一个简单的使用原型属性继承的样例(后面还有很多其它关于继承的内容):

function PrintStuff (myDocuments) {
this.documents = myDocuments;
} //我们为PrintStuff的原型属性添加方法print (),这种话其它实例(对象)能够继承这种方法
PrintStuff.prototype.print = function () {
console.log(this.documents);
} //用构造函数PrintStuff ()创建一个新的对象,由此让这个新对象继承PrintStuff 的属性和方法。
var newObj = new PrintStuff ("I am a new Object and I can print."); // newObj继承了函数PrintStuff全部的属性和方法,包含方法print。如今newObj能够直接调用print,即使我们从来没有为它定义过方法print()。 newObj.print (); //I am a new Object and I can print.

2. 第二个关于JavaScript原型的概念是原型特性。把原型特性想成该对象的一个性质;这个性质表明了该对象的“父母”。简而言之:对象的原型特性能够看成是对象的“父母”--该对象获得它属性的地方。对象特性经常被称为原型对象,而且它是在你创建对象时就自己主动建立的。对此的解释是:每个对象从某个其它的对象那里继承属性,而这里的“某个其它的对象”就是该对象的原型特性或者“父母”。(你能够把原型特性想象成血缘关系或者父母)。

在上面样例的代码中,newObj的原型是PrintStuff.prototype。

注意:全部的对象都有特性,就和对象属性有自己的特性一样。对象特性是原型、类和扩展特性。这些是我们在第二个样例中要讨论的原型特性。

另一点须要注意, “伪”属性__proto__包括一个对象的原型对象(被该对象继承方法和属性的父对象)。

重要提示
构造函数
在我们继续往下阅读之前,让我们来简单的考查一下构造函数。构造函数是一个用来初始化新对象的函数。并且使用新的keyword来调用构造函数。 比如: function Account () {
}
//这是利用构造函数Account来创建对象userAccount
var userAccount = new Account (); 并且。全部继承自还有一个对象的对象。也继承了那个对象的构造函数属性。 而这个构造函数属性就是一个保存或者指向该对象的构造函数的属性(和不论什么变量一样)。 //本例的构造函数是Object ()
var myObj = new Object ();
//而假设你之后想要知道myObj的构造函数:
console.log(myObj.constructor); // Object() // 还有一个样例: Account ()是构造函数
var userAccount = new Account ();
//查看对象userAccount的构造函数
console.log(userAccount.constructor); // Account()

用new Object()或对象式创建的对象的原型特性

全部用对象式或者构造函数Object创建的对象都继承自Object.prototype。因此Object.prototype是全部用Object()或者{}所创建的对象的原型特性(或原型对象)。Object.prototype本身没有从其它不论什么对象那里继承不论什么的方法或者属性。

// 对象userAccount 继承自Object 而且因此它的原型特性就是Object.prototype.
var userAccount = new Object (); // 这个声明用了对象式来创造对象userAccount;该对象userAccount继承自Object;因此,就和上面的对象userAccount一样。它的原型特性是Object.prototype。
var userAccount = {name: “Mike”}

用构造函数所创建的对象的原型特性

用新keyword以及不论什么一种非Object()的构造函数所创建的对象,从该构造函数中获得它们的构造函数。

比如:

function Account () {

}
var userAccount = new Account () // 用构造函数Account ()初始化userAccount而且因此它的原型特性(原型对象)就是 Account.prototype。 相似的,不论什么数组,比方var myArray = new Array (),从Array.prototype获得原型而且继承Array.prototype的属性。

所以,当对象被创建时有两种通用的方式来建立对象的原型特性:

1.假设对象是使用对象式(var newObj = {})创建的。那么它从Object.prototype继承属性,而且我们说它的原型对象(或者原型特性)是Object.prototype。

2.假设对象是使用构造函数,比方 new Object ()或者new Fruit ()或者new Array ()或者 new Anything ()创建的,那么它继承自构造函数 (Object (), Fruit (), Array (), or Anything ())。比如,用一个函数,比方Fruit ()。每次我们创建一个新的水果的实例(var aFruit = new Fruit ()),那么该新实例的原型就来自于构造函数Fruit,也就是
Fruit.prototype。

不论什么用new Array ()所创建的对象都会将Array.prototype作为它的原型。

不论什么用构造函数Object(Obj (), 比方 var anObj = new Object() )创建的对象继承自Object.prototype。

另一点你须要知道的。在ECMAScript 5中,你能够用一个同意你指定新对象的原型的方法Object.create()来创建对象。我们会在兴许的文章中学习ECMAScript 5。

原型为什么重要以及何时使用原型?

在JavaScript中原型有两种重要的用途,就像前文中提到的那样:

1.原型属性:基于原型的继承

在JavaScript中原型之所以重要是由于JavaScript没有(大多数面向对象的语言全部的)经典的基于类的继承。因此JavaScript全部的继承是通过原型属性来实现的。JavaScript有一套基于原型继承的机制。继承是一种能让对象(或者是其他语言中的类)继承其他对象(或类)的属性和方法的编程规范。在JavaScript中,通过原型来实现继承。比如,你能够创建一个Fruit函数(也就是对象。由于全部JavaScript中的函数都是对象)而且给这个Fruit的原型属性加入属性和方法,那么全部Fruit函数的实例会继承Fruit全部的属性和方法。

JavaScript中的继承演示样例:

function Plant () {
this.country = "Mexico";
this.isOrganic = true;
} //把方法showNameAndColor加入到Plant原型属性
Plant.prototype.showNameAndColor = function () {
console.log("I am a " + this.name + " and my color is " + this.color);
} // 把方法amIOrganic加入到Plant原型属性
Plant.prototype.amIOrganic = function () {
if (this.isOrganic)
console.log("I am organic, Baby!");
} function Fruit (fruitName, fruitColor) {
this.name = fruitName;
this.color = fruitColor;
} //将Fruit的原型设为Plant的构造函数,因此继承了Plant.prototype所有的方法和属性
Fruit.prototype = new Plant (); // 用构造函数Fruit创建一个新的aBanana
var aBanana = new Fruit ("Banana", "Yellow"); // 这里aBanana用了来自aBanana对象原型Fruit.prototype的name属性:
console.log(aBanana.name); // Banana //用来自Fruit对象原型Plant.prototype的方法showNameAndColor。 该aBanana对象继承了来自函数Plant和Fruit的所有属性和方法
console.log(aBanana.showNameAndColor()); // I am a Banana and my color is yellow.

注意到,虽然方法showNameAndColor是在对象Plant.prototype的原型链上定义的,可是此方法还是被对象aBanana所继承。

实际上,不论什么使用构造函数Fruit ()的对象。都将继承Fruit.prototype所有的属性和方法以及来自Fruit的原型Plant.prototype的所有的属性和方法。

这就是JavaScript中实现继承的主要方式以及原型链在这一过程中所扮演的整合角色。

很多其它深入的关于JavaScript中的面向对象编程的内容。请阅读Nicholas Zakas的 Principles of Object-Oriented Programming in JavaScript(这本书仅仅要14.99美元)

2.原型特性:获取对象的属性

原型对于获取对象的方法和属性也是非常重要的。原型特性(或原型对象)是那些可继承的属性的“父母”对象,这些可继承的属性原本就是为这些“父母”对象定义的。这就有点类似于你能够从你的父亲--他是你的“原型父母”。那里继承姓。假设我们想知道你的姓是从哪里来的,我们会先看看是否是你自己给自己取了这个姓;假设不是。我们会继续查看是否你是从你的父亲那里继承了这个姓。

假设这个姓不是你父亲的。那么我们会继续查看你父亲的父亲的姓(你父亲的原型父亲)。

与之类似的,假设你想要获取一个对象的原型。你将直接从该对象的属性開始寻找。

假设JS执行时不能再那里找到该属性。那么它会去该对象的原型--该对象得到属性的地方,去查看这个属性。

假设在对象的原型中没有发现该属性。那么对于该属性的搜寻会转移到对象的原型的原型(对象的父亲的父亲--爷爷)那里去。就这样一直持续到没有原型为止(没有很多其它的曾祖父;没有很多其它有遗传来的血缘关系)。

这事实上就是原型链:从对象的原型到对象原型的原型不断向上的一条链。而且JavaScript就用这条原型链来搜寻对象的属性和方法。

假设某个属性在它的整条原型链上的不论什么一个对象的原型中均不存在,那么这个属性就是不存在而且会返回undefined。

这样的原型链机制本质上和我们上面讨论的基于原型的继承是一样的概念,仅仅是在这里我们更注重于JavaScript怎样通过对象原型获取对象的属性和方法。

这个样例演示了对象的原型对象的原型链:

var myFriends = {name: "Pete"};

//为了找到以下的属性name。搜寻会直接从对象myFriends開始。而且会立马找到属性name。由于我们为对象myFriends定义了属性name。

这个能够被想像成有一条链接的原型链。
console.log(myFriends.name); //在这个样例中。将会从对象myFriends開始搜寻方法toString (),可是由于我们从来没有为对象myFriends创建过方法toString,编译器会接着去myFriends的原型(被myFriends继承属性的那个对象)搜寻。 //而且由于全部用对象式创建的对象都继承自Object.prototype,方法toString将在 Object.prototype中被发现--关于全部继承自Object.prototype的属性。请看以下的重要提示
myFriends.toString ();

重要提示
全部对象都会继承的Object.prototype的属性
在JavaScript中全部对象的属性和方法继承自Object.prototype。这些继承来的属性和方法有构造函数,hasOwnProperty (), isPrototypeOf (), propertyIsEnumerable (), toLocaleString (), toString (), and valueOf ()。ECMAScript 5中还新增了四种訪问Object.prototype的方法。 以下是还有一个原型链的样例:
function People () {
this.superstar = "Michael Jackson";
}
// 为People原型定义属性"athlete"以便"athlete" 能够被全部使用构造函数People () 的对象所訪问。
People.prototype.athlete = "Tiger Woods"; var famousPerson = new People ();
famousPerson.superstar = "Steve Jobs"; //对于superstar的搜寻将首先查看对象famousPerson是否有属性superstar。而由于就是在那里定义的这个属性,这就是须要用到的属性。由于我们已经为对象famousPerson反复定义链famousPerson的属性superstar。所以对于superstar的搜寻就不会在原型链上继续上升。 console.log (famousPerson.superstar); // Steve Jobs // 注意在ECMAScript 5中你能够将属性设置为仅仅读。这种话你就不能像我们刚才那样反复定义该属性。 //这里展示了来自famousPerson原型(People.prototype)的属性。由于属性athlete没有为对象famousPerson本身所定义。
console.log (famousPerson.athlete); // Tiger Woods //在这个样例中,在原型链上向上搜寻而且在Object.prototype中找到了方法toString,这种方法来自对象Fruit的继承--像我们前面提到的那样。全部的对象终于继承自Object.prototype
console.log (famousPerson.toString()); // [object Object]

全部已经建立的构造函数 (Array (), Number (), String (), etc.)都是由构造函数Object所创建的。因此它们的原型是Object.prototype。

能够回过头再做做2月7号的那篇关于JavaScript原型的測试。

附加信息

许多其他有关JavaScript内容对象。阅读David Flanagan书面 JavaScript: The Definitive Guide (第六版。2011五月) 本书的第六章。