关于指令(directive)

属性绑定、事件绑定和v-modal底层都是通过指令(directive)实现的,那么什么是指令呢?我们一起看看Directive的定义吧。

//文件 ./src/directives/index.ts

export interface Directive<T = Element> {
(ctx: DirectiveContext<T>): (() => void) | void
}

指令(directive)其实就是一个接受参数类型为DirectiveContext并且返回cleanup

函数或啥都不返回的函数。那么DirectiveContext有是如何的呢?

//文件 ./src/directives/index.ts

export interface DirectiveContext<T = Element> {
el: T
get: (exp?: string) => any // 获取表达式字符串运算后的结果
effect: typeof rawEffect // 用于添加副作用函数
exp: string // 表达式字符串
arg?: string // v-bind:value或:value中的value, v-on:click或@click中的click
modifiers?: Record<string, true> // @click.prevent中的prevent
ctx: Context
}

深入v-bind的工作原理

walk方法在解析模板时会遍历元素的特性集合el.attributes,当属性名称name匹配v-bind或:时,则调用processDirective(el, 'v-bind', value, ctx)对属性名称进行处理并转发到对应的指令函数并执行。

//文件 ./src/walk.ts

// 为便于阅读,我将与v-bind无关的代码都删除了
const processDirective = (
el: Element,
raw, string, // 属性名称
exp: string, // 属性值:表达式字符串
ctx: Context
) => {
let dir: Directive
let arg: string | undefined
let modifiers: Record<string, true> | undefined // v-bind有且仅有一个modifier,那就是camel if (raw[0] == ':') {
dir = bind
arg = raw.slice(1)
}
else {
const argIndex = raw.indexOf(':')
// 由于指令必须以`v-`开头,因此dirName则是从第3个字符开始截取
const dirName = argIndex > 0 ? raw.slice(2, argIndex) : raw.slice(2)
// 优先获取内置指令,若查找失败则查找当前上下文的指令
dir = builtInDirectives[dirName] || ctx.dirs[dirName]
arg = argIndex > 0 ? raw.slice(argIndex) : undefined
} if (dir) {
// 由于ref不是用于设置元素的属性,因此需要特殊处理
if (dir === bind && arg === 'ref') dir = ref
applyDirective(el, dir, exp, ctx, arg, modifiers)
}
}

当processDirective根据属性名称匹配相应的指令和抽取入参后,就会调用applyDirective来通过对应的指令执行操作。

//文件 ./src/walk.ts

const applyDirective = (
el: Node,
dir: Directive<any>,
exp: string,
ctx: Context,
arg?: string
modifiers?: Record<string, true>
) => {
const get = (e = exp) => evaluate(ctx.scope, e, el)
// 指令执行后可能会返回cleanup函数用于执行资源释放操作,或什么都不返回
const cleanup = dir({
el,
get,
effect: ctx.effect,
ctx,
exp,
arg,
modifiers
}) if (cleanup) {
// 将cleanup函数添加到当前上下文,当上下文销毁时会执行指令的清理工作
ctx.cleanups.push(cleanup)
}
}

现在我们终于走到指令bind执行阶段了

//文件 ./src/directives/bind.ts

// 只能通过特性的方式赋值的属性
const forceAttrRE = /^(spellcheck|draggable|form|list|type)$/ export const bind: Directive<Element & { _class?: string }> => ({
el,
get,
effect,
arg,
modifiers
}) => {
let prevValue: any
if (arg === 'class') {
el._class = el.className
} effect(() => {
let value = get()
if (arg) {
// 用于处理v-bind:style="{color:'#fff'}" 的情况 if (modifiers?.camel) {
arg = camelize(arg)
}
setProp(el, arg, value, prevValue)
}
else {
// 用于处理v-bind="{style:{color:'#fff'}, fontSize: '10px'}" 的情况 for (const key in value) {
setProp(el, key, value[key], prevValue && prevValue[key])
}
// 删除原视图存在,而当前渲染的新视图不存在的属性
for (const key in prevValue) {
if (!value || !(key in value)) {
setProp(el, key, null)
}
}
}
prevValue = value
})
} const setProp = (
el: Element & {_class?: string},
key: string,
value: any,
prevValue?: any
) => {
if (key === 'class') {
el.setAttribute(
'class',
normalizeClass(el._class ? [el._class, value] : value) || ''
)
}
else if (key === 'style') {
value = normalizeStyle(value)
const { style } = el as HTMLElement
if (!value) {
// 若`:style=""`则移除属性style
el.removeAttribute('style')
}
else if (isString(value)) {
if (value !== prevValue) style.cssText = value
}
else {
// value为对象的场景
for (const key in value) {
setStyle(style, key, value[key])
}
// 删除原视图存在,而当前渲染的新视图不存在的样式属性
if (prevValue && !isString(prevValue)) {
for (const key in prevValue) {
if (value[key] == null) {
setStyle(style, key, '')
}
}
}
}
}
else if (
!(el instanceof SVGElement) &&
key in el &&
!forceAttrRE.test(key)) {
// 设置DOM属性(属性类型可以是对象)
el[key] = value
// 留给`v-modal`使用的
if (key === 'value') {
el._value = value
}
} else {
// 设置DOM特性(特性值仅能为字符串类型) /* 由于`<input v-modal type="checkbox">`元素的属性`value`仅能存储字符串,
* 通过`:true-value`和`:false-value`设置选中和未选中时对应的非字符串类型的值。
*/
if (key === 'true-value') {
;(el as any)._trueValue = value
}
else if (key === 'false-value') {
;(el as any)._falseValue = value
}
else if (value != null) {
el.setAttribute(key, value)
}
else {
el.removeAttribute(key)
}
}
} const importantRE = /\s*!important/ const setStyle = (
style: CSSStyleDeclaration,
name: string,
val: string | string[]
) => {
if (isArray(val)) {
val.forEach(v => setStyle(style, name, v))
}
else {
if (name.startsWith('--')) {
// 自定义属性
style.setProperty(name, val)
}
else {
if (importantRE.test(val)) {
// 带`!important`的属性
style.setProperty(
hyphenate(name),
val.replace(importantRE, ''),
'important'
)
}
else {
// 普通属性
style[name as any] = val
}
}
}
}

