<筆記>面向對象的設計模式

建立對象

什麼是工廠模式 ?

封裝一個函數 , 用來建立對象並給對象中特定的屬性添加值 , 優勢是能夠循環調用 , 缺點是每個建立出來的對象都是獨立的 , 不能肯定它是哪一個類型的對象 ( 或者說是想要將哪一個對象做爲模板進行建立 )瀏覽器

function func(name, age) {
    var o = {
        "name" : name,
        "age" : age
    };
    return o;
}
var person = func("oswald", 24);
var person2 = func("oswald", 24);
// {name: "oswald", age: 24}
console.log(person === person2);
// false
console.log(person.__proto__ === Object.prototype);     // true
console.log(person2.__proto__ === Object.prototype);    // true
// 每一個對象都是獨立的而且指向 Object.prototype 的 , 不能辨別基於哪一個對象爲原型

什麼是構造函數模式

構造函數模式和工廠模式的區別

構造函數模式就是經過構造函數來建立自定義類型的對象 , 建立出來的對象都指向了同一個構造函數的 prototype , 解決了工廠模式中不能識別對象模板的問題函數

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    };
    /*  沒有顯式的建立對象
        用 this 代替新對象
        沒有 return 語句    */
}
var person1 = new Person("oswald", 24);
console.log(person1);    // {name: "oswald", age: 24}
console.log(person1.__proto__ === Person.prototype);    // true
// person1 指向了 構造函數 func.prototype
console.log(person1 instanceof Person);     // true
// person1 對象是構造函數 Person ( 也能夠叫作 Person 類型 ) 的實例
console.log(person1 instanceof Object);     // true
// person1 對象也是 Object 類型的實例

構造函數模式的缺點

可是 , 上面的構造函數模式也不是沒有缺點的 : 單純的構造函數模式建立對象的時候 , 對象中每一個方法都會在實例對象上從新建立一次 , 也就是說每次都會建立一個看起來相同可是徹底不是一個"方法"的方法oop

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    };
}
var person1 = new Person("oswald", 24);
var person2 = new Person("oswald", 24);
console.log(person1.sayName = person2.sayName);     // false
// 每次建立對象的時候 , 都會生成一個功能同樣可是是不一樣 Function 實例的方法

什麼是原型模式

  • 每一個函數在建立的時候都會有一個 prototype 屬性 , 這個屬性指向一個對象
  • 全部以這個函數爲構造函數建立的實例對象 , 都會鏈接到構造函數的 prototype 屬性指向的這個對象上 , 而且能夠訪問這個對象的全部屬性和方法 , 這個對象就叫作原型對象
  • 原型對象在建立的時候自帶一個不可枚舉的 constructor 屬性 , 指向構造函數
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
    Person.prototype.sayName = function(){
        console.log(this.name);
    };
}

var person1 = new Person();
console.log(person1);
// {} 獲得一個空對象 , 由於建立的屬性和方法都在實例的原型對象上
console.log(Person.prototype);
// {name: "oswald", age: 24, sayName: f(...)}
console.log(person1.name);
// oswald 獲得一個名字 , 由於實例對象能夠從它的原型對象上查找屬性和方法

var person2 = new Person();
console.log(person2.sayName === person1.sayName);   // true
// person1 和 person2 訪問的 sayName 方法都是原型對象上的方法 , 不是它們自身的 , 這樣就解決了構造函數模式屢次建立方法實例的缺點

原型對象中的屬性和方法會和實例對象自有的衝突嗎

若是當前實例對象中已經有了想要查找的屬性和方法 , 會直接使用實例對象的屬性和方法 , 若是沒有才去原型對象中查找this

function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
console.log(person1.name);  // oswald
person1.name = "yin";
console.log(person1.name);  // yin

實例對象怎麼訪問原型對象

  • 在火狐、谷歌等瀏覽器中提供了一個 __proto__ 的屬性 , 能夠訪問它的原型對象
  • ECMAScript 5 中提供了 Object.getPrototypeOf( ) 方法能夠檢測並返回它的原型對象
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype);
// true
console.log(Object.getPrototypeOf(person1) === Person.prototype);
// true

原型對象和原型屬性有什麼區別

  • 實例對象 ( person1 ) 的原型對象 ( __proto__ ) 是建立當前對象的構造函數 ( Person ) 的原型屬性 ( prototype ) , 我的用來區別訪問途徑 , 便於理解
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype);
// true
  • 實例對象能夠查找它的原型對象 , 可是不能查找它本身的原型屬性
function Person(){
}
var person1 = new Person();
person1.prototype = {
    name: "yin",
    age: 23
}
console.log(person1.name);  // undefined

怎麼檢測屬性或者方法在實例對象中是否存在

使用繼承自 Object 對象的 hasOwnProperty( ) 方法能夠檢車要查找的屬性或方法是否來自實例對象而不是實例對象的原型對象prototype

function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
console.log(person1.name);  // oswald
console.log(person1.hasOwnProperty("name"));
// false 當前查找到的 name 屬性來自原型對象
person1.name = "yin";
console.log(person1.name);  // yin
console.log(person1.hasOwnProperty("name"));
// true 當前查找到的 name 屬性來自實例對象

枚舉對象中的屬性和方法有哪些方法

  • for - in 遍歷 , 會將當前對象和當前對象的原型對象上全部能夠枚舉的屬性和方法都返回
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
person1.color = "red";
for( poop in person1 ){
    console.log(poop);
    // color, name, age
}
  • ECMAScript 5 的 Object.key( ) 方法 , 遍歷當前對象的可枚舉屬性和方法 , 不會去找原型對象
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
person1.color = "red";
var p1keys = Object.key(person1);
console.log(p1keys);    // color
  • Object.getOwnPropertyNames( ) 方法 , 遍歷當前對象的全部屬性和方法 , 包括不可枚舉的 , 不會去找原型對象
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
person1.color = "red";
var p1keys = Object.getOwnPropertyNames(person1);
console.log(p1keys);
// color
var Ppkeys = Object.getOwnPropertyNames(Person.prototype);
console.log(Ppkeys);
// constructor、name、age

