zoukankan      html  css  js  c++  java
  • vue官方源码剖析

    1. 变化侦测
      UI = render(state),数据驱动视图,Vue 在表达式中充当 render 这个角色,当 state 改变时,Vue 给出相应的变化到 UI 上,Vue 如何得知 state 变化了呢,即 state 变化怎么通知给 Vue

    2. Object.defineProperty 使对象数据变得可观测

          let car = {
              'brand': 'BMW',
              'price': 300
          }
          let val = 300
          Object.defineProperty(car, 'price', {
              enumerable: true,
              configurable: true,
              get(){
                  console.log('price 属性读取')
                  return val
              },
              set(newVal){
                  console.log('price 属性修改')
                  val = newVal
              }
          })
      

      Object.defineProperty 对对象的属性进行拦截,并执行自己想要的操作,这样就可以检测对象的变化了

         // 源码位置:src/core/observer/index.js
         // Observer类会通过递归的方式把一个对象的所有属性都转化成可观测对象
          export class Observer {
              constructor (value) {
              this.value = value
              // 给value新增一个__ob__属性,值为该value的Observer实例
              // 相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作
              def(value,'__ob__',this)
              if (Array.isArray(value)) {
                  // 当value为数组时的逻辑
                  // ...
              } else {
                  this.walk(value)
              }
              }
      
              walk (obj: Object) {
              const keys = Object.keys(obj)
              for (let i = 0; i < keys.length; i++) {
                  defineReactive(obj, keys[i])
              }
              }
          }
          function defineReactive (obj,key,val) {
              // 如果只传了obj和key,那么val = obj[key]
              if (arguments.length === 2) {
              val = obj[key]
              }
              if(typeof val === 'object'){
                  new Observer(val)
              }
              Object.defineProperty(obj, key, {
              enumerable: true,
              configurable: true,
              get(){
                  console.log(`${key}属性被读取了`);
                  return val;
              },
              set(newVal){
                  if(val === newVal){
                      return
                  }
                  console.log(`${key}属性被修改了`);
                  val = newVal;
              }
              })
          }
      
    3. 依赖收集
      在第一二步中我们将数据变成响应式,可观测的状态,在此过程中我们可以在 getter 中收集依赖,在 setter 中通知依赖更新;
      并在此过程将依赖置于依赖收集器中

      export default class Dep {
          constructor() {
              this.subs = []
          }
          addSubs(sub) {
              this.subs.push(sub)
          }
          removeSubs(sub) {
              remove(this.subs, sub)
          }
          depend() {
              if(window.target) {
                  this.addSubs(window.target)
              }
          }
          notify() {
              const sub = this.subs.slice()
              for(let i = 0;i < sub.length;i++) {
                  subs[i].update()
              }
          }
      }
      
      const remove = (arr, item) => {
          if(arr.length) {
              const index = arr.indexOf(item)
              if(index > -1) {
                  return arr.splice(index, 1)
              }
          }
      }
      

      在收集器中定义一个 subs 来进行依赖收集,并在这个对象中包含了对依赖的增、删、以及更新。有了收集器我们就可以将其置于 getter 中,代码如下:

      function defineReactive(obj, key, val) {
          if(arguments.length === 2) {
              val = obj[key]
          }
          if(typeof val === 'object') {
              new Observe(val)
          }
          const dep = new Dep()
          Object.defineProperty(obj, key, {
              enumerable: true,
              configurable: true,
              get() {
                  dep.depend()
                  return val
              },
              set(newVal) {
                  if(newVal === val) return
                  val = newVal
                  dep.notify()
              }
          })
      }
      

      在 get 中我们进行了依赖收集, 即 dep.depend; 在 set 中我们进行了通知依赖更新,即 dep.notify;但是我们不知道通知谁,或者说我们不知道谁是依赖。
      在 Vue 中定义过一个 Watch 类,在数据变化后,我们不直接通知依赖更新,而是告诉 Watch 类的实例,让实例去通知真正的试图更新

      export default class Watcher {
          constructor(vm, expOrFn, cb) {
              this.vm = vm
              this.cb = ch
              this.getter = parsePath(expOrFn)
              this.value = this.get()
          }
          get() {
              window.taget = this
              const vm = this.vm
              let value = this.getter.call(vm, vm)   // 触发dep的依赖收集  dep.depend
              window.target = undefined   // 依赖存放后,释放window.target
              return value
          }
          update() {
              const oldVal = this.value
              this.value = this.get()
              this.cb.call(this.vm, this.value, oldVal)  // set 触发dep的notify,在notify中遍历watcher里面的依赖,并执行对应的update
          }
      }
      
      
      /*
          Parse simple path
          把形如 a.b.c.d的字符串所表示的值,从真是的data对象里取出来
          data = {a:{b:{c:{d:2}}}}
          parsePath('a.b.c.d')(data) // 2
      
      */
      const bailRE = /[^w.$]/
      function parsePath(path) {
          if(bailRE.test(path)) return
          const segments = path.split('.')
          return function(obj) {
              for(let i=0; i<segments.length; i++) {
                  if(!obj) return
                  obj = obj[segments[i]]
              }
              return obj
          }
      }
      

      不足:对于后续对象添加的属性或者删除已有的属性,object.definePropery 不能够监听,即无法通知依赖,无法驱动视图进行响应式更新
      流程:

      1. Data 通过 obsever 转换成了 getter/setter 的形式追踪变化
      2. 当外界通过 Watcher 进行数据读取时,会触发 getter 将 Watcher 添加到依赖中
      3. 当数据发生变化时,会触发 setter,从而向 Dep 中的依赖(Watcher)发送通知
      4. Watcher 收到通知后,会向外界发送通知,可能会驱动数据更新,也可能触发某些回调
  • 相关阅读:
    Apple Swift编程语言入门教程
    网络请求错误
    Mac使用大全
    MPMovieplayerController添加新控件
    ios9 新变化
    UITableView总结
    UINavigationController的简单学习
    HTML
    谓词(NSPredicate)
    NSString字符串
  • 原文地址:https://www.cnblogs.com/tutao1995/p/14252043.html
Copyright © 2011-2022 走看看