vue响应式原理及自定义事件原理

vue 自定义事件原理和订阅发布模式

订阅发布模式

订阅发布模式重点就是有一个事件中心,注册和触发都是对同一个对象

class EventEmitter {
    constructor() {
       this.subs = Object.create(null) 
    }
    // 订阅事件
    $on(eventType, handler) {
        this.subs[eventType] = this.subs[eventType] || []
        this.subs[eventType].push(handler)
    }
    // 发布事件
    $emit(eventType, ...params) {
        if(this.subs[eventType]) {
            this.subs[eventType].forEach(handler => {
                handler.apply(this, params)
            }) 
        }else{
            console.log(`${eventType} has not emitted yet`)
        }
    }
    // 注销事件
    $off(eventType, handler) {
        if(handler) {
            this.subs[eventType] = this.subs[eventType].filter(item => {
                return handler !== item
            })
        }else{
            delete this.subs[eventType]
        }
    }
    // 监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除
    $once(eventType, handler) {
        // emit后执行相应的事件然后移除
        function myOn (...params) {
            handler.apply(this, params)
            this.$off(eventType, myOn)
        }
        // 注册包装好的事件
        this.$on(eventType, myOn)
    }
}

const e = new EventEmitter()
const f = () => {
    console.log('this is f')
}
e.$on('click', f)
e.$on('click', () => {
    console.log('click is emitted')
})
e.$on('click', () => {
    console.log('click is emitted again')
})
e.$on('change', num => {
    console.log(`change is emitted ${num}`)
})
e.$once('once', num => {
    console.log(`once is emitted ${num}`)
})
e.$emit('click')
e.$emit('change', 5)
e.$emit('once', 8)
e.$emit('once')
e.$off('click', f)
e.$emit('click')
e.$off('change')
e.$emit('change')

原理总结:初始化一个对象,对象拥有自己事件处理函数集合 事件名:函数名,on/emit/off/once挂载在构造函数的原型上,分别去操作事件处理函数集合,
on:订阅事件, 如果 事件处理函数集合 没有该事件,则加入
once: 订阅事件, 触发一次则移除
Emit:发布事件,如果 事件处理函数集合 存在该事件,则执行
Off: 删除事件,如果 事件处理函数集合 有该事件,则删除

以上的前提都是,操作的同一个对象的 事件处理函数集合

vue自定义事件及bus总线的原理

vue自定义事件

在当前的组件内,可以通过如下代码来监听触发事件,通过上面的订阅发布模式代码你也能够很好地理解:

vm.$on('test', function (msg) {
  console.log(msg)
})
vm.$emit('test', 'hi')
// => "hi"
bus总线的原理

在兄弟组件传值或者连个关系较远的组件传值时,常常用到bus总线,通常的操作步骤如下:
1.Vue.prototype.Bus = new Vue({})
2.触发组件内 this.Bus.emit(‘userdifined’, ‘chufa’)
3.接受事件组件内
mounted () {
this.Bus.$on(‘userdifined’, res => console.log(res))
},
destroyed () {
this.Bus.$off(‘userdifined’)
}
Vue构造函数的原型上已经有了$on/$emit/$off函数,Bus挂在的Vue构造函数上原型上,因此无论在哪个组件中,只需要this.Bus就可以访问到,因为this.Bus为 new Vue(),因此也能用Vue构造函数上原型上已经有了的$on/$emit/$off函数。

观察者模式及订阅发布模式的区别

观察者模式

观察者模式总结如下图:
在这里插入图片描述
实现简单的观察者模式:

// 订阅者
class Watcher {
    update () {
        console.log('updating……')
    }
}
// 发布者
class Dep {
    constructor() {
        // 存储订阅者
        this.subs = []
    }

    addSubs(sub) {
        if(sub && sub.update) {
            this.subs.push(sub)
        }
    }

    notify() {
        this.subs.forEach(item => {
            item.update()
        })
    }
}

const watcher1 = new Watcher()
const watcher2 = new Watcher()

const dep = new Dep()
dep.addSubs(watcher1)
dep.addSubs(watcher2)
dep.notify()

观察者模式和订阅发布模式的区别

在这里插入图片描述

双向绑定原理及手写

双向绑定的核心是数据响应式,数据发生变化,绑定视图的数据也相应发生改变,而视图发生变化通过事件很容易改变数据变化。

