Here are some practical JavaScript objects that have encapsulation (翻譯)

(渣渣小怪獸翻譯,若是看翻譯不開心能夠看->原文)javascript

封裝意味着隱藏信息.意思是儘量的隱藏對象內部的部分, 同時暴露出最小公共接口。java

最簡單和最優雅的方式建立一個封裝是使用閉包.能夠將閉包建立爲具備私有狀態的函數.當建立了許多的閉包共享相同的私有狀態, 咱們就會建立一個Object.編程

我將開始建立一些在咱們開發應用中比較實用的Object: Stack, Queue, Event Emitter, and Timer.以上Object的都使用工廠函數建立。數組

讓咱們開始吧。promise

Stack

棧,是一種數據結構, 它有兩個主要操做: push, 往這個集合裏增長一個元素; pop, 移除最近添加的元素.其中,push和pop元素依據先進後出原則。瀏覽器

讓咱們看下一個例子:數據結構

let stack = Stack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.pop(); //3
stack.pop(); //2
複製代碼

讓咱們使用工廠函數實現一個stack:閉包

function Stack(){  
    let list = [];
    
    function push(value){ 
        list.push(value);
        
    }  
    function pop(){ 
        return list.pop(); 
    }    
    
    return Object.freeze({    
        push,    
        pop  
    });
}
複製代碼

這個棧對象有兩個公共方法push()和pop().內部的狀態只能經過這些方法去改變.函數

stack.list; // undefined
複製代碼

我不能直接修改內部狀態:ui

stack.list = 0; //Cannot add property list, object is not extensible
複製代碼

使用class實現Stack結構

若是我使用類完成相同的實現,則沒有實現封裝

let stack = new Stack();
stack.push(1);
stack.push(2);
stack.list = 0; //corrupt the private state
console.log(stack.pop()); //this.list.pop is not a function
複製代碼

這是我使用class實現的stack

class Stack {  
    constructor(){
        this.list = [];  
    }    

    push(value) { 
        this.list.push(value); 
    }  

    pop() { 
        return this.list.pop(); 
    }
}
複製代碼

若是要看更深的對比(class和工廠函數), 能夠看一下這篇class vs Factory function: exploring the way forward

Queue

隊列是一種數據結構, 有兩個主要操做:入隊和出隊。

入隊: 往咱們的集合裏增長元素。

出隊: 移除在集合裏最先加入的元素。

出隊和入隊操做遵循, 先進先出的原則。

這是使用隊列的一個例子

let queue = Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.dequeue(); //1
queue.dequeue(); //2
複製代碼

如下是隊列的實現:

function Queue(){  
    let list = [];    
    function enqueue(value){ 
        list.push(value);
    }  
    function dequeue(){ 
        return list.shift(); 
    }    
    return Object.freeze({    
        enqueue,    
        dequeue  
    });
}
複製代碼

就像咱們以前看到的, 這個對象的內部不能從外部直接訪問。

queue.list; //undefined
複製代碼

Event emitter

一個發佈訂閱機制是一個有發佈和訂閱的API的一個對象.它經常使用於一個應用裏兩個不一樣的部分的通訊.

來看一個使用的例子:

// 初始化事件對象
let eventEmitter = EventEmitter();
// 訂閱事件
eventEmitter.subscribe("update", doSomething);
eventEmitter.subscribe("update", doSomethingElse);
eventEmitter.subscribe("add", doSomethingOnAdd);

// 發佈(觸發以前的訂閱)
eventEmitter.publish("update", {});

function doSomething(value) { };
function doSomethingElse(value) { };
function doSomethingOnAdd(value) { };
複製代碼

首先, 我爲update事件訂閱了兩個函數,併爲add事件添加了一個函數.當事件觸發發佈「updata」事件的時候, doSomething和doSomethingElse都會被調用.

這是事件對象的一個簡單實現:

function EventEmitter(){  
    let subscribers = [];    
    function subscribe(type, callback){    
        subscribers[type] = subscribers[type] || [];    
        subscribers[type].push(callback);   
    }    
    function notify(value, fn){    
        try {      
            fn(value);
        } catch(e) { 
            console.error(e); 
        }  
    }    
    function publish(type, value){    
        if(subscribers[type]){      
            let notifySubscriber = notify.bind(null, value);      
            subscribers[type].forEach(notifySubscriber);    
        }  
    }    

    return Object.freeze({    
        subscribe,    
        publish  
    });
}
複製代碼

訂閱者的狀態和通知方法是私有的。

Timer

衆所周知, js中有兩個計時器函數:setTimeout 和 setInterval.我想以面向對象的方式與計時器一塊兒工做,最終將以以下的方式調用:

let timer = Timer(doSomething, 6000);
timer.start();
function doSomething(){}
複製代碼

可是setInterval函數有一些限制,在進行新的回調以前,它不會等待以前的回調執行完成。即便前一個還沒有完成,也會進行新的回調。更糟糕的是,在AJAX調用的狀況下,響應回調可能會出現故障。

遞歸setTimeout模式能夠解決這個問題.使用這個模式, 一個新的回調造成只能等前一個回到完成以後。

讓咱們建立一個TimerObject:

function Timer(fn, interval){
    let timerId;
    function startRecursiveTimer(){  
        fn().then(function makeNewCall(){    
            timerId = setTimeout(startRecursiveTimer, interval);  
        });
    }

    function stop(){  
        if(timerId){    
            clearTimeout(timerId);    
            timerId = 0;  
        }
    }

    function start(){  
        if(!timerId){    
            startRecursiveTimer();  
        }
    }

    return Object.freeze({  start,  stop});  
}

let timer = Timer(getTodos, 2000);timer.start();
複製代碼

只有start和stop方法是公共,除此以外, 全部的方法和變量都是私有的.調用setTimeout(startRecursiveTimer,interval)時,沒有this(指向問題)丟失的上下文問題,由於工廠函數不使用this。

計時器使用返回promise的回調。

如今, 咱們能夠很簡單的實現,當瀏覽器的tab隱藏的時候, 計時器中止, 當瀏覽器的tab顯示的時候, 計時器繼續計時.

document.addEventListener("visibilitychange", toggleTimer);

function toggleTimer(){   
    if(document.visibilityState === "hidden"){     
        timer.stop();   
    }   
    
    if(document.visibilityState === "visible"){     
        timer.start();   
    }
}
複製代碼

#總結 JavaScript提供了一種使用工廠函數建立封裝對象的獨特方法。對象封裝狀態。

Stack和Queue能夠建立爲基本數組功能的包裝器。

事件對象是在應用程序中的不一樣部分之間進行通訊的對象。

計時器對象易於使用。它有一個清晰的接口start()和stop()。您沒必要處理管理計時器標識(timerId)的內部部分。

您能夠在個人Discover Functional JavaScript一書中找到有關JavaScript中的功能和麪向對象編程的更多內容。

相關文章
相關標籤/搜索