【Vue2深度学习】Vue全局API的实现原理

414 阅读1分钟
前言

全局API和实例方法不同,后者是在Vue的原型上挂载方法,也就是在Vue.prototype上挂载方法,而前者是直接在Vue上挂载方法。例如:

Vue.extend = funciton(extendOptions) {
    //做点什么
}
Vue.extend

Vue.extend的作用是创建一个子类,所以可以创建一个子类,然后让它继承Vue身上的一些功能。

/**
* 参数
* { Object } extendOptions 组件选项的对象
*/
​
  Vue.cid = 0;
  var cid = 1;
  Vue.extend = function (extendOptions) {
       //传入了参数使用传入的如果没穿使用{}
      extendOptions = extendOptions || {};
      // 指向父类,即基础 Vue类;
      var Super = this;
      // 父类的cid属性,无论是基础Vue类还是从基础Vue类继承而来的类,都有一个cid属性,作为该类的唯一标识;
      var SuperId = Super.cid;
      //如果传入的对象参数不存在_Ctor属性,extend会给他添加上这个属性,用于缓存。
      var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
      //如果存在_Ctor这个属性,并且这个对象上存在SuperId这个键的话就会直接返回缓存过的构造函数
      if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
      }
        
      //校验name名称是否符合规则
      var name = extendOptions.name || Super.options.name;
      {
        if (!/^[a-zA-Z][\w-]*$/.test(name)) {
          warn(
            'Invalid component name: "' + name + '". Component names ' +
            'can only contain alphanumeric characters and the hyphen, ' +
            'and must start with a letter.'
          );
        }
      }
      
      //这就是子类的构造函数
      var Sub = function VueComponent (options) {
        this._init(options);
      };
      // 继承vue的原型方法
      Sub.prototype = Object.create(Super.prototype);
      //把constructor的指向自己
      Sub.prototype.constructor = Sub;
      //Sub添加一个自己的属性cid用于缓存
      Sub.cid = cid++;
     //合并配置
      Sub.options = mergeOptions(
        Super.options,
        extendOptions
      );
      //把父类(也就是vue)挂载到super属性方便以后使用
      Sub['super'] = Super;
​
      // //判断是否有props属性如果有就初始化props
      if (Sub.options.props) {
        initProps$1(Sub);
      }
      //判断是否有computed属性如果有就初始化computed
      if (Sub.options.computed) {
        initComputed$1(Sub);
      }
​
      //把父类的extend mixin use 方法挂载到Sub上允许他进一步扩展
      Sub.extend = Super.extend;
      Sub.mixin = Super.mixin;
      Sub.use = Super.use;
​
      //ASSET_TYPES是数组['component','directive','filter']
      //把super(父类)的component,directive,filter 方法挂载到sub(子类)上
      //使sub具有注册组件 ,注册指令,注册自定义过滤器
      ASSET_TYPES.forEach(function (type) {
        Sub[type] = Super[type];
      });
       //name有值的时候将自己存到options.components[name]上
      if (name) {
        Sub.options.components[name] = Sub;
      }
​
      // 将这些options保存起来,一会创建实例的时候会用到.
      Sub.superOptions = Super.options;
      Sub.extendOptions = extendOptions;
      Sub.sealedOptions = extend({}, Sub.options);
​
      // 缓存池,用于缓存创建出来的类
      cachedCtors[SuperId] = Sub;
      return Sub
  };
Vue.directive/Vue.filter/Vue.component

之所以将directive、filter、component三个方法放到一起,是因为这三个方法实现的原理是一模一样的,在vue的源码中,也是一起实现的。

其中,Vue.directive用于注册或获取全局指令。

Vue.filter用于注册或获取全局过滤器。

Vue.component用于注册或获取全局组件。

