资讯专栏INFORMATION COLUMN

浅谈js设计模式

seasonley / 2200人阅读

摘要:假设我们正在编写一个渲染广东省地图的页面,目前从第三方资源里获得了广东省的所有城市以及它们所对应的并且成功地渲染到页面中此处我们遍历老数据把它们添加到空对象中然后返回这个对象使用适配器模式可以解决参数类型有些许不一致造成的问题。

策略模式

</>复制代码

  1. 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。目的是将算法的使用与算法的实现分离开来。

一个基于策略模式的程序至少由两部分组成:

一组策略类,策略类封装了具体的算法,并且负责具体的计算过程。

环境类Context, Context接受客户的请求,随后把请求委托给某一个策略类。

</>复制代码

  1. validator = {
  2. validate: function (value, type) {
  3. switch (type) {
  4. case "isNonEmpty ":
  5. {
  6. return true; // NonEmpty 验证结果
  7. }
  8. case "isNumber ":
  9. {
  10. return true; // Number 验证结果
  11. break;
  12. }
  13. case "isAlphaNum ":
  14. {
  15. return true; // AlphaNum 验证结果
  16. }
  17. default:
  18. {
  19. return true;
  20. }
  21. }
  22. }
  23. };
  24. // 测试
  25. alert(validator.validate("123", "isNonEmpty"));

