js

题目1

观察者和发布订阅有什么关系,什么区别?

my answer:

观察者:

observer监听数据,收集依赖

发布订阅:

订阅时将方法存入观察者容器,发布时通知,并逐一执行容器内的方法

陈小哥同学:

下单订阅外卖 订单进入饭店 排队等候 店家发布信息 做餐完毕 一个个订单取出 让外卖小哥送餐

解答:

观察者observer

被观察者:目标 target

将什么东西送到对面去

特点:

观察者针对目标,观察目标的机能是否有变化,从而去执行一系列的任务

一种设计模式,一个函数内部的某个值/某些值变化去完成的一些额外任务,

observer是一个庞大的结构

发布订阅并不是一种设计模式,一种思路,由观察者思想演变过来的思路

总结:

观察目标中数据进行增删改查的时候增加额外的任务,如组件渲染等

应用:

  • 解决事件绑定的问题
  • vue做数据变化的时候要做组件渲染

如何写:

  1. 先写目标 class Target
  2. 通过对数据源的代理去做更多的事情
  3. 对当前数据校验validateData函数(username,password…)
  4. 实例化的时候校验
  5. 代理proxyData函数,代理对象的属性
  6. get时返回data[key]
  7. set时data[key] = newValue
  8. 完成获取值的时候更新日志class Observer
  9. get时this.observer.updateLog('get',key,data[key])
  10. set时this.observer.updateLog('set',key,data[key])
  11. 实例化Observer时获取节点el
  12. 定义实例里的updateLog方法, 判断get/set,打印日志池/执行log方法
  13. 实例里定义日志池logPool数组属性
  14. getProp方法:将日志对象push到日志池
  15. setProp方法:将日志对象push到日志池
  16. log方法:创建li元素/组装get或set的html字符串模板/插入el
  1. /**
  2. * 观察者 Observer oppsite object
  3. * 目标 Target
  4. */
  5. class Target {
  6. constructor (data) {
  7. // username password age gender
  8. this.data = data;
  9. this.observer = new Observer('#list');
  10. this.init();
  11. }
  12. init () {
  13. this.validateData(this.data);
  14. this.proxyData(this.data);
  15. }
  16. validateData (data) {
  17. const { username, password, age, gender } = data;
  18. username.length < 6 && (data.username = '');
  19. password.length < 6 && (data.password = '');
  20. typeof age !== 'number' && (data.age = 0);
  21. // male female
  22. !['male', 'female'].includes(gender) && (data.gender = 'female');
  23. }
  24. proxyData (data) {
  25. for (let key in data) {
  26. Object.defineProperty(this, key, {
  27. get () {
  28. this.observer.updateLog('get', key, data[key]);
  29. return data[key];
  30. },
  31. set (newValue) {
  32. this.observer.updateLog('set', key, data[key], newValue);
  33. data[key] = newValue;
  34. }
  35. })
  36. }
  37. }
  38. }
  39. class Observer {
  40. constructor (el) {
  41. this.el = document.querySelector(el);
  42. this.logPool = [];
  43. }
  44. updateLog (type, key, oldValue, newValue) {
  45. switch (type) {
  46. case 'get':
  47. this.getProp(key, oldValue);
  48. break;
  49. case 'set':
  50. this.setProp(key, oldValue, newValue);
  51. break;
  52. default:
  53. break;
  54. }
  55. console.log(this.logPool);
  56. }
  57. getProp (key, value) {
  58. const o = {
  59. type: 'get',
  60. dateTime: new Date(),
  61. key,
  62. value
  63. }
  64. this.logPool.push(o);
  65. this.log(o);
  66. }
  67. setProp (key, oldValue, newValue) {
  68. const o = {
  69. type: 'set',
  70. dateTime: new Date(),
  71. key,
  72. oldValue,
  73. newValue
  74. }
  75. this.logPool.push(o);
  76. this.log(o);
  77. }
  78. log (o) {
  79. const { type, dateTime, key } = o;
  80. const oLi = document.createElement('li');
  81. let htmlStr = '';
  82. switch (type) {
  83. case 'get':
  84. htmlStr = `
  85. ${dateTime}:
  86. I got the key '${key}'.
  87. The vaue of the key is ${o.value}
  88. `;
  89. break;
  90. case 'set':
  91. htmlStr = `
  92. ${dateTime}:
  93. I set the value of the key '${key}' '${o.newValue}'
  94. from the old value '${o.oldValue}';
  95. `
  96. break;
  97. default:
  98. break;
  99. }
  100. oLi.innerHTML = htmlStr;
  101. this.el.appendChild(oLi);
  102. }
  103. }

