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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章