总结

通过本文我们以后不单可以使用v-bind:style绑定单一属性,还用通过v-bind一次过绑定多个属性,虽然好像不太建议这样做>_<

后续我们会深入理解v-on事件绑定的工作原理,敬请期待。

petite-vue源码剖析-属性绑定`v-bind`的工作原理的更多相关文章

  1. petite-vue源码剖析-双向绑定`v-model`的工作原理

    前言 双向绑定v-model不仅仅是对可编辑HTML元素(select, input, textarea和附带[contenteditable=true])同时附加v-bind和v-on,而且还能利用 ...

  2. petite-vue源码剖析-事件绑定`v-on`的工作原理

    在书写petite-vue和Vue最舒服的莫过于通过@click绑定事件,而且在移除元素时框架会帮我们自动解除绑定.省去了过去通过jQuery的累赘.而事件绑定在petite-vue中就是一个指令(d ...

  3. Spark源码剖析(八):stage划分原理与源码剖析

    引言 对于Spark开发人员来说,了解stage的划分算法可以让你知道自己编写的spark application被划分为几个job,每个job被划分为几个stage,每个stage包括了你的哪些代码 ...

  4. Vue源码解析---数据的双向绑定

    本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...

  5. [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

    有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...

  6. 源码剖析@ApiImplicitParam对@RequestParam的required属性的侵入性

    问题起源 使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便.最近却遇到了一个有点困惑的 ...

  7. petite-vue源码剖析-逐行解读@vue/reactivity之reactive

    在petite-vue中我们通过reactive构建上下文对象,并将根据状态渲染UI的逻辑作为入参传递给effect,然后神奇的事情发生了,当状态发生变化时将自动触发UI重新渲染.那么到底这是怎么做到 ...

  8. 逐行剖析Vue源码(一)——写在最前面

    1. 前言 博主作为一名前端开发,日常开发的技术栈是Vue,并且用Vue开发也有一年多了,对其用法也较为熟练了,但是对各种用法和各种api使用都是只知其然而不知其所以然,因此,有时候在排查bug的时候 ...

  9. 菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程(转)

    俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔.对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中 ...

随机推荐

  1. Redis 源码简洁剖析 04 - Sorted Set 有序集合

    Sorted Set 是什么 Sorted Set 命令及实现方法 Sorted Set 数据结构 跳表(skiplist) 跳表节点的结构定义 跳表的定义 跳表节点查询 层数设置 跳表插入节点 zs ...

  2. Lesson3——NumPy 数据类型

    NumPy 教程目录 NumPy 数据类型 numpy 支持的数据类型比 Python 内置的类型要多很多,基本上可以和 C 语言的数据类型对应上,其中部分类型对应为 Python 内置的类型. 下表 ...

  3. Atcoder ARC-061

    ARC061(2020.7.10) A 暴力 \(dfs\) 即可. B 考虑统计以每个点为矩阵中心的答案,显然一个黑点只会影响周围九个黑点,使用 \(map\) 来记录这个值,每次修改修改一下答案数 ...

  4. css自定义属性(css变量)

    感谢原文作者:Kuku殿下 原文链接:https://segmentfault.com/a/1190000015948538 前言 刚开始的"css变量",经扩展之后,称为&quo ...

  5. Mysql Json函数创建 (二)

    本节中列出的功能由组件元素组成JSON值. JSON_ARRAY([val[, val] ...]) 计算(可能为空)值列表,并返回包含这些值的JSON数组. mysql> SELECT JSO ...

  6. cmd中删除、添加、修改注册表命令

    转自:http://www.jb51.net/article/30586.htm regedit的运行参数 REGEDIT [/L:system] [/R:user] filename1 REGEDI ...

  7. VUE3 之 动态组件 - 这个系列的教程通俗易懂,适合新手

    1. 概述 暗示效应告诉我们: 巧妙的暗示会在不知不觉中剥夺我们的判断力,对我们的思维形成一定的影响,造成我们行为的些许改变或者偏差. 例如你的朋友说你脸色不太好,是不是病了,此时,你可能就会感觉浑身 ...

  8. MATLAB基础学习篇(1)

    MATLAB中只定义了以2和10为底对数,其它 使用换底公式,例如:log8(7)=log7/log8. x=input('Please enter x:'); y=input('Please ent ...

  9. gitlab登录时出现402的错误

    当登录gitlab时出现402的错误提示,可进行以下操作 重新设置一下系统时间 在new project 时若还出现同样的402问题,则清除一下浏览器的cookie即可

  10. 安卓手机超频CPU(无修饰CPU控制)

    手机软件内存越占越大,手机硬件配置太低运行太卡.其实可以通过超频cpu提升性能,安卓智能手机cpu怎么超频下面我来演示手机如何超频cpu.   方法/步骤     打开无修饰CPU控制.   设置CP ...