博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Vue 源码解析(实例化前) - 初始化全局API(一)
阅读量:5768 次
发布时间:2019-06-18

本文共 12815 字,大约阅读时间需要 42 分钟。

前言

之前,我们在网上,可以看到很多有关vue部分功能的实现原理,尤其是数据双向绑定那一块的,文章很多,但是都是按照同样的思想去实现的一个数据双向绑定的功能,但不是vue的源码。

今天,我在一行一行的去看vue的所有代码,并挨个作出解释,这个时候我们可以发现,vue的细节,很值得我们去学习。

大家觉得写的有用的话,帮忙点个关注,点点赞,有问题可以评论,只要我看到,我会第一时间回复。

话不多说,直接开始了。

正文

初始化

initGlobalAPI(Vue);复制代码

这个时候,初始化调用initGlobalAPI,传入Vue构造函数。这里是在Vue构造函数实例化之前要做的事情,所以这里先不讲Vue对象里面做了什么,先讲实例化之前做了什么。

function initGlobalAPI (Vue) {  // config  var configDef = {};  configDef.get = function () { return config; };  if (process.env.NODE_ENV !== 'production') {    configDef.set = function () {      warn(        'Do not replace the Vue.config object, set individual fields instead.'      );    };  }  Object.defineProperty(Vue, 'config', configDef);  // exposed util methods.  // NOTE: these are not considered part of the public API - avoid relying on  // them unless you are aware of the risk.  Vue.util = {    warn: warn,    extend: extend,    mergeOptions: mergeOptions,    defineReactive: defineReactive  };  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);  });  // this is used to identify the "base" constructor to extend all plain-object  // components with in Weex's multi-instance scenarios.  Vue.options._base = Vue;  extend(Vue.options.components, builtInComponents);  initUse(Vue);  initMixin$1(Vue);  initExtend(Vue);  initAssetRegisters(Vue);}复制代码

这是initGlobalAPI方法的所有代码,行数不多,但是知识点很多。

var configDef = {};复制代码

这个函数声明了一个configDef得空对象;

configDef.get = function () { return config; };复制代码

然后在给configDef添加了一个get属性,这个属性返回得是一个config对象,这个cofig对象里面,有n个属性,下面来一一解释一下:

config对象

var config = ({  optionMergeStrategies: Object.create(null),  silent: false,  productionTip: process.env.NODE_ENV !== 'production',  devtools: process.env.NODE_ENV !== 'production',  performance: false,  errorHandler: null,  warnHandler: null,  ignoredElements: [],  keyCodes: Object.create(null),  isReservedTag: no,  isReservedAttr: no,  isUnknownElement: no,  getTagNamespace: noop,  parsePlatformTagName: identity,  mustUseProp: no,  _lifecycleHooks: LIFECYCLE_HOOKS})复制代码

optionMergeStrategies:选项合并,用于合并core / util / options

默认值:object.creart(null)

注:object.creart(null)去创建的一个是原子,什么是原子呢,就是它是对象,但是不继承Object() ,这里对原子的概念不做深究,大家如果感兴趣,可以百度去查“js元系统”,aimingoo对这方面有做过详细的说明。

silent:是否取消警告

默认值:false

productionTip:项目启动时,是否显示提示信息

默认值:process.env.NODE_ENV !== 'production'

如果是开发环境,则是true,表示显示提示信息,在生产环境则不显示

devtools:是否启用devtools

默认值:同productionTip

performance:是否记录性能

默认值:false

errorHandler:观察程序错误的错误处理程序

默认值:null

warnHandler:观察程序警告的警告处理程序

默认值:null

ignoredElements:忽略某些自定义元素

默认值:[]

keyCodes:v - on的自定义用户keyCode

默认值:object.creart(null)

isReservedTag:检查是否保留了标记,以便它不能注册为组件。这取决于平台,可能会被覆盖

var no = function (a, b, c) { return false; };复制代码

默认值:一个名为no的function,这个function接收三个参数,但是结果永远返回的是false

isReservedAttr:检查属性是否被保留,以便不能用作组件道具。这取决于平台,可能会被覆盖

默认值:同上

isUnknownElement:检查标记是否为未知元素。取决于平台

默认值:同上

getTagNamespace:获取元素的命名空间

function noop (a, b, c) {}复制代码

默认值:一个名为noop的函数,里面什么都没有做

parsePlatformTagName:解析特定平台的真实标签名称

var identity = function (_) { return _; };复制代码

默认值:一个名为identity的函数,输入的什么就输出的什么

mustUseProp:检查是否必须使用属性(例如值)绑定属性。这个取决于平台

默认值:一个名为no的function

_lifecycleHooks:生命周期钩子数组

var LIFECYCLE_HOOKS = [  'beforeCreate',  'created',  'beforeMount',  'mounted',  'beforeUpdate',  'updated',  'beforeDestroy',  'destroyed',  'activated',  'deactivated',  'errorCaptured'];复制代码

默认值:一个数组,里面有所有生命周期的方法名

以上就是config里面所有的属性

config.set

if (process.env.NODE_ENV !== 'production') {    configDef.set = function () {      warn(        'Do not replace the Vue.config object, set individual fields instead.'      );    };  }复制代码

做了一个判断是否是生产环境,如果不是生产环境,给configDef添加一个set方法

Object.defineProperty(Vue, 'config', configDef);复制代码

在这里,为Vue的构造函数,添加一个要通过Object.defineProperty监听的属性config,获取的时候,获取到的是上面描述的那个config对象,如果对这个config对象直接做变更,就会提示“不要替换vue.config对象,而是设置单个字段”,说明,作者不希望我们直接去替换和变更整个config对象,如果有需要,希望去直接修改我们需要修改的值

公开util

Vue.util = {    warn: warn,    extend: extend,    mergeOptions: mergeOptions,    defineReactive: defineReactive};复制代码

在这里,设置了一个公开的util对象,但是它不是公共的api,避免依赖,除非你意识到了风险,下面来介绍一下它的属性:

warn:警示
var warn = noop;var generateComponentTrace = (noop);if (process.env.NODE_ENV !== 'production') {    warn = function (msg, vm) {        var trace = vm ? generateComponentTrace(vm) : '';        if (config.warnHandler) {          config.warnHandler.call(null, msg, vm, trace);        } else if (hasConsole && (!config.silent)) {          console.error(("[Vue warn]: " + msg + trace));        }  };}复制代码

warn是一个function,初始化的时候,只定义了一个noop方法,如果在开发环境,这个warn是可以接收两个参数,一个是msg,一个是vm,msg不用说,大家都知道这里是一个提示信息,vm就是实例化的vue对象,或者是实例化的vue对象的某一个属性。

接下来是一个三元表达式trace,用来判断调用warn方法时,是否有传入了vm,如果没有,返回的是空,如果有,那么就返回generateComponentTrace这个function,这个方法初始化的值也是noop,什么都没有做,目的是解决流量检查问题

如果config.warnHandler被使用者变更成了值,不在是null,那么就把config.warnHandler的this指向null,其实就是指向的window,再把msg, vm, trace传给config.warnHandler

否则判断当前环境使用支持conosle并且开启了警告,如果开启了,那就把警告提示信息打印出来

extend:继承
function extend (to, _from) {  for (var key in _from) {    to[key] = _from[key];  }  return to}复制代码

这个方法是用于做继承操作的,接收两个值to, _from,将属性_from混合到目标对象to中,如果to存在_from中的属性,则直接覆盖,最后返回新的to

mergeOptions:将两个选项对象合并为一个新对象,用于实例化和继承的核心实用程序(这是一个很重要的方法,在后面多处会用到,所以建议大家仔细看这里)
function mergeOptions (parent, child, vm) {  if (process.env.NODE_ENV !== 'production') {    checkComponents(child);  }  if (typeof child === 'function') {    child = child.options;  }  normalizeProps(child, vm);  normalizeInject(child, vm);  normalizeDirecitives(child);  var extendsFrom = child.extends;  if (extendsFrom) {    parent = mergeOptions(parent, extendsFrom, vm);  }  if (child.mixins) {    for (var i = 0, l = child.mixins.length; i < l; i++) {      parent = mergeOptions(parent, child.mixins[i], vm);    }  }  var options = {};  var key;  for (key in parent) {    mergeField(key);  }  for (key in child) {    if (!hasOwn(parent, key)) {      mergeField(key);    }  }  function mergeField (key) {    var strat = strats[key] || defaultStrat;    options[key] = strat(parent[key], child[key], vm, key);  }  return options}复制代码
if (process.env.NODE_ENV !== 'production') {    checkComponents(child);}function checkComponents (options) {  for (var key in options.components) {    validateComponentName(key);  }}function validateComponentName (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.'    );  }  if (isBuiltInTag(name) || config.isReservedTag(name)) {    warn(      'Do not use built-in or reserved HTML elements as component ' +      'id: ' + name    );  }}复制代码

这个方法接收三个参数parent,child,vm,在不是生产环境的情况下,会去检测参数child中,是否存在components,如果存在该对象,遍历所有的componets,进行名称是否符合规范,这里有一个正则,是用来判断以字母开头,以0个或多个任意字母和字符“-”结尾的字符串,如果不符合这个规定的话,就会提示警告信息

if (typeof child === 'function') {    child = child.options;}复制代码

如果child是一个function的话,则把child自己指向child的options属性

接下来要做的就是规范child里面的Props、Inject、Direcitives

normalizeProps(child, vm);normalizeInject(child, vm);normalizeDirecitives(child);复制代码
normalizeProps:规范属性,确保所有的props的规范都是基于对象的
function normalizeProps (options, vm) {  var props = options.props;  if (!props) { return }  var res = {};  var i, val, name;  if (Array.isArray(props)) {    i = props.length;    while (i--) {      val = props[i];      if (typeof val === 'string') {        name = camelize(val);        res[name] = { type: null };      } else if (process.env.NODE_ENV !== 'production') {        warn('props must be strings when using array syntax.');      }    }  } else if (isPlainObject(props)) {    for (var key in props) {      val = props[key];      name = camelize(key);      res[name] = isPlainObject(val)        ? val        : { type: val };    }  } else if (process.env.NODE_ENV !== 'production') {    warn(      "Invalid value for option \"props\": expected an Array or an Object, " +      "but got " + (toRawType(props)) + ".",      vm    );  }  options.props = res;}复制代码
var props = options.props;if (!props) { return }复制代码

一开始,会检查child是否存在props属性,如果不存在,直接return出去,如果存在的话则是去声明了几个变量,一个名为res的对象,还有i, val, name

if (Array.isArray(props)) {    i = props.length;    while (i--) {      val = props[i];      if (typeof val === 'string') {        name = camelize(val);        res[name] = { type: null };      } else if (process.env.NODE_ENV !== 'production') {        warn('props must be strings when using array syntax.');      }    }  }复制代码

检查props是数组还是对象,如果是数组的话,则是去循环它,并判断每一个数组项,是否是字符串,如果是字符串那么就去执行camelize方法。

camelize:

var camelizeRE = /-(\w)/g;var camelize = cached(function (str) {  return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })});复制代码

