最近看了一下 Vue 相关的东西 ,整理一下流程。
最近很慌, 想看源码, 先把简单的相关概念理一理
Object.defineProperty
首先, 概念 :
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
没啥好说的, 直接上例子
1 | // 定义一个对象 |
configurable: false
不可重新定义, 也就是说再执行 Object.defineProperty(obj, 'age', {...})
将会报错, 并且, 属性不可删除
enumerable: false
不可枚举, 也就是再执行Object.keys()
或for in
等将取不到该属性value:18
值writable: false
不可写, 也就是对age
赋值操作将不会生效, 但不会报错get
取值操作 , function
, 默认undefined
set
属性值修改时,触发执行该方法 , function
, 默认undefined
以上这一些属性, 称为描述符, 需要注意的是, 当使用了 getter 或 setter 方法,不允许使用 writable 和 value 这两个属性
get set 看起来就很像 vue 的计算属性
伪数组
伪数组 (ArrayLike) ,又称类数组。是一个类似数组的对象,但是有如下几个特征。
按索引方式储存数据
1 | 0: xxx, 1: xxx, 2: xxx... |
具有 length 属性
1 | 但是length属性不是动态的,不会随着成员的变化而改变 |
不具有数组的 push(), forEach()等方法
1 | arrayLike.__proto__ === Object.prototype //true |
以下是常见伪数组:
- arguments
- NodeList => document.querySelectorAll(‘div’).constructor.name // NodeList
- HTMLCollection => document.getElementsByClassName(‘head_wrapper’).constructor.name // HTMLCollection
- jQuery => $()
转换为真数组方法
- 遍历伪数组存入真数组
- Array.prototype.splice.call(arrayLike)
- Array.from(arrayLike)
- […arrayLike]
- 原型继承,arrayLike.proto=Array.prototype
- 其他工具库中的方法,如 jQuery 中的 makeArray() toArray()等
NodeType
元素,属性,文本等, 都实现了 Node 接口, 都会有 NodeType 属性, 它标识了节点属于的 类型
常见的有:
1 -> 元素 节点
3 -> 文字节点
8 -> 注释节点
11 -> DocumentFragment 节点
1 | <div class="my"> |
上面的 html 片段, 分别用 childNodes 和 children 取值, 结果分别是什么呢
1 | document.querySelector('.my').childNodes // NodeList(5) [text, span, text, span, text] |
从图中可以看到, my div 下, 包含了 5 个 Node, 其中 123, 以及两个小红框位置的换行符, 属于 TextNode , span 标签属于 ElementNode
childNodes 和 children 结果分别是节点 list 和 子元素 list
DocumentFragment
文档片段接口,表示一个没有父级文件的最小文档对象.
通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到 DOM 树。在 DOM 树中,文档片段被其所有的子元素所代替。
因为文档片段存在于内存中,并不在 DOM 树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
1 | let content = document.createDocumentFragment() |
Proxy
Proxy 用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。
1 | var obj = new Proxy( |
上面代码对一个空对象架设了一层拦截,重新定义了属性的读取(get)和设置(set)行为。对设置了拦截行为的对象 obj,去读写它的属性,用自己的定义覆盖了语言的原始定义,运行得到下面的结果
1 | obj.count = 1 |
1. 数据代理 -> Object.defineProperty()
1 | let vm = new Vue({ |
2. 模板解析 -> NodeType -> Reg -> moustache -> v-on -> bind/html/class/text
Vue 2 是虚拟节点 VNode
1 | let vm = new Vue({ |
3. 数据绑定和响应式 -> 使用数据劫持的技术实现
一旦更新 data 中某个属性数据, 所有界面直接使用或间接使用该属性的节点会更新
基本思路: 通过 defineProperty 监视 data 中所有层级数据的变化, 变化则更新界面
1 | vm.a = 3 |
1 | 增加observe => 递归对每个属性 Object.defineProperty 增加 get set, 并且设为configurable:false |
总结
整体实现分为已下步骤
- 实现一个 Observer,对数据进行劫持,通知数据的变化(将使用的要点为:Object.defineProperty()方法)
- 实现一个 Compile,对指令进行解析,初始化视图,并且订阅数据的变更,绑定好更新函数
- 实现一个 Watcher,将其作为以上两者的一个中介点,在接收数据变更的同时,让 Dep 添加当前 Watcher,并及时通知视图进行 update
- 实现一些 VUE 的其他功能(Computed、menthods)
- 实现 MVVM,整合以上几点,作为一个入口函数