IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 响应式原理(Vue2.x)下篇 -> 正文阅读

[JavaScript知识库]响应式原理(Vue2.x)下篇

这两天面试,或多或少都问些Vue响应式原理,问的我头皮发麻。虽然我手写过,懂和能回答还是两回事。所以

  • 纸上得来终觉浅,绝知此事要躬行

手写响应式的完整代码

Vue2.x

接着上篇 你这手写vue2.x/3.x的响应式原理保熟吗?? 继续做下扩展。

上篇手写实现了

  • v-model
  • v-show
  • v-text
  • {{ }}

这里给下完整vue的简单手写响应式代码吧

效果
效果演示

<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>Vue2.x</title>
  <style>
    .show-txt {
      opacity: 1;
      transition: all .5s linear;
    }
    .show-txt.hidden {
      opacity: 0;
      color: #eee;
    }
  </style>
</head>
<body>
  <div id="app">
    <input type="text" v-model="name" /><br/>
    <input id="male" name="sex" type="radio" v-model="sex" value="男">
      <label for="male"></label>
    </input>
    <input id="female" name="sex" type="radio" v-model="sex" value="女">
      <label for="female"></label>
    </input><br/>
    <input name="show" type="checkbox" v-model="show" checked>是否展示</input>
    <div class="show-txt" v-show="show">展示文本示例</div>
    <b>姓名:</b><span>{{ name }}</span> <br/>
    <b>性别:</b><span>{{ sex }}</span>
    <hr />
    <div v-text="text"></div>
  </div>
  <script>
    class Dep {
      constructor (){
        this.subs = []
      }
      addSub (sub){
        if(sub && sub.update) this.subs.push(sub)
      }
      notify(oldValue){
        this.subs.forEach(sub => {
          sub.update(oldValue)
        })
      }
    }

    // 数据更新后调用 update 进行更新
    class Watcher {
      constructor(vm, key, cb){
        this.vm = vm
        this.key = key
        this.cb = cb
        Dep.target = this
        this.oldVal = vm[key]
        Dep.target = null
      }
      update(oldValue){
        this.oldVal = oldValue;
        let newValue = this.vm.$data[this.key]
        if(newValue === this.oldVal) return;
        this.cb(newValue)
      }
    }

    //解析模板template 内容,
    class Compiler {
      constructor(vm) {
        this.vm = vm;
        this.el = vm.$el;
        this.compile(this.el)
      }
      compile(el) {
        let childrenNodes = [...el.childNodes]
        childrenNodes.forEach(node => {
          if(this.isTextNode(node)){
            this.compileText(node)
          }else if(this.isElementNode(node)) {
            this.compileElement(node)
          }
          if(node.childNodes && node.childNodes.length) this.compile(node)
        })
      }
      compileText(node){
        let reg  = /\{\{(.+?)\}\}/;
        let val = node.textContent
        if(reg.test(val)){
          let key = RegExp.$1.trim()
          const value = this.vm[key];
          node.textContent = val.replace(reg, value)
          new Watcher(this.vm, key, (newVal) => {
            node.textContent = newVal
          })
        }
      }
      compileElement(node) {
        // https://developer.mozilla.org/zh-CN/docs/Web/API/Element/attributes
        ![...node.attributes].forEach(attr => {
          let attrName = attr.name
          if(this.isDirective(attrName)){
            attrName = attrName.substring(2);
            let key = attr.value;
            this.update(node, key, attrName)
          }
        })
      }
      update(node, key, attrName) {
        let updateFn = this[attrName+'Update']
        updateFn && updateFn.call(this, node, key, this.vm[key])
      }
      // 文本节点的值有更新就会重新渲染。本质还是利用 js修改 textContent
      textUpdate(node, key, content ){
        node.textContent = content
        new Watcher(this.vm, key, newVal => { node.textContent = newVal })
      }
      modelUpdate(node, key, value) {
        const typeAttr = node.getAttribute('type')
        if(typeAttr == "text") {
          node.value = value;
          new Watcher(this.vm, key, newVal => { node.value = newVal})
          node.addEventListener('keyup', () => {
            this.vm.$data[key] = node.value
          })
        }
        else if(typeAttr === "radio") {
          new Watcher(this.vm, key, newVal => { node.classList.add('class-'+newVal)})
          const nameAttr = node.getAttribute('name')
          // 这里不需要 watch, 
          node.addEventListener('change', (ev) => {
            this.vm.$data[key] = ev.target.value
          })
        }else if(typeAttr === 'checkbox') {
          node.addEventListener('change', (ev) => {
            this.vm.$data[key] = ev.target.checked
          })
        }
      }
      showUpdate(node, key, value){
        const change = (val) => { 
          const operate = !!val ? 'remove' : 'add';
          node.classList[operate]('hidden') 
        }
        change(value);
        new Watcher(this.vm, key, (newVal) => { change(newVal) })
      }
      isDirective(attr) {
        return attr.startsWith('v-')
      }
      isTextNode(node){
        return node.nodeType === 3
      }
      isElementNode(node) {
        return node.nodeType === 1
      }
    }

    /*
      将数据变为响应式对象
    */
    class Observer {
      constructor(data) {
        this.walk(data);
      }
      walk(data) {
        if(!data || typeof data != 'object') return;
        Object.keys(data).forEach(key => {
          this.defineReactive(data, key, data[key]);
        })
      }
      defineReactive(obj, key, value) {
        // 递归的将对象子属性变为响应式
        this.walk(value);
        const self= this;
        let dep = new Dep()
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get(){
            Dep.target && dep.addSub(Dep.target)
            return value
          },
          set (newValue) {
            const oldval = obj[key]
            if(newValue === obj[key]) return;
            value = newValue;
            self.walk(newValue)
            // 更新视图
            dep.notify(oldval)
          }
        })
      }
    }

    class Vue {
      constructor(options) {
        this.$options = options || {}
        this.$el = typeof options.el === 'string' ?
          document.querySelector(options.el) : options.el;
        this.$data = options.data;
        // 处理data中的属性 
        this._proxyData(this.$data);
        // 将data变为响应式
        new Observer(this.$data)  
        // 模板编译
        new Compiler(this)
      }
      // 将data中的属性注册到vue
      _proxyData(data) {
        Object.keys(data).forEach(key => {
          Object.defineProperty(this, key, {
            enumerable: true,
            configurable: true,
            get(){ return data[key]  },
            set (newValue) {
              if(newValue === data[key]) return;
              data[key] = newValue;
            }
          })
        })
      }
    }

    new Vue({
      el: '#app',
      data: {
          name: 'ethan',
          sex: '男',
          text: 'text',
          show: true,
        }
    })
