(渣渣小怪兽翻译,若是看翻译不开心能够看->原文)javascript
封装意味着隐藏信息.意思是尽量的隐藏对象内部的部分, 同时暴露出最小公共接口。java
最简单和最优雅的方式建立一个封装是使用闭包.能够将闭包建立为具备私有状态的函数.当建立了许多的闭包共享相同的私有状态, 咱们就会建立一个Object.编程
我将开始建立一些在咱们开发应用中比较实用的Object: Stack, Queue, Event Emitter, and Timer.以上Object的都使用工厂函数建立。数组
让咱们开始吧。promise
栈,是一种数据结构, 它有两个主要操做: 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
复制代码
若是我使用类完成相同的实现,则没有实现封装
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
队列是一种数据结构, 有两个主要操做:入队和出队。
入队: 往咱们的集合里增长元素。
出队: 移除在集合里最先加入的元素。
出队和入队操做遵循, 先进先出的原则。
这是使用队列的一个例子
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
复制代码
一个发布订阅机制是一个有发布和订阅的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
});
}
复制代码
订阅者的状态和通知方法是私有的。
众所周知, 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中的功能和面向对象编程的更多内容。