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中的功能和面向对象编程的更多内容。

相关文章
相关标签/搜索