手撕Vue源码全过程(上)

1.构建自定义Vue实例

1.要想使用Vue需要先创建Vue实例,创建Vue实例需要通过new来创造,所以说Vue是一个类.如果是仿造Vue,那么就需要定义一个名称叫Vue的类.
2.创建好Vue的实例,Vue就会根据指定的区域(比如el指向APP)和数据(比如data里面的数据),去编译渲染这个区域.所以我们需要在自己编写的Vue实例中拿到数据和控制区域,去编译渲染这个区域.
注意点:

  • 创建Vue实例的时候指定的控制区域可以是一个ID名称,也可以是一个DOM元素.
  • Vue实例会将传递的控制区域(el)和数据(data)都绑定到创建的实例对象上,绑定到$ el/$ data
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./testVue.js"></script>
</head>

<body>
    <div id="APP">
        <p>{
   
   {name}}</p>
        <input type="text" v-model='name'>
    </div>
    <script>
        let vue = new Vue({
     
     
            // 指出需要控制的区域
            el: '#APP',
            // 传递数据
            data: {
     
     
                name: "山竹",
                age: 18
            }
        })
        // console.log(vue.$el);
        // console.log(vue.$data);
    </script>
</body>

</html>
// 模拟创建vue实例(类)
class Vue {
    
    
    // 构造器,接收一个参数,参数为对象
    constructor(value) {
    
    
        // 判断el是否为一个节点
        if (this.isElement(value.el)) {
    
    
            this.$el = value.el
        } else {
    
    
            // 如果没有则根据传入的找
            this.$el = document.querySelector(value.el)
        }
        this.$data = value.data
        // 根据传入的位置和数据渲染
        // 先判断是否el是否存在,否则不渲染
        if (this.$el) {
    
    
            // 传入该实例
            new Compier(this)
        }
    }
    isElement(node) {
    
    
        // console.log(node.nodeType === 1);//false
        return node.nodeType === 1
    }

}
class Compier {
    
    
    constructor(vm) {
    
    
        // 保存vue实例
        this.vm = vm
    }
}

效果

在这里插入图片描述

2.提取元素到内存

如何根据数据编译渲染到指定区域?
可以根据遍历渲染元素,查看是否有v-model或者{ {}},如果有,则替换数据…
弊端:每次替换数据都会触发重新渲染界面,会导致性能降低,为此可以将控制区域的元素放到内存中,使用文档碎片DocumentFragment()将需要渲染的元素储存起来,在内存中全部更新后再统一渲染.

  • DocumentFragment与document相比,最大的区别是DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。

接上编写

// 模拟创建vue实例(类)
class Vue {
    
    
    // 构造器,接收一个参数,参数为对象
    constructor(value) {
    
    
        // 判断el是否为一个节点
        if (this.isElement(value.el)) {
    
    
            this.$el = value.el
        } else {
    
    
            // 如果没有则根据传入的找
            this.$el = document.querySelector(value.el)
        }
        this.$data = value.data
        // 根据传入的位置和数据渲染
        // 先判断是否el是否存在,否则不渲染
        if (this.$el) {
    
    
            // 传入该实例
            new Compier(this)
        }
    }
    isElement(node) {
    
    
        // console.log(node.nodeType === 1);//false
        return node.nodeType === 1
    }

}
class Compier {
    
    
    constructor(vm) {
    
    
        // 保存vue实例
        this.vm = vm
        // 1.将页面元素提取到碎片文档
        let fragment = this.node2fragment(this.vm.$el)
        console.log(fragment);
        // 2.利用指定的数据编译内存中的元素
        // 3.将编译好的内存重新渲染到网页上
    }
    node2fragment(app) {
    
    
        // 1.创建空的文档
        let fragment = document.createDocumentFragment()
        //2.遍历循环去到每一个元素
        let node = app.firstChild
        while (node) {
    
    
            // 判断是否还有元素
            // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
            fragment.appendChild(node)
            node = app.firstChild
        }
        //3.返回储存了所有元素的文档碎片对象
        return fragment
    }
}

效果
在这里插入图片描述
在这里插入图片描述

3.查找指令和模板

根据碎片文件中的元素获取全部节点,判断节点是元素还是文本,是元素就获取属性判断是否是v-开头,文本判断有没有{ {}}

