用通俗易懂的语言介绍JavaScript原型

原型(prototype)是每一个JavaScript开发人员必须理解的基本概念,本文的目标是通俗而又详细地解释JavaScript的原型。若是你读完这篇博文之后仍是不理解JavaScript的原型,请将你的问题写在下面的评论里,我本人会回答全部的问题。 javascript

为了理解JavaScript中的原型,你必须理解JavaScript的对象。若是你对对象还不熟悉,你须要阅读个人文章JavaScript Objects in Detail译文:详解JavaScript对象)。并且你要知道属性就是函数中定义的变量。 java

在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。 学习

注意:全部的对象都有特性,就和对象属性有本身的特性同样。对象特性是原型、类和扩展特性。这些是咱们在第二个例子中要讨论的原型特性。 ui

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

//重要提示
//构造函数
//在咱们继续往下阅读以前,让咱们来简单的考查一下构造函数。构造函数是一个用来初始化新对象的函数,而且使用新的关键字来调用构造函数。
//例如:

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()或对象式建立的对象的原型特性 .net

全部用对象式或者构造函数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”}

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

用新关键字以及任何一种非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中实现继承的主要方式以及原型链在这一过程当中所扮演的整合角色。

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。

相关文章
相关标签/搜索