JavaScript设计模式与开发实践笔记(1)

单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用:只存在一个的浮窗(登录浮窗)、只绑定一次的事件、创建唯一实例对象。
实现原理:用变量标志当前是否为某个类创建过对象,如果有,在下一次获取该类的实例时,返回之前创建的对象。
实现 :

把全局变量当做单例来使用,但是要减少全局变量的使用,相对降低全局变量带来的命名污染。
  • 使用命名空间
  1. 对象字面量
const namespace = {
  a: 1,
  b: () => {
    alert(2);
  }
};
  1. 动态创建命名空间
let MyApp = {};
MyApp.namespace = name => {
  const parts = name.split('.');
  let current = MyApp;
  for (let i in parts) {
    if (!current[parts[i]]) {
      current[parts[i]] = {};
    }
    current = current[parts[i]];
  }
};
MyApp.namespace('event');
MyApp.namespace('dom.style');
  • 使用闭包封装私有变量
const user = (() => {
  const _name = 'sven',
    _age = 29;
  return {
    getUserInfo: () => {
      return _name + '_' + _age;
    }
  };
})();
惰性单例:在需要的时候才创建对象实例。
//抽离管理单例的逻辑
const getSingle = fn => {
  let result;
  return () => {
    // result在闭包中永远不会被销毁
    return result || (result = fn.apply(this, arguments));
  };
};
// 创建dom元素
const createLoginLayer = () => {
  let div = document.createElement('div');
  div.innerHTML = '登录浮窗';
  div.style.display = 'none';
  document.body.appendChild(div);
  return div;
};
const createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById('loginBtn').onclick = () => {
  // 点击时只需要去取已经创建过的实例
  let loginLayer = createSingleLoginLayer();
  loginLayer.style.display = 'block';
};
绑定事件只绑定一次。
const bindEvent = getSingle(() => {
  document.getElementById('div1').onclick = () => {
    alert('click');
  };
  return true;
});
const render = () => {
  bindEvent();
};
render();
render();
render(); // 执行了三次,但是div只绑定了一个事件

策略模式

定义:定义一系列的算法,把他们一个个封装起来,并且使它们可以相互替换,目的是将算法的使用和算法的实现分离开。
实现原理:一个策略模式的程序至少需要两部分组成,一组策略类(封装具体算法,并负责具体的计算过程)和环境类 Context(接受客户发起请求,把请求委托给某一个策略类)。
优点:

  1. 利用组合、委托、多态等技术和思想,可以有效地避免多重条件选择语句。
  2. 提供了开放-封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于拓展。
  3. 算法可以复用在系统的其他地方,从而避免重复工作。
  4. 利用组合和委托让 Context 拥有执行算法的能力,也是继承的一种更轻便的替代方案。

实现:

计算奖金
const strategies = {
  S: salary => {
    return salary * 4;
  },
  A: salary => {
    return salary * 3;
  },
  B: salary => {
    return salary * 2;
  }
};
const calculateBonus = (level, salary) => {
  return strategies[level](salary);
};
策略模式重构表单校验
  1. 将校验逻辑封装成策略对象
const strategies = {
  isNonEmpty: (value, errorMsg) => {
    if (value === '') {
      return errorMsg;
    }
  },
  minLength: (value, length, errorMsg) => {
    if (value.length < length) {
      return errorMsg;
    }
  },
  isMobile: (value, errorMsg) => {
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
      return errorMsg;
    }
  }
};
  1. 定义发送请求
const validateFunc = () => {
  const validator = new Validator();
  // 校验规则
  validator.add([
    { dom: registerForm.userName, rules: [{ rule: 'isNonEmpty', errorMsg: '用户名不能为空' }] },
    { dom: registerForm.password, rules: [{ rule: 'isNonEmpty', errorMsg: '密码不能为空' }, { rule: 'minLength:6', errorMsg: '密码长度不能少于6位' }] },
    { dom: registerForm.phoneNumber, rules: [{ rule: 'isMobile', errorMsg: '手机号码格式不正确' }] }
  ]);
  // 获得校验结果
  const errorMsg = validator.start();
  return errorMsg;
};
registerForm.onsubmit = () => {
  // 如果errorMsg有确切的返回值,说明未通过校验
  const errorMsg = validateFunc();
  if (errorMsg) {
    alert(errorMsg);
    return false; // 阻止表单提交
  }
};
  1. 实现 Vadidator 类(Context),负责接收用户的请求并委托给 strategy 对象
