Vue 源码解析(实例化前) - 初始化全局API(二)

栏目: JavaScript · 发布时间: 5年前

内容简介:由于数据双向绑定的内容会比较多一些,而且涉及到的知识点也比较多,所以我当时就从初始化全局API里面单独拿了一章出来,去讲解 vue 到底是如何实现的数据双向绑定,现在,接着把之前没有讲完的初始化全局API要做的事情,全都给讲完。还是那句老话,如果觉得写的哪里不对了,还希望大家多多指出,欢迎评论;如果觉得不错的话,点点关注,点点赞,谢谢大家,你们的支持,是我继续写下去的动力:pray:

由于数据双向绑定的内容会比较多一些,而且涉及到的知识点也比较多,所以我当时就从初始化全局API里面单独拿了一章出来,去讲解 vue 到底是如何实现的数据双向绑定,现在,接着把之前没有讲完的初始化全局API要做的事情,全都给讲完。

还是那句老话,如果觉得写的哪里不对了,还希望大家多多指出,欢迎评论;

如果觉得不错的话,点点关注,点点赞,谢谢大家,你们的支持,是我继续写下去的动力:pray:

正文

Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;

Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
  Vue.options[type + 's'] = Object.create(null);
});

Vue.options._base = Vue;

extend(Vue.options.components, builtInComponents);

initUse(Vue);
initMixin$1(Vue);
initExtend(Vue);
initAssetRegisters(Vue);
复制代码

这篇文章,讲的就是 initGlobalAPI 函数剩下的这一部分。

设置对象属性(set)

Vue.set = set;
复制代码

该属性,就是用来设置对象的属性,如果属性不存在,则添加新属性并触发更改通知。

function set (target, key, val) {
  if (isUndef(target) || isPrimitive(target)
  ) {
    warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key);
    target.splice(key, 1, val);
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val
  }
  var ob = (target).__ob__;
  if (target._isVue || (ob && ob.vmCount)) {
    warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    );
    return val
  }
  if (!ob) {
    target[key] = val;
    return val
  }
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val
}
复制代码

set 方法,接收三个参数, target (目标)、 key (属性)、val (值);

function isUndef (v) {
    return v === undefined || v === null
}
function isPrimitive (value) {
    return (
      typeof value === 'string' ||
      typeof value === 'number' ||
      typeof value === 'symbol' ||
      typeof value === 'boolean'
    )
}
复制代码

一进来,就先检查,当前的目标是不是上面的这几种类型,如果是的话,是没有办法去做类似根据对应的 key 去做值更新的;

这里其实最主要检查的就是当前的 target 是不是对象或者数组,可是作者在这里,做了除对象和数组以外所有类型的判断,不知道这样做的目的是什么,为什么不直接判断数组和对象呢,那样需要执行的代码也会比较少,如果大家知道为什么要这么做,欢迎评论。

if (Array.isArray(target) && isValidArrayIndex(key)) {
}
复制代码

检查当前的 target 是不是数组,并且 key 是不是有效的数组索引:

function isValidArrayIndex (val) {
    var n = parseFloat(String(val));
    return n >= 0 && Math.floor(n) === n && isFinite(val)
}
复制代码

这里接收到的其实就是数组的 key ,先把 key 转成字符串,然后通过 parseFloat 去格式化它;

然后去看 n 是不是大于0的正整数,并且不是无穷大的。

target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val
复制代码

如果是数组,并且该索引也是合法的,那么用接收到的 val 去替换当前索引为 key 的索引项;

if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val
}
复制代码

这里要判断的是如果当前的 target 是对象,并且存在当前 key , 并且当前 key 不是在 Object 的原型上的,那么就用接收到新的 val 去替换当前的值;

var ob = (target).__ob__;
复制代码

__ ob __ 属性是我们在打印 vue 的实例化对象时经常看到的,这个属性其实就是在实例化 Observer 的时候,调用 def 函数去给当前目标添加的一个属性:

// Observer部分
var Observer = function Observer (value) {
  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);
  }
};
// def部分
function def (obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true
    });
}
复制代码