原型模式建立對象的缺點

原型中的全部屬性和方法都是共享的 , 若是有多個實例 , 經過其中一個實例改變原型對象中的屬性和方法 , 其餘實例訪問的屬性會跟着改變code

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

將對象的私有屬性和方法經過構造函數模式建立 , 將對象的公共屬性和方法經過原型模式建立對象

function Person(name,age){
    /* 私有屬性 */
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function(){
    /* 公共方法 */
    console.log(this.name);
}
var person1 = new Person("oswald", 24);
person1.sayName();  // oswald

繼承

什麼是原型鏈

原型鏈就是鏈接實例對象和原型對象的連接繼承

/* 通常函數的原型鏈 */
function func(){
    console.log(123);
}

console.log(func.__proto__);    // Function.prototype
// func 函數是 Function 構造函數的實例對象
console.log(func.__proto__.__proto__);  // Object.prototype
// func 函數的原型對象是 Object 構造函數的實例對象
console.log(func.__proto__.__proto__.proto__);  // null
// 這裏就是原型鏈的頭了 , 全部原型鏈查到 Object.prototype 再往上就會返回 null

/* 通常構造函數的原型鏈 */
function Obj(){
    Func.prototype.name = "oswald";
}
var obj = new Obj();
console.log(obj);    // {}
console.log(obj.name);    // oswald
// obj 對象經過原型鏈查找到了 name 屬性
console.log(Obj.__proto__);    // Obj.prototype
// obj 對象是 Obj 構造函數的實例對象
console.log(Obj.__proto__.__proto__);    // Object.prototype
// obj 對象的原型對象是 Object 構造函數的實例
console.log(Obj.__proto__.__proto__.__proto__);    // null
// 到頭了

原型鏈繼承

原型鏈繼承的原理就是實例對象能夠訪問原型對象的屬性和方法 , 並經過原型鏈向上查找ip

function Oswald(){
    this.color = "red";
    Oswald.prototype.sayName = function(){
        console.log(this.name)
    };
}
var oswald = new Oswald();

function Yin(name, age){
    this.name = name;
    this.age = age;
    Yin.prototype.sayAge = function(){
        console.log(this.age);
    }
}
Yin.prototype = oswald;
/*
原型鏈繼承的核心 , 把父類型 ( Oswald ) 的實例對象 ( oswald )
設置爲子類型 ( Yin ) 的原型屬性 ( Yin.prototype )
*/

var yin = new Yin("oswald", 24);
// 必需要先繼承再建立實例 , 不然會出現 Yin.prototype != yin.__proto__ 的狀況

yin.sayName();    // oswald  繼承了 Oswald 類型原型上的方法
yin.sayAge();     // 24      繼承了 Yin 類型原型上的方法

借用構造函數式繼承

  • 咱們以前討論的原型鏈繼承 , 不能在建立子類型的實例對象的時候 , 給父類型的構造函數傳遞參數 , 若是要給父類型的構造函數傳遞參數 , 就會影響全部的子類型實例對象
  • 若是咱們想要解決這個問題 , 能夠借調父類型的構造函數 , 在新建立的對象上執行構造函數
function Oswald(color){
    this.color = color;
    this.sayName = function(){
        console.log(this.name)
    };
}

function Yin(name, age, color){
    Oswald.call(this, color);
    // 在 Yin 構造函數建立的新對象中調用 Oswald 函數
    this.name = name;
    this.age = age;
}

var yin = new Yin("yin", 24, "red");

yin.sayName();      // yin

組合式繼承

可是借用構造函數繼承只可以繼承父類型本身的屬性和方法 , 不能繼承原型鏈上 , 這個時候咱們能夠使用原型鏈和借用構造函數的組合式繼承 , 可是這個方法會調用兩次父類型構造函數原型鏈

function Super(color){
    this.color = color;
    // 本身的屬性
    Super.prototype.sayName = function(){
        console.log(this.name)
    };
    // 原型鏈上的方法
}

function Sub(name, age, color){
    Super.call(this, color);        // 第二次調用 Super , 被當作普通函數調用
    // 繼承 Super 構造函數本身的屬性和方法
    this.name = name;
    this.age = age;
}
Sub.prototype = new Super();        // 第一次調用 Super , 被當作構造函數調用
// 繼承 Super 原型鏈上的屬性和方法
var yin = new Sub("yin", 24, "red");

yin.sayName();      // yin

原型式繼承

ES 5 中使用 Object.create( o ) 方法規範了原型式繼承 , 這個方法會返回一個新對象 , 新對象的原型對象指向傳入的參數對象 o

function obj(o){    // Object.create( ) 方法的原理
    function F(){};
    F.prototype = o;
    return new F();
}
var person = {
    name: "oswald",
    color: ["red"]
}

var person1 = obj(person);
var person2 = Object.create(person);

console.log(person1 === person2);   // false
console.log(person1.__proto__ === person2.__proto__);   // true

寄生組合式繼承

目前最優的繼承模式

function Super(color){
    this.color = color;
    Super.prototype.sayName = function(){
        console.log(this.name);
    }
}
function Sub(name, color){
    Super.call(this, color);
    // 借調 Super 構造函數繼承實例屬性
    this.name = name;
}

var F = Object.create(Super.prototype);
F.constructor = Sub;
Sub.prototype = F;


var yin = new Sub("oswald","red");

yin.sayName();      // oswald
相關文章
相關標籤/搜索