JS面向對象編程之封裝

咱們所熟知的面嚮對象語言如 C++、Java 都有類的的概念,類是實例的類型模板,好比Student表示學生這種類型,而不表示任何具體的某個學生,而實例就是根據這個類型建立的一個具體的對象,好比zhangsanlisi,由類生成對象體現了抽象模板到具體化的過程,這叫作基於類的面向對象方式,而 JavaScript 沒有類的概念,是基於原型的面向對象方式(雖然 Es6 增長了 class,實質是對原型方式的封裝)。總結起來就是如下兩點:編程

  • 在基於類的面向對象方式中,對象(object)依靠類(class)來產生。
  • 在基於原型的面向對象方式中,對象(object)則是依靠構造函數(constructor)和原型(prototype)構造出來的。

面嚮對象語言的第一個特性毫無疑問是封裝,在 JS 中,封裝的過程就是把一些屬性和方法放到對象中「包裹」起來,那麼咱們要怎麼去封裝屬性和方法,或者說怎麼去建立對象呢(後文統一說建立對象)?下面用逐步推動的方式闡述:設計模式

對象字面量 --> 工廠模式 --> 構造函數 --> 原型模式 --> 構造函數+原型模式

對象字面量

JS中建立對象最原始的方式有兩種:函數

  • 對象字面量
var  person = {
    name: "leon",
    age: "20",

    greeting: function() {
      alert('Hi!');
    }
}
  • Object實例添加屬性方法
var person = new Object();
person.name = "leon";
person.age = "20";

person.greeting = function() {
  alert('Hi!');
};
  • 優勢:代碼簡單
  • 缺點: 建立多個對象會產生大量的代碼,編寫麻煩,且並無實例與原型的概念。
  • 解決辦法:工廠模式。

工廠模式

工廠模式是編程領域一種廣爲人知的設計模式,它抽象了建立具體對象的過程。JS 中建立一個函數,把建立新對象、添加對象屬性、返回對象的過程放到這個函數中,用戶只需調用函數來生成對象而無需關注對象建立細節,這叫工廠模式:this

function createPerson(name, age) {
  var person = new Object();
  person.name = name;
  person.age = age;

  person.greeting = function() {
    alert('Hi!');
  };
  return person;
}

var person1 = createPerson("leon", "20");
  • 優勢:工廠模式解決了對象字面量建立對象代碼重複問題,建立類似對象可使用同一API。
  • 缺點:由於是調用函建立對象,沒法識別對象的類型。
  • 解決辦法:構造函數

構造函數

JS 中構造函數與其餘函數的惟一區別,就在於調用它的方式不一樣。任何函數,只要經過new 操做符來調用,那它就能夠做爲構造函數。來看下面的例子:prototype

function Person(name, age) {
  this.name = name;
  this.age = age;
  
  this.greeting = function() {
    alert('Hi!');
  };
  // return this;
}

var person1 = new Person("leon", "20");
var person2 = new Person("jack", "21");

經過構造函數new一個實例經歷了四步:設計

  1. 建立一個新對象;
  2. 將構造函數內的this綁定到新對象上;
  3. 爲新對象添加屬性和方法;
  4. 返回新對象(JS 引擎會默認添加 return this;)。

而經過構造函數建立的對象都有一個constructor屬性,它是一個指向構造函數自己的指針,所以就能夠檢測對象的類型啦。:指針

alert(person1.constructor === Person) //true
alert(person1 instanceof Person) // true

可是仍然存在問題:code

alert(person1.greeting == person2.greeting) //false

同一個構造函數中定義了greeting(),而不一樣實例上的同名函數倒是不相等的,意味着這兩個同名函數的內存空間不一致,也就是構造函數中的方法要在每一個實例上從新建立一次。這顯然是不划算的。對象

  • 優勢:解決了相似對象建立問題,且能夠檢測對象類型。
  • 缺點:構造函數方法要在每一個實例上新建一次。
  • 解決辦法:原型模式。

原型模式

終於講到了原型模式,JS 中每一個構造函數都有一個prototype屬性,這個屬性是一個指針,指向原型對象,而這個原型對象包含了這個構造函數全部實例共享的屬性和方法。而實例對象中有一個proto屬性,它指向原型對象,也就是構造函數.prototype == 原型對象 == 對象._proto_,那麼對象就能夠獲取到原型對象中的屬性和方法啦。同時,全部對象中都有一個constructor屬性,原型對象的constructor指向其對應的構造函數。ip

使用原型,就意味着咱們能夠把但願實例共享的屬性和方法放到原型對象中去,而不是放在構造函數中,這樣每一次經過構造函數new一個實例,原型對象中定義的方法都不會從新建立一次。來看下面的例子:

function Person() {
}

Person.prototype.name = "leon";
Person.prototype.age = "20";
Person.prototype.greeting = function() {
  alert('Hi!');
};

var person1 = new Person();
var person2 = new Person();
alert(person1.name); //"leon"
alert(person2.name); //"leon"
alert(person1.greeting == person2.greeting); //true
  • 優勢:與單純使用構造函數不同,原型對象中的方法不會在實例中從新建立一次,節約內存。
  • 缺點:使用空構造函數,實例 person1 和 person2 的 name都同樣了,咱們顯然不但願全部實例屬性方法都同樣,它們仍是要有本身獨有的屬性方法。而且若是原型中對象中有引用類型值,實例中得到的都是該值的引用,意味着一個實例修改了這個值,其餘實例中的值都會相應改變。
  • 解決辦法:構造函數+原型模式組合使用。

另外 JS 中還定義了一些與原型相關的屬性,這裏羅列一下:

  • Object.getPrototypeOf(),取得實例的原型對象。
Object.getPrototypeOf(person1);
  • isPrototypeOf(),判斷是否是一個實例的原型對象。
Person.prototype.isPrototypeOf(person1);
  • hasOwnProperty(),檢測一個屬性是否存在於實例中
person1.hasOwnProperty("name");
  • in,判斷一個屬性是否存在於實例和原型中。
"name" in person1;

構造函數+原型模式

最後一種方式就是組合使用構造函數和原型模式,構造函數用於定義實例屬性,而共享屬性和方法定義在原型對象中。這樣每一個實例都有本身獨有的屬性,同時又有對共享方法的引用,節省內存。

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

Person.prototype = {
  constructor: Person,
  nationality: "China",
  greeting: function() {
    alert(this.name);
  }
}

var person1 = new Person("leon", "20");
var person2 = new Person("jack", "21");
alert(person1.greeting == person2.greeting) //true

上面代碼中用對象字面量的形式重寫了原型對象,這樣至關於建立了一個新的對象,那麼它的constructor屬性就會指向Object,這裏爲了讓它繼續指向構造函數,顯示的寫上了constructor: Person

這種構造函數與原型模式混成的模式,是目前在 JS 中使用最爲普遍的一種建立對象的方法。

相關文章
相關標籤/搜索