开课吧学习笔记之手写Vue

438 阅读4分钟

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)
}

原理分析

  1. new Vue()手写执行初始化, 对data执行响应化处理, 这个过程发生在Observer中
  2. 同时对模板执行编译, 找到其中动态绑定的数据, 从data中获取并初始化视图, 这个过程发生在Compile中
  3. 同时定义一个更新函数和Watcher, 将来对应数据变化时Watcher会调用更新函数
  4. 由于data的某个key在一个视图中可能出现多次, 所以每个key都需要一个管家Dep来管理多个Watcher
  5. 将来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())
    }
}