Vue2.X源码学习笔记数据代理

Posted 我真的好难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue2.X源码学习笔记数据代理相关的知识,希望对你有一定的参考价值。

   接上一篇文章的内容,我们回到_init()函数。

Vue.prototype._init = function (options) {
  var vm = this;
  // a uid
  vm._uid = uid$3++;

  var startTag, endTag;
  /* istanbul ignore if */
  if (config.performance && mark) {
    startTag = "vue-perf-start:" + (vm._uid);
    endTag = "vue-perf-end:" + (vm._uid);
    mark(startTag);
  }

  // a flag to avoid this being observed
  vm._isVue = true;
  // merge options
  if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options);
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    );
  }
  /* istanbul ignore else */
  {
    initProxy(vm);
  }
  // expose real self
  vm._self = vm;
  initLifecycle(vm);
  initEvents(vm);
  initRender(vm);
  callHook(vm, \'beforeCreate\');
  initInjections(vm); // resolve injections before data/props
  initState(vm);
  initProvide(vm); // resolve provide after data/props
  callHook(vm, \'created\');

  /* istanbul ignore if */
  if (config.performance && mark) {
    vm._name = formatComponentName(vm, false);
    mark(endTag);
    measure(("vue " + (vm._name) + " init"), startTag, endTag);
  }

  if (vm.$options.el) {
    vm.$mount(vm.$options.el);
  }
};

  我们在上一篇文章已经分析到了选项的合并操作的代码,接下来,代码将调用initProxy方法,对Vue的实例vm进行一层代理。这层代理可以为Vue在渲染模板时做一层数据筛选。

initProxy = function initProxy (vm) {
  if (hasProxy) {
    // determine which proxy handler to use
    var options = vm.$options;
    var handlers = options.render && options.render._withStripped
      ? getHandler
      : hasHandler;
    vm._renderProxy = new Proxy(vm, handlers);
  } else {
    vm._renderProxy = vm;
  }
};

  hasProxy代表浏览器支持Proxy对象,在上一篇文章中,我们已经对实例化传入的选项进行了一系列的合并操作,此时的options就是合并操作后的选项对象,这个时候,options选项是没有render属性的,所以handlers会被赋值hasHandler对象。此时通过Proxy创建一个新对象赋值给vm._renderProxy,并对vm实例进行代理。

var hasHandler = {
  has: function has (target, key) {
    var has = key in target;
  //isAllowed用来判断模板上出现的变量是否合法。
  //_和$开头的变量不允许出现在定义的数据中,因为它是Vue内部定义的变量开头。
  //warnReserverdPrefix 警告变量不允许以$,_开头,warnNonPresent警告出现的变量在vue实例中未定义。
var isAllowed = allowedGlobals(key) || (typeof key === \'string\' && key.charAt(0) === \'_\' && !(key in target.$data)); if (!has && !isAllowed) { if (key in target.$data) { warnReservedPrefix(target, key); } else { warnNonPresent(target, key); } } return has || !isAllowed } };

  当访问vm._renderProxy对象时,会触发对vm实例的数据筛选,那么,什么时候会访问到vm._renderProxy呢,在源码中有几处地方调用到render函数,我们截取一处来说明。

vnode = render.call(vm._renderProxy, vm.$createElement);

  在这里我们先不管render函数的实现细节,我们只需要知道render函数是包装成with的执行语句,Proxy对象在with作用域下,变量的访问都会触发has钩子,通过代理会对vm实例进行一层层的数据筛选。通过代码注释我们可以知道筛选的策略。当浏览器不支持Proxy对象时,Vue依然能够对vm实例对象进行代理拦截,原因是在初始化数据阶段,Vue已经为数据进行了一层代理。

function initData(vm) {
    vm._data = typeof data === \'function\' ? getData(data, vm) : data || {}
    if (!isReserved(key)) {
        // 数据代理,用户可直接通过vm实例返回data数据
        proxy(vm, "_data", key);
    }
}

function isReserved (str) {
    var c = (str + \'\').charCodeAt(0);
    // 首字符是$, _的字符串
    return c === 0x24 || c === 0x5F
  }
function proxy (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
        // 当访问this[key]时,会代理访问this._data[key]的值
        return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

  由上可知,数据代理与拦截的方式有两种,一种是通过Object.defineProperty,另一种则是通过Proxy,两种方法结合使用,使得Vue内部实现了对数据的拦截处理。这一篇文章篇幅不大,主要分析了initProxy方法执行后究竟发生了什么,下一篇文章我们接着往下分析_init()方法的实现。

 

以上是关于Vue2.X源码学习笔记数据代理的主要内容,如果未能解决你的问题,请参考以下文章

Vue2.X源码学习笔记选项合并

Vue2.x源码学习笔记-Vue构造函数

Vue2.x源码学习笔记-Vue静态方法和静态属性整理

Vue2.x基础笔记学习

vue2.x源码学习

vue2.x源码学习