</script>
</body>

Vue3.x

<body>
  <script>
      // https://juejin.cn/post/7001999813344493581#heading-1
      f1()
      function f1() {
          const targetMap = new WeakMap()
          /*
              targetMap = WeakMap{
                  target: Map{ 
                      key: Set[cb,..]
                      ,... }
                  , ...}
              这里用 WeakMap Map 主要是为了添加时自动去重
          */ 
          function track(target, key) {
              /* 
                  如果此时activeEffect为null则不执行下面
                  这里判断是为了避免例如console.log(person.name)而触发track
              */
              if (!activeEffect.fn) return
              // depsMap: target 的所有 依赖(Set)
              let depsMap = targetMap.get(target)
              if (!depsMap) {
                  targetMap.set(target, depsMap = new Map())
              }
              // dep: 数据项每个 key 值的依赖集合(Set)
              let dep = depsMap.get(key)
              if (!dep) {
                  depsMap.set(key, dep = new Set())
              }
              dep.add(activeEffect.fn) // 把此时的activeEffect添加进去
              // 此时的 dep 才是后面更新的 dep 将其当做更新的 key
              activeEffect.key = dep;
          }

          function trigger(target, key, { oldValue }) {
              let depsMap = targetMap.get(target)
              console.log('depsMap: ', depsMap);
              if (depsMap) {
                  const dep = depsMap.get(key)
                  if (dep) {
                      dep.forEach(effect => effect(oldValue, dep))
                  }
              }
          }

          function reactive(target) {
              const handler = {
                  get(target, key, receiver) {
                      console.log('get: ', target, key, receiver)
                      track(receiver, key) // 访问时收集依赖
                      return Reflect.get(target, key, receiver)
                  },
                  set(target, key, value, receiver) {
                      const oldValue = Reflect.get(target, key, receiver)
                      if(value === oldValue) return;
                      const result = Reflect.set(target, key, value, receiver)
                      trigger(receiver, key, { oldValue }) // 设值时自动通知更新
                      return result
                  }
              }
              return new Proxy(target, handler)
          }
          let activeEffect = {fn: null, key: null}
          function effect(fn) {
              activeEffect.fn = fn
              activeEffect.fn()
              activeEffect = {fn: null, key: null}
          }
          // 测试一下
          // t00()
          function t00() {
              const data = { name: '二蛋' }
              const rData = reactive(data)
              // 有一个 data.name 的依赖
              effect(() => { 
                  console.log('我依赖二蛋', rData.name); 
              })
              rData.name = '王二蛋'
          }
          
          function ref(initValue) {
              return reactive({
                  value: initValue
              })
          }

          function watchEffect(fn) {
              effect(() => fn())
          }
          function computed(fn) {
              const result = ref()
              effect(() => result.value = fn())
              return result
          }

          /*
              watch 简版, 主要实现 对 ref 数据的监听, 
              将新值和旧值保存为Map,方便多源监听时 精确更新,
              注意: 本方法只是简单的实现了 对 ref 的监听, 如果是 reactive, key 会有问题
          */
          function watch(source, fn) {
              let oldValues = new Map(), newValues = new Map(), isArray = false; 
              function handleEffects(rValue){
                  effect((oldValue, upKey = null) => {
                      // 这里去触发get 搜集依赖
                      const newValue = typeof rValue === 'function' ? rValue().value : rValue.value;
                      if(activeEffect.fn) {
                          // 遍历原始数据时,新值旧值都一样, 添加依赖的时候 增加 activeEffect.key
                          oldValues.set(activeEffect.key, newValue)
                          newValues.set(activeEffect.key, newValue)
                      }else {// 同时更新旧值与新值
                          oldValues.set(upKey, oldValue)
                          newValues.set(upKey, newValue)
                          // 同源和多源做不同处理
                          isArray 
                              ? fn([[...newValues.values()], [...oldValues.values()]]) 
                              : fn([...newValues.values()][0], [...oldValues.values()][0]);
                      }
                  })
              }
              // 监听多源
              if(Array.isArray(source)) {
                  isArray = true;
                  source.forEach(rValue => {
                      handleEffects(rValue)
                  })
              }
              // 监听单一源
              else 
                  handleEffects(source)
          }
          // t0()
          function t0(){
              const eData = ref(5);
              watchEffect(() => { console.log('effect测试: ', eData.value) })
              eData.value = 666
          }
          // t01()
          function t01(){
              const wRef = ref(5);
              watch(wRef, (value, preValue) => {
                  console.log('watch监听单源测试:', value, preValue)
              })
              wRef.value = 66
              // watch监听单源测试: 666 5
          }
          t02()
          function t02(){
              const wRef = ref(1);
              const wRef1 = ref(2);
              const wRef2 = ref(3);
              
              watch([wRef, () => wRef1, wRef2], (values, preValues) => { 
                  console.log('watch监听多源测试:', values, preValues)
              })
              wRef.value = 11;
              // watch监听多源测试:[11, 2, 3] [1, 2, 3]
              wRef1.value = 22;
              // watch监听多源测试:[11, 22, 3] [1, 2, 3]
              wRef2.value = 33
              // watch监听多源测试:[111, 22, 33] [11, 2, 3]
              wRef.value = 111
              // watch监听多源测试:[11, 22, 33] [1, 2, 3]
              wRef2.value = 333
              // watch监听多源测试:[111, 22, 33] [11, 2, 33]
          }

          /* 
              Computed 设计要完成如下几件事
                  触发effect, 传入的函数需要触发get收集依赖
                  返回一个响应式对象(Proxy)

              运行 effect 才会有依赖的收集(activeEffect存在),
              运行 Computed 时会运行 effect ,传入的 cb也会执行,
              此时 result.value 会执行, 触发 get ,接着收集依赖(track),
              此时 activeEffect 存在,那么 targetMap 为

          */
         // t1();
         function t1(){
              const ref1 = ref(5);
              const cRef1 = computed(() => {
                  console.log('computed测试: ', ref1);
                  return ref1.value
              });
              ref1.value = 666;
              console.log('last: ', ref1, cRef1)
         }  
      }
  </script>