// 模拟创建vue实例(类)
class Vue {
    
    
    // 构造器,接收一个参数,参数为对象
    constructor(value) {
    
    
        // 判断el是否为一个节点
        if (this.isElement(value.el)) {
    
    
            this.$el = value.el
        } else {
    
    
            // 如果没有则根据传入的找
            this.$el = document.querySelector(value.el)
        }
        this.$data = value.data
        // 根据传入的位置和数据渲染
        // 先判断是否el是否存在,否则不渲染
        if (this.$el) {
    
    
            // 传入该实例
            new Compier(this)
        }
    }
    // 判断是否为元素节点,元素节点为1,属性节点为2
    isElement(node) {
    
    
        // console.log(node.nodeType === 1);//false
        return node.nodeType === 1
    }

}
class Compier {
    
    
    constructor(vm) {
    
    
        // 保存vue实例
        this.vm = vm
        // 1.将页面元素提取到碎片文档
        let fragment = this.node2fragment(this.vm.$el)
        // 2.利用指定的数据编译内存中的元素
        this.buildTemplate(fragment)
        // 3.将编译好的内存重新渲染到网页上
    }
    node2fragment(app) {
    
    
        // 1.创建空的文档
        let fragment = document.createDocumentFragment()
        //2.遍历循环去到每一个元素
        let node = app.firstChild
        while (node) {
    
    
            // 判断是否还有元素
            // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
            fragment.appendChild(node)
            node = app.firstChild
        }
        //3.返回储存了所有元素的文档碎片对象
        return fragment
    }
    // =========================================以下为新增=============================
    buildTemplate(fragment) {
    
    
        // 从元素获取所有节点,伪数组转为数组
        let nodeList = [...fragment.childNodes]
        // console.log(nodeList);//[text, p, text, input, text]
        // 循环判断当前的节点是一个元素还是一个文本
        nodeList.forEach(node => {
    
    
            if (this.vm.isElement(node)) {
    
    
                //是一个元素
                this.buildElement(node)
                //处理子元素,递归
                this.buildTemplate(node)
            } else {
    
    
                //不是一个元素
                this.buildText(node)
            }
        })
    }
    // 元素处理
    buildElement(node) {
    
    
        // attributes 属性返回指定节点的属性集合
        let attrs = [...node.attributes]
        attrs.forEach(attr => {
    
    
            // console.log(attr);//获取到属性
            let {
    
     name, value } = attr
            // console.log(name,value);//单独拿到类型与类型的取值
            if (name.startsWith('v-')) {
    
    
                // startsWith() 方法用于检测字符串是否以指定的子字符串开始。
                // console.log('是v-', name,value);//是v- v-model name
            }
        })
    }
    //文本处理
    buildText(node) {
    
    
        // textContent 属性设置或者返回指定节点的文本内容。
        let content = node.textContent
        let reg=/\{\{.+?\}\}/gi
        if(reg.test(content)){
    
    
            console.log('是{
    
    {}}',content);//是{
    
    {}} {
    
    {name}}
        }
    }
}

4.编译指令数据

1.创建工具类对应不同的指令
2.切割上面步骤的查找指令切割并结构,比如v-model,只需要model这串.
3.将切割出来的字符串匹配工具类执行对应的方法,并传入需要修改的元素、被修改的值、新值。
4.工具类接收参数后切割,time.h–>[time,h],利用reduce遍历逐层取。
5.更新新值并重新渲染到网页上

HTML继上添加

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./testVue.js"></script>
</head>

<body>
    <div id="APP">
        <p>{
   
   {name}}</p>
        <input type="text" v-model='name'>
        <!-- ====================新增================== -->
        <input type="text" v-model='time.h'>
        <input type="text" v-model='time.m'>
        <input type="text" v-model='time.s'>
        <div v-html='html'>我是div</div>
        <div v-text='text'>我是div</div>
    </div>
    <script>
        let vue = new Vue({
     
     
            // 指出需要控制的区域
            el: '#APP',
            // 传递数据
            data: {
     
     
                name: "山竹",
                age: 18,
                // ======================新增===============
                time:{
     
     
                    h:11,
                    m:22,
                    s:33
                },
                html:`<div>我是HTML</div>`,
                text:`<div>我是text</div>`
            }
        })
        // console.log(vue.$el);
        // console.log(vue.$data);
    </script>
