MVVM框架
MVVM框架三要素:数据响应式、模板引擎及其渲染
数据响应式:监听数据变化并在视图中更新
- Object.defineProperty()
- Proxy() 模板引擎:提供描述视图的模板语法
- 插值 {{}}
- 指令 v-bind、 v-on、 v-model, v-for, v-if 渲染: 如何将模板转换为html
- 模板 =>vdom=>dom
数据响应式原理
数据响应式就是,数据变更能够响应在视图中。vue2中利用Object.defineProperty()方法来实现
defineProperty.js
//Object.defineProperty()
//拦截: 对某个对的某个key做拦截, 注:不支持数组
function defineReactive(obj, key, val) {
//如果val是对象, 需要递归处理
observe(val)
Object.defineProperty(obj, key, {
get() {
return val
},
set(newVal){
if(val !== newVal) {
//如果newVal是对象, 需要递归处理
observe(newVal)
val = newVal //形成闭包,保存状态
//更新视图
update()
}
}
})
}
function upate() {
更新视图
dom.innerHTML = xxx
}
// 遍历指定数据对象每个key, 拦截他们
function observe(obj) {
if(typeof obj !== 'object' || obj === null) return obj
Object.keys(obj).forEach(key => {
defineReative(obj, key, obj[key])
})
}
//对新增加的属性进行响应式处理
function set(obj, key, val) {
defineReactive(obj, key, val)
}
原理分析
- new Vue()手写执行初始化, 对data执行响应化处理, 这个过程发生在Observer中
- 同时对模板执行编译, 找到其中动态绑定的数据, 从data中获取并初始化视图, 这个过程发生在Compile中
- 同时定义一个更新函数和Watcher, 将来对应数据变化时Watcher会调用更新函数
- 由于data的某个key在一个视图中可能出现多次, 所以每个key都需要一个管家Dep来管理多个Watcher
- 将来data中数据一旦发生变化, 会首先找到对应的Dep, 通知所有Watcher执行更新函数
涉及类型介绍
- Vue: 框架构造函数
- Observer: 执行数据响应化(分辨数据是对象还是数组)
- Compile: 编译模板,初始化视图,收集依赖(更新函数、watcher创建)
- Watcher: 执行更新函数(更新dom)
- Dep: 管理多个Watcher, 批量更新
vue.js
//1.实现vue构造函数
//2.将data做响应式处理
//数组响应式
// 1.替换数组原型中7个方法
const orginalProto = Array.prototype
//备份一份, 修改备份
const arrayProto = Object.create(orginalProto)
['push', 'pop', 'shift', 'unshift'].forEach(method => {
arrayProto[method] = function() {
//原始操作
orginalProto[method].apply(this, arguments)
// 覆盖操作:通知更新
console.log('数组执行' + method + '操作');
}
})
//Object.defineProperty()
//拦截: 对某个对的某个key做拦截, 注:不支持数组
function defineReactive(obj, key, val) {
//如果val是对象, 需要递归处理
observe(val)
// 管家创建
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
// 依赖收集
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal){
if(val !== newVal) {
//如果newVal是对象, 需要递归处理
observe(newVal)
val = newVal //形成闭包,保存状态
//通知更新 notify(): voild
dep.notify()
}
}
})
}
function upate() {
更新视图
dom.innerHTML = xxx
}
// 遍历指定数据对象每个key, 拦截他们
function observe(obj) {
if(typeof obj !== 'object' || obj === null) return obj
判断传入obj类型
if(Array.isArray(obj)) {
//覆盖原型, 替换7个变更操作
obj.__proto__=arrayProto
//对数组内部元素执行响应化
// const keys = Object.keys(obj)
for(let i = 0; i<obj.length; i++) {
observe(obj[i])
}
} else {
Object.key(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
// 每遇到一个对象, 就创建一个Observer实例
// 创建一个Observer实例去做拦截操作
// new Observer(obj)
}
// proxy 代理函数: 让用户可以直接访问data中的key
function proxy(vm , key) {
Objet.keys(vm[key]).forEach(k => {
Object.devineProperty(vm, k, {
get() {
return vm[key][k]
},
set(v) {
vm[key][k] = v
}
})
})
}
// 根据传入不同的类型,做不同的操作
class Observer {
constructor(value) {
this.value = value
//判断 value 类型
//遍历对象
this.walk(value)
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
//对新增加的属性进行响应式处理
function set(obj, key, val) {
defineReactive(obj, key, val)
}
class Vue {
constructor(options) {
//1. 保存options
this.$options = options
this.$data = options.data
// 2. 将data做响应式处理
observer(this.$data)
// 3. 为$data做代理
Proxy(this, '$data')
}
}
//
class Compile {
// el-宿主元素, vm->Vue实例
constructor(el, vm) {
this.$el= document.querySelectore(el)
this.$vm = vm
// 解析模板
if(this.$el) {
//编译
this.compile(this.$el)
}
}
compile(el) {
// el是宿主元素
// 遍历它, 判断当前遍历元素的类型
el.childNodes.forEach(node => {
if(node.nodeType === 1) {
//节点编译
this.compileElement(node)
//递归
this.compile(node)
} else if(this.isInter(node)) {
//文本, {{xxx}}
this.compileText(node)
}
})
}
//判读插值表达式
isInter(node) {
return node.nodeType === 3 && /\{\{\(.*)\}\}/.test(node.textContent)
}
//编译文本
compileText(node) {
//node.textContent = this.$vm[RegExp.$1]
this.update(node, RegExp.$1, 'text')
}
//编译元素: 分析指令、@事件
compileElement(node){
//获取属性并遍历
const nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach(attr => {
//指令: v-xxx = y
const attrName = attr.name //v-xxxx
const exp = attr.value // xxx
if(this.isDirective(attrName)){
consst dir= attrName.substring(2) //xxx
//指令实际操作方法
this[dir] && this[dir](node, exp)
}
})
// 事件处理
if(this.isEvent(attrName)) {
//@click="onClick"
const dir = attrName.substring(1) // click
//睡觉监听
this.eventHandler(node, exp, dir)
}
}
//是否是指令
isDirective(attr) {
return attr.indexOf('v-') === 0
}
// 是否是事件
isEvent(dir) {
return dir.indexOf('@') === 0
}
eventHandler(node, exp, dir) {
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp]
node.addEventListener(dir, fn.bind(this.$vm))
}
//执行text指令
text(node, exp) {
this.update(node, exp, 'text')
}
// v-text对应操作函数
textUpdater(node, val) {
node.textContent = this.$vm[exp]
}
html(node, exp) {
this.update(node, exp, 'html')
}
htmlUpdater(node, exp) {
node.innerHTML = this.$vm[exp]
}
//v-model='xx'
model(node, exp) {
//update方法只完成赋值和更新
this.update(node, exp, 'model')
//事件监听
node.addEventListener('input', e => {
this.$vm[exp] = e.target.value
})
}
modelUpdater(node, value) {
// 表单元素赋值
node.value = value
}
// 提取update, 初始化喝更新函数创建
update(node, exp, dir) {
const fn = this[dir+'Updater]
//初始化
fn && fn(node, this.$vm[exp])
//更新
new Watcher(this.$vm, exp, function(val) {
fn && fn(node, this.$vm[exp])
})
}
}
// Watcher: 小秘书, 跟视图中依赖1: 1
class Watch {
constructor(vm, key, updaterFn) {
this.vm = vm
this.key = key
this.updaterFn = updaterFn
// 依赖收集触发
Dep.target = this //定义全局变量
this.vm[this.key] // 触发上面得get
Dep.target = null
}
update() {
this.updaterFn.call(this.vm, this.vm[this.key])
}
}
// 管家: 和某个 一一对应, 管理多个秘书, 数据更新时通知他们做更新工作
class Dep {
constructor() {
this.deps = []
}
addDep(watcher) {
this.deps.push(watcher)
}
notify() {
this.deps.forEach(watcher => watcher.update())
}
}