</body>

扩展

主要参考 Vue的MVVM实现原理 ?, 原文还配了视频,针不错📌(我改了一下旧值不跟新的问题,代码结构是基于我之前手写的版本)

主要扩展了

  • 支持 v-html
  • 支持渲染 {{xxx}} - {{ssss}}
  • 支持对象属性更新
  • 支持事件绑定 v-on:event
  • 支持属性绑定 v-bind: attribute

再次给一下 vue2.x 响应式的图(来自参考原文),这张图我认为描述的很清晰

为了读起来不那么吃力(大佬请绕路),建议读懂我上一篇文章

思路我就不再细说了,直接给代码,结合注释看吧

效果
请添加图片描述

全部代码

<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>
  <style>
    .show-txt {
      opacity: 1;
      transition: all .5s linear;
    }

    .show-txt.hidden {
      opacity: 0;
      color: #eee;
    }
  </style>
</head>

<body>
  <div id="app">
    <h2>{{ person.name }}{{ app }}</h2>
    <input type="text" v-model="person.name" /><br />
    <input id="male" name="sex" type="radio" v-model="person.sex" value="男">
    <label for="male"></label>
    </input>
    <input id="female" name="sex" type="radio" v-model="person.sex" value="女">
    <label for="female"></label>
    </input><br />
    <input name="show" type="checkbox" v-model="show" checked>是否展示</input>
    <div class="show-txt" v-show="show">展示文本示例</div>
    <b>姓名:</b><span>{{ person.name }}</span> <br />
    <b>性别:</b><span>{{ person.sex }}</span>
    <hr />
    <button v-on:click="onTest">按钮v-on</button>
    <button @click="aitTest">按钮@</button><img v-bind:src="imgSrc" v-bind:alt="altTitle">
    <div v-text="text"></div>
    <div v-html="htmlStr"></div>
    <hr/>
  </div>
  <script>
    /*
      https://juejin.cn/post/6844904183938678798
    */
    class Dep {
      constructor() {
        this.subs = []
      }
      addSub(sub) {
        if (sub && sub.update) {
          this.subs.push(sub)
        }
      }
      notify(oldValue) {
        this.subs.forEach(sub => {
          sub.update(oldValue)
        })
      }
    }

    // 数据更新后调用 update 进行更新
    class Watcher {
      constructor(vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        Dep.target = this
        this.oldVal = compileUtils.getVal(this.expr, this.vm)
        Dep.target = null
      }
      update(oldValue) {
        this.oldVal = oldValue;
        let newValue = compileUtils.getVal(this.expr, this.vm)
        if (newValue === this.oldVal) return;
        this.cb(newValue)
      }
    }
	// 一些编译的工具函数
    const compileUtils = {
      // express 表达式,获取值,针对对象值做处理
      getVal: (express, vm) => {
        return express.split('.').reduce((data, attr) => {
          return data[attr.trim()];
        }, vm.$data)
      },
      // 注意,这里需要在最后一次做更改,不然一级属性会触发watcher,造成错误
      setVal: (express, vm, value) => {
        return express.split('.').reduce((data, attr, index, arr) => {
          return index === arr.length -1 ? data[attr.trim()] = value : data[attr.trim()];
        }, vm.$data)
      },
      // 对于方法的处理
      getMehods: (express, vm) => {
        return vm.$options.methods[express].bind(vm)
      },
      //主要处理这种情况 '{{3fdsf}}---{{234fsdfds}}'.replace(/\{\{(.+?)\}\}/g, 555) "555---555"
      contentUpdate: (node, vm, oldval) => {
        let reg = /\{\{(.+?)\}\}/g
        const value = oldval.replace(reg, (...args) => {
          const key = args[1];
          new Watcher(vm, key, (newVal) => {
            node.textContent = newVal
          })
          return compileUtils.getVal(args[1], vm)
        })
        node.textContent = value
      }
    }
    // 解析模板template 内容
    class Compiler {
      constructor(vm) {
        this.vm = vm;
        this.el = vm.$el;
        // 利用文档碎片优化
        let fragment = this.node2Fragment(this.el)
        this.compile(fragment)
        // 将所有更改一次性挂载
        this.el.appendChild(fragment)
      }
      // 文档碎片优化方法
      node2Fragment(el){
        let fragment = document.createDocumentFragment();
        let firstChild;
        while(firstChild = el.firstChild){
          // appendChild会将元素移动到新的位置(删除 + 新增)
          fragment.appendChild(firstChild)
        }
        return fragment;
      }
      // 编译
      compile(el) {
        let childrenNodes = [...el.childNodes]
        // 针对不同的节点采取不同的编译方法
        childrenNodes.forEach(node => {
          if (this.isTextNode(node)) {
            this.compileText(node)
          } else if (this.isElementNode(node)) {
            this.compileElement(node)
          }
          if (node.childNodes && node.childNodes.length) this.compile(node)
        })
      }
      // 文本节点编译
      compileText(node) {
        let val = node.textContent
        if (val.includes('{{')) {
          compileUtils.contentUpdate(node, this.vm, val)
        }
      }
      // 元素节点编译
      compileElement(node) {
        // https://developer.mozilla.org/zh-CN/docs/Web/API/Element/attributes
        ![...node.attributes].forEach(attr => {
          let {name, value} = attr;
          if (this.isDirective(name)) {
            let [,attrName] = name.split('-');

            // v-bind: src/v-on:click 
            const [drective, eventName] = attrName.split(':');
            // 注意这里eventName 表示 事件或者属性
            eventName && this.addEventOrAttr(node, drective, eventName, value)

            // 更新文本或指令
            this.update(node, value, attrName)

            // 移除多余的 v-xxx 属性
            node.removeAttribute('v-'+attrName)
          }else{// 处理 @xxx, :xxx
            if(/^[@](.+?)$/.test(name)){
              this.addEventOrAttr(node, 'on', RegExp.$1, value)
            }else if(/^[:](.+?)$/.test(name)){
              this.addEventOrAttr(node, 'bind', RegExp.$1, value)
            }
          }
        })
      }
      addEventOrAttr(node, derective, eventName, express) {
        this[derective + 'Update'](node, eventName, express)
      }
      update(node, key, attrName) {
        let updateFn = this[attrName + 'Update']
        const value = compileUtils.getVal(key, this.vm)
        updateFn && updateFn.call(this, node, key, value)
      }
      // 文本节点的值有更新就会重新渲染。本质还是利用 js修改 textContent
      textUpdate(node, key, content) {
        node.textContent = content
        new Watcher(this.vm, key, newVal => {
          node.textContent = newVal
        })
      }
      // v-model 指令更新
      modelUpdate(node, key, value) {
        const typeAttr = node.getAttribute('type')
        if (typeAttr == "text") {
          node.value = value;
          new Watcher(this.vm, key, newVal => {
            node.value = newVal
          })
          node.addEventListener('input', () => {
            compileUtils.setVal(key, this.vm, node.value)
          }, false)
        } else if (typeAttr === "radio") {
          new Watcher(this.vm, key, newVal => {
            node.classList.add('class-' + newVal)
          })
          const nameAttr = node.getAttribute('name')
          // 这里不需要 watch, 
          node.addEventListener('change', (ev) => {
            compileUtils.setVal(key, this.vm, ev.target.value)
          }, false)
        } else if (typeAttr === 'checkbox') {
          node.addEventListener('change', (ev) => {
            compileUtils.setVal(key, this.vm, ev.target.checked)
          }, false)
        }
      }
      // v-show 指令更新
      showUpdate(node, key, value) {
        const change = (val) => {
          const operate = !!val ? 'remove' : 'add';
          node.classList[operate]('hidden')
        }
        change(value);
        new Watcher(this.vm, key, (newVal) => {
          change(newVal)
        })
      }
      // v-html 指令更新
      htmlUpdate(node, key, htmlContent) {
        node.innerHTML = htmlContent;
        new Watcher(this.vm, key, newVal => {
          node.innerHTML = htmlContent;
        })
      }
      // v-bind 指令更新
      bindUpdate(node, attr, express) {
        new Watcher(this.vm, express, newVal => {
          node.setAttribute(attr, newVal)
        })
        const value = compileUtils.getVal(express, this.vm);
        node.setAttribute(attr, value)
      }
      // v-on 指令更新
      onUpdate(node, eventName, express){
        console.log(eventName, express)
        const cbFn = compileUtils.getMehods(express, this.vm)
        node.addEventListener(eventName, cbFn)
      }
      isDirective(attr) {
        return attr.startsWith('v-')
      }
      isTextNode(node) {
        return node.nodeType === 3
      }
      isElementNode(node) {
        return node.nodeType === 1
      }
    }

    /*
      将数据变为响应式对象
    */
    class Observer {
      constructor(vm) {
        this.vm = vm;
        this.walk(this.vm.$data);
      }
      walk(data) {
        if (!data || typeof data != 'object') return;
        Object.keys(data).forEach(key => {
          this.defineReactive(data, key, data[key]);
        })
      }
      defineReactive(obj, key, value) {
        // 递归的将对象子属性变为响应式
        const self = this;
        self.walk(value);
        let dep = new Dep()
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            Dep.target && dep.addSub(Dep.target)
            return value
          },
          set(newValue) {
            const oldval = compileUtils.getVal(key, self.vm)
            if (newValue === oldval) return;
            value = newValue;
            self.walk(newValue)
            // 更新视图
            dep.notify(oldval)
          }
        })
      }
    }

    class Vue {
      constructor(options) {
        this.$options = options || {}
        this.$el = typeof options.el === 'string' ?
          document.querySelector(options.el) : options.el;
        this.$data = options.data;
        // 处理data中的属性(代理到Vue上)
        this._proxyData(this.$data);
        // 将data变为响应式
        new Observer(this)
        // 模板编译
        new Compiler(this)
      }
      // 将data中的属性注册到vue
      _proxyData(data) {
        Object.keys(data).forEach(key => {
          Object.defineProperty(this, key, {
            enumerable: true,
            configurable: true,
            get() {
              return data[key]
            },
            set(newValue) {
              if (newValue === data[key]) return;
              data[key] = newValue;
            }
          })
        })
      }
    }

    new Vue({
      el: '#app',
      data: {
        app: 'vueApp',
        person: {
          name: 'ethan',
          sex: '男',
        },
        text: 'text',
        show: true,
        htmlStr: '<b>我是html字符串</b>',
        imgSrc:'https://js.tuguaishou.com/img/indexhead/people_vip.png'
      },
      methods: {
        onTest(){
          this.person.name = '伊森'
          console.log('on: ', this.person)
        },
        aitTest(){
          this.imgSrc = 'https://js.tuguaishou.com/img/indexhead/company_vip.png'
          console.log('@: ', this.imgSrc)
        }
      }
    })
  </script>
</body>

注意

从前面效果图展示可能看出来了,本实现还有个Bug:

对于 <span>{{ person.name }} --- {{ person.age }}<span> ,改变其中一个之后,就会出现 span 内仅有一个值(改变那个),而不是两个值的组合,这点我没找到好的解决方式(可能能通过正则那里每个依赖值给个前后索引值能解决),后面再去看看源码吧。如果各位有好的实现或者思路有希望评论交流。


聊聊 MVVM

阐述一下你所理解的MVVM响应式原理

vue是采用数据劫持配合发布者-订阅者模式 通过 Object.defineProperty() 劫持数据各个属性的 gettter, sertter.在数据发生变化时,发布消息给依赖收集器,通知观察者去触发相应依赖的回调函数,去更新视图。

MVVM作为绑定的入口,整合Observer, Compile, Watcher三者,通过 Observer监听model数据变化,通过 Compile 来解析模板指令和动态的内容,最终利用WatcherObserverCompile 联系起来,达到 数据变化 => 视图更新,视图交互变化 => 数据model变更的双向绑定结果。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-13 09:11:00  更:2021-09-13 09:11:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 17:12:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码