Js高級編程筆記--面向對象的程序設計

理解對象

屬性類型

1.數據屬性

特性:javascript

  • Configurable : 表示可否經過 delete 刪除屬性,可否修改屬性特性,可否把屬性改成訪問器屬性
  • Enumerable : 表示可否經過 for in 循環返回
  • Writable : 表示可否修改屬性的值
  • Value : 包含屬性的值,讀取或寫入實際上操做的是這個值

2.訪問器屬性

特性:java

  • Configurable : 表示可否經過 delete 刪除屬性,可否修改屬性特性,可否把屬性改成訪問器屬性
  • Enumerable : 表示可否經過 for in 循環返回
  • Get : 讀取時調用的參數.默認值爲 undefined
  • Set : 寫入時調用的參數。 默認值爲 undefined

3.注意:

  • 訪問器屬性不能直接定義,必須使用 Object.defineProperty()定義。
  • 修改屬性默認的特性,必須使用 Object.defineProperty()方法
  • get,set,並不必定要定義,只定義 get 爲只讀,只定義 set 爲只寫不可讀。
  • 定義多個屬性可使用 Object.defineProperties()方法
  • 讀取屬性的特性,使用 Object.getOwnPropertyDescriptor()

建立對象

1.工廠模式

定義一個方法接受參數,用於建立對象,並將其返回es6

function createPerson(name, age) {
    var o = new Object();
    o.name = name;
    o.age = age;
    return o;
}
var person1 = createPerson('andy_chen', 18);
var person2 = createPerson('andy_chen', 18);
工廠模式能夠建立多個類似對象的問題,卻沒解決對象識別的問題。例如person1的類型是什麼

2.構造函數模式 :

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function() {
        alert(this.name);
    };
}
var person1 = new Person('andy_chen', 18);
var person2 = new Person('andy_chen', 18);
person1.sayName();
person2.sayName();

使用 new 操做符。實際上有如下 4 個步驟:

  • 建立一個新對象
  • 將構造函數的做用域賦給對象(使 this 指向新對象)
  • 執行構造方法(爲這個對象添加屬性)
  • 返回新對象
構造函數的問題在於,每一個方法都要在每一個實例中從新建立一遍。即例子中,person1和person2的sayName的不相等的。
可是,完成一樣的功能的方法,卻每一個實例都要建立一遍,這顯然不合理,因此,又出現了下面的原型模式

3.原型模式:

理解原型對象

一圖勝千言:
原型對象chrome

  • 只要建立了一個新函數,就會根據一組特定規則爲該函數建立一個 prototype,這個屬性指向函數的對象原型。
  • 對象原型中,則默認有一個 constructor 屬性,指向該新函數。
  • 經過新函數建立的實例,有一個[[prototype]]屬性(在 chrome,firefox,safari 中該屬性即爲proto),指向了新函數的 prototype。編程

    注意:該屬性僅僅是執行構造函數的 prototype,也便是說,他們與構造函數沒有直接聯繫了
  • 讀取某個對象的屬性時,會先在實例上找,若是沒找到,則進一步在實例上的 prototype 屬性上找
  • 爲實例添加屬性的時候會屏蔽掉原型上屬性。這個時候即便置爲 null 也無法訪問到原型上的屬性,只有經過 delete 刪掉以後才能夠
  • XXX.prototype.isPrototype(xxx), 能夠用這個方法斷定對象是不是該實例的原型對象
  • Object.getPrototypeOf() 用這個能夠獲取實例對應的原型對象 (ES5 新增方法)

in 操做符

  • 單獨使用時: in 操做符 能夠肯定屬性是否存在於對象上(不管是存在於實例上仍是原型上)
  • 用於 for 循環中時,返回的是全部可以經過對象訪問的,可枚舉的屬性。(IE8 中,若是開發者自定義 toString 相似的系統不可枚舉的方法,瀏覽器仍是不會將它遍歷出來)
ES5:Object.keys() 能夠返回一個包含全部可枚舉屬性的字符串數組
Object.getOwnPropertyNames() 能夠返回全部實例屬性,不管是否可枚舉
//原型模式的實現:
function Person() {}

Person.prototype.name = 'andy chen';

Person.prototype.sayName = function() {
    alert(this.name);
};

更簡單的原型語法

重寫整個 prototype,不過會致使 constructor 改變。因此須要從新指定 constructor.數組

