前言
最开始看到 vanjs 的时候,也是很震惊,短短100多行代码就实现了一个框架。阅读源码时候,越来越震惊,这么点代码连 state、 derive(类 computed) 也实现了,而且实现得十分巧妙。
除了代码风格读起来实在是难受,bind(() => (setter(v.val), dom)) 就这一句就卡了我好久,一直以为是逗号分隔参数,实际却是逗号语句,只是一个参数。
源码理解
由于总共就 100多行代码,就不贴源码了,简单介绍一下思路,有兴趣的大哥可以自己去看一看,逻辑并不是很复杂。
demo
首先,上一个比较简单的使用demo
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="module" src="./src/van.js"></script>
<style>
#div {
background: red;
color: green;
}
</style>
</head>
<body>
<script type="module">
import van from "./src/van.js";
const { button, div } = van.tags;
const counter = van.state(0)
const Counter = () => {
return div(
{
id: 'div',
style: () => `font-size: 16px;margin-top: ${counter.val}px`
},
[
"❤️ ", counter, " ",
button({onclick: () => ++counter.val}, "👍"),
button({onclick: () => --counter.val}, "👎"),
() => {
if (counter.val) {
return 1111
}
return 2222
}
]
)
}
van.add(document.body, Counter())
</script>
</body>
</html>
页面的展现如下图所示

API解析
dom

首先,可以看到demo中使用了 div() button() 这样的函数来创建 dom。
其实是使用 proxy 代理了 document.createElement,
div() => document.createElement('div')。
html
<script>
const tags = new Proxy((name) => document.createElement(name), {
get(target, name) {
return target.bind(undefined, name);
}
})
const { div } = tags;
console.log('div', div());
</script>
然后再来看看内部的参数,第一个参数代表 dom 的 props,由于涉及到了 state 放到下一步细说,第二个参数以及后面的参数,就是表示 div 的 children ,创建 dom 的方法和现在一致,创建成功后,通过 add 函数,append 到父元素下。
children 也可以使用一个函数返回 dom ,类似于上面代码中涉及条件判断时候会使用到, van 会运行后进行依赖收集(如果有)后,返回 dom 。
props
第二个参数就是绑定在 dom 上的,props ,然后根据 val 的类型做了区分。
- 函数的话,运行后(函数中可能会带
state,因此也要收集依赖)获取返回值。 - 直接是
state的话,也封成一个函数用来依赖收集。 - 正常的值,直接返回。
最后返回的值也是通过 dom.setAttribute 修改 dom 属性。
state
一般来说,state 会用在 3 个方面。
- 直接用在
props中{ key: state } - 也是用在
props中,但是通过函数返回{ key: () => state.val } - 返回
dom节点相关操作 ,() => (state ? dom1 : dom2 )
实现原理也很简单,类似于 vue 的依赖收集,在 getter 中收集依赖,然后将操作 state 的相关函数绑定,然后,修改时,触发绑定的函数,然后更新 dom

垃圾收集
因为要涉及新老 dom 的更新替换,所以原有的 dom 会被保存起来,但是我们知道就算 dom 卸载了,如果还存在引用,那这个 dom 节点也会继续存在,导致内存泄露, van 中也根据这个做了一个处理,通过
dom.isConnected 来判断节点是否还存在 document 中,进行释放。
let keepConnected = l => l.filter(b => b._dom?.isConnected)
结语
derive部分的代码粗略看了看,基本和 state 是差不多的逻辑,多了一个 listeners 的绑定,原来应该也是一致的,通过依赖收集重新运行绑定的 fn ,然后 updateDom
就我个人而言,感觉最优秀的设计,就是通过 proxy 和 bind 把 document.createElement 实现成了div() 这样的语法,然后代码中也运用了大量的 bind 来绑定 this 和 固定参数,用并不是很复杂的代码实现了这个 麻雀虽小,五脏俱全 的框架。
上周起立了个每周写一篇笔记的 flag ,结果周末喝酒喝的醉生梦死,还是拖到了周一。=-,希望这礼拜能按时完成(flag + 1)。
最后的最后
帮大哥捞捞人,杭州。 大哥人很好 0.0,天天被我 diss 都没脾气。