专栏首页iOS开发干货分享小白也能秒懂Vue源码中那些精细设计(选项处理)

小白也能秒懂Vue源码中那些精细设计(选项处理)

我"崩"不住了,在彭凡同志锲而不舍的催促下这篇文章终于"蛋"生了。说正经的这篇文章不好写,不好写的原因是我不太擅长写这些类比文,但它还是写出来了。相信大部分人都有开发过功能插件,在写插件的时候普遍应用基本思想是以"默认配置为优先,以用户配置为覆盖"。如果你觉得简单先别着急穿裤子走人继续往下看看。

$("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的值是调用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);

你细品这个代码。

本文分享自微信公众号 - web前端小剧场(webxiaojuchang),作者:李李

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【Vue原理】Vue源码阅读总结大会 - 终

    终于啊终于啊,把 Vue 系列的文章发完了了,如释重负的感jio啊,今天就打算总结下,我这段时间来的历程和收获,本文纯吹水,没有技术含量,各位客官权当娱乐消遣也...

    神仙朱
  • 【Vue原理】Vue源码阅读总结大会

    阅读源码是需要很多的勇气的,特别是对这种 Vue 源码的框架,十分抽象,使用了好多设计模式,封装得十分精密。很难短时间内能看得明白。

    神仙朱
  • 写给初中级前端的高级进阶指南(JS、TS、Vue、React、性能、学习规划)

    我曾经一度很迷茫,在学了Vue、React的实战开发和应用以后,好像遇到了一些瓶颈,不知道该怎样继续深入下去。相信这也是很多一两年经验的前端工程师所遇到共同问题...

    ssh_晨曦时梦见兮
  • 【Vue原理】Mixin - 白话版

    今天我们用白话文解读 mixin 的工作原理,轻松快速理解 mixin 内部工作原理。你说,你只懂怎么用的,却不懂他内部是怎么工作的,这样也不太行。

    神仙朱
  • 面试官到底想看什么样的简历?

    面试一直是程序员跳槽时期非常热门的话题,虽然现在已经过了跳槽的旺季,下一轮跳槽季需要到年底才会出现,但是当跳槽季的时候你再看这篇文章可能已经晚了,过冬的粮食永远...

    桃翁
  • 2 年前端面试心路历程(字节跳动、YY、虎牙、BIGO)

    https://juejin.im/post/5e85ec79e51d4547153d073820ed

    闰土大叔
  • 前端-为什么要立刻放弃 React 而使用 Vue?

    现在,Vue.js 在 Github 上得到的星星数已经超过了 React。这个框架的流行度在不断增长,由于它并没有像 Facebok(React)或 Goog...

    grain先森
  • 009 | 快速入门Web前端开发的正确姿势

    入门标准很简单,就一条:达到能参与 Web 前端实际项目的开发水平。请注意,是实际项目,这就需要了解如今的实际项目开发都用了哪些技术栈。HTML/CSS/Jav...

    Keegan小钢
  • 一个「学渣」从零开始的Web前端自学之路

    从 13 年专科毕业开始,一路跌跌撞撞走了很多弯路,做过餐厅服务员,进过工厂干过流水线,做过客服,干过电话销售可以说经历相当的“丰富”。

    六小登登
  • iOS 开发者的 Weex 伪最佳实践指北

    这篇文章是笔者近期关于Weex在iOS端的一些研究和实践心得,和大家一起分享分享,也算是对学习成果的总结。文章里面提到的做法也许不是最佳实践,也许里面的方法称不...

    一缕殇流化隐半边冰霜
  • 2021前端学习路径书单—自我成长之路

    正式学习前端大概 3 年多了,接触前端大概 4 年了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的...

    秋风的笔记
  • 来自offer杀手的前端面试攻略

    楼主最近面试的公司不算是很多,但是我有一个几个人的小团体。个人感觉如果本篇文章的所有知识点你都掌握了,百度阿里腾讯网易京东,这五个公司,或许需要一部分运气和其他...

    ConardLi
  • 人类高质量 Java 学习路线【一条龙版】

    大家好,我是鱼皮。现在网上的编程资料实在太多了,而且人人肯定都说自己的最好,那就导致大家又不知道怎么选了。大部分的博主推荐资源,也就是把播放量高的视频说一遍,水...

    程序员鱼皮
  • 做前端的你有没有觉得很吃力?

    https://www.zhihu.com/question/425782106/answer/1543007211

    用户4456933
  • 而立之年——回顾我的前端转行之路

    在成为程序员之前,我干过很多工作。由于学历的问题(高中),我的工作基本上都是体力活。包括但不限于:工厂普工、销售(没有干销售的才能)、搬运工、摆地摊等,转行前最...

    谭光志
  • 很哇塞的 3 个 Java 实战项目!

    我遇到过很多应届生,整个大学学了 4 年的编程,自己敲的代码可能还不到 3000 行。你让他做用自己学的编程知识做个什么东西,他就蒙了,不知道从哪里下手。

    Guide哥
  • vue源码分析前置知识必备

    最近利用空闲时间又翻看了一遍Vue的源码,只不过这次不同的是看了Flow版本的源码。说来惭愧,最早看的第一遍时对Flow不了解,因此阅读的是打包之后的vue文件...

    前端老鸟
  • ​vue源码分析前置知识必备

    最近利用空闲时间又翻看了一遍Vue的源码,只不过这次不同的是看了Flow版本的源码。说来惭愧,最早看的第一遍时对Flow不了解,因此阅读的是打包之后的vue文件...

    前端老鸟
  • 【阅读清单】有奖内测体验活动

    完成了内测体验的小伙伴,可以填写问卷,活动结束后我们将统一进行审核,审核结束就进行礼品发放。问卷链接:https://wj.qq.com/s2/7433898/...

    云加社区

扫码关注云+社区

领取腾讯云代金券