在这里对 Observer 不做太多的讲解,因为现在主要讲的是 set 部分,在往下讲,可能会比较乱,大家先自己看一下,看不懂的话,在接下来讲实例化 vue 部分的时候在仔细对 vue 的每一个重要的 api 做详细的讲解。

_isVue 和 vmCount 在 Vue 源码解析(实例化前) - 数据双向绑定的实现原理 这里有做过讲解,大家不了解这两个值是干什么的,可以来这里看一下。

if (!ob) {
    target[key] = val;
    return val
}
复制代码

如果当前的目标没有 __ ob __属性,那么就直接做赋值,并且返回;

defineReactive(ob.value, key, val);
ob.dep.notify();
复制代码

这里在上面的链接里面,就可以看到具体做了哪些事情,这里就不浪费篇幅去写了;

最后直接返回当前值。

这里总结一下,其实就是如果是存在值的话,直接更新值,如果不存在的话,就通过 defineReactive 去绑定一下
复制代码

删除对象属性(del)

Vue.delete = del;
复制代码

这个方法就是在删除属性并在必要时触发更改。

function del (target, key) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target))));
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1);
    return
  }
  var ob = (target).__ob__;
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    );
    return
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key];
  if (!ob) {
    return
  }
  ob.dep.notify();
}
复制代码

这个其实和 set 做的事情差不多,只是修改变成了删除,这里就不多讲了。

nextTick

function nextTick(cb, ctx) {
    var _resolve;
    callbacks.push(function () {
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    if (!pending) {
      pending = true;
      if (useMacroTask) {
        macroTimerFunc();
      } else {
        microTimerFunc();
      }
    }
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
}
复制代码

callbacks 是一个全局的消息频道,在每次调用 nextTick的时候,去添加一个 function ,这个 function 里面最主要做的就是一个判断 cb 是否存在,存在就把 cb 的 this 指向 ctx,如果 _resolve 存在的话,就直接调用它,并把 ctx 传给它。

if (!pending) {
  pending = true;
  if (useMacroTask) {
    macroTimerFunc();
  } else {
    microTimerFunc();
  }
}
复制代码

pending 是一个全局的状态,初始化是 false ,在第一次调用它的时候,直接改为 true ,并且要检查当前执行的是宏任务还是微任务,在执行到任务栈后,pending 变为 false,具体什么是宏任务,什么是微任务,看这里: js事件循环机制(event loop)

if (!cb && typeof Promise !== 'undefined') {
  return new Promise(function (resolve) {
    _resolve = resolve;
  })
}
复制代码

这里代码很简单,这里就不做太多解释了,大家不懂了评论把。:joy:

Vue.options = Object.create(null);
复制代码

给 Vue 的 options 添加一个原子。

ASSET_TYPES.forEach(function (type) {
  Vue.options[type + 's'] = Object.create(null);
});
复制代码

给 options 添加 n 个属性,每个属性都是一个原子:

var ASSET_TYPES = [
  'component',
  'directive',
  'filter'
];
复制代码

这是 ASSET_TYPES 所有的数组项;

Vue.options._base = Vue;

复制代码

这用于标识“基础”构造函数以扩展所有普通对象;

extend(Vue.options.components, builtInComponents);
复制代码

extend 在第一章已经讲过了,在这里: Vue 源码解析(实例化前) - 初始化全局API(一)

使用插件 initUse

initUse(Vue);
复制代码

这个方法,其实实现的就是一个 use 的方法,用来添加插件的:

function initUse(Vue) {
  Vue.use = function (plugin) {
    var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    var args = toArray(arguments, 1);
    args.unshift(this);
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args);
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args);
    }
    installedPlugins.push(plugin);
    return this
  };
}
复制代码

在这里,就是给 Vue 构造函数,在实例化前绑定哪些插件,所有的插件,都在 installedPlugins 里面。

初始化合并 initMixin$1

function initMixin$1(Vue) {
    Vue.mixin = function (mixin) {
      this.options = mergeOptions(this.options, mixin);
      return this
    };
}
复制代码

mergeOptions 方法的实现在这里: Vue 源码解析(实例化前) - 初始化全局API(一)