//更簡單的原型語法
function Person() {}
Person.prototype = {
    constructor: Person, //由於這種寫法會覆蓋掉原來的Person.prototype,須要從新爲constructor賦值
    name: 'andy chen',
    sayName: function() {
        alert(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
原型模式的問題:全部實例都共享一個prototype,相似上面的例子,person1,person2的name屬性是共享的。若是修改其中一個,會致使另外一個也受影響。因此,纔會出現下面構造函數與原型模式組合使用

4.組合使用構造函數和原型模式

建立自定義類型最多見的方式就是組合使用構造函數和原型模式瀏覽器

構造函數定義實例屬性,而原型模式用於定義方法和共享的屬性. 因此,上面的例子能夠改寫成這樣:安全

function Person(name) {
    this.name = name;
}
Person.prototype = {
    constructor: Person,
    sayName: function() {
        alert(this.name);
    }
};
var person1 = new Person('andy chen');
var person2 = new Person('andy chen');

除了使用組合模式建立對象,還有如下幾種方式,能夠針對不一樣的狀況選擇。函數

5.動態原型模式

在構造方法中,判斷是不是第一次進入使用構造方法,若是是,則添加一系列的方法到原型上優化

6.寄生構造函數模式

類基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼而後再返回新建立的對象。

7.穩妥構造函數模式:

穩妥對象

指的是沒有公共屬性,並且其方法也不引用 this 對象。最適合用於一些安全的環境或者在防止數據被其餘程序改動時使用

穩妥構造函數

遵循與寄生構造函數相似的模式,但有兩點不一樣:

  • 新建立的對象實例不引用 this.
  • 不使用 new 操做符調用構造函數

繼承

OO 語言通常擁有兩種繼承方式:接口繼承(只繼承方法簽名)以及實現繼承(繼承實際方法)
ES 沒法像其餘 OO 語言同樣支持接口繼承,只能依靠原型鏈實現 實現繼承

1. 原型鏈

要了解原型鏈的概念,先回顧一下構造函數,原型和實例之間的關係(參考圖 6-1)

  • 每一個構造函數都有一個原型對象,原型對象包含一個指向構造函數的指針.
  • 每一個實例都包含一個指向原型對象的內部指針的內部屬性(在 chrome 中通常爲proto屬性)

那麼,若是咱們有個新的構造函數,並讓它的原型對象等於另外一個類型的實例,結果會怎樣.

對於這個新的構造函數,它的原型對象就變成了另外一個類型的實例,而這個實例中,又包含一個內部屬性,指向了另外一個原型對象(該原型對象內部 constructor 指向另外一個構造函數),若是這個原型對象又是另外一個類型的實例,則它又包含了一個內部屬性,繼續指向上層的原型對象。這樣層層遞進,就造成了原型鏈。

以下圖:
原型鏈

特色

  • 在實例中搜索屬性的時候,即是基於原型鏈來搜索的,先搜索實例,再在原型鏈上一層層往上搜,直到找到或者到原型鏈末端纔會停下來
  • 因爲全部引用類型都繼承了 Object,因此原型鏈的最頂層是 Object
  • 使用原型鏈實現繼承時,不能使用對象字面量建立原型方法,由於這樣會重寫原型鏈

原型鏈實現繼承的方式:

function Animal() {
    this.name = 'animal';
}
Animal.prototype.getName = function() {
    return this.name;
};

function Cat() {
    this.catName = 'cat';
}
Cat.prototype = new Animal();

var cat1 = new Cat();
var cat2 = new Cat();
alert(cat1.getName()); //因爲第10行,將Cat的原型指向Animal的實例,由於實例中有指向Animal.prototype的指針。因此,這裏能夠訪問到getName()

cat1.name = 'changed name';
alert(cat2.getName());

原型鏈的問題:

  • 使用原型鏈,因爲是使用新的實例做爲子類型的原型,實例中卻包含了父類型的屬性,因此原來父類型的屬性,就都到了子類型的原型上了。這就會形成子類型的不一樣實例會共享同個屬性.如上例子中,第 15 行,改變 cat1 實例的 name 屬性影響到了 cat2 的 name 屬性
  • 建立子類型的時候,不能向父類型傳遞參數

2. 借用構造函數

因爲原型鏈存在問題,因此便出現了借用構造函數的方法
在子類型的構造方法中,調用父類型的構造方法:SuperType.call(this); 將父類型的屬性添加到子類型上,而且能夠傳遞參數給父類型

借用構造函數實現繼承的方式:

function Animal() {
    this.name = 'animal';
}
function Cat() {
    Animal.call(this);
}
var cat1 = new Cat();
var cat2 = new Cat();
cat1.name = 'changed name';
alert(cat1.name); //changed name
alert(cat2.name); //animal //借用構造函數的方式,各實例之間的屬性便不會互相影響

借用構造函數問題:

相似建立對象單純使用構造方法同樣,也會形成公有的方法沒法公用。因此通常也不多單獨使用此方式

3. 組合繼承

組合原型鏈以及借用構造函數

  • 使用原型鏈實現對原型屬性和方法的繼承
  • 借用構造函數來實現對實例中屬性的繼承。
function Animal() {
    this.name = 'animal';
}
Animal.prototype.getName = function() {
    return this.name;
};

function Cat() {
    Animal.call(this); //借用構造函數
}
Cat.prototype = new Animal(); //原型鏈方式
Cat.prototype.constructor = Cat;

//這裏能夠
var cat1 = new Cat();
var cat2 = new Cat();
cat1.name = 'changed name';

alert(cat1.getName()); //changed name
alert(cat2.getName()); //animal

組合繼承的問題:

父類的屬性會存在於子類型的原型上,致使被不一樣實例共享。雖然因爲借用構造函數以後,致使實例上又重寫了這些屬性,因此每一個實例有各自的屬性。

另外,instanceof 和 isPrototypeOf 可以識別基於組合繼承建立的對象

**組合繼承,並不完美
由於咱們只須要繼承父類型原型上的屬性而已,不須要父類型實例的屬性。
還有更好的方法,但咱們首先要先了解一下其餘繼承方式**

4. 原型式繼承

//若是o爲某個對象的prototype,則object返回的 對象,包含了該對象原型上的全部方法
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

Es5 新增的 Object.create() ,相似這樣。 在沒有必要建立構造函數,只想讓一個對象與另外一個對象保持相似的狀況下,原型式繼承是徹底能夠勝任的。不過,包含引用類型值的屬性始終會共享

5. 寄生式繼承

在複製新對象後,繼續以某種方式加強對象,即爲寄生式繼承。

function createAnother(original) {
    var clone = object(original);
    clone.sayHi = function() {
        doSomeThing();
    };
    return clone;
}

在主要考慮對象而不是自定義類型和構造函數的時候,適合使用寄生式繼承

缺點: 相似單純的構造函數模式使用,函數不能複用

6. 寄生組合式繼承

經過原型式繼承,繼承父類的原型方法。再經過構造函數方法,繼承父類的屬性。

function Animal() {
    this.name = 'animal';
}
Animal.prototype.getName = function() {
    return this.name;
};

function Cat() {
    Animal.call(this); //借用構造函數
}

//原型繼承方式
function object(superProto) {
    function F() {}
    F.prototype = superProto;
    return new F();
}

Cat.prototype = object(Animal.prototype); //經過一個空的函數做爲媒介,將空函數的原型指向父類型原型,並將子類型的原型指向這個空函數的實例。便只繼承父類原型上的屬性及方法
Cat.prototype.constructor = Cat;
//這裏能夠以後添加子類的方法
Cat.prototype.run = function() {
    alert('cat run');
};

var cat1 = new Cat();
var cat2 = new Cat();
cat1.name = 'changed name';
alert(cat1.getName()); //changed name
alert(cat2.getName()); //animal

最後,寄生組合式繼承是引用類型最理想的繼承範式。
上述代碼還能再進一步優化。

//原型繼承方式
function object(superProto) {
    function F() {}
    F.prototype = superProto;
    return new F();
}
//公用的繼承方法
function inheritPrototype(subType, superType) {
    subType.prototype = object(superType.prototype);
    subType.prototype.constructor = subType;
}

function Animal() {
    this.name = 'animal';
}
Animal.prototype.getName = function() {
    return this.name;
};

function Cat() {
    Animal.call(this); //借用構造函數
}

inheritPrototype(Cat, Animal); //調用此方法繼承原型

//這裏能夠以後添加子類的方法
Cat.prototype.run = function() {
    alert('cat run');
};
var cat1 = new Cat();
var cat2 = new Cat();
cat1.name = 'changed name';
alert(cat1.getName()); //changed name
alert(cat2.getName()); //animal

小結

這是 js 對象的建立以及繼承,es6 中新增了關鍵字classextend。方便咱們進行面向對象的編程。

可是理解背後的繼承原理對咱們編程過程當中也是極有幫助的

:)

喜歡就收藏或者點個讚唄 !!

相關文章
相關標籤/搜索