【讀書筆記】讀《編寫高質量代碼—Web前端開發修煉之道》 - JavaScript原型繼承與面向對象

  JavaScript是基於原型的語言,經過new實例化出來的對象,其屬性和行爲來自於兩部分,一部分來自於構造函數,另外一部分是來自於原型。構造函數中定義的屬性和行爲的優先級比原型中定義的屬性和優先級高,若是構造函數和原型定義了同名的屬性和行爲,構造函數中的屬性和行爲會覆蓋原型中的同名的屬性和行爲。以下圖——編程

      

  當咱們聲明一個類時,其實同時生成了一個對應的原型,例如咱們定義Animal這個類時,會生成一個與Animal類對應的原型,經過Animal.prototype能夠指向這個原型,原型能夠經過constructor指向Animal類,更確切的說,是指向Animal類的構造函數。app

function Animal() {}
var a = Animal.prototype;
b = a.constructor;
alert(b === Animal);    //true
//**************************** 1.開始應用原型 ****************************
(function() {

    function Animal() {}

    //    舊的方式
    //     function Animal(name) {
    //         this.name = name;
    //         this.type = 'animal';
    //         this.say = function () {
    //             alert('Hello');
    //         }
    //     }

    Animal.prototype = {
        name : 'king',
        say : function() {
            //this.name和this.type均能訪問到,與它們屬性所在的前後順序是無關的
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        },
        type : 'animal'
    }
    
    //-----test-----
    var dog = new Animal();
    dog.say();    //I am a animal, my name is king

})();
//**************************** 2.屬性與方法的分離 ****************************
(function() {

    //咱們習慣把屬性放在構造函數裏面
    function Animal(name) {
        this.name = name || 'king';
        this.type = 'animal';
    }

    //將方法放在類的原型裏
    Animal.prototype = {
        say : function() {
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        }
    }
    
    //-----test-----
    var dog = new Animal();
    dog.say();    //I am a animal, my name is king

})();
//**************************** 3.公有和私有 ****************************
//用this.xxx定義的屬性是公有的,用var xxx定義的屬性是私有的
(function() {

    function Animal(name) {
        this.name = name || 'king';
        this.type = 'animal';
        //私有變量
        var age = 20;
        //私有方法
        var move = function() {
            alert('I am a moving dog');
        }
    }

    Animal.prototype = {
        say : function() {
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        }
    }
    
    //-----test-----
    var dog = new Animal('wangcai');
    console.log(dog.age);    //undefined
    dog.say();    //I am a animal, my name is wangcai
    dog.move();    //報錯,has no method 'move'

})();
//**************************** 4.如何訪問到私有變量(方法) ****************************
(function() {

    function Animal(name) {
        this.name = name || 'king';
        this.type = 'animal';
        //私有變量
        var age = 20;
        //私有方法
        var move = function() {
            alert('I am a moving dog');
        }
        //在構造器做用域(私有變量的做用域)中,構造一個可以訪問私有變量的公有方法
        this.say = function() {
            console.log('I am a ' + this.type + ', my name is ' + this.name + ', my age is ' + age);
        }
        this.act = function() {
            move();
        }
    }

    Animal.prototype = {};

    //-----test-----
    var dog = new Animal('wangcai');
    dog.say();    //I am a animal, my name is wangcai, my age is 20
    dog.act();    //I am a moving dog

    /**
     * 將全部屬性和行爲,不管公有仍是私有的屬性和行爲所有寫在構造函數裏,的確是最方便的方式,但並不推薦這麼作。
     * 由於在內存中一個類的原型只有一個,寫在原型中的行爲,能夠被全部實例所共享,實例化的時候,
     * 並不會在實例的內存中再複製一份,而寫在類裏的行爲,實例化的時候會在每一個實例裏複製一份。
     * 把行爲寫在原型裏能夠減小內存消耗,沒有特殊緣由,推薦儘可能把行爲寫在原型裏。
     */

})();
//**************************** 5.如何訪問到私有變量(方法) —— 性能改造****************************
/**
 * 寫在原型裏的行爲必定是公有的,並且沒法訪問私有屬性,因此如何處理私有行爲和私有屬性是個難題。
 * 1>若是對屬性和行爲的私有性有很是高的強制性,咱們不得不犧牲內存,將私有行爲放在構造函數裏,實現真正的私有。
 * 2>若是對屬性和行爲的私有性要求不高,更常見的作法是約定私有行爲,
 *     經過給屬性和行爲的名稱前面加上「_」來約定它是私有的,這是一種命名約定。
 */
(function() {

    function Animal(name) {
        this.name = name || 'king';
        this.type = 'animal';
        //私有變量
        this._age = 20;
    }

    Animal.prototype = {
        _move : function() {
            alert('I am a moving dog');
        },
        say : function() {
            //訪問this._age來標識age屬性是私有的
            console.log('I am a ' + this.type + ', my name is ' + this.name + ', my age is ' + this._age);
        },
        act : function() {
            //訪問this._age來標識move方法是私有的
            this._move();
        }
    };

    //-----test-----
    var dog = new Animal('wangcai');
    dog.say();    //I am a animal, my name is wangcai, my age is 20
    dog.act();    //I am a moving dog
    console.log(dog._age);    //不推薦實例直接調用_age,違反命名約定
    dog._move();    //I am a moving dog 不推薦實例直接調用_move,違反命名約定

})();
//**************************** 6.繼承:繼承構造函數 ****************************
(function () {
    
    function Animal(name) {
        //應用函數調用模式,此時的this指向window
        this.name = name || 'king';
        this.type = 'animal';
    }
    
    Animal.prototype = {
        say: function () {
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        }
    }
    
    function Bird(name) {
        //此時的this是Bird
        Animal(name);    //這個方法調用事後this就變成了window(函數調用模式)
    }
    
    //-----test-----
    var bird = new Bird('xiaocui');
    console.log(bird.type);    //undefined,此時的bird對象沒有任何自身屬性
    
})();
//**************************** 7.繼承:繼承構造函數 —— 改造 ****************************
(function () {
    
    function Animal(name) {
        //此時的this指向Bird
        this.name = name || 'king';
        this.type = 'animal';
    }
    
    Animal.prototype = {
        say: function () {
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        }
    }
    
    //這裏咱們只對構造器進行了繼承操做,但對於Animal類裏面的方法卻沒法繼承過來
    function Bird(name) {
        //此時的this是Bird
        Animal.call(this, name);    //利用call方法改變做用域    
    }
    
    //-----test-----
    var bird = new Bird('xiaocui');
    console.log(bird.type);    //animal
    bird.say();    //報錯,has no method 'say'
    
})();
//**************************** 8.繼承:繼承原型 —— 1 ****************************
//方式1:Bird.prototype = Animal.prototype;
(function () {
    
    function Animal(name) {    //初始化完這個函數後Animal.prototype.contructor === Animal;    //true
        //此時的this指向Bird
        this.name = name || 'king';
        this.type = 'animal';
    }
    
    //對Animal.prototype從新賦值,那麼此時的Animal.prototype.contructor === Animal;    //false
    Animal.prototype = {
        say: function () {
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        }
    }
    
    function Bird(name) {
        Animal.call(this, name);
    }
    
    //原型繼承
    Bird.prototype = Animal.prototype;
    
    //-----test-----
    var bird = new Bird('xiaocui');
    console.log(bird.type);    //animal
    bird.say();    //I am a animal, my name is xiaocui
    
})();
//**************************** 9.繼承:繼承原型 —— 2 ****************************
//方式1:Bird.prototype = Animal.prototype;
//給子類增長獨有方法,如給Bird類增長fly方法
(function () {
    
    /**
     * Animal.prototype = {        //Object
     *         constructor: funciton Animal(name) {...},
     *         __proto__: Object
     * }
     */
    function Animal(name) {
        this.name = name || 'king';
        this.type = 'animal';
    }
    
    /**
     * Animal.prototype = {        //Object
     *         say: funciton () {...},
     *         __proto__: Object
     * }
     */
    Animal.prototype = {
        say: function () {
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        }
    }
    
    /**
     * Bird.prototype = {        //Object
     *         constructor: funciton Bird(name) {...},
     *         __proto__: Object
     * }
     */
    function Bird(name) {
        Animal.call(this, name);
    }
    
    /**
     * 執行完這一句,Bird.prototype與Animal.prototype指向同一個對象,即
     * Bird.prototype = Animal.prototype = {        //Object
     *         say: funciton () {...},
     *         __proto__: Object
     * }
     */
    Bird.prototype = Animal.prototype;
    
    /**
     * 執行完這一句,增長Bird.prototype的屬性(fly方法),即
     * Bird.prototype = Animal.prototype = {
     *         fly: function () {...},
     *         say: function () {...},
     *         __proto__: Object
     * }
     */
    Bird.prototype.fly = function () {
        console.log('I am flying');
    }
    
    //-----test-----
    var bird = new Bird('xiaocui');
    bird.fly();    //I am flying
    
    var dog = new Animal('king');
    dog.fly();    //I am flying
    /**
     * 因爲Bird.prototype和Animal.prototype指向同一個地址,對Bird.prototype新增的方法,
     * 經過對Animal.prototype的屬性訪問也可以訪問到Bird.prototype新增的方法。
     * 顯然,這並非咱們想要的。
     */
    
})();
//**************************** 10.繼承:繼承原型 —— 3 ****************************
//方式二:Bird.prototype = new Animal();
//給子類增長獨有方法,如給Bird類增長fly方法
(function () {
    
    function Animal(name) {
        this.name = name || 'king';
        this.type = 'animal';
    }
    
    Animal.prototype = {
        say: function () {
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        }
    }
    
    function Bird(name) {
        Animal.call(this, name);
    }
    
    /**
     * 執行完這一句,把Animal原型中的say方法繼承過來,即
     * Bird.prototype = {
     *         name: 'king', 
     *         type: 'animal', 
     *         __proto__: {
     *             say: function () {...}
     *         }
     * }
     */
    Bird.prototype = new Animal();
    
    /**
     * 執行完這一句,從新賦值Bird.prototype的構造器屬性,即
     * Bird.prototype = {
     *         constructor: function Bird(name) {...}, 
     *         name: 'king', 
     *         type: 'animal', 
     *         __proto__: {
     *             say: function () {...}
     *         }
     * }
     */
    Bird.prototype.constructor = Bird;
    
    /**
     * 執行完這一句,增長Bird.prototype的屬性(fly方法),即
     * Bird.prototype = {
     *         constructor: function Bird(name) {...}, 
     *         fly: function () {...},
     *         name: 'king', 
     *         type: 'animal', 
     *         __proto__: {
     *             say: function () {...}
     *         }
     * }
     */
    Bird.prototype.fly = function () {
        console.log('I am flying');
    }
    
    //-----test-----
    var bird = new Bird('xiaocui');
    bird.say();    //I am a animal, my name is xiaocui
    bird.fly();    //I am flying 
    
    var dog = new Animal('king');
    dog.fly();    //報錯,has no method 'fly' 
    
})();
//**************************** 11.繼承:封裝繼承函數 ****************************
(function () {
    
    function extend(subClass, superClass) {
        
        var F = function () {};    //構建中間量
        F.prototype = superClass.prototype;    //中間量原型指向父類原型
        
        subClass.prototype = new F();    //將父類原型賦值給子類原型,可以繼承全部父類原型的方法
        
        subClass.prototype.constructor = subClass;    //子類原型構造器屬性指回子類構造器,使得子類的方法不會新增或覆蓋父類的方法
        
        subClass.superclass = superClass.prototype;    //給子類添加superclass屬性,並將其賦值父類原型
                                                    //使得在子類構造器中成功利用superclass屬性來調用父類構造器
        if (superClass.prototype.constructor === Object.prototype.constructor) {
            superClass.prototype.constructor = superClass;    //從新賦值父類原型構造器爲父類
        }
        
    }

    function Animal(name) {        
            this.name = name || 'king';
            this.type = 'animal';
    }
    
    Animal.prototype = {
        say: function () {
            console.log('I am a ' + this.type + ', my name is ' + this.name);
        }
    }
    
    function Bird(name) {
        //具有通用性
        this.constructor.superclass.constructor.apply(this, arguments);
    }
    
    extend(Bird, Animal);
    
    Bird.prototype.fly = function () {
        alert('I am flying');
    }
    
    //-----test-----
    var bird = new Bird('xiaocui');
    bird.say();    //I am a animal, my name is xiaocui 
    bird.fly();    //I am flying
    
})();
//**************************** 12.面向過程與面向對象 ****************************
/**
 * 面向過程編程
 * 1.這種編程方式將程序分紅了「數據」和「處理函數」兩個部分,程序以「處理函數」爲核心,
 *     若是要執行什麼操做,就將「數據」傳給相應的「處理函數」,返回咱們須要的結果。這種編程方式就是面向過程編程。
 * 2.這種編程方式存在的問題以下:
 *     1> 數據和處理函數之間沒有直接的關聯,在執行操做的時候,咱們不但要選擇相應的處理函數,
 *         還要本身準備處理函數須要的數據,也就是說,在執行操做的時候,咱們須要同時關注處理函數和數據。
 *     2> 數據和處理函數都暴露在同一個做用域內,沒有私有和公有的概念,整個程序中全部的數據和處理函數均可以相互訪問,
 *         在開發階段初期也許開發速度很快,但到了開發後期和維護階段,因爲整個程序耦合得很是緊,
 *         任何一個處理函數和數據都有可能關聯到其餘地方,容易牽一髮而動全身,從而加大了修改難度。
 *     3> 面向過程的思惟方式是典型的計算機思惟方式——輸入數據給處理器,處理器內部執行運算,處理器返回結果。
 *         也就是說面向過程的思惟方式是在描述一個個「動做」。
 *         而現實生活中的一個個「物件」(如人{姓名,狀態})很難用面向過程的思惟方式進行描述。
 * 
 * 面向對象編程
 *     這種編程就是拋開計算機思惟,使用生活中的思惟進行編程的編程方式。面向過程的思惟就是描述一個個「動做」,
 *     而面向對象的思惟就是描述一個個「物件」,客觀生活中的物件,在程序中咱們管「物件」叫作「對象」,
 *     對象由兩部分組成:「屬性」和「行爲」,對應客觀世界中物件的「狀態」和「動做」。
 */

//面向過程
(function () {
    
    //定義電話本
    var phonebook = [
        {name: 'adang', tel: '1111'},
        {name: 'king', tel: '2222'}
    ];
    
    //查詢電話
    function getTel(oPhoneBook, oName) {
        var tel = '';
        for (var i = 0; i < oPhoneBook.length; i++) {
            if (oPhoneBook[i].name === oName) {
                tel = oPhoneBook[i].tel;
                break;
            }
        }
        return tel;
    }
    
    //添加記錄
    function addItem(oPhonebook, oName, oTel) {
        oPhonebook.push({name: oName, tel: oTel});
    }
    
    //刪除記錄
    function removeItem(oPhonebook, oName) {
        var index;
        for (var i = 0; i < oPhonebook.length; i++) {
            if (oPhonebook[i].name === oName) {
                index = i;
                break;
            }
        }
        if (index !== undefined) {
            oPhonebook.splice(index, 1);
        }
    }
    
    //-----test-----
    //調用函數-增
    addItem(phonebook, 'xiaoxiao', '3333');
    //調用函數-刪
    removeItem(phonebook, 'xiaoxiao');
    //調用函數-查
    getTel(phonebook, 'king');
    
})();

//面向對象
(function () {
    
    //構造函數專一的是對象所具備的屬性
    function PhonebookManager(oPhonebook) {
        this._phonebook = oPhonebook;
    }
    
    //全部方法均在構造函數的原型中添加
    PhonebookManager.prototype = {
        addItem: function (oName, oTel) {
            this._phonebook.push({name: oName, tel: oTel});
        },
        removeItem: function (oName) {
            var index,
                phonebook = this._phonebook;
            for (var i = 0, len = phonebook.length; i < len; i++) {
                if (phonebook[i].name === oName) {
                    index = i;
                    break;
                }
            }
            if (index !== undefined) {
                phonebook.splice(index, 1);
            }
        },
        getTel: function (oName) {
            var tel = '',
                phonebook = this._phonebook;
            for (var i = 0; i < this._phonebook.length; i++) {
                if (phonebook.name === oName) {
                    tel = phonebook.tel;
                    break;
                }
            }
            return tel;
        }
    }
    
    //------test-----
    var myPhoneManager = new PhonebookManager([
        {name: 'adang', tel: '1111'},
        {name: 'king', tel: '2222'}
    ]);
    
    //調用函數-增
    myPhoneManager.addItem('xiaoxiao', '3333');
    //調用函數-刪
    myPhoneManager.removeItem('xiaoxiao');
    //調用函數-查
    myPhoneManager.getTel('king');
    
})();
相關文章
相關標籤/搜索