把名称格式为“xx-xx”的变为“xxXx”,这里接收的是当前的props属性值,一个字符串

cached:

function cached (fn) {  var cache = Object.create(null);  return (function cachedFn (str) {    var hit = cache[str];    return hit || (cache[str] = fn(str))  })}复制代码

在调用camelize方法的时候,camelize调用了cached,这是一个暂存式函数,对暂存式函数不了解的朋友,可以去看看函数式编程,在cached也是创建了一个原子cache,然后会返回一个cachedFn方法,这里会检测cache是否存在当前props属性值的属性,如果存在,直接返回,如果不存在,则是调用,调用cached的方法传过来的function,在调用cached方法的方法中返回的结果,返回到调用cached方法的方法(这句话我知道很绕口,但是我只会这么解释,哪位大佬有更好的表述方式,欢迎评论,我做修改)

然后把所有的数组项,并且是字符串的,全部都遍历一遍,做这样的处理,然后在res对象里面,去添加一个属性,它是一个对象,属性名就是遍历后的这个遍历后的值(把-转换成大写字母),属性值有一个初始化的type属性,值为null

当然不是生产环境下,并且props虽然是数组,但是数组项不是字符串的话,会警告你“使用数组语法时,props必须是字符串”

var _toString = Object.prototype.toString;function isPlainObject (obj) {  return _toString.call(obj) === '[object Object]'}else if (isPlainObject(props)) {    for (var key in props) {      val = props[key];      name = camelize(key);      res[name] = isPlainObject(val)        ? val        : { type: val };    }}options.props = res;复制代码

