美文网首页VuevueVue
【Vue.js】不要把所有东西都放进data里了(至少2.0是如

【Vue.js】不要把所有东西都放进data里了(至少2.0是如

作者: Kagashino | 来源:发表于2019-08-04 02:01 被阅读143次

    Vue组件实例中的data是我们再熟悉不过的东西了,用来存放需要绑定的数据
    但是对于一些特定场景,data虽然能够达到预期效果,但是会存在一些问题
    我们写下如下代码,建立一个名单,记录他们的名字,年龄和兴趣:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
      <div id="app"> </div>
      <script src="some-data.js"></script>
      <script>
        const template = `
          <div>
            <h3>data列表</h3>
            <ol>
              <li v-for="item in dataList">
                姓名:{{item.name}},年龄:{{item.age}},兴趣:{{item.hobby.join('、')}}
              </li>
            </ol>
          </div>
        `
        new Vue({
          el: '#app',
          data () {
            return {
              dataList: [
                { name: '张三', age: 33, hobby: ['唱','跳','rap','篮球'] },
                { name: '李四', age: 24, hobby: ['唱','跳','rap','篮球'] },
                { name: '王五', age: 11, hobby: ['唱','跳','rap','篮球'] },
                { name: '赵六', age: 54, hobby: ['唱','跳','rap','篮球'] },
                { name: '孙七', age: 23, hobby: ['唱','跳','rap','篮球'] },
                { name: '吴八', age: 55, hobby: ['唱','跳','rap','篮球'] }
              ],
            }
          },
          mounted () {
            console.table(this.dataList) // 打印列表形式的dataList
            console.log(this.dataList) // 打印字面量
          },
          template
        })
      </script>
    </body>
    </html>
    
    

    Vue通过data生成我们能用的绑定数据,大概走了以下几个步骤:
    1.从 initData[1]方法 中获取你传入的data,校验data是否合法
    2.调用observe[2]函数,新建一个Observer[3]实例,将data变成一个响应式对象,而且为data添加 __ob__属性,指向当前Observer实例
    3.Observer保存了你的value值、数据依赖dep[4]和vue组件实例数vmCount
    4.对你的data调用defineReactive$$1[5]递归地监所有的key/value(你在data中声明的),使你的key/value都有自己的dep, getter[6]setter[7]

    我们忽略html的内容,重点放在这个dataList上(我用2种不同的形式打印了dataList),如上述步骤2、3、4所说,data中每个key/value值(包括嵌套的对象和数组)都添加了一个Observer:

    datalist

    之前我们说滥用data会产生一些问题,问题如下:
    设想一下这样的场景,如果你的data属于纯展示的数据,你根本不需要对这个数据进行监听,特别是一些比这个例子还复杂的列表/对象,放进data中纯属浪费性能。
    那怎么办才好?
    放进computed中
    还是刚才的代码,我们创建一个数据一样的list,丢进computed里:

    computed: {
            computedList () {
              return [
                { name: '张三', age: 33, hobby: ['唱','跳','rap','篮球'] },
                { name: '李四', age: 24, hobby: ['唱','跳','rap','篮球'] },
                { name: '王五', age: 11, hobby: ['唱','跳','rap','篮球'] },
                { name: '赵六', age: 54, hobby: ['唱','跳','rap','篮球'] },
                { name: '孙七', age: 23, hobby: ['唱','跳','rap','篮球'] },
                { name: '吴八', age: 55, hobby: ['唱','跳','rap','篮球'] }
              ]
            }
          },
    

    打印computedList,你得到了一个没有被监听的列表


    computedList

    为什么computed没有监听我的data
    因为我们的computedList中,没有任何访问当前实例属性的操作,根据Vue的依赖收集机制,只有在computed中引用了实例属性,触发了属性的getter,getter会把依赖收集起来,等到setter调用后,更新相关的依赖项

    我们来看官方文档对computed的说明:

    computed: {
        // 计算属性的 getter
        reversedMessage: function () {
          // `this` 指向 vm 实例
          return this.message.split('').reverse().join('')
        }
      }
    

    这里强调的是

    所以,对于任何复杂逻辑,你都应当使用计算属性

    但是很少有人注意到api说明中的这一句:

    计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。

    也就是说,对于纯展示的数据,使用computed会更加节约你的内存
    另外 computed 其实是Watcher[6]的实现,有空的话会更新这部分的内容

    为什么说“至少2.0是如此”

    因为3.0将使用Proxy来实现监听,性能将节约不少,参见https://www.jianshu.com/p/f99822cde47c

    源码附录 (v2.6.10)

    [1] initData: 检查data的合法性(比如是否作为函数返回、是否冲突或者没有提供data属性),初始化data

    function initData (vm) {
        var data = vm.$options.data;
        data = vm._data = typeof data === 'function'
          ? getData(data, vm)
          : data || {};
        if (!isPlainObject(data)) {
          data = {};
          warn(
            'data functions should return an object:\n' +
            'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
            vm
          );
        }
        // proxy data on instance
        var keys = Object.keys(data);
        var props = vm.$options.props;
        var methods = vm.$options.methods;
        var i = keys.length;
        while (i--) {
          var key = keys[i];
          {
            if (methods && hasOwn(methods, key)) {
              warn(
                ("Method \"" + key + "\" has already been defined as a data property."),
                vm
              );
            }
          }
          if (props && hasOwn(props, key)) {
            warn(
              "The data property \"" + key + "\" is already declared as a prop. " +
              "Use prop default value instead.",
              vm
            );
          } else if (!isReserved(key)) {
            proxy(vm, "_data", key);
          }
        }
        // observe data
        observe(data, true /* asRootData */);
      }
    

    [2] observe: 对当前对象新建一个Observer实例

    function observe (value, asRootData) {
        if (!isObject(value) || value instanceof VNode) {
          return
        }
        var ob;
        if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
          ob = value.__ob__;
        } else if (
          shouldObserve &&
          !isServerRendering() &&
          (Array.isArray(value) || isPlainObject(value)) &&
          Object.isExtensible(value) &&
          !value._isVue
        ) {
          ob = new Observer(value);
        }
        if (asRootData && ob) {
          ob.vmCount++;
        }
        return ob
      }
    

    [3] Observer类:为对象声明依赖,和响应式方法,同时对数组做兼容处理(Vue可以通过调用使原数组变化的方法如push、reverse、sort等触发监听)

    /**
     * Observer class that is attached to each observed
     * object. Once attached, the observer converts the target
     * object's property keys into getter/setters that
     * collect dependencies and dispatch updates.
     */
    export class Observer {
      value: any;
      dep: Dep;
      vmCount: number; // number of vms that have this object as root $data
    
      constructor (value: any) {
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
        if (Array.isArray(value)) {
          if (hasProto) {
            protoAugment(value, arrayMethods)
          } else {
            copyAugment(value, arrayMethods, arrayKeys)
          }
          this.observeArray(value)
        } else {
          this.walk(value)
        }
      }
    
      /**
       * Walk through all properties and convert them into
       * getter/setters. This method should only be called when
       * value type is Object.
       */
      walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i])
        }
      }
    
      /**
       * Observe a list of Array items.
       */
      observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        }
      }
    }
    

    [4] dep 依赖类Dep的实例实例,当notify被setter调用时触发Watcher更新,建议先看[5][6][7]再回过头来看参考

    /**
     * A dep is an observable that can have multiple
     * directives subscribing to it.
     */
    export default class Dep {
      static target: ?Watcher;
      id: number;
      subs: Array<Watcher>;
    
      constructor () {
        this.id = uid++
        this.subs = []
      }
    
      addSub (sub: Watcher) {
        this.subs.push(sub)
      }
    
      removeSub (sub: Watcher) {
        remove(this.subs, sub)
      }
    
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
    
      notify () {
        // stabilize the subscriber list first
        const subs = this.subs.slice()
        if (process.env.NODE_ENV !== 'production' && !config.async) {
          // subs aren't sorted in scheduler if not running async
          // we need to sort them now to make sure they fire in correct
          // order
          subs.sort((a, b) => a.id - b.id)
        }
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    }
    
    // The current target watcher being evaluated.
    // This is globally unique because only one watcher
    // can be evaluated at a time.
    Dep.target = null
    const targetStack = []
    
    export function pushTarget (target: ?Watcher) {
      targetStack.push(target)
      Dep.target = target
    }
    
    export function popTarget () {
      targetStack.pop()
      Dep.target = targetStack[targetStack.length - 1]
    }
    

    [5] `defineReactive$$1 [6] getter [7] setter,数据绑定的核心方法:通过调用Object.defineProperty对对象中的每一个key添加dep依赖和设置getter和setter,getter触发依赖收集,setter触发依赖更新

    /**
       * Define a reactive property on an Object.
       */
      function defineReactive$$1 (
        obj,
        key,
        val,
        customSetter,
        shallow
      ) {
        var dep = new Dep();
    
        var property = Object.getOwnPropertyDescriptor(obj, key);
        if (property && property.configurable === false) {
          return
        }
    
        // cater for pre-defined getter/setters
        var getter = property && property.get;
        var setter = property && property.set;
        if ((!getter || setter) && arguments.length === 2) {
          val = obj[key];
        }
    
        var childOb = !shallow && observe(val);
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get: function reactiveGetter () {
            var value = getter ? getter.call(obj) : val;
            if (Dep.target) {
              dep.depend();
              if (childOb) {
                childOb.dep.depend();
                if (Array.isArray(value)) {
                  dependArray(value);
                }
              }
            }
            return value
          },
          set: function reactiveSetter (newVal) {
            var value = getter ? getter.call(obj) : val;
            /* eslint-disable no-self-compare */
            if (newVal === value || (newVal !== newVal && value !== value)) {
              return
            }
            /* eslint-enable no-self-compare */
            if (customSetter) {
              customSetter();
            }
            // #7981: for accessor properties without setter
            if (getter && !setter) { return }
            if (setter) {
              setter.call(obj, newVal);
            } else {
              val = newVal;
            }
            childOb = !shallow && observe(newVal);
            dep.notify();
          }
        });
      }
    

    相关文章

      网友评论

        本文标题:【Vue.js】不要把所有东西都放进data里了(至少2.0是如

        本文链接:https://www.haomeiwen.com/subject/dpfddctx.html