理解Vue的数据劫持和发布订阅者模式

vue2.0中实现数据劫持的方法:

Object.defineProperty()

var obj = {};

Object.defineProperty(obj, 'arr', {
  configurable: true, // 默认false
  enumerable: true, // 默认false
  get: function(target, key) {
    return Reflect.get(target, key);
  },
  set: function(target, key, val) {
    Reflect.set(target, key, val)
    console.log('数据更新')
  }
})

configurable

当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
默认为 false

enumerable

当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
默认为 false

value

该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined

writable

当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被改变。
默认为 false

这种方法可以监听到数据的变化,然后在set函数的的时候改变视图。但是这种方法有缺点,无法监听数组的某些变化:

obj.arr = [9,8] // 数据更新
obj.arr.push(3) // 没有打印

数组的元素增加并不能触发set函数,所以这种情况下是无法更新视图的,但是使用过vue的应该都知道,直接调用push方法可以触发视图的更新,这是因为vue内部对一些特定的方法进行了重写:

var arrProtoType = Array.prototype;
var newArrayProtoType = Object.create(arrProtoType);

var injected = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
injected.forEach(function(type) {
  newArrayProtoType[type] = function(args) {
    var args = Array.prototype.slice.call(args)
    var result = arrProtoType[type].apply(this, args);
    console.log('数组改变了');
    return result;
  }
})
var arr = [4,5]
arr.__proto__ = newArrayProtoType;
arr.push(8) // 数据更新

 上面代码重写的Array原型上的一些方法,当调用push的时候,会调用新的push方法,这个方法会调用Array原型上的push方法,然后又做了些新操作,比如说更新视图。也可以理解数组数据的监听是Object.defineProperty和重写原型方法的结合来实现的。

如果我来修改数组的长度,上面的两种方法是都不能监听到的。

vue3.0中实现数据劫持的方法:

Proxy

var target = {};

var obj = new Proxy(target, {
  get: function(target, key) {
    return Reflect.get(target, key)
  },
  set: function(target, key, value) {
    Reflect.set(target, key, value);
    console.log('数据改变了')
  }
})

obj.arr = [3,4];
obj.arr.push(6) // 不会打印

数组的只改变原数组方法依然监听不到改变,但是这种方式比 Object.defineProperty()性能要好,2.0的方法需要把所有的状态都设置为可以监听的属性,而3.0的方法不需要这一步,只需要把data传递进去就可以了。并且改变的新的对象,不是data。

下面是一种vue2.0的数据劫持,发布订阅模式:


    class Vue {
        constructor(options) {
            // 获取到根元素
            this.el = document.querySelector(options.el) // #root
            // 获取到数据
            this.data = options.data
            // 收集数据和订阅者,每一个数据都有很多个订阅者 myText: [订阅者1, 订阅者2, 订阅者3]
            this._derective = {}
            // 调用函数
            this.Observer(this.data)
            this.Compile(this.el)
        }
        // 负责劫持数据
        Observer(data) {
            console.log(data)
            for(let key in data) {
                this._derective[key] = []
                let value = data[key]
                const watch = this._derective[key]
                Object.defineProperty(data, key, {
                    get: () => value,
                    set: (newValue) => {
                        if(newValue !== value) {
                            value = newValue
                            watch.forEach(watcher => {
                                // watcher是订阅实例
                                watcher.update()
                            })
                        }
                    }
                }) 
            }
            // 劫持数据 更新实图
            
        }
        // 负债解析指令 发布订阅者
        Compile(el) {
            console.log(el, 'el')
            // 找到根元素下面所有的子节点
            let nodesArr = el.children
            console.log(nodesArr, 'nodesArr')
            for(let i = 0, length = nodesArr.length; i < length; i++) {
                // 如果每个字节点下面还有节点,需要再走一遍这个函数,这里用到了递归
                if(nodesArr[i].children && nodesArr[i].children.length>0) {
                    // 这里用递归处理
                    this.Compile(nodesArr[i])
                }
                // node是当前元素 先查当前元素有没有指令属性,再取属性的值 然后发布订阅者
                if(nodesArr[i].hasAttribute('v-text')) {
                    const atrributeValue = nodesArr[i].getAttribute('v-text')
                    // 发布订阅者 生成下面格式的数据,每一个订阅者 都是一个使用了该数据的元素。 收集所有的订阅者
                   // {myText: [订阅者1, 订阅者2, 订阅者3], myBox: [订阅者1, 订阅者2, 订阅者3]}
                    this._derective[atrributeValue].push(new Watcher(nodesArr[i], this, atrributeValue, 'innerHTML'))
                }
                if(nodesArr[i].hasAttribute('v-model')) {
                    const atrributeValue = nodesArr[i].getAttribute('v-model')
                    // let dataValue = this.data[atrributeValue]
                    nodesArr[i].addEventListener("input", () => {
                        this.data[atrributeValue] = nodesArr[i].value
                    })
                    this._derective[atrributeValue].push(new Watcher(nodesArr[i], this, atrributeValue, 'value'))
                    
                }
            }
        }
    }
    // 订阅者 主要作用是更新试图
    class Watcher {
        constructor(el, vm, value, op) {
            this.el = el
            this.vm = vm
            this.value = value
            this.op = op
            this.update()
        }
        update() {
            this.el[this.op] = this.vm.data[this.value]
        }
    }
    new Vue({
        el: '#root',
        data:{
            myText: "我是vue的数据双向绑定",
            myBox: "我是vue的另外一个数据双向绑定"
        }
    })

 

 

有时候我们项目中可能又很多全局组件,都需要在main.js中进行全局注册,想这样:

如果量较大的话,就会让main.js看起来很臃肿。可以这样来做优化。

fali.vue

<template>
    <div>
        加载失败 
    </div>
</template>
<script>
export default {
    
}
</script>
<style lang="css" scoped>

</style>

loading.vue 

<template>
    <div>
        数据加载中 
    </div>
</template>
<script>
export default {
    
}
</script>
<style lang="css" scoped>

</style>

index.js

import Loading from "./loading.vue"
import Fail from "./fail.vue"

export default {
    install: (Vue) => {
        Vue.component('loading', Loading)
        Vue.component('fail', Fail)
    }
}

只需要在main.js中引入,然后use,这样,所有的全局组件就都注册上去了,可以在任意地方使用。代码看起来简介了很多。

import Vue from 'vue'
import App from './app.vue'
import GlobalCom from "./common-components"

Vue.use(GlobalCom);

new Vue({
    el: '#app',
    render: h => h(App)

})

vue项目下运行vue ui可以打开vue 的可视化构建页面。类似下面:

组件的创建 

组件注册

 

vue-html-loader解析vue文件中的template,并把外部的style统一归vue管理

vue-style-loader解析vue文件中的style

vue-loader 解析vue文件

vue-template-compiler编译器

https://zhuanlan.zhihu.com/p/84827482

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值