</>复制代码

  1. var validator = {
  2. // 所有可以的验证规则处理类存放的地方,后面会多带带定义
  3. types: {},
  4. // 验证类型所对应的错误消息
  5. messages: [],
  6. // 当然需要使用的验证类型
  7. config: {},
  8. // 暴露的公开验证方法
  9. // 传入的参数是 key => value对
  10. validate: function (data) {
  11. var i, msg, type, checker, result_ok;
  12. // 清空所有的错误信息
  13. this.messages = [];
  14. for (i in data) {
  15. if (data.hasOwnProperty(i)) {
  16. type = this.config[i]; // 根据key查询是否有存在的验证规则
  17. checker = this.types[type]; // 获取验证规则的验证类
  18. if (!type) {
  19. continue; // 如果验证规则不存在,则不处理
  20. }
  21. if (!checker) { // 如果验证规则类不存在,抛出异常
  22. throw {
  23. name: "ValidationError",
  24. message: "No handler to validate type " + type
  25. };
  26. }
  27. result_ok = checker.validate(data[i]); // 使用查到到的单个验证类进行验证
  28. if (!result_ok) {
  29. msg = "Invalid value for *" + i + "*, " + checker.instructions;
  30. this.messages.push(msg);
  31. }
  32. }
  33. }
  34. return this.hasErrors();
  35. },
  36. // helper
  37. hasErrors: function () {
  38. return this.messages.length !== 0;
  39. }
  40. };
  41. // 验证给定的值是否不为空
  42. validator.types.isNonEmpty = {
  43. validate: function (value) {
  44. return value !== "";
  45. },
  46. instructions: "传入的值不能为空"
  47. };
  48. // 验证给定的值是否是数字
  49. validator.types.isNumber = {
  50. validate: function (value) {
  51. return !isNaN(value);
  52. },
  53. instructions: "传入的值只能是合法的数字,例如:1, 3.14 or 2010"
  54. };
  55. // 验证给定的值是否只是字母或数字
  56. validator.types.isAlphaNum = {
  57. validate: function (value) {
  58. return !/[^a-z0-9]/i.test(value);
  59. },
  60. instructions: "传入的值只能保护字母和数字,不能包含特殊字符"
  61. };
  62. var data = {
  63. first_name: "Tom",
  64. last_name: "Xu",
  65. age: "unknown",
  66. username: "TomXu"
  67. };
  68. //该对象的作用是检查验证类型是否存在
  69. validator.config = {
  70. first_name: "isNonEmpty",
  71. age: "isNumber",
  72. username: "isAlphaNum"
  73. };
  74. validator.validate(data);
  75. if (validator.hasErrors()) {
  76. console.log(validator.messages.join("
  77. "));
  78. }

通过策略模式,消除了大片的条件分支语句。将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。同时,算法还可以复用在其他地方,从而避免许多重复的复制粘贴工作。

代理模式

</>复制代码

  1. 代理模式为一个对象提供一种代理以控制对这个对象的访问。

虚拟代理是我们最常用的代理模式,它把一些开销很大的对象,延迟到真正需要用到这个对象的时候才去创建

虚拟代理实现图片预加载

</>复制代码

  1. var addImg = (function(){
  2. var img = document.createElement("img");
  3. document.body.appendChild(img);
  4. return {
  5. setSrc: function(src){
  6. img.src = src;
  7. }
  8. }
  9. })();
  10. var proxyAddImg = (function(){
  11. var img = new Image();
  12. img.onload = function(){
  13. addImg.setSrc(this.src);
  14. }
  15. return {
  16. setSrc: function(src){
  17. addImg.setSrc("loading.gif");
  18. img.src = src;
  19. }
  20. }
  21. })();
  22. proxyAddImg.setSrc("demo.png");

虚拟代理合并Http请求
我们可以通过一个代理函数来收集一段时间之内的请求,最后把请求合并到一起发送给服务器

</>复制代码

  1. var proxySynData = (function(){
  2. var cache = [], //缓存我们需要同步的内容
  3. timer; //定时器
  4. return function(ID){
  5. if(!timer){ //定时器不存在就创建
  6. timer = setTimeout(function(){
  7. synData(cache.join()); //同步合并后的数据
  8. cache.length = 0; //清空缓存
  9. clearTimeout(timer); //清除定时器
  10. timer = null; //方便垃圾回收
  11. }, 2000);
  12. }
  13. cache.push(ID); //存入缓存
  14. }
  15. })();
  16. var list = document.getElementsByTagName("input");
  17. for(var i = 0, item; item = list[i]; i++){
  18. item.onclick = function(){
  19. if(this.checked){
  20. proxySynData(this.id);
  21. }
  22. };
  23. }

缓存代理
缓存代理就很好理解了,可以缓存一些开销很大的运算结果;如果你第二次执行函数的时候,传递了同样的参数,那么就直接使用缓存的结果,如果运算量很大,这可是不小的优化

</>复制代码

  1. var add = function(){
  2. var sum = 0;
  3. for(var i = 0, l = arguments.length; i < l; i++){
  4. sum += arguments[i];
  5. }
  6. return sum;
  7. };
  8. var proxyAdd = (function(){
  9. var cache = {}; //缓存运算结果的缓存对象
  10. return function(){
  11. var args = Array.prototype.join.call(arguments);//把参数用逗号组成一个字符串作为“键”
  12. if(cache.hasOwnProperty(args)){//等价 args in cache
  13. console.log("使用缓存结果");
  14. return cache[args];//直接使用缓存对象的“值”
  15. }
  16. console.log("计算结果");
  17. return cache[args] = add.apply(this,arguments);//使用本体函数计算结果并加入缓存
  18. }
  19. })();
  20. console.log(proxyAdd(1,2,3,4,5)); //15
  21. console.log(proxyAdd(1,2,3,4,5)); //15
  22. console.log(proxyAdd(1,2,3,4,5)); //15
观察者模式

</>复制代码

  1. 观察者模式又叫发布-订阅模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知。

定义一个事件对象,它有以下功能

监听事件(订阅公众号)

触发事件(公众号发布)

移除事件(取订公众号)

</>复制代码

  1. // subscription.js
  2. var CLEARED = null;
  3. var nullListeners = {
  4. notify: function notify() {}
  5. };
  6. function createListenerCollection() {
  7. // the current/next pattern is copied from redux"s createStore code.
  8. // TODO: refactor+expose that code to be reusable here?
  9. var current = [];
  10. var next = [];
  11. return {
  12. clear: function clear() {
  13. next = CLEARED;
  14. current = CLEARED;
  15. },
  16. notify: function notify() {
  17. var listeners = current = next;
  18. for (var i = 0; i < listeners.length; i++) {
  19. listeners[i]();
  20. }
  21. },
  22. get: function get() {
  23. return next;
  24. },
  25. subscribe: function subscribe(listener) {
  26. var isSubscribed = true;
  27. if (next === current) next = current.slice();
  28. next.push(listener);
  29. return function unsubscribe() {
  30. if (!isSubscribed || current === CLEARED) return;
  31. isSubscribed = false;
  32. if (next === current) next = current.slice();
  33. next.splice(next.indexOf(listener), 1);
  34. };
  35. }
  36. };
  37. }
  38. var Subscription = function () {
  39. function Subscription(store, parentSub, onStateChange) {
  40. _classCallCheck(this, Subscription);
  41. this.store = store;
  42. this.parentSub = parentSub;
  43. this.onStateChange = onStateChange;
  44. this.unsubscribe = null;
  45. this.listeners = nullListeners;
  46. }
  47. Subscription.prototype.addNestedSub = function addNestedSub(listener) {
  48. this.trySubscribe();
  49. return this.listeners.subscribe(listener);
  50. };
  51. Subscription.prototype.notifyNestedSubs = function notifyNestedSubs() {
  52. this.listeners.notify();
  53. };
  54. Subscription.prototype.isSubscribed = function isSubscribed() {
  55. return Boolean(this.unsubscribe);
  56. };
  57. Subscription.prototype.trySubscribe = function trySubscribe() {
  58. if (!this.unsubscribe) {
  59. this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.onStateChange) : this.store.subscribe(this.onStateChange);
  60. this.listeners = createListenerCollection();
  61. }
  62. };
  63. Subscription.prototype.tryUnsubscribe = function tryUnsubscribe() {
  64. if (this.unsubscribe) {
  65. this.unsubscribe();
  66. this.unsubscribe = null;
  67. this.listeners.clear();
  68. this.listeners = nullListeners;
  69. }
  70. };
  71. return Subscription;
  72. }();

Redux采用了观察者模式

createStore(rootReducer,initialState,applyMiddleware(thunkMiddleware))

返回值:
(1) dispatch(action): 用于action的分发,改变store里面的state
(2) subscribe(listener): 注册listener,store里面state发生改变后,执行该listener。返回unsubscrib()方法,用于注销当前listener。
(3) getState(): 读取store里面的state
(4) replaceReducer(): 替换reducer,改变state修改的逻辑

所以store内部维护listener数组,用于存储所有通过store.subscribe注册的listener;当store tree更新后,依次执行数组中的listener

</>复制代码

  1. function subscribe(listener) {
  2. if (typeof listener !== "function") {
  3. throw new Error("Expected listener to be a function.");
  4. }
  5. var isSubscribed = true;
  6. ensureCanMutateNextListeners();
  7. nextListeners.push(listener);
  8. return function unsubscribe() {
  9. if (!isSubscribed) {
  10. return;
  11. }
  12. isSubscribed = false;
  13. ensureCanMutateNextListeners();
  14. var index = nextListeners.indexOf(listener);
  15. nextListeners.splice(index, 1);
  16. };
  17. }

dispatch方法主要完成两件事:
1、根据action查询reducer中变更state的方法,更新store tree
2、变更store tree后,依次执行listener中所有响应函数

</>复制代码

  1. function dispatch(action) {
  2. if (!(0, _isPlainObject2["default"])(action)) {
  3. throw new Error("Actions must be plain objects. " + "Use custom middleware for async actions.");
  4. }
  5. if (typeof action.type === "undefined") {
  6. throw new Error("Actions may not have an undefined "type" property. " + "Have you misspelled a constant?");
  7. }
  8. if (isDispatching) {
  9. throw new Error("Reducers may not dispatch actions.");
  10. }
  11. try {
  12. isDispatching = true;
  13. currentState = currentReducer(currentState, action);
  14. } finally {
  15. isDispatching = false;
  16. }
  17. //循环遍历,执行listener,通知数据改变
  18. var listeners = currentListeners = nextListeners;
  19. for (var i = 0; i < listeners.length; i++) {
  20. var listener = listeners[i];
  21. listener();
  22. }
  23. return action;
  24. }
在redux中,我们用connect()方法将react中的UI组件与redux的状态、事件关联起来。

</>复制代码

  1. var Connect = function (_Component) {
  2. _inherits(Connect, _Component);
  3. /*
  4. * 构造函数中,构造一个订阅对象,属性有this.store,方法this.onStateChange.bind(this)
  5. */
  6. function Connect(props, context) {
  7. _classCallCheck(this, Connect);
  8. var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
  9. _this.version = version;
  10. _this.state = {};
  11. _this.renderCount = 0;
  12. _this.store = props[storeKey] || context[storeKey];
  13. _this.propsMode = Boolean(props[storeKey]);
  14. _this.setWrappedInstance = _this.setWrappedInstance.bind(_this);
  15. (0, _invariant2.default)(_this.store, "Could not find "" + storeKey + "" in either the context or props of " + (""" + displayName + "". Either wrap the root component in a , ") + ("or explicitly pass "" + storeKey + "" as a prop to "" + displayName + ""."));
  16. _this.initSelector();
  17. _this.initSubscription();
  18. return _this;
  19. }
  20. ···
  21. // 调用store.subscribe(listener)注册监听方法,对store的变化进行订阅,当store变化的时候,更新渲染view
  22. Connect.prototype.componentDidMount = function componentDidMount() {
  23. if (!shouldHandleStateChanges) return;
  24. this.subscription.trySubscribe(); // //实际调用this.store.subscribe(this.onStateChange);
  25. this.selector.run(this.props);
  26. if (this.selector.shouldComponentUpdate) this.forceUpdate();
  27. };
  28. ···
  29. Connect.prototype.componentWillUnmount = function componentWillUnmount() {
  30. if (this.subscription) this.subscription.tryUnsubscribe(); // 取消订阅
  31. this.subscription = null;
  32. this.notifyNestedSubs = noop;
  33. this.store = null;
  34. this.selector.run = noop;
  35. this.selector.shouldComponentUpdate = false;
  36. };
  37. ···
  38. //初始化订阅逻辑
  39. Connect.prototype.initSubscription = function initSubscription() {
  40. if (!shouldHandleStateChanges) return;
  41. var parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey];
  42. this.subscription = new _Subscription2.default(this.store, parentSub, this.onStateChange.bind(this)); //调用的是Subscription.js中方法,向store内部注册一个listener---this.onStateChange.bind(this)
  43. this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription);
  44. };
  45. Connect.prototype.onStateChange = function onStateChange() {
  46. this.selector.run(this.props);
  47. if (!this.selector.shouldComponentUpdate) {
  48. this.notifyNestedSubs();
  49. } else {
  50. this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate;
  51. this.setState(dummyState);
  52. }
  53. };
  54. ···
  55. return Connect;
  56. }(_react.Component);
装饰者模式

</>复制代码

  1. 在不改变对象自身的基础上,在程序运行期间给对象动态地添加一些额外职责

</>复制代码

  1. //window绑定onload事件
  2. window.onload = function() {
  3. alert(1)
  4. }
  5. var _onload = window.onload || function(){} //维护中间变量,但是如果装饰链太长,或需要的装饰函数太多,这些中间变量的数量就会越来越多
  6. window.onload = function() {
  7. _onload()
  8. alert(2)
  9. }
  10. //可能会存在this被劫持问题
  11. var getId = document.getElementById; //全局函数,this指向window
  12. document.getElementById = function(ID){
  13. console.log(1);
  14. return getId(ID);
  15. }
  16. document.getElementById("demo"); //this预期指向document
  17. //需要手动把document当做上下文this传入getId
AOP装饰函数

</>复制代码

  1. AOP(Aspect Oriented Programming)面向切面编程
    把一些与核心业务逻辑无关的功能抽离出来
    再通过“动态织入”方式掺入业务逻辑模块

</>复制代码

  1. // 前置装饰
  2. Function.prototype.before = function(beforeFunc){
  3. var that = this; //保存原函数的引用
  4. return function(){ //返回了了包含原函数和新函数的代理函数
  5. beforeFunc.apply(this, arguments); // 执行新函数
  6. return that.apply(this, arguments); //执行原函数并返回原函数的执行结果
  7. }
  8. }
  9. document.getElementById = document.getElementById.before(function() {
  10. alet(1)
  11. })

</>复制代码

  1. //定义一个组件
  2. class Home extends Component {
  3. //....
  4. }
  5. //以往从状态树取出对应的数据,通过props传给组件使用通过react-redux自带的connect()方法
  6. export default connect(state => ({todos: state.todos}))(Home);
  7. //使用装饰器的话就变成这样,好像没那么复杂
  8. @connect(state => ({ todos: state.todos }))
  9. class Home extends React.Component {
  10. //....
  11. }
适配器模式

适配器模式是作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能。

假设我们正在编写一个渲染广东省地图的页面,目前从第三方资源里获得了广东省的所有城市以及它们所对应的ID,并且成功地渲染到页面中:

</>复制代码

  1. var getGuangdongCity = function () {
  2. var GuangdongCity = [
  3. {
  4. name:"shenzhen",
  5. id : "11"
  6. },
  7. {
  8. name:"guangzhou",
  9. id:12
  10. }
  11. ];
  12. return GuangdongCity;
  13. };
  14. var render = function (fn) {
  15. console.log("starting render Guangdong map");
  16. document.write(JSON.stringify(fn()));
  17. };
  18. /* var GuangdongCity = {
  19. // shenzhen:11,
  20. // guangzhou:12,
  21. // zhuhai:13
  22. // };
  23. */
  24. var addressAdapter = function (oldAddressfn) {
  25. var address = {},
  26. oldAddress = oldAddressfn();
  27. for(var i = 0 , c; c = oldAddress[i++];){
  28. address[c.name] = c.id; //此处我们遍历老数据把它们添加到空对象中然后返回这个对象
  29. }
  30. return function () {
  31. return address;
  32. }
  33. };
  34. render(addressAdapter(getGuangdongCity));

使用适配器模式可以解决参数类型有些许不一致造成的问题。

redux为了和react适配,所有有 mapStateToProps()这个函数来把state转为Props外部状态,这样就可以从外部又回到组件内了

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94866.html

相关文章

  • 浅谈js单例模式

    摘要:单例模式说到单例设计模式,中经常使用的单例模式通常分两种,懒汉模式和饿汉模式懒汉模式简单写了下私有化构造函数在获取实例的方法中返回实例化对象虽然很多大佬都写过啦,但是小生为了加深记忆便再写一遍虽然实现了单例模式,但是未考虑到线程安全,多个线 java单例模式 说到单例设计模式,Java中经常使用java的单例模式通常分两种,懒汉模式和饿汉模式 懒汉模式 class singleDemo...

    draveness 评论0 收藏0
  • 浅谈秒杀系统架构设计

    摘要:动态生成随机下单页面的为了避免用户直接访问下单需要将动态化,用随机数作为参数,只能秒杀开始的时候才生成。该文件不被缓存的做法随机数。浅谈秒杀系统架构设计如何只允许,第一个提交的单进入订单系统。未超过秒杀商品总数,提交到子订单系统。 秒杀是电子商务网站常见的一种营销手段。 原则 不要整个系统宕机。 即使系统故障,也不要将错误数据展示出来。 尽量保持公平公正。 实现效果 秒杀开始前,...

    maochunguang 评论0 收藏0
  • JS之作用域浅谈

    摘要:作用域链保证对环境中定义的变量和函数的有序访问。通俗来说,执行环境和作用域就是变量或函数有效执行所在的一个环境。总结要想搞清作用域,首先要搞清预解析,然后判断作用域范围,先判断本层环境有无声明及赋值,如果有声明,则判断调用前是否赋值。 这几天看了一下JS高级程序设计里的介绍作用域的章节,也参考了网上的资料,现在结合着自己的理解,给大家分享一下我自己对JS作用域的理解。 作用域及执行环境...

    roland_reed 评论0 收藏0
  • 浅谈工作中设计、制作和程序之间的配合

    摘要:最后再说一下程序方面,因为小编是一名制作,所以程序方面的配合了解到的也就是平常项目中经常遇到的。最后做一下总结,在一个项目的进行中,设计制作程序三者之间是密不可分的,而各个部分之间需要相互配合,才能将项目做的更好。 以下是我在平常工作中所遇到的,文笔不是很好,求大神轻喷。在项目进行的过程中,主要分为设计--制作--程序这三个步骤,首先先从设计页面开始,包括页面上面的诸多效果在内。而制作...

    leone 评论0 收藏0
  • 浅谈工作中设计、制作和程序之间的配合

    摘要:最后再说一下程序方面,因为小编是一名制作,所以程序方面的配合了解到的也就是平常项目中经常遇到的。最后做一下总结,在一个项目的进行中,设计制作程序三者之间是密不可分的,而各个部分之间需要相互配合,才能将项目做的更好。 以下是我在平常工作中所遇到的,文笔不是很好,求大神轻喷。在项目进行的过程中,主要分为设计--制作--程序这三个步骤,首先先从设计页面开始,包括页面上面的诸多效果在内。而制作...

    jsliang 评论0 收藏0
  • js面向对象浅谈(三)

    摘要:还有一个问题,就是不能在创建子类性时,像父类型的构造函数传递参数。组合继承将原型链和借用构造函数组合到一起,发挥两者之长的一张继承模式,下面来看个例子。组合继承最大的问题是无论在什么情况下,都会调用两次父类型构造函数。 继承 继承是面向对象语言中特别重要的概念,js的继承主要是靠原型链实现的。 原型链!!! 看到我给标题打了三个叹号吗,这里真的很重要!这里真的很重要!这里真的很重要!j...

    awkj 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<