初始化继承 initExtend

Vue.extend = function (extendOptions) {
    extendOptions = extendOptions || {};
    var Super = this;
    var SuperId = Super.cid;
    var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    var name = extendOptions.name || Super.options.name;
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name);
    }

    var Sub = function VueComponent (options) {
      this._init(options);
    };
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    Sub.cid = cid++;
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    );
    Sub['super'] = Super;

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps$1(Sub);
    }
    if (Sub.options.computed) {
      initComputed$1(Sub);
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend;
    Sub.mixin = Super.mixin;
    Sub.use = Super.use;

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type];
    });
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub;
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options;
    Sub.extendOptions = extendOptions;
    Sub.sealedOptions = extend({}, Sub.options);

    // cache constructor
    cachedCtors[SuperId] = Sub;
    return Sub
  };
}
复制代码

这里,就是 extend 的方法的实现,其实最主要就是输出了一个函数,继承了 Vue 构造函数的所有属性和方法

extendOptions = extendOptions || {};
var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) {
  return cachedCtors[SuperId]
}
复制代码

检查 extendOptions 是否存在,如果存在 extendOptions 上是否存在 _Ctor 并且存在 SuperId ,如果存在直接就返回 cachedCtors[SuperId];

var name = extendOptions.name || Super.options.name;
if (process.env.NODE_ENV !== 'production' && name) {
  validateComponentName(name);
}
复制代码

这里检查的就是 name 的格式,是否是 xxx-xxx 这种格式的,在第一章里面有详细解释;

if (Sub.options.props) {
  initProps$1(Sub);
}
function initProps$1 (Comp) {
  var props = Comp.options.props;
  for (var key in props) {
    proxy(Comp.prototype, "_props", key);
  }
}
复制代码

这里做了初始化属性的遍历,给所有属性都绑定了 Object.defineProperty;

var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
复制代码

这是 proxy 函数的实现过程。

if (Sub.options.computed) {
  initComputed$1(Sub);
}
function initComputed$1 (Comp) {
  var computed = Comp.options.computed;
  for (var key in computed) {
    defineComputed(Comp.prototype, key, computed[key]);
  }
}
function defineComputed (
  target,
  key,
  userDef
) {
  var shouldCache = !isServerRendering();
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef;
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop;
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop;
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        ("Computed property \"" + key + "\" was assigned to but it has no setter."),
        this
      );
    };
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

复制代码

这段代码是对 computed 做的处理,也是绑定了 Object.defineProperty,这种类似的代码处理条件,在前两章已经讲了很多了,大家这里看一下其实就差不多了,如果不明白,就去看看前两章把。

Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;

ASSET_TYPES.forEach(function (type) {
  Sub[type] = Super[type];
});

if (name) {
  Sub.options.components[name] = Sub;
}

Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);

// cache constructor
cachedCtors[SuperId] = Sub;
return Sub
复制代码

这里其实就是让继承 Vue 的子级方法,可以做 Vue 可以做的事情。

初始化资源注册 initAssetRegisters

function initAssetRegisters(Vue) {
  ASSET_TYPES.forEach(function (type) {
    Vue[type] = function (
      id,
      definition
    ) {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        if (type === 'component') {
          validateComponentName(id);
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id;
          definition = this.options._base.extend(definition);
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition };
        }
        this.options[type + 's'][id] = definition;
        return definition
      }
    };
  });
}
复制代码

这个方法,就是把 ASSET_TYPES 所有的数组项当作 Vue 的方法名来定义,这里就只有注册,没有做太多的事情,最后返回处理后的 definition。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精通HTML

精通HTML

Paul Haine / 杨明军 / 人民邮电出版社 / 2008-2 / 35.00元

本书深入地探讨了(X)HTML及相关技术包括CSS、微格式、语义网等,重点阐述了如何在恰当的时候使用恰当的标签,全书始终贯彻现代的Web设计理念,从而使读者可以学习如何充分利用各种标记提供的多样性,创建语义丰富和结构合理的网站。 本书适合具备初步HTML和CSS知识的Web设计开发人员阅读。一起来看看 《精通HTML》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具