</body>

</html>

js继上添加

// ==============================创建工具类对应不同指令======================
let CompilerUtil = {
    
    
    getValue(vm, value) {
    
    
        //time.h-->[time,h],利用reduce遍历逐层取
        //reduce接收的第一个参数为上一次调用回调返回的值,或者是提供的初始值
        //  第二个参数为数组中当前被处理的元素
        // vm.$data作为data第一次调用初始值
        return value.split('.').reduce((data, currentKey) => {
    
    
            //第一次执行:data=$data,currentKey=time
            // 第二次执行:data=time,currentKey=h
            return data[currentKey]
        }, vm.$data)
    },
    model: function (node, value, vm) {
    
    
        // console.log(node, value, vm);
        // v-model作用在input上
        // 根据被替换内容,获取对应的数据
        // node.value=vm.$data[value];
        //遇到复杂类型情况下vm.$data[time.h]-->vm.$data[time]-->time[h]
        let val = this.getValue(vm, value)
        node.value = val
    },
    html: function (node, value, vm) {
    
    
        let val = this.getValue(vm, value)
        node.innerHTML = val
    },
    text: function (node, value, vm) {
    
    
        let val = this.getValue(vm, value)
        node.innerText = val
    }
}
// 模拟创建vue实例(类)
class Vue {
    
    
    // 构造器,接收一个参数,参数为对象
    constructor(value) {
    
    
        // 判断el是否为一个节点
        if (this.isElement(value.el)) {
    
    
            this.$el = value.el
        } else {
    
    
            // 如果没有则根据传入的找
            this.$el = document.querySelector(value.el)
        }
        this.$data = value.data
        // 根据传入的位置和数据渲染
        // 先判断是否el是否存在,否则不渲染
        if (this.$el) {
    
    
            // 传入该实例
            new Compier(this)
        }
    }
    // 判断是否为元素节点,元素节点为1,属性节点为2
    isElement(node) {
    
    
        // console.log(node.nodeType === 1);//false
        return node.nodeType === 1
    }

}
class Compier {
    
    
    constructor(vm) {
    
    
        // 保存vue实例
        this.vm = vm
        // 1.将页面元素提取到碎片文档
        let fragment = this.node2fragment(this.vm.$el)
        // 2.利用指定的数据编译内存中的元素
        this.buildTemplate(fragment)
        // ========================新增============================
        // 3.将编译好的内存重新渲染到网页上
        this.vm.$el.appendChild(fragment)
    }
    node2fragment(app) {
    
    
        // 1.创建空的文档
        let fragment = document.createDocumentFragment()
        //2.遍历循环去到每一个元素
        let node = app.firstChild
        while (node) {
    
    
            // 判断是否还有元素
            // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
            fragment.appendChild(node)
            node = app.firstChild
        }
        //3.返回储存了所有元素的文档碎片对象
        return fragment
    }
    buildTemplate(fragment) {
    
    
        // 从元素获取所有节点,伪数组转为数组
        let nodeList = [...fragment.childNodes]
        // console.log(nodeList);//[text, p, text, input, text]
        // 循环判断当前的节点是一个元素还是一个文本
        nodeList.forEach(node => {
    
    
            if (this.vm.isElement(node)) {
    
    
                //是一个元素
                this.buildElement(node)
                //处理子元素,递归
                this.buildTemplate(node)
            } else {
    
    
                //不是一个元素
                this.buildText(node)
            }
        })
    }
    // 元素处理
    buildElement(node) {
    
    
        // attributes 属性返回指定节点的属性集合
        let attrs = [...node.attributes]
        attrs.forEach(attr => {
    
    
            // console.log(attr);//获取到属性
            let {
    
     name, value } = attr
            // console.log(name,value);//单独拿到类型与类型的取值
            if (name.startsWith('v-')) {
    
    
                // v-开头的有很多,比如v-model/v-html/v-if...
                // startsWith() 方法用于检测字符串是否以指定的子字符串开始。
                // console.log('是v-', name,value);//是v- v-model name
                // =================更新部分=====================
                // 切割并解构,不需要v-
                let [, directive] = name.split('-')//v,model
                // console.log(directive);//model
                // 找到对应的工具类执行对应的方法
                // node:修改的节点,value:修改的值,this.vm新值
                CompilerUtil[directive](node, value, this.vm)
            }
        })
    }
    //文本处理
    buildText(node) {
    
    
        // textContent 属性设置或者返回指定节点的文本内容。
        let content = node.textContent
        let reg = /\{\{.+?\}\}/gi
        if (reg.test(content)) {
    
    
            // console.log('是{
    
    {}}',content);//是{
    
    {}} {
    
    {name}}
        }
    }
}

