JS進階學習筆記-設計模式

JS進階學習筆記-設計模式

本文絕大多數(shù)概念是基于ES5之前的,本人認為這些都是學習或者掌握ES6必須要理解的坑律,因為ES6其實就是對ES5之前的方法的包裝,理解透徹ES5之前的概念,那么學ES6就極其簡單柴淘。

1. 初窺

1.1 靈活的JS

JS極其靈活,對于同樣一段業(yè)務代碼秘通,有的可以寫的很復雜为严,有的卻可以寫的很簡單。

例如:啟動和停止一個動畫

面向過程方式:

// 開始
function startAnimation() {
    console.log("start...")
}
// 結(jié)束
function stopAnimation(){
    console.log("stop...")
}
// 調(diào)用
startAnimation();
stopAnimation()

面向?qū)ο蠓绞揭唬?/p>

// 創(chuàng)建一個對象
var Animation = function(){}
Animation.prototype.start = function(){
    console.log("start...")
}

Animation.prototype.stop = function(){
    console.log("stop...")
}

// 使用
var test = new Animation();
test.start();
test.stop();

面向?qū)ο蠓绞蕉?/p>

var Animation = function(){}
Animation.prototype = {
    start: function(){
        console.log("start...")
    },
    stop: function(){
        console.log("stop...")
    }
}

更進一步:

// 在Function上增加方法
Function.prototype.method = function(name, fn){
    this.prototype[name] = fn;
    return this; // 為了可以鏈式調(diào)用
}
var Animation = function(){};
Animation.method('start', function(){
    console.log("start...");
    return this;// 為了可以鏈式調(diào)用
})
Animation.method('stop',function(){
    console.log('stop...');
})

// 使用
var test = new Animation();
// 鏈式調(diào)用
test.start().stop();
new Animation().start().stop(); 

