小白也能秒懂Vue源码中那些精细设计(选项处理)
我"崩"不住了,在彭凡同志锲而不舍的催促下这篇文章终于"蛋"生了。 说正经的这篇文章不好写,不好写的原因是我不太擅长写这些类比文,但它还是写出来了。 相信大部分人都有开发过功能插件,在写插件的时候普遍应用基本思想是以"默认配置为优先,以用户配置为覆盖"。如果你觉得简单先别着急穿裤子走人继续往下看看。
Validator插件
$("form").Validator();
之前写过一个轻量级数据校验插件使用非常简单,你只需要找到form表单节点调用调用Validator 方法即可,就能在文本框中输入值进行自定校验。
$("form").Validator({ initEvent: "change", //自定义校验事件 password: "* 密码必须是6-12个字符且包含大小写字母" //自定义密码校验失败错误信息 });
当然也会给用户相对的自由度如"校验事件、错误提示信息 …"你只需要按照规定来配置就好了。
分割线*
在写Vue代码的小伙伴同样要写很多的选项:“el、data、props、template、render、mounted…” 那Vue在处理这些选项也是使用"非黑即白"的处理思想吗?明确的说没有 Vue 在处理选项有非常多限制如:
- el 只在用 new 创建实例时生效。
- data 组件的定义只接受function。
- 直接在 DOM中使用组件时,组件名只有是 kebab-case 才有效。
- 生命周期钩子…
也就是说在Vue 中"非黑即白"的思想并不适用,Vue需要针对特殊选项做不同的处理,有的选项处理逻辑是再此能不能用,有的选项处理逻辑是校验Value合法性,有的选项的逻辑是需要合并处理。… 这种处理方式比较官方的说法叫"选项自定义策略处理"。
选项自定义策略处理
在讲选择自定义策略处理之前先说说vm.$option实例属性,它是用于当前 Vue 实例的初始化选项。需要在选项中包含自定义属性时会有用处:
var vm = new Vue({ el: "#app", data: { message: "hello Vue", }, count: 9, })
输出vm.$option:
{ el: "#app", data: function mergedInstanceDataFn(){}, count: 9 }
有没有感觉奇怪在实例初始化选项中data的值是一个对象,为什么vm.$option中data的值变成一个函数了?开始揭秘…
Vue构造函数
function Vue(options) { if (!(this instanceof Vue)) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); }
在创建Vue实例的时候你传递进来的自定义选项对象会传递给this._init这个方法。
Vue.prototype._init = function(options) { var vm = this; //省略... vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); //省略... }
vm.options属性定义在Vue.prototype.init原型方法中。vm.options 属性定义在Vue.prototype._init 原型方法中。 vm.options属性定义在Vue.prototype.init原型方法中。vm.options的值是调用mergeOptions函数的返回值。
mergeOptions
function mergeOptions(parent, child, 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 }
先来看看调用mergeOptions的三个参数。
- resolveConstructorOptions - 这个函数的作用是用来获取当前实例构造者的 options 属性(注:涉及到组件相关的内容暂时不解释,在此你可以默认他传递是一个纯对象)
- options - 自定义选项对象
- vm - Vue实例
mergeOptions 最终返回的是在函数内置的options纯对象。 options 所拥有的属性就是调用mergeField函数传递进来的key。
举个栗子:
你在创建Vue的根实例,并且传递了一个自定义选项对象。
var vm = new Vue({ el: "#app", data: { message: "hello Vue", }, count: 9, })
自定义选项对象会作为实参传递给mergeOptions函数的child形参。
for (key in child) { if (!hasOwn(parent, key)) { mergeField(key); } }
通过for…in… 语句把child对象上可枚举的属性名作为参数传递给mergeField。
hasOwn是检测关于组件中父实例中是否key属性如果有将不会重复的调用mergeField,因为父子组件实例中相同的属性只需要做一次策略处理就可以了。(注:不扩展讲解)
当前栗子中"el"、“data”、“count” 这三个属性名作为字符串会作为参数传递给mergeField函数。那在mergeField函数中会给options 扩展 options.el = undefined 、 options.data = undefined 、 options.count = undefined 三个属性,为什么这三个属性的值都是undefined的呢?
原因是他们的value都需要通过调用 strat(parent[key], child[key], vm, key) 函数返回值来确定所以都暂定undefined,那strat 是什么?
var strat = strats[key] || defaultStrat;
strats是自定义策略对象,strats[key]是检测在这个自定义策略对象上有没有[key]这个属性,如果有就表示针对[key]属性写了策略函数反之就没写。
defaultStrat 默认策略函数
var defaultStrat = function(parentVal, childVal) { return childVal === undefined ? parentVal : childVal };
默认策略函数要弄懂它你只需要明白parentVal 、childVal 这两个参数。
- parentVal 就是在调用strat 传递进来的parent[key],因为我们默认parent为纯函数所以parentVal 永远为undefined。
- childVal 就是在调用strat 传递进来的childVal[key],也就是自定义选项对象中的[key]属性的值。
strats 自定义策略对象
var strats = config.optionMergeStrategies;
strats 是获取了config 全局配置对象上optionMergeStrategies属性的值。
var config = { optionMergeStrategies: Object.create(null) //省略... }
你是不是觉得奇怪**为什么不直接通过字面量方式创建一个纯对象赋值给strats,而要通过Object.create(null)创建纯对象。**这样不麻烦了吗? 问题暂时保留。往下看Vue中自定义的策略函数。
strats.el = strats.propsData = function(parent, child, vm, key) { if (!vm) { warn( "option \"" + key + "\" can only be used during instance " + 'creation with the `new` keyword.' ); } return defaultStrat(parent, child) }; strats.data = function(parentVal, childVal, vm) { if (!vm) { if (childVal && typeof childVal !== 'function') { warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ); return parentVal } return mergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) }; //省略...
可以看到Vue中对"el", “data”,“watch”,"props"等等…选项都写了策略函数。 在回归到mergeField函数你是否能顿悟了。
function mergeField(key) { var strat = strats[key] || defaultStrat; options[key] = strat(parent[key], child[key], vm, key); }
如果某个选项写了策略函数那么就会调用这个策略函数,返回值会成为options[key] 的value。最后:mergeOptions 函数执行完毕返回options引用给到vm.$options。
现在来解释刚刚的那个问题。**为什么不直接通过字面量方式创建一个纯对象赋值给strats,而要通过Object.create(null)创建纯对象?**原因是Vue想给用户自定义选项自由度,也能添加策略函数。
举个栗子:
你在创建Vue的根实例,并且传递了一个自定义选项对象。
var vm = new Vue({ el: "#app", data: { message: "hello Vue", }, count: 9, })
我想针对count写个添加策略函数怎么办。
Vue.config.optionMergeStrategies._my_option = function(parentVal, childVal, vm) { return childVal >= 99 ? childVal : 99 }
这个策略函数很简单,监听childVal的值: 如果大于99返回本身,小于99 返回99。count策略函数返回值给到vm.$options.count。
那Vue.config是怎么访问到的呢?
function initGlobalAPI(Vue) { // config var configDef = {}; configDef.get = function() { return config; }; configDef.set = function() { warn( 'Do not replace the Vue.config object, set individual fields instead.' ); }; Object.defineProperty(Vue, 'config', configDef); } initGlobalAPI(Vue);
你细品这个代码。 看不懂留言请相信我一定会假装看不见。
推荐:
- 020 持续更新,精品小圈子每日都有新内容,干货浓度极高。
- 结实人脉、讨论技术 你想要的这里都有!
- 抢先入群,跑赢同龄人!(入群无需任何费用)
- 点击此处,与前端开发大牛一起交流学习
申请即送:
-
BAT大厂面试题、独家面试工具包,
-
资料免费领取,包括 各类面试题以及答案整理,各大厂面试真题分享!
- vue源码分析——path解析的状态机设计
- 第九章 IP选项处理(我再去看IP选项的那些基础概念再来看)
- vue源码浅析(对象和数组依赖处理)
- jquery源码解析----回溯处理的设计
- 详解Vue CLI3 多页应用实践和源码设计
- Vue项目(vue-cli3 + antd)使用babel对IE进行兼容处理之小白踩坑史
- 面试题:你简历中写到熟悉Spring源码,那你给我说说它用到了那些设计模式?...
- vue源码--响应式设计原理
- 读懂源码系列-FileZilla Server 设计原则分析-socket 事件处理流程(4)
- 小白学Spring源码(一):spring源码环境搭建以及异常处理
- 详解Vue内部怎样处理props选项的多种写法
- 面试题:你简历中写到熟悉Spring源码,那你给我说说它用到了那些设计模式?
- Spring源码(七)-IOC中的那些设计模式
- Vue项目中750设计稿px自动转化成rem方法(小白一个,记录自己遇到的小白问题,大家勿怪)
- nginx源码学习——命令行选项处理
- Vue.js源码学习一 —— 数据选项 State 学习
- 设计调查选项处理&Answer实体的分析设计&开发的经验技巧&数据库使用等
- 基于opencl的ffmpeg视频优化处理设计与实现
- GEF源码分析(二) 模拟GEF设计思路,解剖GEF 1
- vue利用axios处理开发环境和生成环境的跨域问题