mini-vue开发准备工作

452 阅读3分钟

mini-vue代码已放到github上。

数据驱动

  • 数据响应式、双向绑定、数据驱动
  • 数据响应式
    • 数据模型仅仅是普通的javascript对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率。
  • 双向绑定
    • 数据改变,视图改变,视图改变,数据也随之改变
    • 我们可以使用v-model在表单元素上创建双向数据绑定
  • 数据驱动时Vue最独特的特性之一
    • 开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图

vue2.x响应式原理

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

下面我们来演示下Object.defineProperty如何使用

<template>
    <div id='app'>hello</div>
</template>

<script>
    // 模拟vue中的data
    let data = {
        msg: 'hello'
    }
    // 模拟vue实例
    let vm = {}
    // 数据劫持: 当访问或者设置vm中的成员的时候,做一些干预操作
    Object.defineProperty(vm, 'msg', {
        // 可枚举
        enumerable: true,
        // 可配置
        configurable: true,
        // 取值操作
        get() {
            console.log('get:', data.msg)
            return data.msg
        },
        // 设置值操作
        set(newValue) {
            if(newValue === data.msg) {
                return
            }
            data.msg = newValue
            
            document.querySelector('#app').textContent = data.msg
        }
    })
</script>

上述代码我们演示了单个属性的转化方法,如果多个属性的话即需要循环的给属性添加defineProperty即可,这块不做演示了

vue3.x响应式原理

vue3.x中使用proxy来进行监听对象,而不是属性,此外proxy IE不支持。

下面我们通过代码来演示下proxy

// 模拟vue中的data
    let data = {
        msg: 'hello'
    }
    // 模拟vue实例
    let vm = new Proxy(data, {
        // 当访问vm对象的时候执行get方法
        get(target, key) {
            return target(key)
        },
        // 当设置vm的成员是会执行set方法
        set(target, key, newValue) {
            if(target[key] === newValue) {
                return
            }
            target[key] = newValue
            document.querySelector('#app').textContent = target[key]
        }
    })
</script>

发布订阅模式

我们假定存在一个‘信号中心’,某个任务执行完成,就向信号中心发布(publish)一个信号,其他任务可以向信号中心‘订阅(subscibe)’这个信号,从而知道什么时候自己可以开始执行,这就叫做‘发布/订阅模式’(publish-subscribe pattern)

我们用兄弟组件通信过程来演示下发布订阅模式

// eventBus.js

// 事件中心
let eventBus = new Vue();

// ComponentA.vue
// 发布者
addTodo: function() {
    // 发布消息
    eventBus.$emit('add-todo', {text: '发布新消息了'})
}

// ComponentB.vue
// 订阅者
create: function() {
    //订阅消息
    eventBus.$on('add-todo', () => {})
}

下面我们来模拟下vue自定义事件

let vm = new Vue();

vm.$on('dataChange', () => {
    console.log('dataChange')
})

vm.$emit('dataChange')
class EventEmitter {
    constructor() {
        this.subs = Object.create(null);
    }
    
    $on(eventType, handler) {
        this.subs[eventType] = this.subs[eventType] || [];
        this.subs[eventType].push(handler)
    }
    
    $emit(eventType) {
        if(this.subs[eventType] && Array.isArray(this.subs[eventType])) {
            this.subs[eventType].forEach(handler => {
                handler()
            })
        }
        
    }
}

观察者模式

  • 观察者(订阅者)--Watcher
    • update() 当事件发生时,具体要做的事情
  • 目标(发布者)--Dep
    • subs数组 存储所有观察者
    • addSub() 添加观察者
    • notify() 当事件发生时,调用所有观察者的update方法
  • 没有事件中心

下面我们用一段代码演示观察者模式

// 发布者-目标
class Dep {
    constructor() {
        this.subs = []
    }
    
    addSub(sub) {
        if(sub && sub.update) {
            this.subs.push(sub)
        }
    }
    
    notify() {
        this.subs.forEach(sub => {
            sub.update()
        })
    }
}

// 订阅者 Watcher
class Watcher() {
    update() {
        console.log('update')
    }
}

总结:

  • 观察者模式是有具体的目标调度,比如事件触发,Dep就会去调用观察者方法,所以观察者模式的订阅者与发布者之间时存在依赖的
  • 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。