zishu's blog

zishu's blog

一个热爱生活的博主。https://zishu.me

面向物件的程式設計

面向對象把構成問題的 transaction 分解成各個對象,而建立對象的目的也不是為了完成一個個步驟,而是為了描述某個事物在解決整個問題的過程中所發生的行為,意在寫出通用代碼,加強代碼重用,屏蔽差異性。

一、什麼是面向對象編程#

js 是基於原型的,基於面向對象編程

面向對象就是把數據和對數據的操作方法放在一起,作為一個整體 —— 對象。對同類對象抽象出其共性,形成類

image

1. 面向過程程序設計#

將一個項目(或者一個事件)從頭到尾按順序,一步一步完成,先做什麼,後做什麼,一直到結束,也是我們人做事的方法。

自上而下,先確定一個整體的框架,然後添磚加瓦,逐步實現想要得到的效果,適用於簡單的系統,容易理解。但是難以應對複雜的系統,不易維護擴展,難以復用

面向過程是分析解決問題的步驟,然後用函數把這些步驟一步一步的實現,然後在使用的時候一一調用則可。強調的是完成這件事兒的動作,更接近我們日常處理事情的思維。

2. 面向對象程序設計#

將一個項目(或者一個事件)分成更小的項目,每一個部分負責一方面的功能,最後由這些部分組成一個整體,先設計組件,在完成拼裝,適用於大型複雜的系統

面向對象把構成問題的 transaction 分解成各個對象,而建立對象的目的也不是為了完成一個個步驟,而是為了描述某個事物在解決整個問題的過程中所發生的行為,意在寫出通用代碼,加強代碼重用,屏蔽差異性。

想要弄明白面向對象,需要先理解類和對象的概念

《什麼是類和對象?》

二、創建對象的方法#

1. 創建字面量和實例#

window.onload = function() {
    // 實例
    var person = new Object();
    person.name = '小明';
    person.age = 22;
    person.year = function() {
        console.log(this.name + '今年' + this.age + '歲了!')
    };
    person.year();

    // 字面量
    var student = {
        name: '小明',
        age: 22,
        year: function () {
            console.log(this.name + '今年' + this.age + '歲了!')
        }
    }
    student.year();
}

// 小明今年22歲了!

兩者輸出的結果是一樣的,控制台輸出:
image

缺點:重複實例化對象,代碼冗餘高

2. 工廠模式#

window.onload = function() {
    function createObj(name, age) {
        var obj = new Object();
        obj.name = name,
        obj.age = age,
        obj.year = function() {
            console.log(this.name + '今年' + this.age + '歲了!')
        }
        return obj;
    }
    var obj = createObj('小明', 22);
    obj.year();
}

// 小明今年22歲了!

優點:解決重複實例化對象的問題
缺點:無法識別對象的類型,因為所有的實例都指向一個原型

3. 構造函數#

window.onload = function() {
    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.year = function() {
            console.log(this.name + '今年' + this.age + '歲了!')
        }
    }
    var student = new Person('小明', 22);
    student.year();
}

// 小明今年22歲了!

優點:可以識別對象的類型
缺點:多個實例重複創建方法,無法共享

4. 原型模式#

window.onload = function() {
    function Par() {}
    Par.prototype = {
        constructor: 'Par',
        name: '小明',
        age: 22,
        year: function() {
            console.log(this.name + '今年' + this.age + '歲了!')
        }
    };
    var son = new Par();
    son.year();
}

// 小明今年22歲了!

缺點:所有實例共享他的屬性和方法,不能傳參和初始化屬性值

5. 混合模式 (推薦使用)#

是構造函數和原型模式混合的寫法,擁有各自的優點,構造函數共享實例屬性,原型模式共享方法和想要共享的屬性,可以傳參和初始化屬性值

先用構造函數定義對象的屬性方法,然後用原型模式創建方法,使用的屬性通過 prototype 獲取,有一個 constructor 屬性,可以指向要操作的函數對象(構造函數)

比如constructor: Par,就代表下面這個原型方法指向Par()對象(構造函數)

window.onload = function() {
    function Par(name, age) {
        this.name = name;
        this.age = age;
    }
    Par.prototype = {
        constructor: Par,
        year: function() {
            console.log(this.name + '今年' + this.age + '歲了!');
        }
    };
    var son = new Par('小明', 22)
    son.year();
}

// 小明今年22歲了!

三、原型,原型鏈#

1. 原型對象#

  1. 函數對象都具有prototype屬性,它指向函數的原型對象 (瀏覽器內存創建的對象),原型對象都具有constructor屬性,它指向prototype屬性所在的函數對象 (構造函數)
