以前,咱们在网上,能够看到不少有关vue部分功能的实现原理,尤为是数据双向绑定那一块的,文章不少,可是都是按照一样的思想去实现的一个数据双向绑定的功能,但不是vue的源码。javascript
今天,我在一行一行的去看vue的全部代码,并挨个做出解释,这个时候咱们能够发现,vue的细节,很值得咱们去学习。vue
你们以为写的有用的话,帮忙点个关注,点点赞,有问题能够评论,只要我看到,我会第一时间回复。java
话很少说,直接开始了。ios
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方法的全部代码,行数很少,可是知识点不少。api
var configDef = {};
复制代码
这个函数声明了一个configDef得空对象;数组
configDef.get = function () { return config; };
复制代码
而后在给configDef添加了一个get属性,这个属性返回得是一个config对象,这个cofig对象里面,有n个属性,下面来一一解释一下:less
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 / optionside
默认值: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里面全部的属性
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对象,若是有须要,但愿去直接修改咱们须要修改的值
Vue.util = {
warn: warn,
extend: extend,
mergeOptions: mergeOptions,
defineReactive: defineReactive
};
复制代码
在这里,设置了一个公开的util对象,可是它不是公共的api,避免依赖,除非你意识到了风险,下面来介绍一下它的属性:
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而且开启了警告,若是开启了,那就把警告提示信息打印出来
function extend (to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to
}
复制代码
这个方法是用于作继承操做的,接收两个值to, _from,将属性_from混合到目标对象to中,若是to存在_from中的属性,则直接覆盖,最后返回新的to
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);
复制代码
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属性(这一个方法的内容真多,各类知识点,有没有,点波赞吧)
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 }
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源码解析的起始篇,接下来我会持续更新该系列的文章,欢迎你们批评和点评,仍是老话,多点关注,多点赞😊
谢谢你们。