博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Here are some practical JavaScript objects that have encapsulation (翻译)
阅读量:7251 次
发布时间:2019-06-29

本文共 4531 字,大约阅读时间需要 15 分钟。

(渣渣小怪兽翻译,如果看翻译不开心可以看->)

封装意味着隐藏信息.意思是尽可能的隐藏对象内部的部分, 同时暴露出最小公共接口。

最简单和最优雅的方式创建一个封装是使用闭包.可以将闭包创建为具有私有状态的函数.当创建了许多的闭包共享相同的私有状态, 我们就会创建一个Object.

我将开始创建一些在我们开发应用中比较实用的Object: Stack, Queue, Event Emitter, and Timer.以上Object的都使用工厂函数创建。

让我们开始吧。

Stack

栈,是一种数据结构, 它有两个主要操作: push, 往这个集合里增加一个元素; pop, 移除最近添加的元素.其中,push和pop元素依据先进后出原则。

让我们看下一个例子:

let stack = Stack();stack.push(1);stack.push(2);stack.push(3);stack.pop(); //3stack.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复制代码

我不能直接修改内部状态:

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 stateconsole.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和工厂函数), 可以看一下这篇

Queue

队列是一种数据结构, 有两个主要操作:入队和出队。

入队: 往我们的集合里增加元素。

出队: 移除在集合里最早加入的元素。

出队和入队操作遵循, 先进先出的原则。

这是使用队列的一个例子

let queue = Queue();queue.enqueue(1);queue.enqueue(2);queue.enqueue(3);queue.dequeue(); //1queue.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)的内部部分。

您可以在我的一书中找到有关JavaScript中的功能和面向对象编程的更多内容。

转载地址:http://dlebm.baihongyu.com/

你可能感兴趣的文章
Python基础24_正则表达式,re模块,
查看>>
mysql 开源 ~ canal+otter系列(2)
查看>>
跟我一起写 Makefile (Linux )
查看>>
CC2640R2F&TI-RTOS 拿到 TI CC2640R2F 开发板 第二件事就是 LED 驱动 ,点个灯
查看>>
ELASTIC SEARCH 性能调优
查看>>
Java并发总结(三):中断线程
查看>>
Beer Refrigerator
查看>>
hadoop输入分片计算(Map Task个数的确定)
查看>>
TYVJ P1008 传球游戏
查看>>
MVC基础
查看>>
【BZOJ】 Hash Killer I II III
查看>>
为什么st2 chrome无法显示api中的例子
查看>>
Python 3.6 -win64环境安装PIL模块
查看>>
redis事务需要注意的坑------RedisConnectionFailureException
查看>>
SPOJ 4110 Fast Maximum Flow (最大流模板)
查看>>
ECMAScript面向对象(二)——之创建对象方法总结
查看>>
git实践:对比svn
查看>>
1 管理入门
查看>>
C#递归遍历指定目录下的所有文件(包括子目录下的文件)
查看>>
SpringMVC的工作流程
查看>>