效果
在这里插入图片描述

扫描二维码关注公众号,回复: 13433272 查看本文章

5.编译模板数据

let CompilerUtil = {
    
    
    getValue(vm, value) {
    
    
        //time.h-->[time,h],利用reduce遍历逐层取
        //reduce接收的第一个参数为上一次调用回调返回的值,或者是提供的初始值
        //  第二个参数为数组中当前被处理的元素
        // vm.$data作为data第一次调用初始值
        return value.split('.').reduce((data, currentKey) => {
    
    
            //第一次执行:data=$data,currentKey=time
            // 第二次执行:data=time,currentKey=h
            return data[currentKey.trim()]
        }, vm.$data)
    },
    // ================新增部分==========
    getContent(vm, value) {
    
    
        let reg = /\{\{(.+?)\}\}/gi
        let val = value.replace(reg, (...args) => {
    
    
            return this.getValue(vm, args[1])
        })
        return val
    },
    model: function (node, value, vm) {
    
    
        // console.log(node, value, vm);
        // v-model作用在input上
        // 根据被替换内容,获取对应的数据
        // node.value=vm.$data[value];
        //遇到复杂类型情况下vm.$data[time.h]-->vm.$data[time]-->time[h]
        let val = this.getValue(vm, value)
        node.value = val
    },
    html: function (node, value, vm) {
    
    
        let val = this.getValue(vm, value)
        node.innerHTML = val
    },
    text: function (node, value, vm) {
    
    
        let val = this.getValue(vm, value)
        node.innerText = val
    },
    // ====================更新部分===============
    content: function (node, value, vm) {
    
    
        // console.log(value);//{
    
    {name}}-->取出name-->将$data[name]的数据赋值
        let val = this.getContent(vm, value)
        node.textContent = val
    }
}
// 模拟创建vue实例(类)
class Vue {
    
    
    // 构造器,接收一个参数,参数为对象
    constructor(value) {
    
    
        // 判断el是否为一个节点
        if (this.isElement(value.el)) {
    
    
            this.$el = value.el
        } else {
    
    
            // 如果没有则根据传入的找
            this.$el = document.querySelector(value.el)
        }
        this.$data = value.data
        // 根据传入的位置和数据渲染
        // 先判断是否el是否存在,否则不渲染
        if (this.$el) {
    
    
            // 传入该实例
            new Compier(this)
        }
    }
    // 判断是否为元素节点,元素节点为1,属性节点为2
    isElement(node) {
    
    
        // console.log(node.nodeType === 1);//false
        return node.nodeType === 1
    }

}
class Compier {
    
    
    constructor(vm) {
    
    
        // 保存vue实例
        this.vm = vm
        // 1.将页面元素提取到碎片文档
        let fragment = this.node2fragment(this.vm.$el)
        // 2.利用指定的数据编译内存中的元素
        this.buildTemplate(fragment)
        // 3.将编译好的内存重新渲染到网页上
        this.vm.$el.appendChild(fragment)
    }
    node2fragment(app) {
    
    
        // 1.创建空的文档
        let fragment = document.createDocumentFragment()
        //2.遍历循环去到每一个元素
        let node = app.firstChild
        while (node) {
    
    
            // 判断是否还有元素
            // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
            fragment.appendChild(node)
            node = app.firstChild
        }
        //3.返回储存了所有元素的文档碎片对象
        return fragment
    }
    buildTemplate(fragment) {
    
    
        // 从元素获取所有节点,伪数组转为数组
        let nodeList = [...fragment.childNodes]
        // console.log(nodeList);//[text, p, text, input, text]
        // 循环判断当前的节点是一个元素还是一个文本
        nodeList.forEach(node => {
    
    
            if (this.vm.isElement(node)) {
    
    
                //是一个元素
                this.buildElement(node)
                //处理子元素,递归
                this.buildTemplate(node)
            } else {
    
    
                //不是一个元素
                this.buildText(node)
            }
        })
    }
    // 元素处理
    buildElement(node) {
    
    
        // attributes 属性返回指定节点的属性集合
        let attrs = [...node.attributes]
        attrs.forEach(attr => {
    
    
            // console.log(attr);//获取到属性
            let {
    
     name, value } = attr
            // console.log(name,value);//单独拿到类型与类型的取值
            if (name.startsWith('v-')) {
    
    
                // v-开头的有很多,比如v-model/v-html/v-if...
                // startsWith() 方法用于检测字符串是否以指定的子字符串开始。
                // console.log('是v-', name,value);//是v- v-model name
                // 切割并解构,不需要v-
                let [, directive] = name.split('-')//v,model
                // console.log(directive);//model
                // 找到对应的工具类执行对应的方法
                // node:修改的节点,value:修改的值,this.vm新值
                CompilerUtil[directive](node, value, this.vm)
            }
        })
    }
    //文本处理
    buildText(node) {
    
    
        // textContent 属性设置或者返回指定节点的文本内容。
        let content = node.textContent
        let reg = /\{\{.+?\}\}/gi
        if (reg.test(content)) {
    
    
            // console.log('是{
    
    {}}',content);//是{
    
    {}} {
    
    {name}}
            // ======================================新增部分=============================
            // 找到对应的工具类执行对应的方法
            // node:修改的节点,content:修改的值,this.vm新值
            CompilerUtil['content'](node, content, this.vm)
        }
    }
}

