首頁  >  文章  >  web前端  >  深入理解JavaScript原型概念程式碼範例(圖)

深入理解JavaScript原型概念程式碼範例(圖)

黄舟
黄舟原創
2017-03-11 14:44:571572瀏覽

原型是JavaScript中一個比較難理解的概念,原型相關的屬性也比較多,物件有”[[prototype]]”屬性,函數物件有”prototype”屬性,原型物件有”constructor”屬性。

為了弄清楚原型,以及原型相關的這些屬性關係,就有了這篇文章。

相信透過這篇文章一定能夠清楚的體認到原型,現在就開始原型之旅吧。

在認識原型

開始原型的介紹之前,先來認識什麼是原型?

在JavaScript中,原型也是一個對象,透過原型可以實現物件的屬性繼承,JavaScript的物件中都包含了一個」[[Prototype]]」內部屬性,這個屬性所對應的就是該物件的原型。

「[[Prototype]]」作為物件的內部屬性,是不能直接存取的。所以為了方便查看一個物件的原型,Firefox和Chrome中提供了」__proto__」這個非標準(不是所有瀏覽器都支援)的存取器(ECMA引入了標準物件原型存取器」Object. getPrototype(object)”)。

實例分析

下面透過一個例子來看看原型相關概念:

function Person(name, age){
    this.name = name;
    this.age = age;

    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    };
}

var will = new Person("Will", 28);

在上面的程式碼中,透過了Person這個建構子建立了一個will物件。下面就透過will這個物件一步步展開了解原型。

Step 1: 檢視物件will的原型

透過下面程式碼,可以查看物件will的原型:

console.log(will.__proto__);
console.log(will.constructor);

結果分析:

  • 「Person {}」物件就是物件will的原型,透過Chrome展開式可以看到,」Person {}」作為一個原型對象,也有」__proto__」屬性(對應原型的原型)。

  • 在這段程式碼中,還用到了」constructor」屬性。 在JavaScript的原型物件中,也包含一個」constructor」屬性,這個屬性對應到建立所有指向該原型的實例的建構子

    • 透過」constructor」這個屬性,我們可以來判斷一個物件是不是陣列型別

      function isArray(myArray) {
          return myArray.constructor.toString().indexOf("Array") > -1;
      }
    • 在這裡,will物件本身並沒有」constructor」這個屬性,但是透過原型鏈查找,找到了will原型(will.__proto__)的」constructor」屬性,並且得到了Person函數。

Step 2: 檢視物件will的原型(will.__proto__)的原型

既然will的原型”Person {}」也是一個對象,那麼我們就同樣可以來查看」will的原型(will.__proto__)的原型」。

執行下面的程式碼:

console.log(will.__proto__ === Person.prototype);
console.log(Person.prototype.__proto__);
console.log(Person.prototype.constructor);
console.log(Person.prototype.constructor === Person);

結果分析:

  • #首先看“will.__proto__ === Person.prototype”,在JavaScript中,每個函數都有一個prototype屬性,當一個函數被用作建構函數來建立實例時,該函數的prototype屬性值將會被當作原型賦值給所有物件實例(也就是設定實例的__proto__屬性),也就是說,所有實例的原型所引用的是函數的prototype屬性。了解了構造函數的prototype屬性之後,一定就明白為什麼第一句結果為true了。

    • prototype屬性是函數物件特有的,如果不是函數對象,將不會有這樣一個屬性。

  • 當透過”Person.prototype.__proto__」語句取得will對象原型的原型時候,將得到”Object {}」對象,後面將會看到所有對象的原型都會追溯到”Object {}”物件。

  • 對於原型物件”Person.prototype”的“constructor”,根據前面的介紹,將對應Person函數本身。

透過上面可以看到,「Person.prototype」物件和Person函數物件透過」constructor」和」prototype」屬性實作了相互引用(後面會有圖展示這個相互引用的關係)

Step 3: 檢視物件Object的原型

透過前一部分可以看到,will的原型的原型是」Object {}”對象。實際上在JavaScript中,所有物件的原型都會追溯到”Object {}”物件。

下面透過一段程式碼看看”Object {}”物件:

console.log(Person.prototype.__proto__ === Object.prototype);
console.log(typeof Object);
console.log(Object);
console.log(Object.prototype);
console.log(Object.prototype.__proto__);
console.log(Object.prototype.constructor);

透過下面的程式碼可以看到:

  • Object物件本身是一個函數物件。

  • 既然是Object函數,一定會有prototype屬性,所以可以看到」Object.prototype」的值就是”Object {}」這個原型物件。

  • 反過來,當訪問”Object.prototype”物件的”constructor”這個屬性的時候,就得到了Obejct函數。

  • 另外,当通过”Object.prototype.__proto__”获取Object原型的原型的时候,将会得到”null”,也就是说”Object {}”原型对象就是原型链的终点了。

Step 4: 查看对象Function的原型

在上面的例子中,Person是一个构造函数,在JavaScript中函数也是对象,所以,我们也可以通过”__proto__”属性来查找Person函数对象的原型。

console.log(Person.__proto__ === Function.prototype);
console.log(Person.constructor === Function)
console.log(typeof Function);
console.log(Function);
console.log(Function.prototype);
console.log(Function.prototype.__proto__);
console.log(Function.prototype.constructor);

结果分析 :

  • 在JavaScript中有个Function对象(类似Object),这个对象本身是个函数;所有的函数(包括Function,Object)的原型(__proto__)都是”Function.prototype”。

  • Function对象作为一个函数,就会有prototype属性,该属性将对应”function () {}”对象。

  • Function对象作为一个对象,就有”__proto__”属性,该属性对应”Function.prototype”,也就是说,”Function.__proto__ === Function.prototype”

  • 对于Function的原型对象”Function.prototype”,该原型对象的”__proto__”属性将对应”Object {}”

