1. 程式人生 > >js設計模式第二章 讀書筆記

js設計模式第二章 讀書筆記

建立一個類,可在類內部通過this增加屬性和方法,也可通過原型增加,如下

var Book = function(id, bookname, price) {
    this.id = id;
    this.bookname = bookname;
    this.price = price;
}
Book.prototype.display = function() {
    // 展示這本書
};

那麼用this新增的方法、屬性和用prototype新增的屬性、方法有什麼區別?

通過this新增的屬性和方法是在當前物件上新增的。然後js是一種基於原型prototype的語言,所以建立一個物件的時候,它都有一個原型物件prototype用於指向其繼承的屬性、方法。這樣通過prtotype繼承的方法並不是物件自身的。所以使用這些方法的時候,需要通過prototype一級一級查詢來得到。當我們建立新的物件的時候,這些方法是不會再建立的。
而this建立是屬於物件自身的。我們建立物件的時候,相當於執行了一個類,因此類裡面的方法肯定會複製一份。

(1)封裝(屬性和方法的封裝)

function Book(name){
	//私有屬性/方法
    var num=1;
    //共有屬性/方法
    this.getName=function(){console.log(name)};
    //特權方法  能夠操縱私有屬性、方法
    this.getNum=function(){console.log(num)};
    this.setNum=function(n){num=n};
}
//類靜態共有屬性 (物件不能訪問)
Book.isChinese=true;
//類靜態共有方法 (物件不能訪問)
Book.resetTime=function(){
    console.log(new Date());
};
Book.prototype={
    //共有屬性
    isJSBook:true,
    //共有方法
    display:function(){}
};
var b=new Book('JavaScript');
console.log(b.num);       //undefined
console.log(b.isJSBook);  //true
console.log(b.isChinese);  //undefined

共有屬性/方法用於建立物件後呼叫。類靜態屬性、私有方法和方法物件不能呼叫

閉包

    var Book=(function(){
        //靜態私有變數
        var bookNum=0;
        function checkBook(name){}
        //返回建構函式
        return function(id,name,price){
            //私有變數
            var name,price;
            //特權方法
            this.getName=function(){};
            this.setName=function(){};
            //公有屬性/方法
            this.id=id;
            this.copy=function(){};
            //呼叫 靜態私有變數
            bookNum++;
            if(bookNum>100){throw new Error('我們僅出版100本書')}
        }
    })();
    //類靜態共有屬性 (物件不能訪問)
    Book.isChinese=true;
    //類靜態共有方法 (物件不能訪問)
    Book.resetTime=function(){
        console.log(new Date());
    };
    Book.prototype={
        //共有屬性
        isJSBook:true,
        //共有方法
        display:function(){}
    };
    var b=new Book('JavaScript');
 
    console.log(b.bookNum);   //undefined
    console.log(b.isJSBook);  //true
    console.log(b.isChinese); //undefined

閉包是有權訪問另外一個函式作用域中變數的函式,即在一個函式內部建立另外一個函式。我們將這個閉包作為建立物件的建構函式,這樣他既是閉包you又是可例項物件的函式,既可訪問類函式作用域中的變數,也可將checkBook稱之為靜態私有方法。

(2)繼承

// 宣告父類
function SuperClass(){
    this.superValue = true;
}
// 為父類原型新增公有方法,可供所有例項呼叫
SuperClass.prototype.getSuperValue = function(){
    return this.superValue;
}
// 宣告子類
function SubClass(){
    this.subValue = false;
}
// 子類繼承父類,子類prototype原型為父類例項
SubClass.prototype = new SuperClass();
// 為子類原型新增公有方法,可供所有例項呼叫
SubClass.prototype.getSubValue = function(){
    return this.subValue;
}
var instance = new SubClass();
instance.getSuperValue();   // true
instance.getSubValue();     // false

類式繼承:將父類例項賦值給子類原型

通過類的原型物件可以為類新增公有方法,建立一個父類例項,會複製一套父類建構函式中的屬性與方法,並將例項的__proto__指向父類原型物件,因此新建立的父類例項擁有了父類原型物件上的屬性和方法,並且能訪問到父類原型上的屬性和方法,將這個父類例項賦值給子類原型,子類原型就能訪問到父類原型上的屬性,方法以及從父類的建構函式中複製的屬性和方法

instanceof作用:檢測物件是否為某個類的例項(是否繼承了某個類)

console.log(instance instanceof SuperClass);// true prototype鏈存在SuperClass
console.log(instance instanceof SubClass);  // true prototype鏈存在SubClass
console.log(SubClass instanceof SuperClass);            // false 
console.log(SubClass.prototype instanceof SuperClass);  // true
console.log(instance.prototype instanceof Object);//true所有物件都是Object例項

類式繼承的缺點

子類通過prototype原型對父類例項化,實現子類繼承父類
所有子類例項的prototype原型都指向同一個父類物件,
此時,如果父類中的公有屬性為引用型別,就會被所有子類例項所共用
所以,任何一個子類例項修改從父類建構函式中繼承來的公有屬性就會影響到其他子類例項

function SuperClass(){
    // 父類公有屬性-陣列為引用型別
    this.books = ['js', 'html', 'css']
}
// 建立子類
function SubClass(){}
// 子類繼承父類
SubClass.prototype = new SuperClass();
//建立2個子類例項
var instance1 = new SubClass();
var instance2 = new SubClass();
console.log(instance1.books);//  ['js', 'html', 'css']
instance2.books.push('js設計模式');
console.log(instance1.books);//  ['js', 'html', 'css', 'js設計模式']