如果child的props不是数组,使用isPlainObject去判断props是否是对象,这个方法代码就一行,很简单,也比较好理解,我也就不浪费篇幅去解释了;

如果是对象的话,就去遍历它,把所有的属性名按照上面数组项的处理方式,去处理所有的数组名,并且当作res的属性名,该属性名的值需要去判断原props的该属性的值是否是对象,如果是对象,直接当作当前属性名的属性值,如果不是的话,则给当前处理后的属性名,传一个对象,type属性的值就是原props该属性名的属性值

这里,就把child里面所有的props给规范化了,最后覆盖了源child的props属性(这一个方法的内容真多,各种知识点,有没有,点波赞吧)

normalizeInject:规范Inject
function normalizeInject (options, vm) {  var inject = options.inject;  if (!inject) { return }  var normalized = options.inject = {};  if (Array.isArray(inject)) {    for (var i = 0; i < inject.length; i++) {      normalized[inject[i]] = { from: inject[i] };    }  } else if (isPlainObject(inject)) {    for (var key in inject) {      var val = inject[key];      normalized[key] = isPlainObject(val)        ? extend({ from: key }, val)        : { from: val };    }  } else if (process.env.NODE_ENV !== 'production') {    warn(      "Invalid value for option \"inject\": expected an Array or an Object, " +      "but got " + (toRawType(inject)) + ".",      vm    );  }}复制代码

