Vue_2 -- 底层原理

1. MVVM模型

1. M: 模型(Model)

对应 Vue data中的数据

2. V: 视图(View)

模版

3. VM: 视图模型(ViewModel)

Vue 实例对象

2. 数据绑定之数据代理技术

通过一个对象代理另一个对象中属性的读写操作就是数据代理

2.1 Object.defineProperty

1. 设置属性

这样设置的属性age,不可被枚举(循环)

<script>
    let person = {
        name: "张三",
        sex: "男",
    }

    // 给对象设置属性,如果想要这个属性可被枚举,必须设置 enumerable: true,
    Object.defineProperty(person, 'age', {
        value: 18,
    })
    console.log(Object.keys(person))
</script>

2. 设置属性可迭代

如果想要这个属性可被枚举,必须设置 enumerable: true

<script>
    let person = {
        name: "张三",
        sex: "男",
    }

    Object.defineProperty(person, 'age', {
        value: 18,
        enumerable: true,  // 设置属性可迭代
    })
    console.log(Object.keys(person))
</script>

3. 设置属性可修改

<script>
    let person = {
        name: "张三",
        sex: "男",
    }
    
    Object.defineProperty(person, 'age', {
        value: 18,
        writable: true,		// 设置属性可修改
    })
    console.log(Object.keys(person))
</script>

3. 设置属性可删除

<script>
    let person = {
        name: "张三",
        sex: "男",
    }
    
    Object.defineProperty(person, 'age', {
        value: 18,
        configurable: true,		// 设置属性可删除
    })
    console.log(Object.keys(person))
</script>

4. get()

<script>
    let person = {
        name: "张三",
        sex: "男",
    }

    let number = 18
    
    // 为保证person的age值跟随者number的改变而同时改变,需要用到以下函数
    Object.defineProperty(person, 'age', {
        get(){  // 当读取person的age属性时,get函数就会被调用,此函数的返回值为age 的值
            return number
        }
    })
    console.log(person)
    number = 19
    console.log(person)
</script>

5. set()

<script>
    let person = {
        name: "张三",
        sex: "男",
    }

    let number = 18
    
    Object.defineProperty(person, 'age', {
        set(value){  // 当修改person的age属性时,set函数就会被调用,此函数的返回值为age 的值
            number = value
        }
    })
    console.log(number)
    person.age = 19
    console.log(number)
</script>

6. 示例

<script>
    let obj = {x:100}
    let obj2 = {x:200}
    
    Object.defineProperty(obj2,'x',{
        get(){
            return obj.x
        },
        set(value){
            obj.x = value
        }
    })
</script>

2. Vue中对数据代理的应用

Vue将data对象中的每个属性进行代理,并保存到在自身的_data属性中

Vue的数据代理

总结

1. Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
2. Vue中数据代理的好处: 更加方便的操作data中的数据
3. 基本原理:
	a. 通过Object.defineProperty()把data对象中所有属性添加到vm上
	b. 为每一个添加到vm上的属性,都指定一个getter/setter
	c. 在getter/setter内部取操作(读/写) data中对应的属性

3. 数据绑定之_data的数据劫持技术

4. 数据绑定之数据监视原理

1. 往数组中的首位更新数据时会出现的问题

<div id="app">
    <h1>更新时的问题</h1>
    <ul>
        <li v-for="(p,index) in persons" :key="p.id">
            {{p.name}}-{{p.age}}
        </li>
    </ul>
    <button @click="updateMdm2">更新马冬梅的信息</button>

</div>
<script>

    new Vue({
        el: "#app",
        data: {
            keyWord: "",
            sortType: 0,
            persons: [
                {id: 1, name: "马冬梅", age: 18},
                {id: 2, name: "周冬雨", age: 19},
                {id: 3, name: "周杰伦", age: 40},
                {id: 4, name: "王兆伦", age: 25},
            ],
        },
        methods: {
            updateMdm() {   // 通过属性修改时Vue是能同时修改页面显示的
                this.persons[0].name = "马老师"
                this.persons[0].age = 50
            },
            updateMdm2() {   // 直接修改数组中的元素是无法被Vue监测到的,所以代码层面和数据已经改了,但是页面显示并未发生变化
                this.persons[0] =  {id: 1, name: "马老师", age: 50}

            }
        }
    })
</script>

2. Vue是如何监测对象中的数据变化的

1. 模仿Vue做数据代理

<script>
	let data = {
        name: "小明"
    }
    
    Object.defineProperty(data, "name", {   // 如果有人访问data中的name,就会执行get()
        get() {
            return data.name  // 这里同样是在访问data中的name,同样需要执行get(),会造成递归,set()同理
        }
        set(val) {
            data.name = val
        }
    })
</script>

2. Vue如何解决上述问题的

Vue中写的是对data的递归查找,会找到data中对象中的对象,所有层,这里的示例代码并没有考虑到对象中有对象

<script>
    let data = {
        name: "小明"
    }

    // 创建一个监视的实例对象,用于监视data中属性的变化
    const obs = new Observer(data)

    function Observer(obj) {
        // 汇总对象中的所有属性形成一个数组
        const keys = Object.keys(obj)

        // 遍历
        keys.forEach((k) => {
            // this 是Observer的实例对象
            Object.defineProperty(this,k,{
                get(){
                    return obj[k]
                },
                set(val){
                    console.log(`${k} 被改了,接下来,解析模板,生成虚拟DOM......`)
                    obj[k] = val
                }
            })
        })
    }
    let vm = {}
    // 这里相当于将obs同时复制给Vue中定义的data,和_data
    vm._data = data = obs
    console.log(vm)
