内容简介:之前,我们在网上,可以看到很多有关vue部分功能的实现原理,尤其是数据双向绑定那一块的,文章很多,但是都是按照同样的思想去实现的一个数据双向绑定的功能,但不是vue的源码。今天,我在一行一行的去看vue的所有代码,并挨个作出解释,这个时候我们可以发现,vue的细节,很值得我们去学习。大家觉得写的有用的话,帮忙点个关注,点点赞,有问题可以评论,只要我看到,我会第一时间回复。
之前,我们在网上,可以看到很多有关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源码解析的起始篇,接下来我会持续更新该系列的文章,欢迎大家批评和点评,还是老话,多点关注,多点赞:blush:
谢谢大家。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- vue 源码学习 - 实例挂载
- 深入剖析Vue源码 - 实例挂载,编译流程
- Vue 源码解析 - 实例化 Vue 前(二)
- 内核通信之 Netlink 源码分析和实例分析
- 内核通信之 Netlink 源码分析和实例分析
- 连载四:PyCon2018|恶意域名检测实例(附源码)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
jQuery基础教程 (第4版)
[美] Jonathan Chaffer、[美] Karl Swedberg / 李松峰 / 人民邮电出版社 / 2013-10 / 59.00
本书由jQuery API网站维护者亲自撰写,第一版自2008上市以来,一版再版,累计重印14次,是国内首屈一指的jQuery经典著作! 作为最新升级版,本书涵盖jQuery 1.10.x和jQuery 2.0.x。本书前6章以通俗易懂的方式讲解了jQuery的核心组件,包括jQuery的选择符、事件、动画、DOM操作、Ajax支持等。第7章和第8章介绍了jQuery UI、jQuery M......一起来看看 《jQuery基础教程 (第4版)》 这本书的介绍吧!
图片转BASE64编码
在线图片转Base64编码工具
Base64 编码/解码
Base64 编码/解码