发布订阅

以事件为核心,一个事件到底有没有触发,只要触发,和事件相关的处理函数依次执行

如何写:

  1. 定义类EventEmitter
  2. const ev = new EventEmitter()
  3. 定义handle1/handle2/handle3/handle4/handle5/handle6函数
  4. 给6个函数绑定事件ev.on('test',handle1)
  5. ev.trigget('test')
  6. 定义实例里的on方法
  7. on方法:如果this.handlers[type]不存在,把每个handle函数放进容器
  8. 定义容器this.handles={}
  9. 希望的写法{test:[handle1,...],test2:[handle4,...]}
  10. 定义实例里的trigger方法
  11. trigger方法:如果this.handlers[type]存在,拿到每个handle并依次执行
  1. class EventEmitter {
  2. constructor () {
  3. this.handlers = {};
  4. }
  5. on (type, handler, once) {
  6. if (!this.handlers[type]) {
  7. this.handlers[type] = [];
  8. }
  9. if (!this.handlers[type].includes(handler)) {
  10. this.handlers[type].push(handler);
  11. handler.once = once;
  12. }
  13. console.log(this.handlers);
  14. }
  15. once (type, handler) {
  16. this.on(type, handler, true);
  17. }
  18. off (type, handler, callback) {
  19. if (this.handlers[type]) {
  20. this.handlers[type] = this.handlers[type].filter(h => {
  21. return h !== handler;
  22. });
  23. callback && callback(type);
  24. }
  25. }
  26. trigger (type, data, context, callback) {
  27. if (this.handlers[type]) {
  28. this.handlers[type].forEach(handler => {
  29. handler.call(context ? context : this);
  30. callback && callback(data);
  31. if (handler.once) {
  32. this.off(type, handler);
  33. }
  34. });
  35. }
  36. }
  37. }
  38. const ev = new EventEmitter();
  39. function handler1 () {
  40. console.log('handler1');
  41. }
  42. function handler2 () {
  43. console.log('handler2');
  44. }
  45. function handler3 () {
  46. console.log('handler3');
  47. }
  48. function handler4 () {
  49. console.log('handler4');
  50. }
  51. function handler5 () {
  52. console.log('handler5');
  53. }
  54. function handler6 () {
  55. console.log('handler6');
  56. }
  57. ev.on('test', handler1);
  58. ev.on('test', handler2);
  59. ev.on('test', handler3);
  60. // ev.once('test1', handler4);
  61. // ev.once('test1', handler5);
  62. // ev.once('test1', handler6);
  63. ev.trigger('test', {a: 1, b: 2}, {c: 3, d: 4}, (data) => {
  64. console.log(data);
  65. });
  66. // ev.trigger('test1');
  67. // ev.trigger('test1');

课外:

vuex/redux

中央状态管理器

  • state仓库 放数据
  • action 行为 事件
  • mutation 处理方法

流程闭环:backend API -> actions(commit) -> mutation(mutate) -> state -> render component(dispatch) -> actions…

注意:必须遵守上面的流程规矩

题目2

什么是闭包?

  • 是极为简单的概念和机能
  • 闭包从来跟return没有半毛钱关系
  • 只跟环境作用域有关系

函数嵌套的目的是:形成内部访问外部的访问体系

纯函数:内外不相关,标准输入输出

闭包可以实现属性和方法私有化