(渣渣小怪獸翻譯,若是看翻譯不開心能夠看->原文)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中的功能和麪向對象編程的更多內容。