window.onload = function() {
    function Par(name, age) {
        this.name = name;
        this.age = age;
    }
    Par.prototype = {
    // constructor指向對象
        constructor: Par,
        year: function() {
            console.log(this.name + '今年' + this.age + '歲了!');
        }
    };
    var son = new Par('小明', 22)
    son.year();

/*********************************************/
    console.log(Par.prototype)
    console.log(Par.prototype.constructor)
/*********************************************/
}

通過控制台可以看到

構造函數的prototypr屬性指向原型對象

原型對象的construcyor屬性指向構造函數

image

  1. 當調用構造函數創建一個實例後,該實例會有一個隱藏屬性__proto__ ,它指向構造函數的原型對象
console.log(son.__proto__ === Par.prototype)

// true
  1. 所有的構造函數的 prototype 都是 object 類型
console.log(typeof Par.prototype)

// object
  1. Function 的 prototype 是一個空函數,所有內置函數的__proto__屬性都指向這個空函數
console.log(Math.__proto__)

image

  1. 如果構造函數實例和原型對象中同時定義了一個屬性,在調用時,會屏蔽原型對象中的屬性,如果想要訪問原型對象中的屬性值,需要通過delete方法將同名屬性在實例(構造函數)中徹底刪除
window.onload = function () {
    function Par(name) {
        this.name = name;
    }
    Par.prototype.name = "張三";
    var son = new Par("李四");
    console.log(son.name); // 李四
    console.log(son.__proto__.name); // 張三

    // 使用delete刪除實例的同名屬性值
    console.log(delete son.name);   // true
    console.log(son.name); // 張三
}
  1. 通過hasOwnProperty(屬性名)可以判斷一個屬性存在於構造函數中,還是原型對象中

true表示存在構造函數中;false表示存在原型對象中

console.log(Par.hasOwnProperty(name));  // false
  1. 操作符in,可以判斷一個屬性是否存在(存在於構造函數和原型對象中皆可)
window.onload = function () {
    function Par(name, age) {
        this.name = name;
        this.age = age;
    }
    Par.prototype = {
        constructor: Par,
        year: function() {
            console.log(this.name + this.age)
        }
    };
    var son = new Par('xm', '22')
    son.year();
    console.log('name' in Par); // true
    console.log('age' in Par);  // false
}

同樣的兩個屬性,判斷其是否存在於實例或者原型對象中,輸出的結果不一樣

參考:《對象中是否有某一個屬性 in》https://www.cnblogs.com/IwishIcould/p/12333739.html

2.__proto__和 prototype 的區別#

  1. prototype屬性只有函數對象上才有,而__proto__屬性所有對象都有

  2. prototype是由函數對象指向原型對象,而__proto__是由實例指向函數對象的原型對象

  3. 原型鏈,將父類型的實例作為子類型的原型對象,這種鏈式關係叫做原型鏈

image

3. 繼承#

  1. 原型鏈繼承

優點:父類原型定義的屬性和方法可以復用
缺點:子類實例沒有自己的屬性,不能向父類傳遞參數

function test1() {
    function SuperType() {
        this.city = [ "北京", "上海", "天津" ];
        this.property = true;
    }
    SuperType.prototype = {
        constructor : SuperType,     // 保持構造函數和原型對象的完整性
        age : 15,
        getSuperValue : function() {
            return this.property;
        }
    };
    function SonType() {
        this.property = false;
    }

    // 重寫子類的原型指向父類的實例:繼承父類的原型
    SubType.prototype = new SuperType();

    SubType.prototype = {
        constructor : SubType,
        getSonType : function() {
            return this.property;
        }
    };

    // 優點驗證
    let son = new SubType();
    console.log(son.age); // 15
    console.log(son.getSuperValue()); // false

    // 缺點驗證
    let instance1 = new SubType();
    instance1.city.push("重慶");
    console.log(instance1.city); // ["北京", "上海", "天津", "重慶"]

    let instance2 = new SubType();
    console.log(instance2.city); // ["北京", "上海", "天津", "重慶"]

}

// test1();
  1. 構造函數繼承

優點:子類實例有自己的屬性,可以向父類傳遞參數,解決原型鏈繼承的缺點
缺點:父類原型的屬性和方法不可復用

function test2() {
    function SuperType(name) {
        this.name = name;
        this.city = [ "北京", "上海", "天津" ]
    }
    SuperType.prototype = {
        constructor : SuperType,
        age : 18,
        showInfo : function() {
            return this.name;
        }
    };

    function SubType() {
        // 父類調用call()或者apply()方法和子類共用同一個this,實現子類實例屬性的繼承
        SuperType.call(this, "張三");
    }

    // 優點驗證
    let instance = new SubType();
    instance.city.push("重慶");
    console.log(instance.city); // ["北京", "上海", "天津", "重慶"]

    let instance1 = new SubType();
    console.log(instance1.city); // ["北京", "上海", "天津"]

    // 缺點驗證
    console.log(instance.age); // undefined
    instance.showInfo(); // son.showInfo is not a function
}

// test2();
  1. 組合繼承(推薦)

優點:原型的屬性和方法可以復用,每個子類實例都有自己的屬性
缺點:父類構造函數調用了兩次,子類原型中的父類實例屬性被子類實例覆蓋

function test3() {
    function SuperType(name) {
        this.name = name;
        this.city = [ "北京", "上海", "天津" ]
    }
    SuperType.prototype = {
        constructor : SuperType,
        showInfo : function() {
            console.log(this.name + "今年" + this.age + "歲了");
        }
    };

    function SubType(name, age) {
        // 1. 透過構造方法繼承實現實例屬性的繼承
        SuperType.call(this, name);
        this.age = age;
    }

    // 2. 透過原型鏈繼承實現原型方法的繼承
    SubType.prototype = new SuperType();

    // 優點驗證
    let instance = new SubType("張三", 15);
    instance.showInfo(); // 張三今年15歲了

    let instance1 = new SubType();
    instance1.city.push("重慶");
    console.log(instance1.city); // ["北京", "上海", "天津", "重慶"]

    let instance2 = new SubType();
    console.log(instance2.city); // ["北京", "上海", "天津"]

}

// test3();
  1. 寄生組合繼承(推薦)

優點:解決了組合繼承的缺點,效率高
缺點:基本沒有

function test4() {
    function inheritPrototype(subType, superType) {
        // 1. 繼承父類的原型
        var prototype = Object.create(superType.prototype);
        // 2. 重寫被污染的construct
        prototype.constructor = subType;
        // 3. 重寫子類的原型
        subType.prototype = prototype;
    }
    function SuperType(name) {
        this.name = name;
        this.city = [ "北京", "上海", "天津" ];
    }

    SuperType.prototype.sayName = function() {
        console.log(this.name);
    };

    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }

    // 將父類原型指向子類
    inheritPrototype(SubType, SuperType);

    SubType.prototype.sayAge = function() {
        console.log(this.age);
    }

    // 優點驗證
    let instance = new SubType("張三", 15);
    instance.sayName(); // 張三

    let instance1 = new SubType();
    instance1.city.push("重慶");
    console.log(instance1.city); // ["北京", "上海", "天津", "重慶"]

    let instance2 = new SubType();
    console.log(instance2.city); // ["北京", "上海", "天津"]
}

// test4();

4.ES6 新方法--class#

新的關鍵字class在 es6 開始被引入到 javascript 中來,class的目的就是讓定義類更簡單

用函數方法實現:

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

Person.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}

var son = new Person('xm')
son.hello();    // Hello, xm!

class來實現:

class Person {
    constructor(name) {
        this.name = name;
    }

    hello() {
        console.log('Hello, ' + this.name + '!');
    }
}

var son = new person('xm')
son.hello();    // Hello, xm!

可以在看到,在定義class中,直接包含了構造函數constructor屬性,和原型對象上的函數hello()方法,省略掉了function關鍵字

需要注意:原來的寫法是,構造函數和原型對象分散開來寫,現在用class可以直接把兩者串在一個對象中,只有最後傳參和調用方法時寫法是相同的

class 繼承

class定義對象的另一個巨大的好處是繼承更方便了。想一想我們從Person派生一個PrimaryPerson需要編寫的代碼量。現在,原型繼承的中間對象,原型對象的構造函數等等都不需要考慮了,直接通過extends來實現:

class PrimaryPerson extends Person {
    constructor(name, grade) {
        super(name); // 記得用super調用父類的構造方法!
        this.grade = grade;
    }

    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

注意PrimaryPerson的定義也是通過 class 關鍵字實現的,而extends則表示原型鏈對象來自Person,子類的構造函數可能會和父類的不太相同

例如,PrimaryPerson需要namegrade兩個參數,並且需要通過super(name)來調用父類的構造函數,否則父類的name屬性無法正常初始化。

PrimaryPerson已經自動獲得了父類Personhello方法,我們又在子類中定義了新的myGrade方法。

ES6 引入的class和原有的JavaScript原型繼承有什麼區別呢?

實際上它們沒有任何區別,class的作用就是讓 JavaScript 引擎去實現原來需要我們自己編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。

但是!

目前並不是所有的瀏覽器都支持class,所以在選擇的時候一定要慎重!

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。