vue2框架简易版响应式设计(观察者模式)

对于vue.js中的属性值我们要格外关注:

$attrs 获取当前传递的参数

$listeners 获取当前组件的自定义事件

$children 获取当前组件所有子组件

$parent 获取当前组件所有父组件

$options 获取当前vue实例参数信息

$refs 获取ref所有的引用节点

设计原则:单一职责,一个类或一个函数,只做一件事。

数据定义后页面更新

页面数据切换操作data数据

添加数据劫持方法

javascript 复制代码
// Vue.js 
//Observer专门用于数据劫持
class Observer {
    data;
    constructor(data) {
        this.data = data
        this.walk()
    }

    defineProperty(data, key, value) {
        Object.defineProperty(data, key, {
            get() {
                console.log(`使用了${key}这个属性`);
                return value
            },
            set(val) {
                console.log(`修改了${key}属性`, val);
                value = val
            }
        })
    }
    walk() {
        Object.keys(this.data).forEach(el => {
            this.defineProperty(user, el, this.data[el])
        })
    }
}

const user = {
    username:'xiaowang',
    age:11
}

new Observer(user)
console.log(user.username);

对于data来说,我们自己数据劫持存了一份在data,并且还将data中每个数据都再次存在了vue实例上,创建Vue类。

javascript 复制代码
class Vue {
    constructor(options) {
        this.$options = options
        this.$data = options.data()
        this.$el = options.el
        //$data上的所有数据都要数据劫持
        new Observer(this.$data)
        //$data存放所有的数据
        //会将$data的数据挂并挂载到this身上
        this.proxy()
    }
    proxy() {
        Object.keys(this.$data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return this.$data[key]
                },
                set(val) {
                    this.$data[key] = val
                }
            })
        })
    }
}

进行模版渲染,这里仅仅渲染一层,如果多层可以执行递归。

javascript 复制代码
//模版渲染
class Compile{
    constructor(el,data){
        this.$el = document.querySelector(el)
        this.$data = data
        this.compiler()
    }
    compiler(){
        [...this.$el.children].forEach(item=>{
            if(/\{\{([a-zA-z0-9]+)\}\}/.test(item.innerHTML)){
                const key =RegExp.$1.trim()
                item.innerHTML=this.$data[key]
            }
        })
    }
}

观察者模式

观察者模式是一种设计模式,就是一种代码规范。有两个非常重要的元素:发布者和订阅者。一个发布者可能对应多个订阅者。

对应到我们的代码,使用{{}}的标签,意味着是我们需要标记的标签,作为订阅者,在项目中提供一个发布者,一旦数据发生变化,发布者通知订阅者更新页面。

javascript 复制代码
//订阅者
class Watcher {
    constructor(callback) {
        Dep.target = this
        this.callback = callback
        this.update()
        Dep.target = null
    }
    update() {
        //这一步并不是直接修改,而是更新虚拟dom,这里只是简化了
        this.callback()
    }
}

//发布者
class Dep {
    constructor() {
        this.subs = []
    }
    notify() {
        this.subs.forEach(item => {
            item.update()
        })
    }
}

于此同时,在set get中需要收集wacher以及通知watcher更新数据,那么也要修改

javascript 复制代码
Object.defineProperty(data, key, {
            get() {
                if (Dep.target) { //只收集编译时的watcher
                    //依赖收集,使用这个属性就生成一个watcher
                    dep.subs.push(Dep.target)
                }
                return value
            },
            set(val) {
                //一旦数据更新,Dep通知watcher更新
                value = val
                dep.notify()
            }
        })

此刻,我们简易的响应式就算完成了。

附完整代码:

javascript 复制代码
//vue.js
// Observer专门用于数据劫持
class Observer {
    data;
    constructor(data) {
        this.data = data
        this.walk()
    }

    defineProperty(data, key, value) {
        const dep = new Dep()
        Object.defineProperty(data, key, {
            get() {
                if (Dep.target) { //只收集编译时的watcher
                    //依赖收集,使用这个属性就生成一个watcher
                    dep.subs.push(Dep.target)
                }
                return value
            },
            set(val) {
                //一旦数据更新,Dep通知watcher更新
                value = val
                dep.notify()
            }
        })
    }
    walk() {
        Object.keys(this.data).forEach(el => {
            this.defineProperty(this.data, el, this.data[el])
        })
    }
}

class Vue {
    constructor(options) {
        this.$options = options
        this.$data = options.data()
        this.$el = options.el
        //$data上的所有数据都要数据劫持
        new Observer(this.$data)
        //$data存放所有的数据
        //会将$data的数据挂并挂载到this身上
        this.proxy()
        new Compile(this.$el, this.$data)
    }
    proxy() {
        Object.keys(this.$data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return this.$data[key]
                },
                set(val) {
                    this.$data[key] = val
                }
            })
        })
    }
}


//模版渲染
class Compile {
    constructor(el, data) {
        this.$el = document.querySelector(el)
        this.$data = data
        this.compiler()
    }
    compiler() {
        [...this.$el.children].forEach(item => {
            if (/\{\{([a-zA-z0-9]+)\}\}/.test(item.innerHTML)) {
                const key = RegExp.$1.trim()
                //实际上vue底层不是直接innerHTML
                // item.innerHTML=this.$data[key]
                const render = () => item.innerHTML = this.$data[key]
                new Watcher(render)
            }
        })
    }
}
//订阅者
class Watcher {
    constructor(callback) {
        Dep.target = this
        this.callback = callback
        this.update()
        Dep.target = null
    }
    update() {
        //这一步并不是直接修改,而是更新虚拟dom,这里只是简化了
        this.callback()
    }
}

//发布者
class Dep {
    constructor() {
        this.subs = []
    }
    notify() {
        this.subs.forEach(item => {
            item.update()
        })
    }
}
html 复制代码
<!DOCTYPE html>
<html lang="en">

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

<body>
    <div id="app">
        <p>{{username}}</p>
        <p>{{age}}</p>
    </div>
</body>
<script src="./vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                username: 'xiaoming',
                age: 10
            }
        }
    })
    console.log(app);

</script>

</html>
相关推荐
前端大卫1 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友1 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理3 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻3 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
mapbar_front4 小时前
在职场生存中如何做个不好惹的人
前端
牧杉-惊蛰4 小时前
纯flex布局来写瀑布流
前端·javascript·css
一袋米扛几楼986 小时前
【软件安全】什么是XSS(Cross-Site Scripting,跨站脚本)?
前端·安全·xss
向上的车轮6 小时前
Actix Web适合什么类型的Web应用?可以部署 Java 或 .NET 的应用程序?
java·前端·rust·.net
XiaoYu20026 小时前
第1章 核心竞争力和职业规划
前端·面试·程序员
excel6 小时前
🧩 深入浅出讲解:analyzeScriptBindings —— Vue 如何分析 <script> 里的变量绑定
前端