6.监听数据变化

vue双向数据绑定原理

let CompilerUtil = {
    
    
    getValue(vm, value) {
    
    
        //time.h-->[time,h],利用reduce遍历逐层取
        //reduce接收的第一个参数为上一次调用回调返回的值,或者是提供的初始值
        //  第二个参数为数组中当前被处理的元素
        // vm.$data作为data第一次调用初始值
        return value.split('.').reduce((data, currentKey) => {
    
    
            //第一次执行:data=$data,currentKey=time
            // 第二次执行:data=time,currentKey=h
            return data[currentKey.trim()]
        }, vm.$data)
    },
    getContent(vm, value) {
    
    
        let reg = /\{\{(.+?)\}\}/gi
        let val = value.replace(reg, (...args) => {
    
    
            return this.getValue(vm, args[1])
        })
        return val
    },
    model: function (node, value, vm) {
    
    
        // console.log(node, value, vm);
        // v-model作用在input上
        // 根据被替换内容,获取对应的数据
        // node.value=vm.$data[value];
        //遇到复杂类型情况下vm.$data[time.h]-->vm.$data[time]-->time[h]
        let val = this.getValue(vm, value)
        node.value = val
    },
    html: function (node, value, vm) {
    
    
        let val = this.getValue(vm, value)
        node.innerHTML = val
    },
    text: function (node, value, vm) {
    
    
        let val = this.getValue(vm, value)
        node.innerText = val
    },
    content: function (node, value, vm) {
    
    
        // console.log(value);//{
    
    {name}}-->取出name-->将$data[name]的数据赋值
        let val = this.getContent(vm, value)
        node.textContent = val
    }
}
// 模拟创建vue实例(类)
class Vue {
    
    
    // 构造器,接收一个参数,参数为对象
    constructor(value) {
    
    
        // 判断el是否为一个节点
        if (this.isElement(value.el)) {
    
    
            this.$el = value.el
        } else {
    
    
            // 如果没有则根据传入的找
            this.$el = document.querySelector(value.el)
        }
        this.$data = value.data
        // 根据传入的位置和数据渲染
        // 先判断是否el是否存在,否则不渲染
        if (this.$el) {
    
    
            // ==================更新================
            // 1.给外界传入的所有数据都添加get/set方法,这样就可以监听数据变化了
            // 监听数据变化
            new Observer(this.$data)
            // 传入该实例
            new Compier(this)
        }
    }
    // 判断是否为元素节点,元素节点为1,属性节点为2
    isElement(node) {
    
    
        // console.log(node.nodeType === 1);//false
        return node.nodeType === 1
    }

}
class Compier {
    
    
    constructor(vm) {
    
    
        // 保存vue实例
        this.vm = vm
        // 1.将页面元素提取到碎片文档
        let fragment = this.node2fragment(this.vm.$el)
        // 2.利用指定的数据编译内存中的元素
        this.buildTemplate(fragment)
        // 3.将编译好的内存重新渲染到网页上
        this.vm.$el.appendChild(fragment)
    }
    node2fragment(app) {
    
    
        // 1.创建空的文档
        let fragment = document.createDocumentFragment()
        //2.遍历循环去到每一个元素
        let node = app.firstChild
        while (node) {
    
    
            // 判断是否还有元素
            // 注意点:只要元素添加到文档碎片对象中,name这个元素就会自动从网页上消失
            fragment.appendChild(node)
            node = app.firstChild
        }
        //3.返回储存了所有元素的文档碎片对象
        return fragment
    }
    buildTemplate(fragment) {
    
    
        // 从元素获取所有节点,伪数组转为数组
        let nodeList = [...fragment.childNodes]
        // console.log(nodeList);//[text, p, text, input, text]
        // 循环判断当前的节点是一个元素还是一个文本
        nodeList.forEach(node => {
    
    
            if (this.vm.isElement(node)) {
    
    
                //是一个元素
                this.buildElement(node)
                //处理子元素,递归
                this.buildTemplate(node)
            } else {
    
    
                //不是一个元素
                this.buildText(node)
            }
        })
    }
    // 元素处理
    buildElement(node) {
    
    
        // attributes 属性返回指定节点的属性集合
        let attrs = [...node.attributes]
        attrs.forEach(attr => {
    
    
            // console.log(attr);//获取到属性
            let {
    
     name, value } = attr
            // console.log(name,value);//单独拿到类型与类型的取值
            if (name.startsWith('v-')) {
    
    
                // v-开头的有很多,比如v-model/v-html/v-if...
                // startsWith() 方法用于检测字符串是否以指定的子字符串开始。
                // console.log('是v-', name,value);//是v- v-model name
                // 切割并解构,不需要v-
                let [, directive] = name.split('-')//v,model
                // console.log(directive);//model
                // 找到对应的工具类执行对应的方法
                // node:修改的节点,value:修改的值,this.vm新值
                CompilerUtil[directive](node, value, this.vm)
            }
        })
    }
    //文本处理
    buildText(node) {
    
    
        // textContent 属性设置或者返回指定节点的文本内容。
        let content = node.textContent
        let reg = /\{\{.+?\}\}/gi
        if (reg.test(content)) {
    
    
            // console.log('是{
    
    {}}',content);//是{
    
    {}} {
    
    {name}}
            // 找到对应的工具类执行对应的方法
            // node:修改的节点,content:修改的值,this.vm新值
            CompilerUtil['content'](node, content, this.vm)
        }
    }
}
// =================================新增部分======================
class Observer {
    
    
    // 只要将需要监听的那个对象传递给Observer这个类
    // 这个类就可以快速的给传入的对象的所有属性都添加get/set方法
    // data代表接收的对象
    constructor(data) {
    
    
      this.observer(data);
    }
    // 给属性添加get/set;
    observer(obj) {
    
    
      // 判断是不是对象
      if (obj && typeof obj === "object") {
    
    
        //遍历取出传入对象的所有属性,给遍历到的属性都增加get/set方法
        for (let key in obj) {
    
    
          // 参数为:对象,属性,值
          this.defineRecative(obj, key, obj[key]);
        }
      }
    }
    // obj:需要操作的对象
    // attr:需要新增get/set方法的属性
    // value:需要新增get/set方法属性的取值
    defineRecative(obj, attr, value) {
    
    
      //如果对象属性里面嵌套对象,则开始递归添加get/set
      this.observer(value);
      Object.defineProperty(obj, attr, {
    
    
        // get方法直接返回值
        get() {
    
    
          return value;
        },
        // set方法接收新值并返回
        set: (newValue) => {
    
    
          if (value !== newValue) {
    
    
            // 如果新值里面也有对象,也需要添加get/set
            this.observer(newValue);
            value = newValue;
            console.log("监听到数据的变化,需要去更新UI");
          }
        },
      });
    }}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_47886687/article/details/113531049