自定义指令

首先直接说一下自定义指令的简单认识:

注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令

在上述官方文档中说的也很清楚,自己的体会就是,自定义指令通过提供钩子函数暴露出当前指令绑定的dom元素、指令值(binding对象)、vnode等从而提供了底层操作的可能,方便开发者去实现封装、重用dom处理的逻辑。

总而言之,当有一些通用的dom操作想重用,或者复杂的dom操作不想揉杂到代码里,不妨用一下自定义指令。

v-append解析

接下来就是自己接触到的一个v-append指令,好奇是怎么实现的,于是做了一个简单的解析

源码地址:vue-append

需求:使用vue的v-html的时候,对传入的html字符串的js部分并不能执行,而有没有这样的功能能执行传入的js呢?

我们知道eval可以对字符串求值,那别人是怎么做的呢?

根据源码写了一下大致的伪代码流程图:

文字版:

首先调用exec函数,参数为:v-append传入的html字符串和指令绑定的元素target

exec函数内部,先innerHTML=''的方式清空内容,然后调用append函数,append函数接受三个参数,由html字符串转化来的domList(通过fragment函数),targetappend成功之后的回调函数

继续看append函数内部:

遍历domList,并通过traverseNode进行递归遍历dom,接受的是两个参数:插入成功后返回的dom(调用insertBefore把dom插入到target),对返回的dom进行处理的callback,

再看callback是怎么处理的:

很简单的判断:是否是script标签,是否有src,从而决定是否需要下载js,最终调用window.eval去执行js

最后:

对源码中也增加了自己对源码理解的注释:xixigiggling/vue-append

有趣点:

可以从流程图中看到,有一个有趣的点,html怎么变成了domList?也就是fragment函数是怎么把html字符串转化成了domList?

// 采用div+innerHTML
container.innerHTML = '' + html
// 为什么要调用removeChild来得到返回的dom节点呢?
// 可能的原因:
// 保证container不用占据太多内存,同时尽快被垃圾回收?
// 如果直接使用container.childNodes + cloneNodes呢?
dom = slice.call(container.childNodes).map(function (child) {
  return container.removeChild(child)
})

抛开其他代码,其实就这三行,我们传入的html字符串,被document.createElement('div')的一个container包裹起来(使用innerHTML),这样的话转化成了一个大的dom元素,但是此时是没有插入到document的,然后,再遍历container.childNodes,对其中的每一个子元素,进行删除操作,而removeChild方式在删除元素之后是会返回这个子节点(是不是一个很骚气的操作)

被移除的这个子节点仍然存在于内存中,只是没有添加到当前文档的DOM树中,因此,你还可以把这个节点重新添加回文档中,当然,实现要用另外一个变量比如上例中的oldChild来保存这个节点的引用

--Node.removeChild

那么为什么作者要这么做呢?,我的理解就是container是一个临时使用的 、不会被插入到文档中的dom,主要目的就是为了利用它的innerHTML来把html字符串转化成dom,但转化成dom还不够,我们需要的是domList,那么我们可以直接使用遍历container.childNodes + cloneNodes来做?但又与作者的removeChild方式有什么区别呢?

已知container是一个注定要被垃圾回收的元素,在这个前提下:

那么如果innerHTML之后不用removeChild删除的话,container一定是占据着较多的内存(你不知道用户会给你传大多的html)

这样一想就可以发现其实是很精妙的,学到了哈哈

最后:

根据源码的启发,以后当我们自己遇到,想要将这样一段html,但是是用后端获取(也就意味着html字符串需要使用v-html写入到文档中的时候),我们可以怎么做呢?

<div>
  <p>hello, world</p>
</div>
<script>
console.log('js execute here');
</script>
  1. 如果只是普通的dom元素,可以使用div+innerHTML的方式来写入到文档
  2. 如果是script这种呢?可以使用insertBefore插入dom,等dom插入成功返回后,再去调用eval进行执行

标签: vue

添加新评论