/**
* 参数
* {string} id
* { Function | Object } [definition]
*/
​
​
  var ASSET_TYPES = [
     'component', // 组件
     'directive', // 指令
     'filter' // 注册
   ];
   ASSET_TYPES.forEach(function (type) {
      Vue[type] = function (id, definition) {
        // 如果没有传递 definition 的话,直接返回 id 对应的 definition,即获取指令/过滤器/组件
        if (!definition) {
          return this.options[type + 's'][id]
        } else {
          // 下面进行注册操作,其实所谓的注册操作:只是将要注册的东西找个地方存放起来而已。
          {
            if (type === 'component' && config.isReservedTag(id)) {
             // 判断注册组件的 tag 是不是 Vue 内置的组件,或者是 HTML 的保留标签
​
              warn(
                'Do not use built-in or reserved HTML elements as component ' +
                'id: ' + id
              );
            }
          }
          if (type === 'component' && isPlainObject(definition)) {
               // 进行组件 name 的处理,如果 definition 中没有 name 的话,则使用 id 作为组件的 name
​
            definition.name = definition.name || id;
             //_base 属性其实就是 Vue 构造函数
             // 组件的本质就是 Vue 构造函数的子级构造函数
            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
        }
      };
  });
Vue.use

Vue.use的作用是注册插件。

/**
* 参数
* { Function | Object } plugin
*/
​
  Vue.use = function (plugin) {// plugin 插件一般来说是一个实例对象
      //插件只会被安装一次
      if (plugin.installed) {
        return
      }
      // arguments删除第一个参数后转换成真正的数组,并且添加this作为数组的第一个元素
      var args = toArray(arguments, 1);
      args.unshift(this);
      // 判断use方法的第一个参数的install属性是否为函数,又或者第一个参数是否为函数,如果有一个满足则执行apply方法调用第一个参数函数,并且把use方法剩余的参数传入。
      if (typeof plugin.install === 'function') {
        plugin.install.apply(plugin, args);
      } else if (typeof plugin === 'function') {
        plugin.apply(null, args);
      }
      plugin.installed = true;
      //返回this,能够实现链式调用
      return this
  };
​
Vue.mixin

Vue.mixin的作用是全局注入一个混入,影响注册之后创建的每个Vue.js实例。

其实现原理并不复杂,只是将用户传入的对象与vue.js自身的options属性合并在一起。

/**
* 参数
* { Object } minxin
*/
​
Vue.mixin = function (mixin) {
      this.options = mergeOptions(this.options, mixin);
 };

其中,mergeOptions方法会将用户传入的mixin与this.options合并成一个新对象,然后将这个生成的新对象覆盖this.options(即Vue.options)属性。

因为mixin方法修改了Vue.options属性,而之后创建的每个实例都会用到该属性,所以会影响创建的每个实例。

Vue.compile

Vue.compile用来编译模板字符串并返回包含渲染函数的对象。

并不是所有vue.js的构建版本都存在Vue.compile方法,Vue.compile只存在于完整版中。

Vue.compile方法只需要调用编译器就可以实现功能。其代码如下:

Vue.compile = compileToFunctions

其中compileToFunctions方法可以将模板编译成渲染函数。

Vue.version

Vue.version提供字符串形式的Vue.js安装版本号。可以根据不同版本号采取不同的策略。

Vue.version是一个属性。在构建文件的过程中,会读取package.json文件中的version,并将读取出的版本号设置到Vue.version上。

具体实现步骤是:Vue.js在构建文件的配置中定义了__VERSION__常量,使用rollup-plugin-replace插件在构建的过程中将代码中的常量__VERSION__替换成package.json文件中的版本号。

rollup-plugin-replace插件的作用是在构建过程中替换字符串。所以在代码中只需将__VERSION__赋值给Vue.version就可以在构建时将package.json文件中的版本号赋值给Vue.version。源码如下

Vue.version = '__VERSION__'
Vue.nextTick

在下次DOM更新循环结束之后执行延迟回调,修改数据之后立即使用这个方法获取更新后的DOM。

Vue.nextTick的实现原理与我们前面介绍的vm.$nextTick一样,代码如下:

/**
* 参数
* { Function } [callback]
* { Object } [context]
*/
​
import { nextTick } from '../util/index'
Vue.nextTick = nextTick
​

nextTick方法是封装的公共方法,供Vue.nextTick与vm.$nextTick共同调用。

Vue.set

Vue.set用于设置对象的属性。如果对象是响应性的,确保属性被创建后也是响应性的,同时触发视图更新。这个方法主要用于避开Vue不能检测属性被添加的限制。

Vue.set与vm.$set的实现原理相同,代码如下:

/**
* 参数
* { Object | Array } target
* { String | number } key
* { any } value
*/
​
import { set } from '../observer/index'
Vue.set = set

set 方法是封装的公共方法,供Vue.set与vm.$set共同调用。

Vue.delete

删除对象的属性。如果对象是响应性的,确保删除能触发视图更新。这个方法主要用于避开Vue不能检测属性被删除的限制。

Vue.delete与vm.$delete的实现原理相同,代码如下:

/**
* 参数
* { Object | Array } target
* { String | number } key/index
*/
​
import { del } from '../observer/index'
Vue.delete = del

del 方法是封装的公共方法,供Vue.delete与vm.$delete共同调用。

因nextTick、set、del方法已在Vue常用实例方法的实现原理一文中详细介绍了,故此处不在赘述。