談談對Javascript構造函數和原型對象的理解

對象,是javascript中很是重要的一個梗,是否能透徹的理解它直接關係到你對整個javascript體系的基礎理解,說白了,javascript就是一羣對象在攪。。(嗶!)。
 

經常使用的幾種對象建立模式javascript

使用new關鍵字建立
最基礎的對象建立方式,無非就是和其餘多數語言同樣說的同樣:沒對象,你new一個呀!java

var gf = new Object();
gf.name = "tangwei";
gf.bar = "c++";
gf.sayWhat = function() {
  console.log(this.name + "said:love you forever");
}

使用字面量建立
c++

這樣彷佛妥妥的了,可是宅寂的geek們豈能喜歡如此複雜和low土的定義變量的方式,做爲一門腳本語言那應該有和其餘兄弟們同樣的範兒,因而出現了對象字面量的定義方式:bootstrap

var gf = {
  name : "tangwei",
  bar : "c++",
  sayWhat : function() {
    console.log(this.name + "said:love you forever");
  }
}

 

工廠模式
瀏覽器

實際上這是咱們在實際中最經常使用的對象定義方式,可是我要有好多擁有類似屬性的對象(想一想都讓人激動。。。)怎麼辦呢?那要是一個個的定義,就會產生大量的代碼,何不建個工廠,批量的生產出咱們的對象呢,因而,javascript世界中第一個充氣娃。。。不,「工廠模式」誕生了!函數

function createGf(name, bar) {
  var o = new Object();
  o.name = name;
  o.bar = bar;
  o.sayWhat = function() {
    alert(this.name + "said:love you forever");
  }
  return o;
}
var gf1 = createGf("bingbing","d");
var gf2 = createGf("mimi","a");

構造函數
優化

工廠模式解決了多個類似對象的建立問題,可是問題又來了,這些對象都是Object整出來的,怎麼區分它們的對象具體類型呢?這時候咱們就須要切換到另外一種模式了,構造函數模式:this

function Gf(name,bar){
  this.name = name;
  this.bar = bar;
  this.sayWhat = function(){
    alert(this.name + "said:love you forever");
  }
}
var gf1 = new Gf("vivian","f");
var gf2 = new Gf("vivian2","f");

 

這裏咱們使用一個大寫字母開頭的構造函數替代了上例中的createGf,注意按照約定構造函數的首字母要大寫。在這裏咱們建立一個新對象,而後將構造函數的做用域賦給新對象,調用構造函數中的方法。
上面的方式彷佛沒什麼不妥,可是咱們能夠發現,兩個實例中調用的構造函數中的sayWhat方法不是同一個Function實例:spa

console.log(gf1.sayWhat == gf2.sayWhat); //false

調用同一個方法,卻聲明瞭不一樣的實例,實在浪費資源。咱們能夠優化一下將sayWhat函數放到構造函數外面聲明:firefox

function Gf(name,bar){
  this.name = name;
  this.bar = bar;
  this.sayWhat = sayWhat
}
function sayWhat(){
  alert(this.name + "said:love you forever");
}

這樣解決了,多個實例屢次定義同一個方法實例的問題,可是新問題又來了,咱們定義的sayWhat是一個全局做用域的方法,但這個方法實際上是無法直接調用的,這就有點矛盾了。如何更優雅的定義一個具有必定封裝性的對象呢?咱們來看一下javascript原型對象模式。

原型對象模式

理解原型對象
當咱們建立一個函數時,該函數就會具有一個prototype屬性,這個屬性指向經過構造函數建立的那個函數的原型對象。通俗點講原型對象就是內存中爲其餘對象提供共享屬性和方法的對象。

在原型模式中,沒必要再構造函數中定義實例屬性,能夠將屬性信息直接賦予原型對象:

function Gf(){
  Gf.prototype.name = "vivian";
  Gf.prototype.bar = "c++";
  Gf.prototype.sayWhat = function(){
    alert(this.name + "said:love you forever");
  }
}
var gf1 = new Gf();
gf1.sayWhat();
var gf2 = new Gf();

 


和構造函數不一樣的是這裏新對象的屬性和方法是全部實例均可以共享的,換句話說gf1和gf2訪問的是同一份屬性和方法。原型對象中除了咱們賦予的屬性外,還有一些內置的屬性,全部原型對象都具有一個constructor屬性,這個屬性是一個指向包含prototype屬性函數的一個指針(敢不敢再繞點!)。經過一幅圖咱們來清楚的理一下這個繞口的流程:

全部的對象都有一個原型對象(prototype),原型對象中有一個constructor屬性指向包含prototype屬性的函數,Gf的實例gf1和gf2都包含一個內部屬性指向原型對象(在firefox瀏覽器中表現爲私有屬性proto),當咱們訪問一個對象中的屬性時,首先會詢問實例對象中有沒有該屬性,若是沒有則繼續查找原型對象。

使用原型對象
在前面的示例中,咱們注意到在爲原型對象添加屬性時,須要每一個都增長Gf.prototype,這個工做很重複,在上面對象的建立模式中,咱們知道能夠經過字面量的形式建立一個對象,這裏咱們也能夠改進一下:

function Gf(){}
Gf.prototype = {
  name : "vivian",
  bar : "c++",
  sayWhat : function(){
    alert(this.name + "said:love you forever");
  }
}

這裏有一個地方須要特別注意下,constructor屬性再也不指向對象Gf,由於每定義一個函數,就會同時爲其建立一個prototype對象,這個對象也會自動獲取一個新的constructor屬性,這個地方咱們使用Gf.prototype本質上覆寫了原有的prototype對象,所以constructor也變成了新對象的constructor屬性,再也不指向Gf,而是Object:

var gf1 = new Gf();
console.log(gf1.constructor == Gf);//false
console.log(gf1.constructor == Object)//true

通常狀況下,這個微妙的改變是不會對咱們形成影響的,但若是你對constructor有特殊的需求,咱們也能夠顯式的指定下Gf.prototype的constructor屬性:

Gf.prototype = {
  constructor : Gf,
  name : "vivian",
  bar : "c++",
  sayWhat : function() {
    alert(this.name + "said:love you forever");
  }
}
var gf1 = new Gf();
console.log(gf1.constructor == Gf);//true

 

經過對原型對象模式的初步瞭解,咱們發現全部的實例對象都共享相同的屬性,這是原型模式的基本特色,但每每對於開發者來講這是把「雙刃劍」,在實際開發中,咱們但願的實例應該是具有本身的屬性,這也是在實際開發中不多有人單獨使用原型模式的主要緣由。

構造函數和原型組合模式

在實際開發中,咱們可使用構造函數來定義對象的屬性,使用原型來定義共享的屬性和方法,這樣咱們就能夠傳遞不一樣的參數來建立出不一樣的對象,同時又擁有了共享的方法和屬性。

function Gf(name,bar){
  this.name = name;
  this.bar = bar;
}
Gf.prototype = {
  constructor : Gf,
  sayWhat : function() {
    alert(this.name + "said:love you forever");
  }
}
var gf1 = new Gf("vivian", "f");
var gf2 = new Gf("vivian1", "c");

 

在這個例子中,咱們再構造函數中定義了對象各自的屬性值,在原型對象中定義了constructor屬性和sayWhat函數,這樣gf1和gf2屬性之間就不會產生影響了。這種模式也是實際開發中最經常使用的對象定義方式,包括不少js庫(bootstrap等)默認的採用的模式。

相關文章
相關標籤/搜索