class Validator {
  constructor() {
    // 保存校验规则
    this.cache = [];
  }
  add(ruleArr) {
    ruleArr.forEach(item => {
      const { dom, rules } = item;
      rules.forEach(rules => {
        const { rule, errorMsg } = rules;
        let array = rule.split(':');
        this.cache.push(() => {
          //移除规则名称,剩一个限制值或空
          const strategy = array.shift();
          array.unshift(dom.value);
          array.push(errorMsg);
          // array中有domvalue,限制值或无,errormsg
          return strategies[strategy].apply(dom, array);
        });
      });
    });
  }
  start() {
    for (let i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
      const msg = validatorFunc(); //开始校验,并获得返回信息
      if (msg) {
        return msg;
      }
    }
  }
}

代理模式

定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。
分类:

  • 虚拟代理(把一些开销很大的对象,延迟到真正需要它的时候才去创建)
  • 保护代理(用于控制不同权限的对象对目标对象的访问)
  • 缓存代理(为一些开销大的运算结果提供暂时的存储,下次运算时如果参数一致,则可以直接返回之前存储的运算结果)

实现:

虚拟代理实现图片预加载

如果不用代理,预加载的实现:

const myImage = (() => {
  const imgNode = document.createElement('img');
  document.body.appendChild(imgNode);
  let img = new Image();
  img.onload = () => {
    imgNode.src = img.src;
  };
  return {
    setSrc: src => {
      imgNode.src = 'loading.gif';
      img.src = src;
    }
  };
  myImage.setSrc('realImage.jpg');
})();

使用了虚拟代理:

const myImage = (() => {
  const imgNode = document.createElement('img');
  document.body.appendChild(imgNode);

  return {
    setSrc: src => {
      imgNode.src = src;
    }
  };
})();
// 代理对象,在图片被真正加载好之前,出现占位图
const proxyImage = (() => {
  return {
    setSrc: src => {
      myImage.setSrc('loading.gif');
      img.src = src;
    }
  };
})();
// 调用
proxyImage.setSrc('realImage.jpg');

意义:
       符合单一职责原则(就一个类而言,应该仅有一个引起它变化的原因),如果一个对象承担的职责过多,职责的耦合会导致脆弱和低内聚的设计,当发生变化时,设计可能会遭到意外的破坏。
       符合开放-封闭原则,如果需求不再需要预加载,可以直接改变调用,而不是改动 myImage 对象,给 img 节点设置 src 和图片预加载两个功能被隔离在两个对象里,它们可以各自变化而不影响对方。

虚拟代理合并 HTTP 请求

       通过代理函数收集一段时间内的请求,最后一次性发送给服务器。

const synchronousFile = id => {
  // 发送请求
};
const proxySynchronousFile = (() => {
  let cache = [], //数据暂存
    timer;
  return id => {
    cache.push(id);
    //确保不会覆盖已经启动的定时器
    if (timer) {
      return;
    }

    timer = setTimeout(() => {
      synchronousFile(cache.join(',')); //两秒后发送请求
      // 清空定时器和数据
      clearTimeout(timer);
      timer = null;
      cache.length = 0;
    }, 2000);
  };
})();

// 调用代理函数
const checkbox = document.getElementsByTagName('input');
for (let i = 0, c; (c = checkbox[i++]); ) {
  c.onclick = function() {
    if (this.checked === true) {
      proxySynchronousFile(this.id);
    }
  };
}
缓存代理做计算
// 因为箭头函数没有绑定arguments,所以还是用function
const mult = function() {
  let a = 1;
  for (let i = 0, l = arguments.length; i < l; i++) {
    a *= arguments[i];
  }
};
const plus = function() {
  let a = 0;
  for (let i = 0, l = arguments.length; i < l; i++) {
    a += arguments[i];
  }
};
// 创建缓存代理的工厂
const createProxyFactory = fn => {
  let cache = {};
  // 这里接收要计算的参数
  return function() {
    const args = Array.prototype.join.call(arguments, ',');
    if (cache[args]) {
      return cache[args];
    }
    return (cache[args] = fn.apply(this, arguments));
  };
};
// 传入高阶函数
const proxyMult = createProxyFactory(mult);
const proxyPlus = createProxyFactory(plus);
proxyMult(1, 2, 3, 4);
proxyPlus(1, 2, 3, 4);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值