MVVM
- 数据驱动视图:view(DOM / 模板)、Model(模型 data) 、viewModel(vue核心、vue实例:监听事件,指令等)
- view 和 Model 通过 viewModel 实现互动
响应式数据原理
- Object.defineproperty 接收3个参数
- 参数1:源数据
- 参数2:属性
- 参数3:配置项
writable
:可读写、enumerable
:可遍历、configurable
:可配置/删除、value
:默认值、get
:读取、set
:修改- 缺陷1:深度监听要递归到底,计算量大,一次性计算量大
- 缺陷2:无法监听新增/删除属性( Vue.set Vue.delete )
代码演示:
function updateView() {
// ... do something
console.log('视图更新')
}
重新定义数组原型: defineproperty 无法满足数组的监听
const arrayFuncs = ['push', 'pop', 'shift', 'unshift', 'splice'] // 模拟数组所有的原型方法
const oldArrayProperty = Array.prototype
const vueArray = Object.create(oldArrayProperty)
// vueArray 添加实例方法
arrayFuncs.forEach(methodName => {
vueArray[methodName] = function () {
updateView() // 先执行视图更新
// Array.prototype.push.call(this,...arguments)
oldArrayProperty[methodName].call(this, ...arguments) // 再执行数组原型方法
}
})
以上代码:oldArrayProperty
保存 Array
的原型,声明新对象 vueArray
且隐式原型指向 Array 的原型,vueArray
重新封装 Array
原有的原型方法并作为自己的实例方法,执行完视图更新操作之后再使用 Array
的原型方法。
重新定义属性 :
function observer(target) {
// 只监听对象
if (typeof target !== 'object' || target === null) {
return target
}
// 数组
if (Array.isArray(target)) {
target.__proto__ = vueArray
return target
}
for (let key in target) {
defineReactive(target, key, target[key])
}
}
递归深度监听:
function defineReactive(target, key, value) {
observer(value) // 递归深度监听
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
/**
* 为什么修改后需要继续深度监听
* 有可能修改的值是一个引用类型,那么引用类型需要继续深度监听
*/
observer(newValue) // 继续深度监听
/**
* 注意,value 一直在闭包中
* 此处设置完之后,再 get 时也是会获取最新的值
*/
value = newValue
updateView() // 触发更新视图
}
}
})
}
准备数据:
const data = {
name: 'zhangsan',
age: 20,
info: {
address: '北京'
},
arr: [10, 20]
}
// 监听数据
observer(data)
data.name = 'lisi'
data.info = {
id: '001'
}
data.arr.push(30)
console.log(data, '??')
with
常规定义对象引用类型,以下 obj.c
输出结果为undefined
const obj = {
a: 100,
b: 200,
}
console.log(obj.a)
console.log(obj.b)
console.log(obj.c) // undefined
使用with
能改变{}内自由变量的查找方式(当作obj
的属性来查找)、缺陷会破坏作用域规则
with (obj) {
console.log(a)
console.log(b)
console.log(c) // 报错
}
模板编译
vue-template-compile
将模板编译成render
函数,执行render
生成vnode
,执行patch
函数将虚拟DOM渲染成真实DOM;
以下 vue 源码 缩写函数含义:
function installRenderHelpers (target) {
target._o = markOnce;
target._n = toNumber;
target._s = toString;
target._l = renderList;
target._t = renderSlot;
target._q = looseEqual;
target._i = looseIndexOf;
target._m = renderStatic;
target._f = resolveFilter;
target._k = checkKeyCodes;
target._b = bindObjectProps;
target._v = createTextVNode;
target._e = createEmptyVNode;
target._u = resolveScopedSlots;
target._g = bindObjectListeners;
target._d = bindDynamicKeys;
target._p = prependModifier;
}
例
const compiler = require("vue-template-compiler")
const template = `<p>{{message}}</p>`
const res = compiler.compile(template)
以上编译结果:
with(this){return _c('p',[_v(_s(message))])}
this
指const vm = new Vue()
,_c
为 createElement
类似于h
函数,p
为标签,_v
对应createTextVNode
:创建text
节点子元素,_s
对应toString
:创建string
类型字符串,完整结果为以下代码
with (this) {
return createElement('p', [createTextVNode(toString(message))])
}
children
const template = `
<div id="div1" class="container">
<img :src="imgUrl"/>
</div>
with (this) {
return _c(
"div", // div标签
{ staticClass: "container", attrs: { id: "div1" } }, // 属性
[
// 创建IMG标签
_c("img", { attrs: { src: imgUrl } }),
]
)
}
v-if
const template = `
<div>
<p v-if="flag === 'a'">A</p>
<p v-else>B</p>
</div>
`
with (this) {
return _c(
"div",
[flag === "a" ?
_c("p", [_v("A")]) :
_c("p", [_v("B")])]
)
}
v-for
const template = `
<ul>
<li v-for="item in list" :key="item.id">{{item.title}}</li>
</ul>
`
with (this) {
return _c(
'ul',
_l(list, function (item) {
return _c('li', { key: item.id }, [_v(_s(item.title))])
}),
0
)
}
event
const template = `
<button @click="clickHandler">submit</button>
`
with (this) {
return _c('button', { on: { click: clickHandler } }, [_v('submit')])
}
定义组件: template 形式
Vue.component('my-component', {
template: `<div id='box'>{{name}}</div> `,
data() {
return {
name: '张三'
}
}
})
定义组件: h = createElement
<body>
<div id="root">
<my-component />
</div>
<script>
Vue.component('my-component', {
render(h) {
return h(
'div',
{
attrs: {
id: 'box'
}
},
'张三'
)
}
})
new Vue({
el: '#root'
})
</script>
</body>
渲染过程
- 渲染过程:解析 vue 模板,生成
render
函数 —> 执行render
函数( h 函数 )生成vnode
—>patch(elem,vnode)
渲染出真实 - 更新过程:data 改变触发
setter
—> 重新执行render
函数 —> 生成newVnode
—>patch(vnode,newVnode)
- Vue 是异步渲染的,无论方法中对 data 改变多少次都会汇总成一次
Component render function
即模板编译成render
函数,执行render
函数渲染真实 dom 结构,同时会touch
到data
,即会触发data
中的getter
,watcher
则会收集起来进行观察,当进行数据的更改时则会触发setter
并通知进行re-render
history 路由用的是 api pushState replaceState popState