当前位置:   article > 正文

利用Object.defineProperty简单实现vue的数据响应式原理_能够使用object.defineproperty实现数据的响应式

能够使用object.defineproperty实现数据的响应式

前言:最近学了vue的响应式原理,但是学了之后,感觉模模糊糊的,不知道自己是否真的理解其中的精髓,所以就自己动手简单的实现一下vue的响应式原理。毕竟概念终究还是概念,实践才是检验自己会不会的硬道理!

在开始之前,我们需要了解一下基础的知识:
  • Object.defineProperty():它的作用是直接在一个对象上定义一个属性,或者去修改一个已经存在的属性。obj:表示需要定义属性的当前对象。prop:当前需要定义的属性名。 desc:属性描述符,就是更精确的控制对象属性。
Object.defineProperty(obj,prop,desc)
  • 1
  • vue的数据响应式原理:就是vm中的data数据在页面上有渲染,当data数据改变的时候,页面上渲染的数据也跟着改变。
    例如页面上我用了data中的msg渲染了,如图:
    在这里插入图片描述
    当我data.msg的值改变了的时候:
    在这里插入图片描述

进入正题:

目标:成品就跟上面截图的那样,当你的数据发生变化,页面的也跟着变化。
需要知识:
  • Object.defineProperty()
  • es6部分语法
实践:

html页面:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
</head>

<body>
    <div id="app">
        msg
    </div>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

开始编写js代码:

第一步:定义我们要渲染的数据
    let data = {
        msg: '你好'
    }
  • 1
  • 2
  • 3

解释:定义一个我们要渲染到页面的数据。

第二步:获取页面的变量并渲染
    class Render {
        constructor() {
            this.app = document.querySelector('#app')
            this.arg = this.app.innerText
            this.render()
        }
        render() {
            this.app.innerText = data[this.arg]
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

解释:为了方便获取参数,我们就使用一个参数简单模拟,不像vue那样用{{}}来包括变量,如果用{{}}包裹变量,就需要利用正则表达式获取里面内容。

第三步:监听数据的改变。
    class Observe {
        constructor() {
            this.init(data.msg)
        }
        init(value) {
            Object.defineProperty(data, 'msg', {
                set(newVal) {
                    if (newVal !== value) {
                        value = newVal
                        dep.notify()
                    }
                },
                get() {
                    return value
                }
            })
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

解释:我们想要知道一个变量的值有没有改变呢,就需要监听数据的变化。vue中就是使用Object.defineProperty(obj,prop,desc)这个方法中的set()get()来监听的。set()就是当值要改变的时候,就触发。而get()就是当你获取这个变量的时候触发。我们利用这个方法就可以监听得到数据的变化了。

第三步:创建收集者
    class Dep {
        constructor() {
            this.subs = []
        }
        addSub(watcher) {
            this.subs.push(watcher)
        }
        notify() {
            this.subs.forEach(w => w.update())
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

解释:收集者的作用就存储观察者和通知观察者去更新页面的。(观察者在下面。)

第三步:创建观察者
    class Watcher {
        constructor(node, arg, callback) {
            this.node = node  // 变量所在的节点
            this.arg = arg // 变量名
            this.oldVal = this.getOldVal() // 没更新前的值
            this.callback = callback // 值更新后执行的操作
        }
        getOldVal() {
            Dep.target = this
            let oldVal = data[this.arg]
            Dep.target = null
            return oldVal
        }
        update() {
            this.callback(data.msg)
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

解释:我们想要知道一个值有没有改变,改变之后重新渲染的操作是怎么样的。这个时候就需要一个观察者了。在数据渲染到页面的时候,为这个数据添加一个观察者,当这个数据改变的时候,就执行回调函数,去更新页面。

第五步:调用收集者和观察者
let data = {
        msg: '你好'
    }
    class Dep {
        constructor() {
            this.subs = []
        }
        addSub(watcher) {
            this.subs.push(watcher)
        }
        notify() {
            this.subs.forEach(w => w.update())
        }
    }
    class Watcher {
        constructor(node, arg, callback) {
            this.node = node
            this.arg = arg
            this.oldVal = this.getOldVal()
            this.callback = callback
        }
        getOldVal() {
            Dep.target = this
            let oldVal = data[this.arg]
            Dep.target = null
            return oldVal
        }
        update() {
            this.callback(data.msg)
        }
    }
    class Render {
        constructor() {
            this.app = document.querySelector('#app')
            this.arg = this.app.innerText
            this.render()
        }
        render() {
        	// 安排观察者监视数据
            new Watcher(this.app, this.arg, (newVal) => {
                this.app.innerText = newVal
            })
            this.app.innerText = data[this.arg]
        }
    }
    class Observe {
        constructor() {
            this.init(data.msg)
        }
        init(value) {
            let dep = new Dep() // 创建收集者
            Object.defineProperty(data, 'msg', {
                set(newVal) {
                    if (newVal !== value) {
                        value = newVal
                        dep.notify() // 通知观察者
                    }
                },
                get() {
                    Dep.target && dep.addSub(Dep.target)  // 添加观察者
                    return value
                }
            })
        }
    }
    new Observe()
    new Render()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

解释:在有注释的方法,就是使用收集者和观察者的地方。

代码的基本运行逻辑:
  1. 首先我们先使用Object.defineProperty去监听所有数据,然后获取页面中的内容,看看页面有没有使用data中的属性,如果有,就将对应的变量渲染成对应的值。
  2. 渲染的时候,我们要帮他创建一个观察者(watcher),传入当前的dom节点、属性名、还有一个回调函数,观察者内部就会获取参数的值,保存在oldval中。回调函数,就是当数据更新之后才触发的。
  3. 我们在获取oldVal,先为Dep.target设置为this,然后再获取oldVal,获取的时候就会触发get方法,Dep.target有值就会添加进收集者(Dep)中,只会把Dep.target该成null,因为get方法会在很多时候触发,添加进收集者中,我就们就不要要添加了。
  4. 在值改变的时候,就会触发set()方法,dep.notify()就会通知它里面的观察者就进更新页面的操作,watcher就会调用他们的callback函数更新页面。

总的代码:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
</head>

<body>
    <div id="app">
        msg
    </div>
</body>
<script>
    let data = {
        msg: '你好'
    }
    class Dep {
        constructor() {
            this.subs = []
        }
        addSub(watcher) {
            this.subs.push(watcher)
        }
        notify() {
            this.subs.forEach(w => w.update())
        }
    }
    class Watcher {
        constructor(node, arg, callback) {
            this.node = node
            this.arg = arg
            this.oldVal = this.getOldVal()
            this.callback = callback
        }
        getOldVal() {
            Dep.target = this
            let oldVal = data[this.arg]
            Dep.target = null
            return oldVal
        }
        update() {
            this.callback(data.msg)
        }
    }
    class Render {
        constructor() {
            this.app = document.querySelector('#app')
            this.arg = this.app.innerText
            this.render()
        }
        render() {
            new Watcher(this.app, this.arg, (newVal) => {
                this.app.innerText = newVal
            })
            this.app.innerText = data[this.arg]
        }
    }
    class Observe {
        constructor() {
            this.init(data.msg)
        }
        init(value) {
            let dep = new Dep()
            Object.defineProperty(data, 'msg', {
                set(newVal) {
                    if (newVal !== value) {
                        value = newVal
                        dep.notify()
                    }
                },
                get() {
                    Dep.target && dep.addSub(Dep.target)
                    return value
                }
            })
        }
    }
    new Observe()
    new Render()
</script>

</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/259067
推荐阅读
相关标签
  

闽ICP备14008679号