对比prototype和__proto__

对于”prototype”和”__proto__”这两个属性有的时候可能会弄混,”Person.prototype”和”Person.__proto__”是完全不同的。

在这里对”prototype”和”__proto__”进行简单的介绍:

  • 对于所有的对象,都有__proto__属性,这个属性对应该对象的原型

  • 对于函数对象,除了__proto__属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性)

图解实例

通过上面结合实例的分析,相信你一定了解了原型中的很多内容。

但是现在肯定对上面例子中的关系感觉很凌乱,一会儿原型,一会儿原型的原型,还有Function,Object,constructor,prototype等等关系。

现在就对上面的例子中分析得到的结果/关系进行图解,相信这张图可以让你豁然开朗。

对于上图的总结如下:

  • 所有的对象都有”__proto__”属性,该属性对应该对象的原型

  • 所有的函数对象都有”prototype”属性,该属性的值会被赋值给该函数创建的对象的”__proto__”属性

  • 所有的原型对象都有”constructor”属性,该属性对应创建所有指向该原型的实例的构造函数

  • 函数对象和原型对象通过”prototype”和”constructor”属性进行相互关联

通过原型改进例子

在上面例子中,”getInfo”方法是构造函数Person的一个成员,当通过Person构造两个实例的时候,每个实例都会包含一个”getInfo”方法。

var will = new Person("Will", 28);
var wilber = new Person("Wilber", 27);

前面了解到,原型就是为了方便实现属性的继承,所以可以将”getInfo”方法当作Person原型(Person.__proto__)的一个属性,这样所有的实例都可以通过原型继承的方式来使用”getInfo”这个方法了。

所以对例子进行如下修改:

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
};

修改后的结果为:

原型链

因为每个对象和原型都有原型,对象的原型指向对象的父,而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。

在”理解JavaScript的作用域链“一文中,已经介绍了标识符和属性通过作用域链和原型链的查找。

这里就继续看一下基于原型链的属性查找。

属性查找

当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部(也就是 “Object.prototype”), 如果仍然没有找到指定的属性,就会返回 undefined。

看一个例子:

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.MaxNumber = 9999;
Person.__proto__.MinNumber = -9999;

var will = new Person("Will", 28);

console.log(will.MaxNumber);
// 9999
console.log(will.MinNumber);
// undefined

在这个例子中分别给”Person.prototype “和” Person.__proto__”这两个原型对象添加了”MaxNumber “和”MinNumber”属性,这里就需要弄清”prototype”和”__proto__”的区别了。

“Person.prototype “对应的就是Person构造出来所有实例的原型,也就是说”Person.prototype “属于这些实例原型链的一部分,所以当这些实例进行属性查找时候,就会引用到”Person.prototype “中的属性。

属性隐藏

当通过原型链查找一个属性的时候,首先查找的是对象本身的属性,如果找不到才会继续按照原型链进行查找。

这样一来,如果想要覆盖原型链上的一些属性,我们就可以直接在对象中引入这些属性,达到属性隐藏的效果。

看一个简单的例子:

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
};

var will = new Person("Will", 28);
will.getInfo = function(){
    console.log("getInfo method from will instead of prototype");
};

will.getInfo();
// getInfo method from will instead of prototype

对象创建方式影响原型链

会到本文开始的例子,will对象通过Person构造函数创建,所以will的原型(will.__proto__)就是”Person.prototype”。

同样,我们可以通过下面的方式创建一个对象:

var July = {
    name: "July",
    age: 28,
    getInfo: function(){
        console.log(this.name + " is " + this.age + " years old");
    },
}

console.log(July.getInfo());

当使用这种方式创建一个对象的时候,原型链就变成下图了,July对象的原型是”Object.prototype”也就是说对象的构建方式会影响原型链的形式。

hasOwnProperty

“hasOwnProperty”是”Object.prototype”的一个方法,该方法能判断一个对象是否包含自定义属性而不是原型链上的属性,因为”hasOwnProperty” 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

相信你还记得文章最开始的例子中,通过will我们可以访问”constructor”这个属性,并得到will的构造函数Person。这里结合”hasOwnProperty”这个函数就可以看到,will对象并没有”constructor”这个属性。

从下面的输出可以看到,”constructor”是will的原型(will.__proto__)的属性,但是通过原型链的查找,will对象可以发现并使用”constructor”属性。

“hasOwnProperty”还有一个重要的使用场景,就是用来遍历对象的属性。

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
};

var will = new Person("Will", 28);

for(var attr in will){
    console.log(attr);
}
// name
// age
// getInfo

for(var attr in will){
    if(will.hasOwnProperty(attr)){
        console.log(attr);
    }
}
// name
// age

总结

本文介绍了JavaScript中原型相关的概念,对于原型可以归纳出下面一些点:

  • 所有的对象都有”[[prototype]]”属性(通过__proto__访问),该属性对应对象的原型

  • 所有的函数对象都有”prototype”属性,该属性的值会被赋值给该函数创建的对象的”__proto__”属性

  • 所有的原型对象都有”constructor”属性,该属性对应创建所有指向该原型的实例的构造函数

  • 函数对象和原型对象通过”prototype”和”constructor”属性进行相互关联

还有要强调的是文章开始的例子,以及通过例子得到的一张”普通对象”,”函数对象”和”原型对象”之间的关系图,当你对原型的关系迷惑的时候,就想想这张图(或者重画一张当前对象的关系图),就可以理清这里面的复杂关系了。

通过这些介绍,相信一定可以对原型有个清晰的认识。

以上是深入理解JavaScript原型概念程式碼範例(圖)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn