首頁 > web前端 > js教程 > 淺析在javascript中創建物件的各種模式_javascript技巧

淺析在javascript中創建物件的各種模式_javascript技巧

WBOY
發布: 2016-05-16 15:01:50
原創
1392 人瀏覽過

最近在看《javascript高級程式設計》(第二版)

javascript中物件的建立

•工廠模式

•建構子模式

•原型模式

•結合建構子與原型模式

•原型動態模式

物件導向的語言大都有一個類別的概念,透過類別可以建立多個具有相同方法和屬性的物件。雖然從技術上講,javascript是一門物件導向的語言,但是javascript沒有類別的概念,一切都是物件。任意一個物件都是某種引用類型的實例,都是透過現有的參考類型建立;引用類型可以是原生的,也可以是自訂的。原生的參考類型有:Object、Array、Data、RegExp、Function。 !引用類型就是一種資料結構,將資料和功能組織在一起,通常被稱為類別。 在缺乏類別概念的javascript中,需要解決的問題就是如何有效率的創建物件。

1.1.0.建立物件的一般方法

var person = {}; //对象字面量表示,等同于var person = new Objcect();

person.name = 'evansdiy';
person.age = '22';
person.friends = ['ajiao','tiantian','pangzi'];
person.logName = function() {
  console.log(this.name);
}
登入後複製

基於Object引用類型,建立了一個對象,該對象包含四個屬性,其中一個為方法。如果需要很多類似person的實例,那就會有許多重複的程式碼。

1.1.1.工廠模式 [top]

透過一個可以包含了物件細節的函數來建立對象,然後傳回這個對象。

function person(name,age,friends) {

  var o = {
    name: name,
    age: age,
    friends: friends,
    logName: function() {
      console.log(this.name);
    }
  };

  return o;

}

var person1 = person('Evansdiy','22',['ajiao','tiantian','pangzi']);
登入後複製

每次調用person函數,都會透過該函數內部的對象o創建新的對象,然後返回,除此之外,這個為了創建新對象而存在的內部對象o沒有其他的用途。另外,無法判斷工廠模式所建立的物件的類型。

1.1.2.建構子模式 [top]

function Person(name,age,job) {

  this.name = name;
  this.age = age;
  this.job = job;
  this.logName = function() {
    console.log(this.name);
  }

}

//通过new操作符创建Person的实例
var person1 = new Person('boy-a','22','worker');

var person2 = new Person('girl-b','23','teacher');

person1.logName(); //boy-a

person2.logName(); //girl-a
登入後複製

對比工廠模式,可以發現,這裡並不需要創建中間對象,沒有return。另外,可以將建構函式的實例標識為一種特定的類型,這就解決了物件辨識的問題(透過檢查實例的constructor屬性,或利用instanceof操作符來檢查該實例是否透過某個建構函式建立)。

console.log(person1.constructor == Person);//constructor位於建構函數原型中,並指向建構函數,結果為true

console.log(person1 instanceof Person);//透過instanceof操作符,判斷person1是否為建構子Person的實例但建構子模式也有自己的問題,實際上,logName方法在每個實例上都會被重新創建一次,需要注意的是,透過實例化創建的方法且並不相等,以下程式碼將會得到false:

console.log(person1.logName == person2.logName);//false我們可以將方法移到建構器外部(變成全域函數)來解決這個問題:

function logName() {
  console.log(this.name);
}

function logAge() {
  console.log(this.age);
}
登入後複製

但是,在全域下創建的全域函數實際上只能被經由Person創建的實例調用,這就有點名不副實了;如果方法很多,還需要逐一定義,缺少封裝性。

1.1.3.原型模式 [top]

javascript中的每一個函數都包含一個指向prototype屬性的指針(大部分瀏覽器可以透過內部屬性__proto__存取),prototype屬性是一個對象,其中包含了由某種引用類型創建的所有實例共享的屬性和方法。 

function Person() {}

Person.name = 'evansdiy';

Person.prototype.friends = ['ajiao','jianjian','pangzi'];

Person.prototype.logName = function() {
  console.log(this.name);
}

var person1 = new Person();

person1.logName();//'evansdiy'
登入後複製

以上程式碼做了這幾件事:

1.定義了一個建構函式Person,Person函式自動取得一個prototype屬性,該屬性預設只包含一個指向Person的constructor屬性;

2.透過Person.prototype加入三個屬性,其中一個作為方法;

3.建立一個Person的實例,隨後在實例上呼叫了logName()方法。

這裡要注意的是logName()方法的呼叫過程:

1.在person1實例尋找logName()方法,發現沒有這個方法,於是追溯到person1的原型

2.在person1的原型上尋找logame()方法,有這個方法,於是呼叫該方法基於這樣一個查找過程,我們可以透過在實例上定義原型中的同名屬性,來阻止該實例存取原型上的同名屬性,要注意的是,這樣做並不會刪除原型上的同名屬性,只是阻止實例存取。

var person2 = new Person();

person2.name = 'laocai';如果我們不再需要實例上的屬性時,可以透過delete操作符刪除。

delete person2.name;利用for-in迴圈枚舉出實例可以存取到的所有屬性(無論該屬性存在於實例或是原型中):

for(i in person1) {
  console.log(i);
}
登入後複製

同时,也可以利用hasOwnProperty()方法判断某个属性到底存在于实例上,还是存在于原型中,只有当属性存在于实例中,才会返回true:

console.log(person1.hasOwnProperty('name'));//true!hasOwnProperty来自Object的原型,是javascript中唯一一个在处理属性时不查找原型链的方法。[via javascript秘密花园] 另外,也可以通过同时使用in操作符和hasOwnProperty()方法来判断某个属性存在于实例中还是存在于原型中:

console.log(('friends' in person1) && !person1.hasOwnProperty('friends'));先判断person1是否可以访问到friends属性,如果可以,再判断这个属性是否存在于实例当中(注意前面的!),如果不存在于实例中,就说明这个属性存在于原型中。 前面提到,原型也是对象,所以我们可以用对象字面量表示法书写原型,之前为原型添加代码的写法可以修改为:

Person.prototype = {

  name: 'evansdiy',
  friends: ['ajiao','jianjian','pangzi'],
  logName: function() {
    console.log(this.name);
  }

}
登入後複製

由于对象字面量语法重写了整个prototype原型,原先创建构造函数时默认取得的constructor属性会指向Object构造函数:

//对象字面量重写原型之后

console.log(person1.constructor);//Object不过,instanceof操作符仍会返回希望的结果:

//对象字面量重写原型之后

console.log(person1 instanceof Person);//true当然,可以在原型中手动设置constructor的值来解决这个问题。

Person.prototype = {

  constructor: Person,
  ......

}
登入後複製

如果在创建对象实例之后修改原型对象,那么对原型的修改会立即在所有对象实例中反映出来:

function Person() {};

var person1 = new Person();

Person.prototype.name = 'evansdiy';

console.log(person1.name);//'evansdiy'
登入後複製

实例和原型之间的连接仅仅是一个指针,而不是一个原型的拷贝,在原型实际上是一次搜索过程,对原型对象的所做的任何修改都会在所有对象实例中反映出来,就算在创建实例之后修改原型,也是如此。 如果在创建对象实例之后重写原型对象,情况又会如何?

function Person() {};

var person1 = new Person1();//创建的实例引用的是最初的原型

//重写了原型
Person.prototype = {
  friends: ['ajiao','jianjian','pangzi']
}

var person2 = new Person();//这个实例引用新的原型

console.log(person2.friends);

console.log(person1.friends);
登入後複製

以上代码在执行到最后一行时会出现未定义错误,如果我们用for-in循环枚举person1中的可访问属性时,会发现,里头空无一物,但是person2却可以访问到原型上的friends属性。 !重写原型切断了现有原型与之前创建的所有对象实例的联系,之前创建的对象实例的原型还在,只不过是旧的。

//创建person1时,原型对象还未被重写,因此,原型对象中的constructor还是默认的Person()

console.log(person1.constructor);//Person()

//但是person2的constructor指向Object()

console.log(person2.constructor);//Object()需要注意的是,原型模式忽略了为构造函数传递参数的过程,所有的实例都取得相同的属性值。同时,原型模式还存在着一个很大的问题,就是原型对象中的引用类型值会被所有实例共享,对引用类型值的修改,也会反映到所有对象实例当中。

function Person() {};

Person.prototype = {
  friends: ['ajiao','tiantian','pangzi']
}

var person1 = new Person();

var person2 = new Person();

person1.friends.push('laocai');

console.log(person2.friends);//['ajiao','tiantian','pangzi','laocai']
登入後複製

修改person1的引用类型值friends,意味着person2中的friends也会发生变化,实际上,原型中保存的friends实际上只是一个指向堆中friends值的指针(这个指针的长度是固定的,保存在栈中),实例通过原型访问引用类型值时,也是按指针访问,而不是访问各自实例上的副本(这样的副本并不存在)。

1.1.4.结合构造函数和原型模式创建对象 [top]

结合构造函数和原型模式的优点,弥补各自的不足,利用构造函数传递初始化参数,在其中定义实例属性,利用原型定义公用方法和公共属性,该模式应用最为广泛。

function Person(name,age) {

  this.name = name;
  this.age = age;
  this.friends = ['ajiao','jianjian','pangzi'];

}

Person.prototype = {

  constructor: Person,
  logName: function() {
    console.log(this.name);
  }

}

var person1 = new Person('evansdiy','22');

var person2 = new Person('amy','21');

person1.logName();//'evansdiy'

person1.friends.push('haixao');

console.log(person2.friends.length);//3
登入後複製

1.1.5.原型动态模式 [top]

原型动态模式将需要的所有信息都封装到构造函数中,通过if语句判断原型中的某个属性是否存在,若不存在(在第一次调用这个构造函数的时候),执行if语句内部的原型初始化代码。

function Person(name,age) {

  this.name = name;
  this.age = age;

  if(typeof this.logName != 'function') {
    Person.prototype.logName = function() {
      console.log(this.name);
    };
    Person.prototype.logAge = function() {
      console.log(this.age);
    };
  };

}

var person1 = new Person('evansdiy','22');//初次调用构造函数,此时修改了原型

var person2 = new Person('amy','21');//此时logName()方法已经存在,不会再修改原型
登入後複製

需要注意的是,该模式不能使用对象字面量语法书写原型对象(这样会重写原型对象)。若重写原型,那么通过构造函数创建的第一实例可以访问的原型对象不会包含if语句中的原型对象属性。

function Person(name,age) {

  this.name = name;
  this.age = age;

  if(typeof this.logName != 'function') {
    Person.prototype = {
      logName: function() {
        console.log(this.name);
      },
      logAge: function() {
        console.log(this.Age);
      }
    }
  };

}

var person1 = new Person('evansdiy','22');

var person2 = new Person('amy','21');

person2.logName();//'amy'

person1.logName();//logName()方法不存在
登入後複製

需要说明的是,各模式都有自己的应用场景,无所谓优劣。

以上这篇浅析在javascript中创建对象的各种模式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板