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

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

内容简介:由于数据双向绑定的内容会比较多一些,而且涉及到的知识点也比较多,所以我当时就从初始化全局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。


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

查看所有标签

猜你喜欢:

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

Out of their Minds

Out of their Minds

Dennis Shasha、Cathy Lazere / Springer / 1998-07-02 / USD 16.00

This best-selling book is now available in an inexpensive softcover format. Imagine living during the Renaissance and being able to interview that eras greatest scientists about their inspirations, di......一起来看看 《Out of their Minds》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具