view -> model 通过事件触发通知数据改变
model -> view 利用的是观察者模式和Object.defineProperty
在这里插入图片描述
首先需要在数据发生变化时能通知我们。当一个普通的JavaScript对象传给Vue实例来作为它的data选项时,会递归遍历它所有的属性,给每个用Object.defineProperty都加上set函数和get函数。这样的话,给这个对象的某个值赋值,就会触发set,这样就能监听到数据变化。
我们需要一个发布者Dep, Dep来容纳属性的一个个订阅者watcher,并且带有添加订阅者、通知订阅者的功能。每一个属性都有自己的Dep,劫持到属性相应变化后,set函数会执行Dep的通知订阅者功能,也就是执行watcher的update函数去执行相应的更新函数。
接下来就到订阅者watcher了,wactcher接受到了通知,比对新旧值是否变化,如果变化了,那么执行更新函数(回调函数)。
最后,需要知道属性是对应哪个几个订阅者,需要一个complie解析模板指令,解析到符合条件的指令,则初始化对应的视图值,并初始化相应的订阅者,绑定watcher的更新函数。

因此,总结来说:
在初始化时,observer给data所有属性加上getter/setter,初始化相应data的dep,在get中往dep中添加订者,setter中触发dep的通知函数通知订阅者执行订阅者的回调函数。compiler解析相关指令和差值表达式,并初始化视图,同时new Watcher,建立回调函数和对应data数据的关系,并通过访问data,触发get方法,将自身添加到dep中。
当数据发生变化时,setter触发dep的通知函数通知订阅者执行订阅者的回调函数。

最后,在初始化时,compiler解析到v-model指令,就往该节点添加input更新相应数据的事件,由此完成了双向绑定。

各个文件的功能:

vue.js初始化vue实例
observer.js为数据添加getter/setter,数据发生变化时通知dep
dep.js订阅者容器,数据发生变化时通知相关订阅者的update方法
watcher.js订阅者,数据发生变化时调用数据相应的回调函数
compiler.js解析指令,初始化视图,初始化watcher,初始化事件

最后,通过手写一个简易版的双向绑定原理,就能够很好地理解:
详见:简易版双向绑定

vue2.0与3.0响应式的区别

2.0中 使用Object.defineProperty来监听数据的变化

  • 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,性能差
  • 无法检测到对象属性的添加,vue实例初始化后新添加的对象属性不具备响应式,需要用$set来设置

3.0 使用es6 proxy来监听数据的变化

  • 可以劫持整个对象,效率更高(嵌套对象仍然需要递归代理)
  • 对象新增属性可以实时响应式,可以检测到数组下标的变化
  • 返回的是一个新对象,可以操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
  • 因为Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11

关于响应式的几个问题

给data中的某个属性重新设置为对象,该对象是响应式的吗?

是的,当给data的某个属性重新设置值的时候,会触发setter,在setter中会触发walk函数去遍历该对象,为该对象的所以属性添加getter和setter

初始化vue实例后,给data中新增属性,该属性是响应式的吗?

不是的,只是在vue实例上添加了一个普通的属性,因为给data中属性添加getter和setter是在初始化vue实例时进行的。可以通过Vue.set或者在组件中用this.$set来设置响应式属性

初始化vue实例后,给data中新增属性,该属性是响应式的吗?

不是的,只是在vue实例上添加了一个普通的属性,因为给data中属性添加getter和setter是在初始化vue实例时进行的。

修改了数组数值,视图没有更新?

产生的原因:
数组:
1.利用索引值直接设置一个数组项时;
2.修改数组长度时;

defineProperty可以监控到数组下标的变化,但对数组做了特殊处理,不执行walk,不会对每一项添加get和set(性能问题),导致直接通过数组的下标给数组设置值,不能实时响应,也无法监听到push等方法,vue重写Array.prototype.push等方法,并生成一个新的数组赋值给数据,这样数据双向绑定就会触发。

对象:
1、往现有对象中增加新属性
新增的对象属性并没有设置set get函数劫持

对象数组响应式检测:
1、更换新索引(对象可使用object.assign())
2、用Vue.set或者this.$set
3、数组可用splice实现

MVC和MVVM的区别

MVC
MVC数据传递的方式是单向的,如果用原生的HTML+JS来比喻形容的话,JS(controller)通过事件监听,监听到view的变化,然后通过Ajax(model)进行数据的交互(向服务端的接收和发送),随即更新数据

MVVM
MVVM数据传递的方式是双向的,比如说vue,VM指的是连接view和model的桥梁,他有两个方向,一个是将模型转化为视图,即将后端传递的数据转化为所看到的页面,实现的方式是数据绑定,二是将视图转化为模型,即将所看到的页面转化为后端的数据,实现的方式是DOM监听

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值