這里加了返回值return this,是便于鏈式調(diào)用肺稀,stop()中沒有加第股,是為了考慮邏輯,即new Animation().stop().start(); `是錯誤的话原。

對于在原型prototype上增加的方法夕吻,如Function.prototype.method等,必須使用new關(guān)鍵字繁仁,才能調(diào)用涉馅。后續(xù)會講解。

1.2 JS是弱類型語言

在js中定義變量黄虱,不像java控漠,是不需要聲明其類型。但是,這并不意味著js中沒有類型盐捷,其實偶翅,js中變量在一初始化的時候就決定了變量的類型,再給變量賦予不同的類型時碉渡,變量對應的類型就隨之改變聚谁。

也就是說,js中的變量會根據(jù)所賦予的值而改變類型滞诺。

類型 分類 內(nèi)存位置 typeof 輸出
boolean布爾 基本數(shù)據(jù)類型(又稱原始數(shù)據(jù)類型) boolean
number數(shù)字 基本數(shù)據(jù)類型(NAN也屬于number) number
string字符串 基本數(shù)據(jù)類型 string
null空 基本數(shù)據(jù)類型 object
undefined未定義 基本數(shù)據(jù)類型 undefined
symbol獨一無二的值 基本數(shù)據(jù)類型 symbol
Object對象 引用數(shù)據(jù)類型(function形导,array...) 堆,棧中保存引用的地址 function习霹、object

一般朵耕,可以使用typeof來判斷數(shù)據(jù)類型,但是并不準確淋叶。為了精確判斷阎曹,建議使用這個方法:Object.prototype.toString.call(要判斷的變量),它返回如:"[object Function]"

1.3 函數(shù)是一等公民

在JS中煞檩,函數(shù)是一等公民(First-class Function)处嫌。它可以存儲在變量中,可以作為參數(shù)傳給其他函數(shù)斟湃,可以作為返回值從其他函數(shù)傳出熏迹,還可以運行時進行構(gòu)造。

  • 匿名函數(shù)自執(zhí)行(IIFE)凝赛,又稱立即調(diào)用函數(shù)表達式注暗。

    // 無參
    (function(){
      var x = 3;
      var y = 7;
      console.log(x*y)
    })()
    // 有參
    (function(x,y){
      console.log(x*y)
    })(3,7)
    

    除了使用()包裹的方式墓猎,其實還有~function(){...}()!function(){...}()等方式友存,都可以實現(xiàn)自執(zhí)行。一般建議使用括號的方式陶衅,安全易讀。

1.4 對象的易變性

JS雖然不像java是強制面向?qū)ο缶幊陶Z言直晨,但是在JS中一切都是對象(基本數(shù)據(jù)類型搀军,也可以包裝成對象),而且所有的對象都是異變的勇皇。例如罩句,給函數(shù)添加屬性:

function test(){
    test.num++;
    console.log(test.num)
}
test.num = 10;
test(); // 輸出 11

這意味著,我們可以對先前定義的類和實例化的對象進行修改敛摘,例如:

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

Person.prototype = {
    getName: function(){
        return this.name
    },
    getAge: function(){
        return this.age
    }
}
// 實例化對象
var tom = new Person('Tom',12);
var jerry = new Person('jerry',12);
// 給對象新增方法
Person.prototype.sayHi = function() {
    return 'Hi, ' + this.getName() + '!';
}

jerry.sayHello = function(){
    return 'Hello, ' + this.getName() + '!';
}

tom.sayHi(); // Hi, Tom门烂!
jerry.sayHi(); // Hi, Jerry!
jerry.sayHello(); // Hello, Jerry!
tom.sayHello(); // Cannot set property 'sayHello' of undefined

這里,我們在實例化Person對象之后新增了sayHi方法,并且成功調(diào)用屯远,即我們可以擴展實例化之后的對象蔓姚。通過prototype我們給所有的實例都新增了sayHi的方法。這里需要理解繼承與原型鏈,后面會講慨丐。

每個實例對象( object )都有一個私有屬性(稱之為 proto )指向它的構(gòu)造函數(shù)的原型對象(prototype )坡脐。該原型對象也有一個自己的原型對象( proto ) ,層層向上直到一個對象的原型對象為 null房揭。根據(jù)定義备闲,null 沒有原型,并作為這個原型鏈中的最后一個環(huán)節(jié)捅暴。

幾乎所有 JavaScript 中的對象都是位于原型鏈頂端的 Object 的實例恬砂。

1.5 繼承

JS中使用的是基于對象的繼承,即原型式(prototype)繼承蓬痒。比如泻骤,Function B繼承Function A

  • 類型被定義在 .prototype
  • Object.create() 來繼承
function A(){};
A.prototype = {
    varA: "AAA",
    doSomething: function(){
        console.log("AAA...")
    }
}

function B(){};
B.prototype = Object.create(A.prototype, {
    varB: {
        value: "BBB", 
        enumerable: true, 
        configurable: true, 
        writable: true 
    },
    doSomething : { 
    value: function(){ // override覆寫
     // A.prototype.doSomething.apply(this, arguments); 
     console.log("BBB...")
    },
    enumerable: true,
    configurable: true, 
    writable: true
  }
})

B.prototype.constructor = B;


var b = new B();
b.doSomething(); // BBB...
console.log(b.varA); // AAA
console.log(b.varB); // BBB

還可以模仿基于類(class)的繼承:ECMAScript6 引入了一套新的關(guān)鍵字用來實現(xiàn) class,但JavaScript 仍然基于原型,這里只不過相當于語法糖乳幸,背后的實現(xiàn)還是原型式繼承瞪讼。這些新的關(guān)鍵字包括 class, constructorstatic粹断,extendssuper符欠。

"use strict";

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

class Student extends Person{
    // 構(gòu)造方法
    constructor(name,age,score){
        // super可以認為指代父類
        super(name,age)
        // 擴展
        this.score = score
    }
    get getScore() {
        return this.score;
    }
    set setScore(score) {
        this.score = score
    }
}

這里只是簡單的了解了一下JS中的繼承,后面會深入研究瓶埋。

1.6 JS中的設計模式

JS是如此的靈活希柿,有如此的簡單與復雜,為了更加方便維護代碼养筒,寫出可重用的代碼曾撤,還是得使用設計模式椎眯。

  • 可維護性潦闲,設計模式有利于降低模塊間的耦合,便于開發(fā)和維護煌抒。

  • 易于表達巫湘,使用不同的設計模式装悲,可以清晰的表達不同業(yè)務邏輯的實現(xiàn)意圖。

  • 提升性能尚氛,使用設計模式诀诊,在某些程度上可以減少代碼量。

2. <a name="interface">接口</a>

“函數(shù)要面向接口阅嘶,而不是實現(xiàn)” —— GoF《設計模式》

設計模式Java版

Design-Pattern

2.1 接口的概念

接口可以規(guī)定一個對象中應該具有哪些屬性和方法属瓣,但不用具體實現(xiàn)這些方法载迄,實現(xiàn)了這些接口的對象,必須實現(xiàn)這項屬性或方法抡蛙。也就是說接口的出現(xiàn)給對象增加了標準护昧。

2.2 接口的優(yōu)缺點

優(yōu)點:

  • 增加類的重用性。比如A和B均實現(xiàn)了接口C溜畅,若我會使用A類捏卓,那么B類也很容易就會使用了;或者我熟悉C接口慈格,不管誰實現(xiàn)了這個接口怠晴,我就能很容易去使用它。
  • 增加代碼的可讀性浴捆。比如我事先吧接口文檔寫好蒜田,然后實現(xiàn)了一些程序,當其他程序員來接手時选泻,只需要看接口文檔冲粤,就能很容易知道我的程序中類的特性,從而更容易更改程序以及實現(xiàn)自己的業(yè)務邏輯页眯。
  • 增加代碼的可維護性梯捕,接口可以降低對象間的耦合度。利用接口窝撵,各個組件之間只暴露少許接口共其他組件調(diào)用傀顾,如果修改了某個組件,并不會影響其他組件碌奉。
  • 測試和調(diào)試更方便短曾。如果一些類都實現(xiàn)了某個接口,但是卻沒能完全繼承接口中定義的標準赐劣,程序很容易就被發(fā)現(xiàn)嫉拐;如果業(yè)務擴展,接口需要新增一個方法魁兼,那么實現(xiàn)了這些接口的類婉徘,如果忘了添加這個方法,也會報錯咐汞。

缺點:

  • JS是弱類型語言盖呼,接口的使用原則會強化類型的作用,降低JS語言的靈活性碉考。
  • 接口一旦被定義,如果再次改動挺身,需要把所有實現(xiàn)了該接口的類改動侯谁,比如,某些類不需要這個方法,就會增加額外的開銷墙贱。
  • JS沒有提供對接口的內(nèi)置支持热芹,就算某些類實現(xiàn)了這個接口,如果不遵循接口的標準惨撇,并且不加嚴格的限制與檢查伊脓,是無法保證接口的實現(xiàn)的。

2.3 JS模仿接口

2.3.1 用注釋描述接口

用注釋描述接口是最簡單也是效果最差的方法魁衙。即使用interface 和 implements關(guān)鍵字表示接口报腔,但是將他們寫在注視中,因為JS并不認識剖淀,避免與法錯誤纯蛾。

通俗地講,注釋中告知纵隔,該類需現(xiàn)了這些接口翻诉,需要定義這些方法:

/* 定義接口
接口一
interface Composite{
    function add(child);
    function remove(child);
    function getChild(index);
}
接口二
interface FormItem{
    function save();
}
*/

var CompositeForm = function(id, method, action){// implements Composite,FormItem
};
// 實現(xiàn)接口Composite
CompositeForm.prototype.add = function(child){console.log("add...")}
CompositeForm.prototype.remove = function(child){console.log("remove...")}  
CompositeForm.prototype.getChild = function(index){console.log("getChild...")}   
// 實現(xiàn)接口FormItem
CompositeForm.prototype.save = function(){console.log("save...")}        

呵,有點東施效顰的味道捌刮。這個方式不能保證完全實現(xiàn)了接口碰煌,就算不按照接口來,也不會報錯绅作,對測試和調(diào)試也無幫助芦圾。但是勝在簡單,也確是實現(xiàn)了接口的意圖棚蓄。

2.3.2 用屬性檢查模仿接口

其實堕扶,這種方式就是在上面的方式基礎(chǔ)上,增加了屬性判斷梭依,判斷類中實現(xiàn)了那些接口稍算,稍微嚴謹一些。接口仍然寫在注釋中役拴。

var CompositeForm = function(id,method,action){
    // 顯示地申明糊探,本類實現(xiàn)了如下接口
    this.implementsInterfaces = ['Composite', 'FormItem'];
}

// 定義一個添加實例的方法,并檢測實例是否實現(xiàn)了接口
function addForm(formInstance) {
    // 判斷實例是否實現(xiàn)了接口河闰,否則拋出錯誤
    if(!implements(formInstance, 'Composite','FormItem')){
        throw new Error('Object does not implements a required interface!');
    }
}
// 定義判斷是否實現(xiàn)接口的方法
function implements(object) {
    for (var i = 1; i < arguments.length; i++) {
        var interfaceName = arguments[i];
        var interfaceFound = false;
        // 遍歷implementsInterfaces科平,看是否實現(xiàn)了對應的接口
        for (var j = 0; j < object.implementsInstances.length; j ++) {
            if (object.implementsInterfaces[j] == interfaceName) {
                interfaceFound = true; 
                break;
            }
        }
        if(!interfaceFound){
            return false; // 沒有實現(xiàn)接口
        }
    }
    return true; // 實現(xiàn)了接口
}

這里CompositeForm說他實現(xiàn)了某某接口,那就判斷一下姜性,發(fā)現(xiàn)確實實現(xiàn)了瞪慧,OK,否則就會跑出錯誤部念。但是這個簡陋的判斷并沒有保證弃酌,完全實現(xiàn)接口中定義的方法氨菇,所以還是會埋下隱患。

2.3.3 用鴨式辯型模仿接口

“像鴨子一樣走路并且嘎嘎叫的就是鴨子” —— James Whitcomb Riley鴨子類型

基于這種思想:類是否申明自己支持哪些接口并不重要妓湘,只要它具有這些接口中的方法就行查蓉。即如果對象具有與接口定義的方法同名的所有方法,那么就可以認為它實現(xiàn)了這個接口榜贴。

var Interface = function(name, methods) {
    // 保證傳入方法名和方法集合豌研,兩個參數(shù)
    if (arguments.length !== 2) {
        throw new Error(
            "Interface constructor called with " + arguments.length + "arguments, but expected exactly 2."
        );
    }

    this.name = name;
    this.methods = [];
    for (var i = 0, len = methods.length; i < len; i++) {
        // 方法名類型string
        if (typeof methods[i] !== "string") {
            throw new Error(
                "Interface constructor expects method names to be passed in as a string."
            );
        }
        this.methods.push(methods[i]);
    }
};
// 定義static方法,可以直接通過類名調(diào)用而不用實例化
Interface.ensureImplements = function(object) {
    if (arguments.length < 2) {
        throw new Error(
            "Function Interface.ensureImplements called with " + arguments.length + " arguments, but expected at least 2."
        );
    }

    for (var i = 1, len = arguments.length; i < len; i++) {
        var interface = arguments[i];
        if (interface.constructor !== Interface) {
            throw new Error(
                "Function Interface.ensureImplements expects arguments two and above to be instance of Interface."
            );
        }
        for (var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
            var method = interface.methods[j];
            if (!object[method] || typeof object[method] !== "function") {
                // 拋出錯誤:沒有實現(xiàn)接口以及方法沒有找到
                throw new Error(
                    "Function interface.ensureImplements:object does not implements the " +
                        interface.name + "interface. Method " + method + " was not found"
                );
            }
        }
    }
};

上面的類定義了接口以及接口的檢測唬党,可以作為工具類使用鹃共。


// 使用:
// 定義接口,以及接口中的方法
var Composite = new Interface("Composite", ["add", "remove", "getChild"]);
var FormItem = new Interface("FormItem", ["save"]);

var CompositeForm = function(id, method, action) {
    // implements Composite,FormItem
};

var myForm = new CompositeForm();
// 實現(xiàn)接口
myForm.prototype.add = function(){}
myForm.prototype.remove = function(){}

// 檢測實例是否完全實現(xiàn)了接口
function addForm(formInstance) {
    Interface.ensureImplements(formInstance, Composite, FormItem);
    // 其他操作
}
addForm(myForm);

Interface.ensureImplements方法會對實例嚴格檢測初嘹,如果實例中沒有完全實現(xiàn)對應的接口就會拋出錯誤及汉。這樣,我們就實現(xiàn)了接口的概念屯烦。

2.3.4 依賴于接口的設計模式

  • 工廠模式
  • 組合模式
  • 裝飾者模式
  • 命令模式

3. 封裝

封裝(encapsulation)就是將不必要暴露的方法或?qū)傩噪[藏起來坷随,不讓外部方法訪問,以防止篡改或濫用驻龟,降低代碼間的耦合度温眉,提升安全性。

JS中沒有像Java中的關(guān)鍵字private翁狐,實現(xiàn)封裝的方法——閉包(closure)类溢。

3.1 類中屬性的私有化

在類中,一般使用下劃線開頭來定義私有屬性露懒,這是總所周知的命名規(guī)范闯冷,它表明一個屬性(或方法)僅提供在對象內(nèi)部使用。

注意懈词,這種方式只是一種約定蛇耀,并不能強制性決定外部不能訪問。

// 定義一個三角形類坎弯,構(gòu)造方法
var Triangle = function(x,y,z){
    this.setX(x);
    this.setY(y);
    this.setZ(z);
}

// 判斷能否構(gòu)成三角形纺涤,靜態(tài)方法,通過類名調(diào)用抠忘,不用new實例化
Triangle.isTriangle = function(x,y,z){
    return x+y>z && x+z>y && y+z>x;
}
// 實例方法撩炊,必須new之后才能調(diào)用
Triangle.prototype = {
    setX: function(x){
        this._x = x;
    },
     setY: function(y){
        this._y = y;
    },
     setZ: function(z){
        this._z = z;
    },
    
    getX: function(){
        if(!Triangle.isTriangle(this._x,this._y,this._z)){
            throw new Error('不能構(gòu)成三角形!')
        }
        return this._x;
    },
    getY: function(){
          if(!Triangle.isTriangle(this._x,this._y,this._z)){
            throw new Error('不能構(gòu)成三角形!')
        }
        return this._y;
    },
    getZ: function(){
          if(!Triangle.isTriangle(this._x,this._y,this._z)){
            throw new Error('不能構(gòu)成三角形!')
        }
        return this._z;
    } 
}

這樣一來,雖然我們傳進去的參數(shù)是x,y,z崎脉,我們是直接獲取不到的拧咳,可以通過get方法獲取。

var san = new Triangle(3,4,5);
san.x; // undefined;
san.getX(); // 3
san._x; // 3

但是囚灼,我們其實是可以通過_x獲取的骆膝。加上下劃線只是表明這些屬性是私有的砾淌,最好不要直接訪問。

3.2 作用域谭网、嵌套函數(shù)和閉包

在JS中,只有函數(shù)具有作用域赃春。即愉择,在一個函數(shù)內(nèi)部聲明的變量在函數(shù)外部通常是無法訪問的。

function outer(){
    var a=10;
    
    function inner(){
        a *= 2;
    }
    inner();
    return a;
}

outer(); // 20
outer(); // 20
a; // undefined

這里织中,在函數(shù)內(nèi)部定義函數(shù)锥涕,內(nèi)部函數(shù)訪問了外部函數(shù)定義的變量a,函數(shù)外面是訪問不到變量a的狭吼。即层坠,函數(shù)內(nèi)部有局部作用域。那么刁笙,如果想訪問變量a呢?

function outer(){
    var a=10;
    // 將內(nèi)部函數(shù)返回
    return function(){
        a *= 2;
        return a;
    };
}

var test = outer();
test(); // 20
test(); // 40
test(); // 60

var demo = outer();
demo(); // 20

這樣就形成了閉包破花。閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合。

閉包疲吸,即通過函數(shù)內(nèi)部定義函數(shù)座每,在函數(shù)外面通過訪問這個內(nèi)部函數(shù)(可以訪問外部函數(shù)的定義的變量)從而實現(xiàn)了操作函數(shù)內(nèi)的局部變量的現(xiàn)象。

閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁摘悴。通過閉包峭梳,可以訪問函數(shù)內(nèi)的局部變量。

var Triangle = (function(){
    // private static attributes 私有靜態(tài)屬性
    var id = 0;
    // private static method 私有靜態(tài)方法
    function yy(){
        console.log('I am the best tiangle!')
    }
    yy();
    
    // return constructor 返回構(gòu)造方法
    return function(x,y,z){
        // private attributes 私有屬性
        var _x,_y,_z;
        console.log(this);
        // privileged methods 特權(quán)方法
        this.getX = function(){
            return _x;
        }
        this.setX = function(x){
            _x = x;
        }
        this.getY = function(){
            return _y;
        }
        this.setY = function(y){
            _y = y;
        }
        this.getZ = function(){
            return _z;
        }
        this.setZ = function(z){
            _z = z;
        }
        this.getId = function(){
            return id;
        }
        // Constructor code 構(gòu)造區(qū)代碼
        id++; // 統(tǒng)計Triangle實例化對象個數(shù)
        this.setX(x);
        this.setX(y);
        this.setX(z);
    }
})()

// public static method 公有靜態(tài)方法
Triangle.isTriangle = function(x,y,z){
     return x+y>z && x+z>y && y+z>x; 
}

// public no-privileged methods 公有非特權(quán)方法
Triangle.prototype = {
    
}

// 判斷能不能構(gòu)成三角形
if(Triangle.isTriangle(3,4,5)){
   var t1 = new Triangle(3,4,5) 
   t1.getId(); // 1
    t1.getX(); // 3
    
    var t2 = new Triangle(3,4,5);
    t1.getId(); // 2
}

下面蹂喻,我們來理解一下這些“新概念”:

var A = (function(){
    // 這里定義私有屬性和方法
    var haha = "我沒有鄙視你!反正你也看不到葱椭!";
    function hehe(){
        console.log(haha);
    }
    hehe();
    
    // 返回構(gòu)造方法
    return function(x){
        // 定義私有屬性
        var _x;
        // 定義特權(quán)方法,外部通過特權(quán)方法訪問和操作內(nèi)部私有屬性
        this.getX = function(){
            return _x;
        }
        this.setX = function(x){
            _x = x;
        }
        // 通過特權(quán)方法口四,不僅可以訪問內(nèi)部函數(shù)的局部變量孵运,還可以訪問外部函數(shù)的局部變量
         this.getHaha = function(){
            console.log("自己暴露了自己!G宰!掐松!(⊙o⊙)…完蛋,被發(fā)現(xiàn)了粪小!");
            return haha;
        }
        
       // 構(gòu)造區(qū)代碼塊,只要實例化就會被調(diào)用
        this.setX(x);
        console.log("我被實例化了");
    }
})()

// 普通公有方法(實例屬性)大磺,不能訪問內(nèi)部變量
A.prototype = {
    say: function(){
        console.log("我是公有方法,你需要new之后才能調(diào)用");
    },
    sayHi: function(){
        console.log("Hello world!");
    },
    hehe: "heheheheh"
};

A.staticMethod = function(){
    console.log("我是靜態(tài)方法探膊,你不要new杠愧,就可以通過類名調(diào)用");
}

這樣,我們就實現(xiàn)了對類的私有屬性以及方法的封裝逞壁。需要理解的概念有閉包流济,靜態(tài)方法锐锣,原型鏈new關(guān)鍵字绳瘟,作用域雕憔,構(gòu)造方法

4. <a name="extend">繼承</a>

4.1 類式繼承

// 定義一個Person類
function Person(name) {
    this.name = name;
}

Person.prototype.getName = function(){
    return this.name;
}
Person.prototype = {
    className: "Person",
    getName: function(){
        return this.name
    },
}
    
// 使用new關(guān)鍵字實例化類
var p = new Person('Tom');
p.getName();

下面糖声,通過原型鏈來實現(xiàn)繼承:

// 定義一個Author類,繼承Person
function Author(name, books) {
    // 1.調(diào)用父類構(gòu)造方法
    Person.call(this, name);
    this.books = books;
}
// 2.將子類的原型指向父類
Author.prototype = new Person();
// 3.更新子類的構(gòu)造方法
Author.prototype.constructor = Author;
// 多態(tài)斤彼,子類實現(xiàn)自己的屬性
Author.prototype = {
    className: "Author",
    getBooks: function(){
        return this.books;
    }
}

// 使用new關(guān)鍵字實例化類
var a = new Author('Jerry', 'Tom and Jerry!');
a.getName(); // Jerry

這樣就實現(xiàn)了Author類繼承Person類的操作。三步走:

  • 1.子類創(chuàng)造構(gòu)造函數(shù)蘸泻,并在構(gòu)造函數(shù)中調(diào)用父類的構(gòu)造函數(shù)琉苇,將參數(shù)傳給父類。Person.call(this,name)

  • 2.設置子類的原型鏈(prototype,要么指向另一個對象悦施,要么指向null)并扇,使他指向父類對象。Author.prototype=new Author()

  • 3.將子類的構(gòu)造方法更新為子類對象抡诞。Author.prototype.construtor=Author

    原型鏈查找:在訪問對象的某個屬性時(如name屬性)穷蛹,首先在該類的實例屬性上(Author.name)查找,沒找到昼汗,就會在該類的原型上查找(Author.__proto__),即prototype所指向的對象俩莽;如果還沒找到,繼續(xù)向上查找(Author.__proto__.__proto__),直到直到找到這個屬性或者查到原型鏈最頂端(Object.prototype最終指向null)乔遮。

4.2 封裝繼承的方法

為了簡化類的聲明扮超,ES6中實現(xiàn)了extends關(guān)鍵字,如果我們自己實現(xiàn)一個extend函數(shù)呢蹋肮?

/*extend function*/
function extend(subClass, superClass){
    // 空函數(shù)F出刷,避免創(chuàng)建超類的實例
    var F = function(){};
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
   
    subClass.prototype.constructor = subClass;
}

// 實現(xiàn)繼承
function Author(name,books){
    // 更改父類的this指向,相當于ES6繼承中的super
    Person.call(this,name);
    this.books=books;
}
// 繼承
extend(Author,Person);

// 必須繼承之后再新增方法
Author.prototype.getBooks = function(){
    return this.books;
}

上面可以實現(xiàn)繼承坯辩,子類中需要直接調(diào)用父類的構(gòu)造方法Person.call(this,name)馁龟,來更新this指向,否則子類獲取不到this對象漆魔。下面對這一部分進行改善:

/*extend function*/
function extend(subClass, superClass){
    // 空函數(shù)F坷檩,避免創(chuàng)建超類的實例
    var F = function(){};
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
    
    // superclass屬性弱化子類和超類之間的耦合
    // 將父類保存起來
    subClass.superclass = superClass.prototype;
    if(superClass.prototype.constructor==Object.prototype.constructor){
        subClass.prototype.constructor = superClass;
    }
}

// 實現(xiàn)繼承
function Author(name,books){
    // 改善
    Author.superclass.constructor.call(this,name);
    this.books=books;
}
extend(Author,Person);

Author.prototype.getBooks = function(){
    return this.books;
}

4.3 原型式繼承

原型式繼承和類式繼承截然不同。原型式繼承改抡,要從對象的角度考慮矢炼。下面我們用對象的方式重新實現(xiàn)上面的Person、Author類的繼承:

  • 1.用一個類聲明定義對象的結(jié)構(gòu)

    /* Person Prototype Object*/
    var Person = {
        name: 'person',
        getName: function(){
            return this.name;
        }
    }
    
  • 2.實例化該類創(chuàng)建一個新對象

    var Author = clone(Person);
    Author.name = 'author'
    Author.books = [];
    Author.getBooks = function(){
        return this.books;
    }
    
    // 每次實例化都要使用clone方法
    var author = clone(Author);
    author.name = 'zkk'
    author.books = ['JavaScript設計模式', '深入淺出ES6'];
    author.getName();
    author.getBooks();
    
  • 3.編寫clone方法阿纤,實現(xiàn)繼承

    // 創(chuàng)建一個以superClass為原型的空對象
    function clone(superClass) {
        function F(){};
        F.prototype = object;
        return new F;
    }
    

4.4 類式繼承與原型式繼承

類式繼承的用法更廣泛句灌,一般封裝的庫,都是用這種方式。

原型式繼承一般用得比較少胰锌,但是最好還是要理解這種方式骗绕。

原型式繼承更能節(jié)省內(nèi)存空間,并且更簡潔资昧。但類式繼承因為是模仿傳統(tǒng)語言的繼承方式酬土,更容易被理解,所以被廣泛應用格带。

鑒于此诺凡,一般建議還是使用類式繼承,但不要忘記還有原型式繼承践惑。

4.5 部分繼承——摻元類(Mixin Class)

摻元類(或混合類 mixin class): 即通過擴充(augmentation)的方式擁有某些類中的方法而不用繼承這些類,那么提供這些方法的類(被共享方法的類)就是摻元類。

  1. 定義mixin class提供方法:

     // 定義一個Mixin class 
    var Mixin = function(){};
    Mixin.prototype = {
        serialize: function(){
            var output = [];
            for(key in this){
                output.push(key + ': ' + this[key]);
            }
            return output.join(', ');
        },
        other: function(){};
    }
    
  2. 將mixin class中提供的函數(shù)擴充到其他類中:

    // 利用augemnt將Mixin中的serialize方法擴充到Author類中
    augment(Author, Mixin, 'serialize')
    var author = new Author('Joe', ['HTML','CSS']);
    var str = author.serialize();
    
  3. 實現(xiàn)擴充方法augment:

    // 參數(shù):接受的類嘶卧,提供的類尔觉,方法1,方法2芥吟,侦铜。。钟鸵。(方法名為可選參數(shù))
    function augment(receive, giveClass){
        if(arguments[2]){ // 明確要擴充的方法钉稍,那么只提供這些方法
            for(var i=2,len=arguments.length;i<len;i++){
                receiveClass.prototype[arguments[i]] = giveClass.prototype[arguments[i]];
            }
        } else { // 沒有傳入要擴充的方法,默認全部提供
            for(methodName in giveClass.prototype){
                if(!receiveClass.prototype[methodName]){
                    receiveClass.prototype[methodName] = giveClass.prototype[methodName];
                }
            }
        }
    }
    

    JS中一個對象只能有一個原型對象棺耍,所以不允許子類繼承多個父類贡未,但是可以通過摻元類的方式實現(xiàn)擴充多個類中的不同方法。

5. 鏈式調(diào)用

5.1 什么是鏈式調(diào)用蒙袍?

例如俊卤,在Jquery項目中,有:

$("#id").addClass('active').siblings().removeClass('active');

這就是 鏈式調(diào)用,即利用.操作符害幅,在目標對象上連續(xù)調(diào)用函數(shù)的方式消恍。這種方式,可以減少代碼量以现,但是會覆蓋上一次函數(shù)的返回值狠怨。

5.2 鏈式調(diào)用原理

鏈式調(diào)用的核心技術(shù)就是在每個方法后面返回當前對象,即return this邑遏,比如:一個人佣赖,吃完飯,工作记盒,然后睡覺茵汰,實現(xiàn)如下:

function Person(){};
Person.prototype = {
    eat: function(){
        console.log("eat...");
    },
    work: function(){
        console.log("work...")
    },
    sleep: function(){
        console.log("sleep...")
    }
}
// 調(diào)用
var p = new Person();
p.eat();
p.work();
p.sleep();

這是最基本的實現(xiàn)方式,如果用鏈式調(diào)用呢孽鸡?

function Person(){};
Person.prototype = {
    eat: function(){
        console.log("eat...");
        return this;
    },
    work: function(){
        console.log("work...");
        return this;
    },
    sleep: function(){
        console.log("sleep...");
        return this;
    }
}
// 調(diào)用
var p = new Person();
p.eat().work().sleep();

// 或者
new Person().eat().work().sleep();

通過改造蹂午,在每個方法中都返回了當前對象栏豺,即return this,這樣每次函數(shù)的返回值都是當前對象實例豆胸,就可以繼續(xù)調(diào)用它的方法了奥洼。

5.3 使回調(diào)函數(shù)從鏈式調(diào)用中獲取數(shù)據(jù)

發(fā)現(xiàn)一個問題,就是如果我想要方法返回數(shù)據(jù)晚胡,而不是this對象灵奖,那么怎么實現(xiàn)呢?

鏈式調(diào)用適合于賦值器方法估盘,但對于取值器方法(返回特定數(shù)據(jù))并不友好瓷患,但我們可以使回調(diào)函數(shù)獲取所要的數(shù)據(jù)∏餐祝或者在特定的get方法后面直接返回數(shù)據(jù)而不是當前對象擅编。

function Person(name){
    var _name = name;
    // 特權(quán)方法
    this.setName = function(name){
        _name = name;
        return this;
    };
    this.getName = function(){
        // 這里沒有返回this
        return _name;
    }
}

// 這里只能先調(diào)用setName,在鏈式調(diào)用get
new Person('zkk').setName('ZKKK').getName();
new Person('zkk').getName();
// 而不能先get在set
new Person('zkk').getName().steName();
var myname;
function getMyName(name){
    myname = name;
}

// 改造上面的getName
this.getName = function(callback){
    // 通過回調(diào)函數(shù)獲取name
    callback.call(this, _name)
    return this;
};

// 這時,我就可以隨意獲取或者修改name了
new Person('zkk').getName(getMyName).setName('zkkk').getName(getMyName)...

5.4 設計自己的JS庫——支持鏈式調(diào)用

// 定義自己的庫
(function(window){
    // 給Function添加新的方法箫踩,使每個函數(shù)實例都能使用
Function.prototype.method = function(name, fn){
    this.prototype[name] = fn;
    return this;
}   
    
    function _zkk(name, age){
        this.name = name;
        this.age = age;
    };
    
    zkk.method('getName', function(){
        return this.name;
    });
    zkk.method('setName', function(name){
        this.name = name;
        return this;
    });
    
    // 掛載到window上
    window.zkk = function(name,age) {
        return new _zkk(name,age);
    }
})(window)  

6. 單例模式(Singleton)

6.1 基本概念

單體類爱态,只有一個實例對象,即只會被實例化一次境钟,所有的屬性共享相同的資源锦担。

通過對象字面量創(chuàng)建一個最簡單的Singleton:

因為JS中普通的對象是不能通過new關(guān)鍵字來實例化的,所以它只有一個實例對象就是它本身慨削。

/*Basic Singleton*/
var Singleton = {
    key1: "value1",
    key2: "value2",
    
    func1: function(){},
    func2: function(){}
}

6.2 基本用途

利用單例模式劃分命名空間

比如:A.jsB.js中都有一個方法sayHi()

// A.js
function sayHi(){console.log('Hi! AAA')}
// B.js
function sayHi(){console.log('Hi! BBB')}

當兩者都被引入同一個文件時洞渔,后引入的會覆蓋縣引入的,這是通過命名空間就可以解決這個問題缚态。

/* use namespace*/
var A = {
    sayHi:function(){
        console.log('Hi! AAA')
    }
};

var B = {
    sayHi:function(){
        console.log('Hi! BBB')
    }
};
// 通過命名空間調(diào)用痘煤,不會產(chǎn)生覆蓋的問題很容易區(qū)分
A.sayHi(); 
B.sayHi();

6.3 私有屬性的單例——閉包

// 創(chuàng)建擁有私有變量的單例---閉包
var ss  = (function() {
    // private attribute
    var name = "我是private屬性"
    // public attribute
    return {
        key1: "我是lublic屬性;
        ",
        key2: "value2",
        
        // public訪問私有屬性
        func1: function() {
            console.log(name);
        },
        func2: function() {}
    };
})();

// 測試
var a = ss;
var b = ss;
a.key1; // 我是lublic屬性;
b.key1; // 我是lublic屬性;

a.key1 = "aaa";
b.key1; // aaa;

a===b; // true

單例類一旦被實例化之后,所有的實例(其實就只有一個實例化對象)都共享相同的資源猿规,改變其中一個衷快,另一個也會鞥這改變。利用這個特性我們可以使多個對象共相同的全局資源姨俩。

6.4惰性實例化

單體對象都是在被一加載時實例化的蘸拔,能不能在我需要的時候再實例化呢?這樣可以節(jié)省資源环葵,這就是惰性實例化调窍,即懶加載技術(shù)(lazy loading)。懶加載單體的關(guān)鍵是借助一個靜態(tài)方法(static method)张遭,調(diào)用Singleton.getInstance().methodName()來實現(xiàn)邓萨。getInstance方法會檢測單體是否已經(jīng)被實例化,如果還沒有就創(chuàng)建并返回實例。

  • 1.把單體的所有代碼移到private方法constructor
  • 2.通過public方法getInstance調(diào)用private方法constructor來控制實例的創(chuàng)建
  • 3.通過private屬性uniqueInstance來檢測單例是否已經(jīng)實例化
var Singleton = (function() {
    // 保存單體實例化對象
    var uniqueInstance;
    // 將所有的單體內(nèi)容包裹在這個私有方法里面
    function constructor() {
        // private attribute
        var name = "我是private屬性";
        // public attribute
        return {
            key1: "我是lublic屬性",
            key2: "value2",

            // public訪問私有屬性
            func1: function() {
                console.log(name);
            },
            func2: function() {}
        };
    }
    
    return {
        // 通過這個public方法暴露出去
        getInstance: function(){
            if(!uniqueInstance){ // 檢測是否已經(jīng)實例化
                uniqueInstance = constructor();
            }
            return uniqueInstance;
        }
    }
})();

// 測試
Singleton.getInstance().func1();

簡單梳理一下缔恳,結(jié)構(gòu)如下:

var singleton = (function(){
    // 保存單體類對象
    var uniqueInstance;
    function constructor(){
        // 單體類主體
        return {}
    }
    return {
        getInstance: function(){
            // 控制單體類實例化
            if(!uniqueInstance){
                uniqueInstance = constructor();
            }
            return uniqueInstance;
        }
    }
})();

6.5 分支技術(shù)

分支(branching)是一種用來把瀏覽器見得差異封裝到在運行期間進行設置的動態(tài)方法中的技術(shù)宝剖。比如創(chuàng)建XHR對象,大多數(shù)瀏覽器使用new XMLHttpRequest()創(chuàng)建歉甚,低版本IE確是用new ActiveXObject("Microsoft.XMLHTTP")來創(chuàng)建万细,如果不使用分支技術(shù),每次調(diào)用的時候纸泄,瀏覽器都要檢測一次赖钞。可以利用分支技術(shù)聘裁,在初始化的時候加載特定的代碼雪营,再次調(diào)用的時候就不要再次檢測了。

大概邏輯如下:

var s = 'A';
var Singleton = (function(str){
    var A = {
        func1: function(){}
    };
    var B = {
        func1: function(){}
    };
    
    return str === 'A'? A: B;
})(s)

封裝好的創(chuàng)建XHR對象的函數(shù):

function createXMLHttpRequest() {
    try {
        // 主流瀏覽器
        return new XMLHttpRequest();
    } catch (e) {
        try {
            // IE6
            return new ActiveXObject("Msxml2.XMLHTTP");
        } catch(e) {
            try {
                // IE5.5及以下
                return new ActiveXObject("Microsoft.XMLHTTP");
            } catch (e) {
                // 未知
                throw e;
            }
        }
    }
}

下面使用分支技術(shù)創(chuàng)建XHR對象:

var XHRFactory = (function() {
    var standard = {
        createXMLHttpRequest: function() {
            return new XMLHttpRequest();
        }
    };
    var activeXNew = {
        createXMLHttpRequest: function() {
            return new ActiveXObject("Msxml2.XMLHTTP");
        }
    };
    var activeXOld = {
        createXMLHttpRequest: function() {
            return new ActiveXObject("Microsoft.XMLHTTP");
        }
    };

    try {
        standard.createXMLHttpRequest();
        return standard;
    } catch (e) {
        try {
            activeXNew.createXMLHttpRequest();
            return activeXNew;
        } catch (e) {
            try {
                activeXOld.createXMLHttpRequest();
                return activeXOld;
            } catch (e) {
                throw e;
            }
        }
    }
})();

這樣只需調(diào)用XHRFactory.createXMLHttpRequest()就可以獲取特定瀏覽器環(huán)境的XML對象衡便。這里并不是說用分支創(chuàng)建的XHR方式更好献起,而是強調(diào)怎么使用分支技術(shù)來實現(xiàn)按需加載。

7. 工廠模式(Factory)

如果一個類中有成員屬性是另一個類的實例砰诵,常規(guī)操作是用new關(guān)鍵字類構(gòu)造方法創(chuàng)建實例,這樣一來捌显,就會導致兩個類產(chǎn)生依賴性茁彭,即耦合度,為了降低耦合度扶歪,可以考慮使用工廠模式理肺。

7.1 初探工廠模式

比如超市提供各色商品,顧客去超市買食物:

var Market = function() {};
Market.prototype = {
    getFood: function(name) {
        var food;
        switch (name) {
            case 'milk':
                // 超市自己制造的食品
                food = new Milk();
                break;
            case 'bread':
                food = new Bread();
                break;
            default:
                food = null;
        }
        // food通過檢查了嗎善镰?
        return food;
    },
};

function Milk(){};
function Bread(){};

new Market().getFood('milk');

很顯然妹萨,超市不應該可以制造食物,而是提供食物炫欺,食物應該交由食品加工廠制造乎完,而且還要確保制造的食物是健康的、可食用的品洛,即校驗合格的树姨。

下面,將食品加工交給食品加工廠桥状,并且要校驗合格:

// 食物標準:健康帽揪、綠色、可食用
var Food = new Interface('Food', ['isHealth', 'isGreen', 'isEdible']);

// 食物加工廠
var FoodFactory = {
    createFood: function(name) {
        var food;
        switch (name) {
            case "milk":
                food = new Milk();
                break;
            case "bread":
                food = new Bread();
                break;
            default:
                food = null;
        }
        // 食物檢測
        Interface.ensureImplements(food,Food);
        return food;
    }
};


function Milk(){};
// 實現(xiàn)標準
Milk.prototype = {
    isHealth: function(){},
    isGreen: function(){},
    isEdiable: function(){}
}

var Market = function() {};
Market.prototype = {
    getFood: function(name) {
        var food = FoodFactory.createFood(name);
        food.isHealth();
        food.isGreen();
        food.isEdible();
        // 現(xiàn)在可以放心出售了
        return food;
    }
};

這里使用接口定義了食物標準辅斟,并且在工廠類中檢測是否實現(xiàn)了這些標準转晰。Interface類的具體實現(xiàn)請看第2章-接口2.2.3

這樣,我們把食物制造工作從超市移交給食品加工廠了查邢,完成了解耦操作蔗崎。這是只是最簡單的工廠模式,真正的工廠模式比這更復雜侠坎。

7.2 深入工廠模式

工廠是一個將其成員對象的實例化通過子類來實現(xiàn)蚁趁。

繼續(xù)用超市的例子來理解,現(xiàn)在超市盈利了实胸,開辦了自己的食品超市A專門自產(chǎn)自銷食品他嫡,食品加工不再借由外面的加工廠了,而是自己的食品超市自己加工(自產(chǎn)自銷)庐完。

var Market = function(){};
Market.prototype = {
    getFood: function(name)
        // 這里將成員對象實例化交給抽象類
        var food = this.createFood(name);
        
        food.isHealth();
        food.isGreen();
        food.isEdible();
        
        return food;
    },
    // 這是一個抽象類钢属,需要子類來實現(xiàn)它
    createFood: function(name){
        throw new Error('Unsupported operation on an abstract class.');
    }
}

上面 定義抽象類來實現(xiàn)成員實例化方法,但是抽象類是不能直接調(diào)用的门躯,它需要通過子類繼承才能被實例化淆党。

var FoodMarketA = function() {};
// 實現(xiàn)繼承
extend(FoodMarketA, Market);
// 實現(xiàn)抽象類方法
FoodMarketA.prototype.createFood = function(name) {
    var food;
    switch (name) {
        case "milk":
            food = new Milk();
            break;
        case "bread":
            food = new Bread();
            break;
        default:
            food = null;
    }
    // 食物檢測
    Interface.ensureImplements(food, Food);
    return food;
};

// 這樣顧客就可以到專門的食品超市購買食物
var foodMarketA = new FoodMarketA();
var milk = foodMarketA.getFood('milk');

FoodMarketA繼承了Market,并且實現(xiàn)了抽象方法讶凉,Market就相當于一個的連鎖超市總部染乌,可以在全開連鎖店,不僅可以有FoodMarketA懂讯,還可以擴張FoodMarketB等荷憋,只需要實現(xiàn)繼承就可以了。

這里的繼承extend方法具體實現(xiàn)請參考第4節(jié)-繼承4.2褐望。

7.3 工廠模式利弊

  • 利:弱化對象間的耦合勒庄,防止代碼的重復。通過工廠模式瘫里,可以先創(chuàng)建一個抽象的父類实蔽,然后子類實現(xiàn)繼承實現(xiàn)具體的方法,從而把成員對象的創(chuàng)建交由專門的類去創(chuàng)建谨读。關(guān)鍵技術(shù):接口繼承局装,即制定標準和實現(xiàn)標準。
  • 弊:邏輯復雜劳殖,有可能使問題復雜化贼邓,代碼不夠清晰,閱讀代碼需要查接口以及繼承關(guān)系闷尿。

8. 橋接模式(Bridge)

8.1 橋接模式概述

“將抽象與其實現(xiàn)隔離開來塑径,以便二者獨立變化√罹撸” —— GOF

在實現(xiàn)API的時候统舀,橋接模式非常有用匆骗。再設計一個Javascript API的時候,可以與整個模式來弱化它與所有使用它的類和對象之間的耦合誉简。

在一個類中碉就,你可能定義有許多獲取方法(getter)、設置方法(setter)以及其他方法闷串,無論是用來創(chuàng)建Web服務API還是普通的取值器(accessor)方法還是賦值器(mutator)方法瓮钥,都借助橋接模式可以簡潔代碼。

8.2 用橋接模式聯(lián)結(jié)多個類

在現(xiàn)實中通過媒介可以把多個不同的事物聯(lián)結(jié)起來烹吵,例如:人用電腦打字碉熄,簡單實現(xiàn) 如下:

var People = function(name,age){
    this.name = name;
    this.age = age;
};
People.prototype = {
    type: function(){
        console.log('type...');
    }
};

var Computer = function(brand) {
    this.brand = brand;
};

var Word = function(text){
    this.text = text;
};

var Print = function(name,age,brand,text){
    this.people = new People(name,age);
    this.computer = new Computer(brand);
    this.word = new Word(text);
    
    this.print = function(){
        console.log('一個名叫' + this.people.name + this.people.age + '歲的人,用' + this.computer.brand + '電腦肋拔,打印出了一段話:' + this.word.text);
    } 
}

// 
var p = new Print('zkk', 24, 'Apple', 'Hello World!');
p.print();

這里定義了三個類:People锈津、ComputerWord凉蜂,通過橋接類Print將它們聯(lián)系在一起琼梆。Print不用關(guān)注具體的人、電腦窿吩、詞語是怎么產(chǎn)生的茎杂,只需調(diào)用即可。

8.3 接模式應用

比如手動實現(xiàn)一個最簡單的forEach遍歷數(shù)組:

Array.prototype.myForEach = function(fn) {
    for(var i=0,len=this.length;i<len;i++){
        fn.call(this, this[i], i, this);
    }
}

這里forEach不用關(guān)注回調(diào)函數(shù)fn的具體實現(xiàn)纫雁,這里只是調(diào)用一下煌往。回調(diào)函數(shù)fn想怎么實現(xiàn)就怎么實現(xiàn)先较,是壓根不影響forEach函數(shù)的携冤。

還有比如悼粮,事件監(jiān)聽回調(diào)闲勺、借助特權(quán)函數(shù)訪問私有數(shù)據(jù)的手段健蕊,等等都有用到橋接模式丝里。

記住翠忠,橋接模式的核心在于將抽象與具體實現(xiàn)分離忱详,完成解耦操作击孩。

8.4 橋接模式利弊

  • 利:將抽象與實現(xiàn)分離趾断,獨立管理各個部分的邏輯匆浙,降低耦合刃泌,有利于代碼的維護昧穿。
  • 弊:提高了系統(tǒng)的復雜度勺远。

9. 組合模式(composite)

9.1 理解組合模式

組合模式又稱部分-整體模式,將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu)时鸵,組合模式使得用戶對單個對象和組合對象的使用具有一致性胶逢。掌握組合模式的重點是要理解清楚 “部分/整體” 還有 ”單個對象“ 與 "組合對象" 的含義厅瞎。

組合模式主要有三個角色:

  • 抽象組件(Component):抽象類,主要定義了參與組合的對象的公共接口
  • 子對象(Leaf):組成組合對象的最基本對象
  • 組合對象(Composite):由子對象組合起來的復雜對象
組合模式結(jié)構(gòu)與二叉樹類似

9.2 組合模式應用

比如DOM操作中最常用的就是給一個元素添加或刪除一個樣式:

var div = document.getElementById('div');
div.classList.add('red');
div.classList.remove('green');
// 再來一個
var p = document.getElementById('p');
p.classList.add('red')
p.classList.remove('green');

每次操作都要調(diào)用各種方法初坠,很是繁瑣和簸,我想要一次操作大量的節(jié)點,就很是費力了碟刺。

var OperateClass = function(){
    this.domList = [];
};
OperateClass.prototype = {
    addDom: function(classNameOrId) {
        var dom = document.querySelector(classNameOrId);
        this.domList.push(dom);
    },
    removeClass: function(className) {
        for(var i=0,len=this.domList.length;i<len;i++){
            if(this.domList[i] && this.domList[i].classList.contains(className)){
                this.domList[i].classList.remove(className);
            }
        }
    },
    addClass:function(className) {
        for(var i=0,len=this.domList.length;i<len;i++){
            if(this.domList[i] && !this.domList[i].classList.contains(className)){
                this.domList[i].classList.add(className);
            }
        }
        }
}

var oc = new OperateClass();
oc.addDom('.item-1');
oc.addDom('.item-2');
oc.addDom('.item-3');

oc.addClass('red');
oc.addClass('bg-green');

通過定義一個組合對象锁保,把每個Dom操作都統(tǒng)一起來,可以實現(xiàn)一次性對大量Dom進行添加或者刪除className的操作半沽。

總結(jié)一下爽柒,完整的模式如下:

// 1.抽象組件(Component)定義公共接口
var Component = new Interface('Component', ['func1', 'func2']);

// 2.組合對象(Composite)
var Composite = function(){
    this.leafList = [];
};
// 3.實現(xiàn)繼承,并實現(xiàn)方法
extend(Composite, Component);
Composite.prototype = {
    add: function(leaf){
        this.leafList.push(leaf);
    },
    func1: function(){
        for(var i=0,len=this.leafList.length;i<len;i++){
            if(this.leafList[i] instanceof Component){
                this.leafList[i].func1();
            }
        }
    },
    func2:function(){
                for(var i=0,len=this.leafList.length;i<len;i++){
            if(this.leafList[i] instanceof Component){
                this.leafList[i].func2();
            }
        }
    }    
};
// 4.葉子對象(Leaf)
var Leaf1 = function(){};
extend(Leaf1, Component);

Leaf1.prototype = {
    func1: function(){
        console.log('func1...');
    },
    func2:function(){
        console.log('func2...');
    }
};
var Leaf2 = function(){};
Leaf2.prototype = {
    func1: function(){
        console.log('func1...');
    },
    func2:function(){
        console.log('func2...');
    }
}; 

// 操作所有葉子節(jié)點
var composite = new Composite();
composite.add(new Leaf1());
composite.add(new Leaf2());

composite.func1();
composite.func2();

這里定義了一個公共接口Component抄囚,所有的組件(CompositeLeaf)都繼承了這個接口霉赡,然后把Leaf組件打包給Composite ,由Composite一次性完成對Leaf所有的操作幔托。

上面代碼中使用了兩個函數(shù)interfaceextend穴亏,具體實現(xiàn)請看第2節(jié)和第4節(jié)。

9.3 組合模式利弊

  • 利:在組合模式中重挑,各個對象之間的耦合很松散嗓化,只要實現(xiàn)了相同的接口,相同的操作就可以很方便實現(xiàn)谬哀。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刺覆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子史煎,更是在濱河造成了極大的恐慌谦屑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篇梭,死亡現(xiàn)場離奇詭異氢橙,居然都是意外死亡,警方通過查閱死者的電腦和手機恬偷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門悍手,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人袍患,你說我怎么就攤上這事坦康。” “怎么了诡延?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵滞欠,是天一觀的道長。 經(jīng)常有香客問我肆良,道長筛璧,這世上最難降的妖魔是什么赤兴? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮隧哮,結(jié)果婚禮上桶良,老公的妹妹穿的比我還像新娘。我一直安慰自己沮翔,他們只是感情好陨帆,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著采蚀,像睡著了一般疲牵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榆鼠,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天纲爸,我揣著相機與錄音,去河邊找鬼妆够。 笑死识啦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的神妹。 我是一名探鬼主播颓哮,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸵荠!你這毒婦竟也來了冕茅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛹找,失蹤者是張志新(化名)和其女友劉穎姨伤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庸疾,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡乍楚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了彼硫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炊豪。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡凌箕,死狀恐怖拧篮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牵舱,我是刑警寧澤串绩,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站芜壁,受9級特大地震影響礁凡,放射性物質(zhì)發(fā)生泄漏高氮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一顷牌、第九天 我趴在偏房一處隱蔽的房頂上張望剪芍。 院中可真熱鬧,春花似錦窟蓝、人聲如沸罪裹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽状共。三九已至,卻和暖如春谁帕,著一層夾襖步出監(jiān)牢的瞬間峡继,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工匈挖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碾牌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓儡循,卻偏偏與公主長得像小染,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贮折,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容