</script>

3. 对于初始化时并未被Vue管理的对象属性,后面通过代码添加的属性并不会被Vue管理和响应式

<div id="app">
    <h1>更新时的问题</h1>
    <ul>
        <li v-for="(p,index) in persons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
    </ul>
    <button @click="addAttr">添加性别到马冬梅</button>

</div>
<script>

    new Vue({
        el: "#app",
        data: {
            keyWord: "",
            sortType: 0,
            persons: [
                {id: 1, name: "马冬梅", age: 18},
                {id: 2, name: "周冬雨", age: 19},
                {id: 3, name: "周杰伦", age: 40},
                {id: 4, name: "王兆伦", age: 25},
            ],
        },
        methods: {
            addAttr() {
                this.persons[0].sex = "男"
            }
        }
    })
</script>

如何解决这个问题

1.一开始就定义好sex为空字符串,就会被Vue给管理,从而做响应式

<div id="app">
    <h1>更新时的问题</h1>
    <ul>
        <li v-for="(p,index) in persons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
    </ul>
    <button @click="addAttr">添加性别到马冬梅</button>

</div>
<script>

    new Vue({
        el: "#app",
        data: {
            keyWord: "",
            sortType: 0,
            // 一开始就定义好sex为空字符串,就会被Vue给管理,从而做响应式
            persons: [
                {id: 1, name: "马冬梅", age: 18, sex: ""},
                {id: 2, name: "周冬雨", age: 19, sex: ""},
                {id: 3, name: "周杰伦", age: 40, sex: ""},
                {id: 4, name: "王兆伦", age: 25, sex: ""},
            ],
        },
        methods: {
            addAttr() {
                this.persons[0].sex = "男"
            }
        }
    })
</script>

2. 通过Vue.set()来添加属性

<div id="app">
    <h1>更新时的问题</h1>
    <ul>
        <li v-for="(p,index) in persons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
    </ul>
    <button @click="addAttr">添加性别到马冬梅</button>

</div>
<script>
    new Vue({
        el: "#app",
        data: {
            keyWord: "",
            sortType: 0,
            persons: [
                {id: 1, name: "马冬梅", age: 18},
                {id: 2, name: "周冬雨", age: 19},
                {id: 3, name: "周杰伦", age: 40},
                {id: 4, name: "王兆伦", age: 25},
            ],
        },
        methods: {
            addAttr() {
                // 通过Vue.set() 来设置属性
                Vue.set(this.persons[0],"sex","男")
            }
        }
    })
</script>

3. vm.$set()

<div id="app">
    <h1>更新时的问题</h1>
    <ul>
        <li v-for="(p,index) in persons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
    </ul>
    <button @click="addAttr">添加性别到马冬梅</button>

</div>
<script>
    new Vue({
        el: "#app",
        data: {
            keyWord: "",
            sortType: 0,
            persons: [
                {id: 1, name: "马冬梅", age: 18},
                {id: 2, name: "周冬雨", age: 19},
                {id: 3, name: "周杰伦", age: 40},
                {id: 4, name: "王兆伦", age: 25},
            ],
        },
        methods: {
            addAttr() {
                // 通过 this.$set() 来设置属性
                this.$set(this.persons[0],"sex","男")
            }
        }
    })
</script>

4. Vue是如何监测数组的

Vue对于data中的数组元素,并未创建对应的getter()和setter(),这就表示,当使用数组索引 (arr[0]) 来查询和修改数组中的元素时并不会被Vue监测到

Vue中对于数组的检测,并不是通过getter()和setter()来检测的,而是用数组的一些方法,如push(),pop(),shift(),unshift(),splice(),sort(),reverse()方法才会被Vue检测到

<div id="app">
    <h1>更新时的问题</h1>
    <ul>
        <li v-for="(p,index) in persons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
    </ul>
    <button @click="addAttr">添加性别到马冬梅</button>

</div>
<script>
    new Vue({
        el: "#app",
        data: {
            keyWord: "",
            sortType: 0,
            persons: [
                {id: 1, name: "马冬梅", age: 18},
                {id: 2, name: "周冬雨", age: 19},
                {id: 3, name: "周杰伦", age: 40},
                {id: 4, name: "王兆伦", age: 25},
            ],
        },
        methods: {
            addAttr() {
                // 使用索引修改元素,并不会被Vue监测到,从而刷新页面显示
                this.persons[0] = {id: 1, name: "马冬春", age: 22}
            }
        }
    })
</script>

需要通过上述数组方法来修改

<div id="app">
    <h1>更新时的问题</h1>
    <ul>
        <li v-for="(p,index) in persons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
    </ul>
    <button @click="addAttr">添加性别到马冬梅</button>

</div>
<script>
    new Vue({
        el: "#app",
        data: {
            keyWord: "",
            sortType: 0,
            persons: [
                {id: 1, name: "马冬梅", age: 18},
                {id: 2, name: "周冬雨", age: 19},
                {id: 3, name: "周杰伦", age: 40},
                {id: 4, name: "王兆伦", age: 25},
            ],
        },
        methods: {
            addAttr() {
                this.persons.splice(0,1,{id: 1, name: "马冬春", age: 22})
            }
        }
    })
</script>

那么Vue是如何检测到你执行了这些方法呢?

Vue对数组的push(),pop(),shift(),unshift(),splice(),sort(),reverse()方法,做了一层封装,并非是原数组的底层操作方法
posted @ 2024-02-01 17:20  河图s  阅读(150)  评论(0)    收藏  举报