• vue响应式原理,去掉优化,只看核心


    Vue响应式原理

    作为写业务的码农,几乎不必知道原理。但是当你去找工作的时候,可是需要造原子弹的,什么都得知道一些才行。所以找工作之前可以先复习下,只要是关于vue的,必定会问响应式原理。

    核心:

    //es5
    Object.defineProperty(obj,key,{
        get() {
            // 获取obj[key]的时候触发
        },
        set(val) {
           // obj[key] = 'xxx'时触发
        }
    })
    

    其实,只需要在修改data值的时候,需要触发一个回调方法,来更新与此值有关的数据,就完了。但是你面试的时候,那些大佬可不会觉得是这样的,需要把整个流程说明白才行。

    简单的Vue响应式代码如下:

    Vue.js:

    class Vue {
      constructor(opts) {
        this.opts = opts
        if (opts.data) this.initData(opts.data);
        if (opts.watch) this.initWatch(opts.watch);
        if (opts.computed) this.initComputed(opts.computed);
        if (opts.el) this.$mount(opts.el)
      }
      initData(data) {
        // 让data上的数据被get的时候能够搜集watcher,data变为观察者
        new Observable(data);
        this.data = data
        // 数据可在this上直接读取
        Object.keys(data).forEach(key => {
          this.proxy(key)
        })
      }
    
      initWatch(watch) {
        Object.keys(watch).forEach(key => {
          // 
          new Watcher(this, key, watch[key])
        })
      }
      initComputed(data) {
        Object.keys(data).forEach(key => {
          new Watcher(this, key)
          this.proxy(key, {
            get: data[key],
          })
        })
    
      }
    
      $mount(el) {
        el = document.querySelector(el)
        this.template = el.innerHTML
    
        this.el = el
        const fn = _ => {
          const nwTemp = this.parseHTML(this.template)
          this.el.innerHTML = nwTemp
          if(this.opts.mounted) {
            this.opts.mounted.call(this)
          }
        }
        new Watcher(this, fn)
      }
      parseHTML(template) {
    
        return template.replace(/{{(.*?)}}/g, (str, str1) => {
          return this[str1.trim()]
        })
      }
    
    
      // 将数据直接挂到this上,使用getterSetter代理,获取vm.data上的值
      proxy(key, getterSetter) {
        const vm = this
        getterSetter = getterSetter || {
          set(value) {
            vm.data[key] = value
          },
          get() {
            return vm.data[key]
          }
        }
        Object.defineProperty(vm, key, getterSetter)
      }
    }
    // 给data作为观察者用的
    class Observable {
      constructor(obj) {
    
        Object.keys(obj).forEach(key => {
          this.defineReact(obj, key, obj[key])
        });
      }
    
      defineReact(obj, key, value) {
      
        const dep = new Dep()
        Object.defineProperty(obj, key, {
          get() {
            // 获取data里面信息的时候,能够搜集依赖,这些依赖都是watcher实例
            if (Dep.target) {
              dep.append(Dep.target)
            }
            return value
          },
          set(val) {
            value = val
            // 修改data里面数据的时候,去通知已搜集的依赖更新
            dep.notify()
          }
        })
      }
    
    }
    
    // 搜集与触发wacher
    class Dep {
    
      constructor() {
        this.subs = []
      }
      append(watcher) {
        // 避免重复添加watcher
        // watcher 在update的时候,会重新获取值,此时不必再添加
        if(this.subs.includes(watcher)) return
        this.subs.push(watcher)
      }
      notify() {
        this.subs.forEach(watcher => {
          watcher.update()
        })
      }
    }
    Dep.target = null
    
    
    class Watcher {
      // keyOrFn 为字符串或者function,opts.watch为字符串,computed,$mount中为function
      constructor(vm, keyOrFn, cb) {
        this.cb = cb
        if (typeof keyOrFn === 'string') {
          this.getter = function () {
            return vm[keyOrFn] // 例:initWatch时,watch:{a(){}} ,a为data里的数据,此处获取vm.a会触发a的收集
          }
        } else {
          
          this.getter = keyOrFn // 如果为fn(computed中)值为此函数的返回值
        }
    
        this.value = this.get()
      }
      get() {
        Dep.target = this // 此watcher记录下来
        const value = this.getter(this.vm)
         // 运行getter,如果是watch,获取一次data里的值,完成收集。
         // 如果是computed,运行其方法,其方法中含有data的值时,会触发收集
        // 当更新时,也会触发getter
         Dep.target = null 
        return value
      }
    
      update() {
        // 更新
        const oldValue = this.value
        // 重新获取值
        this.value = this.get()
        if (this.cb) {
          //如果是watch,会触发watch的函数
          this.cb.call(this.vm, this.value, oldValue)
        }
      }
    }
    

    index.html

      <div id="app">
          <h2>Vue响应式原理</h2>
          <br />
          msg: {{ msg }}
          <br />
          <p>num: {{ num }}</p>
    
          <p>num+1计算属性值:{{ add1 }}</p>
          <button>add</button>
        </div>
        <script src="./Vue.js"></script>
    
        <script>
          const app = new Vue({
            el: "#app",
            data: {
              msg: "这是msg",
              num: 1
            },
            watch: {
              msg(newVal, oldVal) {
                console.log(newVal, oldVal);
              }
            },
            computed: {
              add1() {
                return this.num + 1;
              }
            },
            mounted() {
              const btn = document.querySelector("button");
              btn.onclick = _ => {
                this.num += 1;
              };
            }
          });
        </script>
    

    总结

    1.  需要this.xxx 访问的做proxy
    2.  data做 Observer 动态数据化处理。
    3.  data每一个属性都new Dep 管理此属性的订阅及改变数据后的发布。
    4.  computed、watch各属性及$mount 做 new  Watch订阅。
    

  • 相关阅读:
    finalShell 文件上传拖拽失败
    centos6.x 启动docker报错
    笔记本查看当前登录用户
    保存文件到D盘时显示“你没有权限在此文件夹中保存文件,请联系管理员“其他文件夹正常?
    关于MongoDB配置文件的一个小细节
    ubuntu: mongoDB安装,无需下载
    Java 连接虚拟机中MongoDB 所需依赖
    信息知识竞赛刷题助手
    python超多常用知识记录
    python字典键或值去重
  • 原文地址:https://www.cnblogs.com/gsgs/p/11498277.html
Copyright © 2020-2023  润新知