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>
相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试