导航

[[深刻01] 执行上下文](https://juejin.im/post/684490...
[[深刻02] 原型链](https://juejin.im/post/684490...
[[深刻03] 继承](https://juejin.im/post/684490...
[[深刻04] 事件循环](https://juejin.im/post/684490...
[[深刻05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...
[[深刻06] 隐式转换 和 运算符](https://juejin.im/post/684490...
[[深刻07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...
[[深刻08] 前端平安](https://juejin.im/post/684490...
[[深刻09] 深浅拷贝](https://juejin.im/post/684490...
[[深刻10] Debounce Throttle](https://juejin.im/post/684490...
[[深刻11] 前端路由](https://juejin.im/post/684490...
[[深刻12] 前端模块化](https://juejin.im/post/684490...
[[深刻13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490...
[[深刻14] canvas](https://juejin.im/post/684490...
[[深刻15] webSocket](https://juejin.im/post/684490...
[[深刻16] webpack](https://juejin.im/post/684490...
[[深刻17] http 和 https](https://juejin.im/post/684490...
[[深刻18] CSS-interview](https://juejin.im/post/684490...
[[深刻19] 手写Promise](https://juejin.im/post/684490...
[[深刻20] 手写函数](https://juejin.im/post/684490...

[[react] Hooks](https://juejin.im/post/684490...

[[部署01] Nginx](https://juejin.im/post/684490...
[[部署02] Docker 部署vue我的项目](https://juejin.im/post/684490...
[[部署03] gitlab-CI](https://juejin.im/post/684490...

[[源码-webpack01-前置常识] AST形象语法树](https://juejin.im/post/684490...
[[源码-webpack02-前置常识] Tapable](https://juejin.im/post/684490...
[[源码-webpack03] 手写webpack - compiler简略编译流程](https://juejin.im/post/684490...
[[源码] Redux React-Redux01](https://juejin.im/post/684490...
[[源码] axios ](https://juejin.im/post/684490...
[[源码] vuex ](https://juejin.im/post/684490...
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...
[[源码-vue02] computed 响应式 - 初始化,拜访,更新过程 ](https://juejin.im/post/684490...

前置常识

一些单词

subject:指标对象observer:观察者对象pattern:模式notify:告诉Publisher:发布者Subscriber:订阅者directive:指令compile:编译Recursive:递归 adjrecursion:递归 nfactorial:阶乘

splice

arr.splice(start, count, addElement1, addElement2, ...);
start:开始地位,从0开始( 如果是正数,示意从倒数地位开始删除 )
count:删除的个数
addElement1...:被增加的元素

作用:splice 删除( 原数组 )的一部分成员,并能够在删除的地位( 增加 )新的数组成员
返回值:被删除的元素组成的数组,留神是一个数组
留神:splice 扭转原数组
特地:如果只有一个参数( 第一个参数 ),则变相相当于把数组拆分成两个数组,一个是返回值数组,一个是扭转后的原数组

for...in 和 for...of

  • <font color=red>for...in:能够用于 ( 数组 ) 或 ( 对象 )</font>
  • <font color=red>for...of:只能用于( 数组 ),因为对象没有部署 iterator 接口</font>

    for...in 和 for...of(1)for...in:能够用于数组 或 对象for...of:只能用于数组,因为对象没有部署 iterator 接口
  • 数组
  • for(let i of arr) ----------- i 示意值
  • for(let i in arr) ----------- i 示意下标
  • 对象
  • 对象只能用for in循环,不能用for of循环
  • 因为对象没有部署iterator接口,不必应用for of循环
  • for (let i in obj) ----------- i 示意key

<font color=red>如何优雅的遍历对象</font>

  • ( Object对象 ) 和 ( Array.prototype对象 ) 都部署了三个办法 keysvaluesentries
  • <font color=red>Object.keys(obj) , Object.values(obj),Object.entries(obj) ---- 静态方法</font>
  • Array.prototype.keys(),Array.prototype.values(),Array.prototype.entries() - 返回 Iterator 遍历器对象

    如何优雅的遍历对象const arr = [{name: '123'},2,3,4]const obj = {name: '111', age: 222}对象 // (留神Object.keys() values() entries() 能够用于对象,也能够用于数组)for(let [key, value] of Object.entries(obj)) { // object || arrayconsole.log(key, value)// name 111//  age 222// Object.entries(obj) 返回一个数组,每个成员也是一个数组,由( 键,值 )组成的数组}// 数组// for(let [key, value] of arr.entries()) { // 返回 iterator 对象接口,能够用for...of遍历//  console.log(key, value)// }

<font color=red>Element.children 和 Node.ChildNodes</font>

  • Element.children

    • 返回一个相似数组的对象,<font color=red>( HTMLCollection )</font> 实例
    • 包含 <font color=red>以后元素节点的( 所有子元素 ) ---- 只包含元素节点</font>
    • 如果以后元素没有子元素,则返回的对象蕴含 0 个成员
    • 留神:<font color=red>返回的是以后元素的所有子( 子元素节点 ),不包含其余节点</font>
  • Node.childNodes

    • 返回一个相似数组的对象,<font color=red>( NodeList )</font> 汇合
    • 包含 <font color=red>以后元素的所有( 子节点 ) ---- 包含元素节点,文本节点,正文节点</font>
  • Node.childNodes 和 Element.children的区别

    • Node.childNodes是NodeList汇合, Element.children是HTMLCollection汇合
    • Node.childNodes包含以后节点的所有子节点,包含元素节点,文本节点,正文节点
    • Element.children包含以后元素节点的所有子元素节点,只包含元素节点
    • 留神:<font color=red>相似数组的对象具备length属性,且 键 是0或正整数</font>
    <div id='content'><div>111</div><div>  <div>222</div>  <div>333</div></div><p1>444</p1></div><script>const contentHTMLCollection = document.getElementById('content') ---------- HTMLCollectionconsole.log(content.children)// HTMLCollection(3) [div, div, p1]const contentNodeList = document.querySelector('div') --------------------- NodeListconsole.log(contentNodeList.childNodes)// NodeList(7) [text, div, text, div, text, p1, text]</script>

document.querySelector(css选择器)

  • 返回第一个匹配的元素节点
  • document.querySelectorAll() 返回所有匹配的元素节点

<font color=red size=5>递归 尾递归 尾调用</font>

  • 递归的含意:函数调用本身,尾调用本身,尾递归
  • 递归的条件:边界条件,递归后退段,递归返回段

    • 不满足边界条件,递归后退
    • 满足边界条件,递归返回
  • 尾调用:函数外部最初一个动作是函数调用,该调用的返回值,间接返回给函数
  • 尾调用和非尾调用的区别:

    • 执行上下文栈变动不一样( <font color=red>即函数调用栈不一样,内存占用不一样</font> )
    尾调用 和 非尾调用尾调用function a() {  return b()}非尾调用function a() {  return b() + 1  // 非尾调用  // 因为调用b()时,a函数并未执行完,未出栈 => b执行完后 + 1 ,这时a函数才执行结束}
----尾调用优化----// (1) 失常版 - 阶乘函数function factorial(number) {  if(number === 1 ) return number;  return number * factorial(number - 1)}const res = factorial(3)console.log(res)// 调用栈// 1 ------------------------------ 3 * factorial(2)// factorial(3) // 2 ------------------------------ 3 * factorial(2)// factorial(2)// factorial(3)// 3 ------------------------------ 3 * 2 * factorial(1)// factorial(1)// factorial(2)// factorial(3)// 4 ------------------------------ 3 * 2 * 1// factorial(2) // factorial(3)// 5 ------------------------------ 6// factorial(3)// (2) 尾递归版 - 阶乘函数function factorial(number, multiply) {  if (number === 1) return multiply;  return factorial(number-1, number * multiply)}const res = factorial(4, 1)console.log(res)// factorial(4, 1)// factorial(3, 4*1)// factorial(2, 3 * (4 * 1))// factorial(1, 2 * (3 * 4 * 1))// return 1 * 3 * 4 * 1 // 12// 每次都是尾递归,所以执行上线问栈中始终只有一个factorial()// 即下一个 factorial 调用时, 外层的 factorial 曾经执行结束出栈了// (3) 优化尾递归版 - 阶乘函数function factorialTemp(multiply, number) {  if (number === 1) return multiply;  return factorialTemp(number * multiply, number-1)}function partial(fn) { // --------------------------------------- 偏函数  let params = Array.prototype.slice.call(arguments, 1) // ------ 固定局部参数  return function closure() {    params = params.concat([...arguments])    if (params.length < fn.length) { // 参数小于fn形参个数,就持续收集参数      return closure    }    return fn.apply(this, params) // 否则,证实参数收集结束,执行fn  }}const factorial = partial(factorialTemp, 1) // 固定参数1const res = factorial(4)console.log(res)

观察者模式

概念

  • 对程序中的某个对象进行察看,并在其产生扭转时失去告诉
  • 存在( <font color=red>观察者对象</font> ) 和 ( <font color=red>指标对象</font> )两种角色
  • 指标对象:subject
  • 观察者对象:observer

在观察者模式中,subject 和 observer 互相独立又互相分割

  • <font color=red>一个指标对象对应多个观察者对象 ( 一对多 )</font>
  • <font color=red>观察者对象在指标对象中( 订阅事件 ),指标对象( 播送事件 )</font>
  • Subject 指标对象:保护一个观察者实例组成的数组,并且具备( 增加,删除,告诉 )操作该数组的各种办法
  • Observer 观察者对象:仅仅只须要保护收到告诉后( 更新 )操作的办法

代码实现 - ES5

阐明:(1) Subject构造函数- Subject构造函数的实例( 指标对象 ),保护一个( 观察者实例对象 ) 组成的数组- Subject构造函数的实例( 指标对象 ),领有( 增加,删除,公布告诉) 等操作观察者数组的办法(2) Observer构造函数- Observer构造函数的实例( 观察者对象 ),仅仅只须要保护收到告诉后,更新的办法---------代码:function Subject() { // 指标对象的构造函数  this.observes = [] // 观察者对象实例组成的数组}Subject.prototype = { // 原型上挂载操作数组的办法  add(...params) { // --------------------------------- 增加观察者    // 对象办法的缩写,增加观察者对象    // params是ES6中的( rest参数数组 ),用来代替 arguments 对象,能够接管多个参数    // this在调用时确定指向,Subject构造函数的实例在调用,所以实例具备observes属性    this.observes = this.observes.concat(params)  },  delete(obj) { // ------------------------------------ 删除观察者    const cashObservers = this.observes // ------------ 缓存能进步性能,因为上面有屡次用到    cashObservers.forEach((item, index) => {      if (item === obj) cashObservers.splice(index, 1)      // 这里是三等判断,因为 obj 和 item 的指针都执行同一个堆内存地址      // 举例      // const a = {name: 'woow_wu7'}      // const b = [a, 1, 2]      // b[0] === a       // 下面的后果是true - 因为b[0]和a指向了同一个堆内存地址            // 留神:cashObservers.splice(index, 1)      // 删除cashObservers相当于删除this.observes      // 因为:this.observes赋值给了cashObservers,并且this.observes是复合类型的数据,所以两个变量指针统一    })  },  notify() { // --------------------------------------- 告诉观察者,并执行观察者各自的更新函数    if(this.observes.length) this.observes.forEach(item => item.update())  }}Subject.prototype.constructor = Subject // ------------- 扭转prototype的同时批改constructor,避免援用出错function Observer(fn) { // ----------------------------- 观察者对象仅仅保护更新函数  this.update = fn}const obsObj1 = new Observer(() => console.log(111111))const obsObj2 = new Observer(() => console.log(222222))const subObj = new Subject()subObj.add(obsObj1, obsObj2)subObj.notify()subObj.delete(obsObj1)subObj.notify()

代码实现 - ES6

-----------观察者模式 - es6语法实现class Subject {  constructor() {    this.observers = []  }  add = (...rest) => {    this.observers = this.observers.concat(rest)  }  delete = (obj) => {    const cachObservers = this.observers    cachObservers.forEach((item, index) => {      if (item === obj) cachObservers.splice(index, 1)    })  }  notify = () => {    this.observers.forEach(item => {      item.update()    });  }}class Observer {  constructor(fn) {    this.update = fn  }}const objserverIns = new Observer(() => console.log(1111))const objserverIns2 = new Observer(() => console.log(2222))const subjectIns = new Subject()subjectIns.add(objserverIns, objserverIns2)subjectIns.notify()

2021/4/7 观察者模式优化

  • 优化 add => subscribe订阅
  • 优化 delete => unSubscribe勾销订阅

    <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><script>  function Subject() {    this.observes = [] // 观察者实例对象 组成的数组  }  Subject.prototype = {    subscribe(...params) {      this.observes = this.observes.concat(params)    },    unSubscribe(obj) {      this.observes.forEach((item, index) => {        if (item === obj) {          this.observes.splice(index, 1)        }      })    },    notify() {      this.observes.length && this.observes.forEach(item => item.update())    }  }  Subject.prototype.constructor = Subject  function Observer(fn) {    this.update = fn  }  const fn1 = () => console.log('fn1')  const fn2 = () => console.log('fn2')  const observer1 = new Observer(fn1)  const observer2 = new Observer(fn2)  const subject = new Subject()  subject.subscribe(observer1, observer2) // ------- 订阅  subject.notify() // ------------------------------ 公布  console.log(subject.observes)  subject.unSubscribe(observer1) // ---------------- 勾销订阅  console.log(subject.observes)</script></body></html>
  • 2020/12/28 温习

公布订阅模式

角色

  • 发布者:Publisher
  • 订阅者:Subscriber
  • 中介:Topic/Event Channel
  • <font color=red>( 中介 ) 既要 '接管' ( 发布者 )所公布的音讯事件,又要将音讯 '派发' 给( 订阅者 )</font>
  • 所以中介要依据不同的事件,存储响应的订阅者信息
  • <font color=red>通过中介对象,齐全解耦了发布者和订阅者</font>

公布订阅模式 es5代码实现

公布订阅模式 - 代码实现var pubsub = {}; // 中介对象(function(pubsub) {  var topics = {}   // topics对象,寄存每个事件对应的( 订阅者对象 )组成的( 数组 )  // key: 事件名   // eventName:[{functionName: fn.name, fn: fn}]    pubsub.subscribe = function(eventName, fn) {    // ------------------------------------------------------------ 订阅     if (!topics[eventName]) topics[eventName] = [] // 不存在,新建    topics[eventName].push({      functionName: fn.name, // 函数名作为标识,留神函数名不能雷同      fn, // 更新函数    })  }  pubsub.publish = function(eventName, params) {    // ---------------- --------------------------------------------- 公布     if (!topics[eventName].length) return; // 如果空数组,即没有该事件对象的订阅者对象组成的数组,跳出整个函数    topics[eventName].forEach(item => {      item.fn(params) // 数组不为空,就循环执行该数组所有订阅者对象绑定更新函数    })  }  pubsub.unSubscribe = function(eventName, fn) { // 这里其实能够有化成传入回调函数名而不必传入整个回调函数    // -------------------------------------------------------------- 勾销订阅    if (!topics[eventName]) {      return    }    topics[eventName].forEach((item, index) => {      if (item.functionName = fn.name) {        // 如果:事件名 和 函数名 雷同        // 就:删除这个事件对应的数组中的订阅者对象,该对象蕴含函数名,fn两个属性        topics[eventName].splice(index)      }    })  }})(pubsub)function closoleLog(args) { // 订阅者收到告诉后的更新函数  console.log(args)} function closoleLog2(args) {  console.log(args)}pubsub.subscribe('go', closoleLog) // 订阅pubsub.subscribe('go', closoleLog2)pubsub.publish('go', 'home') // 公布pubsub.unSubscribe('go', closoleLog) // 勾销公布console.log(pubsub)

公布订阅模式 es6代码实现

class PubSub {  constructor() {    this.topics = {}  }  subscribe(eventName, fn) {    if (!this.topics[eventName]) {      this.topics[eventName] = []    }    this.topics[eventName].push({      fname: fn.name,      fn    })  }  publish(eventName, params) {    if (!this.topics[eventName].length) {      return    }    this.topics[eventName].forEach((item, index) => {      item.fn(params)    })  }  unSubscribe(eventName, fname) {    if (!this.topics[eventName]) {      return    }    this.topics[eventName].forEach((item, index) => {      if (item.fname === fname) {        this.topics[eventName].splice(index, 1)      }    })  }}const pubsub = new PubSub()function goFn1(params) {  console.log('goFn1', params)}function goFn2(params) {  console.log('goFn2', params)}pubsub.subscribe('go', goFn1)pubsub.subscribe('go', goFn2)pubsub.publish('go', '1') // 公布pubsub.unSubscribe('go', goFn2.name) // 勾销订阅pubsub.publish('go', '2')

2020/12/29 温习 - 公布订阅模式ES5实现

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title></head><body>  <script>    // 中介对象    const pubsub = {}    // 留神:( 小括号 ) 和 ( 中括号 ) 结尾的 ( 前一条语句 ) 必须加分号,或者在小括号或中括号的最后面加分号    ;(function(pubsub) {      const topic = {}      // 订阅       // subscribe(订阅的事件名, 事件触发的回调函数)      pubsub.subscribe = function(eventName, fn) {        if (!topic[eventName]) topic[eventName] = [];        topic[eventName].push({          fnName: fn.name,          fn,        })        console.log('topic[eventName]', topic[eventName])      }      // 公布      // publish(事件名,事件触发对应的回调函数的参数)      pubsub.publish = function(eventName, params) {        console.log('topic[eventName]', topic[eventName])        if (topic[eventName]) {          topic[eventName].forEach(observer => {            observer.fn(params)          })        }      }      // 勾销订阅      // unScribe(须要勾销的事件名, 须要勾销的回调函数名)      pubsub.unScribe = function(eventName, fnName) {        if (topic[eventName]) {          topic[eventName].forEach((observer, index) => {            if (observer.fnName === fnName) {              topic[eventName].splice(index, 1)            }          })        }      }    })(pubsub)    pubsub.subscribe('go', function go1(address1){console.log(`${address1}one`)})    pubsub.subscribe('go', function go2(address2){console.log(`${address2}two`)})    pubsub.publish('go', 'home')    pubsub.unScribe('go','go1') // 勾销订阅go1函数    pubsub.publish('go', 'work')  </script></body></html>

2020/12/29 温习 - 公布订阅模式ES56实现

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title></head><body>  <script>    class PubSub {      constructor() {        this.topic = {}      }      subscribe = (eventName, fn) => {        if (!this.topic[eventName]) this.topic[eventName] = [];        this.topic[eventName].push({          fnName: fn.name,          fn        })      }      publish = (eventName, params) => {        if (this.topic[eventName]) {          this.topic[eventName].forEach(observer => {            observer.fn(params)          })        }      }      unSubscribe = (eventName, fnName) => {        if (this.topic[eventName]) {          this.topic[eventName].forEach((observer, index) => {            if ( observer.fnName === fnName) {              this.topic[eventName].splice(index, 1)            }          })        }      }    }    const pubSub = new PubSub()    pubSub.subscribe('go', function go1(params) { console.log(`${params+'1'}`)})    pubSub.subscribe('go', function go2(params) { console.log(`${params+'2'}`)})    pubSub.publish('go', 'home')    pubSub.unSubscribe('go', 'go1') // 勾销订阅    pubSub.publish('go', 'work')  </script></body></html>

观察者模式,公布-订阅模式的区别和分割

(1)区别

  • 观察者模式:须要观察者本人定义事件产生时的响应函数
  • 公布-订阅模式:在(发布者对象),和(订阅者对象)之间,减少了(中介对象)

(2)分割

  • 二者都升高了代码的(耦合性)
  • 都具备消息传递的机制,以(数据为核心)的设计思维

vue双向数据绑定

前置常识:1. Element.children- 返回一个相似数组的对象(HTMLCollection实例)- 包含以后元素节点的所有子元素- 如果以后元素没有子元素,则返回的对象蕴含0个成员2. Node.childNodes- 返回一个相似数组的对象(NodeList汇合),成员包含以后节点的所有子节点- NodeList是一个动静汇合3. Node.childNodes 和 Element.children 的区别- Element.children只蕴含元素类型的子节点,不蕴含其余类型的子节点- Node.childNodes蕴含元素节点,文本节点,正文节点
代码-------------<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title></head><body>  <div id="app">    <div v-text="name"></div>    <input type="text" v-model="name">  </div>  <script>    // 监听类    // 次要作用是:将扭转之后的data中的 ( 最新数据 ) 更新到 ( ui视图 ) 中    class Watcher {      constructor(directiveName, el, vm, exp, attr) {        this.name = directiveName // 指令的名字,比方 'v-text','v-model'        this.el = el // 每个具体的DOM节点        this.vm = vm // MyVue实例对象        this.exp = exp // el中的directiveName属性对应的属性值        this.attr = attr // el的属性,须要需改的属性        this._update() // 留神这里在实例化Watcher时,会执行_update()办法      }      _update() {        this.el[this.attr] = this.vm.$data[this.exp]        // 将MyVue实例的data属性的最新值更新到ui视图中      }    }    class MyVue {      constructor(options) {        const {el, data} = this.options = options // 参数构造赋值        this.$el = document.querySelector(el) // 获取el选择器的元素节点        this.$data = data // data数据        this._directive = {}        // key:data对象中的 key,递归循环data从而获取每一层的属性作为key        // value:数组,用来寄存Watcher实例                this._observes(this.$data)         // 1. 递归循环,获取data每一层的key        // 2. 把_directive对象,用每个key做( 键 ),用一个Watcher实例组成的数组做( 值 )        // 3. 监听data中每个key对应的value是否变动            // 变动就执行 => _directive对象中key对应的数组中的Watcher实例对象的_update()办法            this._compile(this.$el)        // 1. 递归循环,获取所有$el对象的元素节点的 所有子元素节点 Element.children        // 2. 如果元素节点有 v-text 指令,就把Watcher实例push进_directive对象该指令值对应的数组            // 因为执行了new Watcher(),所以constructor中调用了_update,所以会执行一次            // 即push实例的同时,也会执行_update(),曾经是会把data的数据写入到对应的指令所在的元素节点中        // 3. 如果元素节点有 v-model指令,同理            // 不同的是:须要监听( input事件 ),键最新的input框的value值,赋值给data            // data扭转,会被Object.defineProperty监听,从而执行更新函数            // 更新函数负责把最新的data中的数据更新到页面中      }      _observes(data) {        for(let [key, value] of Object.entries(data)) {          if (data.hasOwnProperty(key)) {            this._directive[key] = []          }          if (typeof value === 'object') { // 数组 或 对象            this._observes(value) // 递归调用          }          const _dir = this._directive[key]          Object.defineProperty(this.$data, key, { // ------------- 监听属性变动            enumerable: true,            configurable: true,            get() {              return value            },            set(newValue) {              if (newValue !== value) { // ------------------------ 属性变动后执行所以订阅者对象的更新函数                value = newValue                _dir.forEach(item => item._update())              }            }          })        }      }      _compile(el) {        for(let [key, value] of Object.entries(el.children)) {          if (value.length) {            _compile(value)          }          if (value.hasAttribute('v-text')) {            const attrValue = value.getAttribute('v-text')            this._directive[attrValue].push(new Watcher('input', value, this, attrValue, 'innerHTML'))          }          if(value.hasAttribute('v-model') && (value.tagName === 'INPUT' || value.tagName === 'TEXTAREA')) {            const attrValue = value.getAttribute('v-model')            this._directive[attrValue].push(new Watcher('v-model', value, this, attrValue, 'value'))            let that = this            value.addEventListener('input', function() { // input事件监听              that.$data[attrValue] = value.value               // 获取最新的input框值,赋值给data => Object.defineProperty监听到data变动 => 执行更新函数更新视图            })          }        }      }    }    // 实例化MyVue    new MyVue({       el: '#app',       data: {        name: 'woow_wu7'      }    })  </script></body></html>

2020/12/29 vue双向数据绑定 - 亲测可用

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title>  <!-- 引入 Vue 通过CDN引入 -->  <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> --></head><body>  <div id="root">    <div type="text" v-text="name">v-text的内容</div>    <input type="text" v-model="name">    <script>      class MyVue {        constructor(options) {          const { el, data } = this.$options = options          this.$el = document.getElementById(el)          this.$data = data          this._directive = {}          // key:data对象中的key          // value:           this._observer(this.$data)          this._compile(this.$el)        }        _observer = (data) => {          for (let [key, value] of Object.entries(data)) {            // key就是data对象中的key;  value就是data对象中每个key对应的值            // data: {name: 'woow_wu7'} => key=name,value='woow_wu7'            if (data.hasOwnProperty(key)) {              this._directive[key] = [] // data中每个key都对应一个数组            }            if (typeof value === 'object') this._observer(value);            const that = this            Reflect.defineProperty(this.$data, key, {              enumerable: true,              configurable: true,              get() {                return value              },              set(newValue) {                if (value !== newValue) {                  value = newValue                  that._directive[key].forEach(item => item._update())                }              }            })          }        }        _compile = (el) => {          for (let [key, value] of Object.entries(el.children)) {            if (value.length) {              this._compile(value)            }            if (value.hasAttribute('v-text')) {              const attrubuteValue = value.getAttribute('v-text')              this._directive[attrubuteValue].push(new Watcher('input', value, this, attrubuteValue, 'innerHTML'))              // 留神:              // attrubuteValue是v-text对应的值 => 其实就是data中的key值,和_observer中的申明保持一致了            }            if (value.hasAttribute('v-model') && value.tagName === 'INPUT' || value.tagName === 'TEXTAREA') {              const attributeValue = value.getAttribute('v-model')              this._directive[attributeValue].push(new Watcher('v-model', value, this, attributeValue, 'value'))              const that = this              value.addEventListener('input', (e) => {                // 1. input事件批改data中的属性                // 2. data中的属性被批改,触发 Reflect.defineProperty 的 setter() 函数                this.$data[attributeValue] = e.target.value              }, false)            }          }        }      }      class Watcher {        constructor(directiveName, el, vm, exp, attr) {          this.name = directiveName // 指令的名字,比方 'v-text','v-model'          this.el = el // 每个具体的DOM节点          this.vm = vm // MyVue实例对象          this.exp = exp // el中的directiveName属性对应的属性值          this.attr = attr // el的属性,须要需改的属性          this._update()        }        _update = () => {          this.el[this.attr] = this.vm.$data[this.exp]          // 将MyVue实例的data属性的最新值更新到ui视图中        }      }      new MyVue({        el: 'root',        data: {          name: 'woow_wu7'        }      })    </script></body></html>

材料

具体 https://juejin.im/post/684490...
精简 https://juejin.im/post/684490...
实现vue数据双向绑定 https://juejin.im/post/684490...
https://segmentfault.com/a/11...
https://juejin.im/post/684490...
我的简书:https://www.jianshu.com/p/bdb...