由於2個子類原型指向同一個父類例項,共享了陣列引用型別的屬性,導致相互影響

建構函式繼承

// 宣告父類
function SuperClass(id){
    // 公有屬性-陣列為引用型別
    this.books = ['js', 'html', 'css'];
    // 公有屬性-值型別
    this.id = id;
}
// 父類原型方法
SuperClass.prototype.showBooks = function(){
    console.log(this.books);
}
// 宣告子類
function SubClass(id){
    SuperClass.call(this, id);
}
// 建立兩個子類例項
var instance1 = new SubClass(1);
var instance2 = new SubClass(2);
// 修改instance1的books陣列
instance1.books.push("js設計模式");
// 列印修改後的結果
console.log(instance1.id);      // 1
console.log(instance1.books);   // ['js', 'html', 'css', 'js設計模式']
console.log(instance2.id);      // 2
console.log(instance2.books);   // ['js', 'html', 'css']
instance1.showBooks();          // TypeError 父類原型方法未被子類繼承

SuperClass.call(this, id);call方法可以更改函式的作用環境,this指代當前環境在子類(SubClass)中對父類(SuperClass)呼叫call方法,讓父類在子類環境中執行。父類執行時會給this繫結屬性,而this是子類環境,所以子類就繼承了父類的公有屬性,也正是因此,這種繼承方式並沒有涉及到prototype原型。

建構函式繼承的優缺點

優點:
相比於類式繼承,建構函式繼承克服了例項共享引用型別屬性和不能初始化屬性的缺點
缺點:
但由於沒有使用prototype原型,導致子類不能繼承父類原型

// 宣告父類
function SuperClass(name){
    // 公有屬性-陣列為引用型別
    this.books = ['js', 'html', 'css'];
    // 公有屬性-值型別
    this.name = name;
}
// 父類原型公有方法
SuperClass.prototype.getName = function(){
    console.log(this.name);
}
// 宣告子類
function SubClass(name, createTime){
    // 建構函式式繼承
    SuperClass.call(this, name);
    // 子類新增公有屬性
    this.createTime = createTime;
}
//類式繼承-子類原型繼承父類例項
SubClass.prototype = new SuperClass();
// 子類原型新增公有方法
SubClass.prototype.getCreateTime(){
    console.log(this.time);
}
// 例項化兩個子類物件並對其進行操作
var instance1 = new SubClass("js設計模式", 2018);
instance1.books.push("js設計模式");
console.log(this.books);    // ['js', 'html', 'css', 'js設計模式']
instance1.getName();        // js設計模式
instance1.getCreateTime();  // 2018
var instance2 = new SubClass("javascrpit", 2017);
console.log(instance2.books);   // ['js', 'html', 'css']
instance2.getName();            // javascrpit
instance2.getCreateTime();      // 2017

父類的建構函式被呼叫了兩次,造成了資源的浪費

寄生組合式繼承

//1, 原型式繼承: 以一個已有的物件為原型,創造一個新的物件
    function inheritObject(o) {
        function F() {}
        F.prototype=o;
        return new F();
    }

     //2, 寄生式繼承: 在原型式繼承的基礎上, 為新的物件新增新的方法
    function createObj(proto) {
        var o=inheritObject(proto);
        o.getName=function () {
            console.log(name);
        }
        return o;
    }

    //3, 寄生組合式繼承: 在子類和父類中間新增一層次,
    //比如該層次的物件為p, p的原型指向父類的原型,子類的原型指向p,
    //p的建構函式指向子類.
    function inheritProto(subClass,superClass) {
        //使用原型繼承建立一個父類的子類;
        var middle=inheritObject(superClass);
        //子類的原型指向middle
        subClass.prototype=middle;
        //middle的構造屬性指向子類
        middle.constructor=subClass;
    }

    //4,缺陷
    //因為寄生組合繼承只是解決了繼承鏈的問題,
    //沒有解決例項屬性的問題,
    //所以在子類建構函式中,需要用建構函式式繼承,解決例項屬性繼承的問題

    //5, 測試用例
    function SuperClass(name) {
        this.name=name;
        this.colors=['red','blue','green'];
    }
    SuperClass.prototype.showColors=function () {
        console.log(this.colors);
    }
    function SubClass(name,time) {
        SuperClass.call(this,name);
        this.time=time;
    }
    var instance1=new SubClass("sub1","2014");
    var instance2=new SubClass("sub2","2015");

    instance1.colors.push("yellow");
    instance2.colors.push("gray");

    console.log(instance1);
    console.log(instance2);

(3)多型

function Add() {

    // 無引數
    function zero() {
        return 10;
    }

    //一個引數
    function one(num) {
        return 10 + num;
    }

    //兩個引數
    function two(num1, num2) {
        return 10 + num1 + num2;
    }

    this.add = function () {
        var arg = arguments,
            len = arguments.length;
        switch (len) {
            case 0:
                return zero();
            case 1:
                return one(arguments[0]);
            case 2:
                return two(arguments[0], arguments[1]);
            default:
                return 0;
        }
    }
}

var instance = new Add();
console.log(instance.add())     // 10
console.log(instance.add(1))    // 11
console.log(instance.add(1, 2)) // 13

呼叫例項的add方法,會根據引數數量,選擇不同演算法進行計算

請da大神多多賜教。qq:274501366