和props一样,先检查是否存在,不存在直接返回;

如果存在的话,把child的inject存在一个变量inject里,把child里面的inject变成空对象,并且把该值传给一个normalized的变量;

如果inject是一个数组的话,则遍历它,normalized的每一个属性名,就是每一个inject的数组项,每一个属性值都是一个对象,对象的属性from的值,就是每一个inject的数组项

如果inject是一个对象的话,则遍历它,把每一个属性值存为变量val,normalized的key,就是inject的key,如果val是一个对象的话,则把{ from: key }和val合并,val覆盖{ from: key }

normalizeDirectives:规范Directives
function normalizeDirectives (options) {  var dirs = options.directives;  if (dirs) {    for (var key in dirs) {      var def = dirs[key];      if (typeof def === 'function') {        dirs[key] = { bind: def, update: def };      }    }  }}复制代码

源码里只处理了child.directives的对象格式,如果存在的话遍历它,如果每一个属性值def都是function的话则把每一个directives的属性值改为{ bind: def, update: def };

到这里,规范化的事情就做完了,休息一下,点个关注点个赞,咱们继续。

var extendsFrom = child.extends;if (extendsFrom) {parent = mergeOptions(parent, extendsFrom, vm);}复制代码

看child是否存在extends,递归当前的mergeOptions方法,parent就是当前的parent,child就是当前child的extends的值;

if (child.mixins) {    for (var i = 0, l = child.mixins.length; i < l; i++) {      parent = mergeOptions(parent, child.mixins[i], vm);    }}复制代码

检测child是否存在mixins,如果存在的话,递归当前的mergeOptions方法,并把最新的结果,去覆盖上一次调用mergeOptions方法的parent;

var defaultStrat = function (parentVal, childVal) {  return childVal === undefined    ? parentVal    : childVal};var strats = config.optionMergeStrategies;//这只是初始化的值var options = {};var key;for (key in parent) {    mergeField(key);}for (key in child) {    if (!hasOwn(parent, key)) {      mergeField(key);    }}function mergeField (key) {    var strat = strats[key] || defaultStrat;    options[key] = strat(parent[key], child[key], vm, key);}return options复制代码

现在声明了一个options的对象,然后分别去遍历了parent和child,parent和child的key传给了一个mergeField的方法;

在mergeField中声明一个start变量,如果strats下的存在当前这个key的属性,则返回,否则就返回一个默认的defaultStrat;

defaultStrat接收两个参数,第一个参数是parent,第二个是child,如果child存在就返回child,否则就返回parent;

把mergeField接收到的key,当作之前optins的key,它的值就是前面返回的变量start方法返回的值;

最后,把整个options返回。

结束语

到这里,Vue.util的四个属性已经讲了三个了,第四个属性是一个defineReactive方法,我不打算在这一篇去讲,因为这个方法,就是实现一个数据双向绑定的核心方法,内容可能会比较多,而且这一篇的内容也已经够长了,写的再多的话,不适合学习了,所以我打算在下一篇单独去讲一下defineReactive这个方法。

这篇文章,是vue源码解析的起始篇,接下来我会持续更新该系列的文章,欢迎大家批评和点评,还是老话,多点关注,多点赞?

谢谢大家。

转载地址:http://qybux.baihongyu.com/

你可能感兴趣的文章
Python中的画图初体验
查看>>
Java程序员的日常 —— 响应式导航Demo
查看>>
objective-c内存管理基础
查看>>
sap关于价值串的说法(转载)
查看>>
Migration to S/4HANA
查看>>
sed 对目录进行操作
查看>>
什么是代码
查看>>
移动端开发单位——rem,动态使用
查看>>
系列文章目录
查看>>
手把手教你如何提高神经网络的性能
查看>>
前端布局原理涉及到的相关概念总结
查看>>
递归调用 VS 循环调用
查看>>
使用sstream读取字符串中的数字(c++)
查看>>
树莓派下实现ngrok自启动
查看>>
javascript静态类型检测工具—Flow
查看>>
MachineLearning-Sklearn——环境搭建
查看>>
node学习之路(二)—— Node.js 连接 MongoDB
查看>>
Goroutine是如何工作的?
查看>>
《深入理解java虚拟机》学习笔记系列——垃圾收集器&内存分配策略
查看>>
TriggerMesh开源用于多云环境的Knative Event Sources
查看>>