DefineProperty的尝试

132 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

前言

都知道,vue2的数据响应式用的是 Object.defineProperty 做的数据劫持,而在vue3中用的proxy做的数据劫持, 这就能体现出两者的不同的,既然放弃了Object.defineProperty选择proxy,那么两者之间有什么不同点呢?

  • defineProperty劫持的是对象的属性,proxy代理的是整个对象
  • defineProperty劫持的数据类型有限,proxy更加丰富
  • defineProperty返回的是被修改的对象,proxy对象用于创建一个对象的代理
  • defineProperty无法监听新增属性和删除属性

Object.defineProperty

认识

它是可以修改或者添加对象的属性,并返回改对象

Object.defineProperty(obj,prop,descriptor)

obj:要定义属性的对象

prop:要定义或者修改的属性的名称或者symbol

descriptor:要定义或者修改的属性的配置:

  • value:该属性对应的有效的JavaScript值;
const person = {};
 Object.defineProperty(person, 'age', {
      value: 42,
});
console.log(person.age);//42
  • writable:为true时属性值才能被修改,默认为false
 'use strict'
  const person = {};
  Object.defineProperty(person, 'age', {
  value: 42,
  writable: false
});
 person.age = 77;
 // 在严格模式下:Cannot assign to read only property 'age' of object '#<Object>'
  console.log(person.age);
  • get:访问当前属性时,会执行该函数
'use strict'
        const person = {};
        var age = 42
        Object.defineProperty(person, 'age', {
            get() { 
               console.log("get函数被执行")//get函数被执行
               return age
             }
        });
        console.log(person.age);//42
  • set:当属性值被修改时,会调用此函数,该方法接受一个参数(新值),会传入赋值时的this对象
 'use strict'
        const person = {};
        var age = 42
        Object.defineProperty(person, 'age', {
            get() {
                console.log("get函数被执行")
                return age
            },
            set(val) {
                console.log(`未修改之前的age属性值=${this.age}`)
                console.log("set函数被执行")
                age = val
            }
        });
        person.age = 77;
        console.log(person.age);
       打印的执行顺序:
       get函数被执行
       未修改之前的age属性值=42
       set函数被执行
       get函数被执行
       77
  • enumerable:该属性是否能出现在对象的枚举属性中
     'use strict'
        const person = {};
        person.name = '张三'
        var age = 42
        Object.defineProperty(person, 'age', {
            // value: 42,
            // writable: true,
            get() {
                console.log("get函数被执行")
                return age
            },
            set(val) {
                console.log(`未修改之前的age属性值=${this.age}`)
                console.log("set函数被执行")
                age = val
            }
        });
        Object.keys(person).forEach(it=>{
             console.log(it)
        })
        打印结果:name
       
       

由此可见:enumrable默认为false,当直接使用赋值的方式创建对象的属性时,为true

修改后:

     'use strict'
        const person = {};
        person.name = '张三'
        var age = 42
        Object.defineProperty(person, 'age', {
            // value: 42,
            // writable: true,
            enumrable:true
            get() {
                console.log("get函数被执行")
                return age
            },
            set(val) {
                console.log(`未修改之前的age属性值=${this.age}`)
                console.log("set函数被执行")
                age = val
            }
        });
        Object.keys(person).forEach(it=>{
             console.log(it)
        })
        打印结果:name age
  • ``configurable:判断当前属性是否可以被删除及除valuewritable`之外其他特性是不是能被修改
       1.
       'use strict'
        const person = {};
        person.name = '张三'
        var age = 42
        Object.defineProperty(person, 'age', {
            enumerable:true,
            get() {
                console.log("get函数被执行")
                return age
            },
            set(val) {
                console.log(`未修改之前的age属性值=${this.age}`)
                console.log("set函数被执行")
                age = val
            }
        });
        delete person.age  // Cannot delete property 'age' of #<Object>
       
        
        加入configurable:true之后
        console.log(person.age) // undefined
        
        2.
        'use strict'
        const person = {};
        person.name = '张三'
        var age = 42
        Object.defineProperty(person, 'age', {
            enumerable:true,
            // configurable:true,
            get() {
                console.log("get函数被执行")
                return age
            },
            set(val) {
                console.log(`未修改之前的age属性值=${this.age}`)
                console.log("set函数被执行")
                age = val
            }
        });
        Object.defineProperty(person,'age',{
            get(){
                return 1
            }
        })
        
        结果:Cannot redefine property: age
        configurable:trueconsole.log(person.age) //1
        
  • 注:属性不能同时拥有 valuewritablegetset 

监听对个对象属性

上面的例子都是使用一个属性,这里我们监听多个属性,使用Object.keys()或者for in  结合来做:

'use strict'
        const person = {
            name: "张三",
            age: "20"
        };
        const observe = (obj, key, value) => {
            Object.defineProperty(person, key, {
                enumerable: true,
                configurable: true,
                get() {
                    console.log("get函数被执行")//第1个执行,最后打印person.name时第5个执行
                    return value
                },
                set(val) {
                    console.log(`未修改之前的age属性值=${value}`) //第3个执行
                    console.log("set函数被执行") //第四个执行
                    value = val
                }
            });
        }
        Object.keys(person).forEach(key => {
            observe(person, key, person[key])
        })
        console.log(person.age)//第2个执行
        person.name = '李四'
        console.log(person.name)//第6个执行
        
        结果执行顺序:
        get函数被执行
        20
        未修改之前的age属性值=张三
        set函数被执行
        get函数被执行
        李四
        

递归监听深层对象

这里我们使用递归来做一个深层的监听,基于上述代码可以看到,observe是一个只对属性进行监听的函数,object.keys返回一个包含对象的属性的数组,循环进行把参数传入observe函数,那我们可以在observe函数里面判断value值是不是object类型的,是的话,再使用object.keys循环不就好了吗?,~,把提取属性和循环包装成一个函数吧:

'use strict'
        const person = {
            name: "张三",
            age: "20",
            children: {
                name: "张三-1",
                age: "20-1"
            }
        };
        const observe = (obj, key, value) => {
            if (typeof value === 'object') {
                observer(value)
            }
            Object.defineProperty(obj, key, {
                enumerable: true,
                configurable: true,
                get() {
                    console.log("get函数被执行")
                    return value
                },
                set(val) {
                    console.log(`未修改之前的age属性值=${value}`)
                    console.log("set函数被执行")
                    value = val
                }
            });
        }
        const observer = (obj) => {
            if (typeof obj !== 'object' || obj === null) return
            Object.keys(obj).forEach(key => {
                observe(obj, key, obj[key])
            })
        }
        observer(person)

        console.log(person.age) // 20
        person.name = '李四'
        console.log(person.name) // 李四
        console.log("打印children里面的属性") //打印children里面的属性
        console.log(person.children.name) //张三-1
        person.children.age = 300;
        console.log(person.children.age) //300
        浏览器打印的结果顺序为:
        get函数被执行
 20
 未修改之前的age属性值=张三
 set函数被执行
 get函数被执行
 李四
 打印children里面的属性
 get函数被执行
 get函数被执行
 张三-1
 get函数被执行
 未修改之前的age属性值=20-1
 set函数被执行
 get函数被执行
get函数被执行
 300

监听数组

现在我们用object.defineProperty来监听数组的变化,使用pushpopshiftunshift这个方法及以数组下标为key(相当于对象的属性名)探讨是否可以监听到数组的变化:

const arr = [1, 2, 3, 4];
        const observeArr = (arr, key, val) => {
            Object.defineProperty(arr, key, {
                get() {
                    console.log("执行get函数", `当前的key=${key}`, `当前的val值${val}`)
                    return val
                },
                set(newval) {
                    console.log("执行set函数", `当前的key=${key}`, `当前的val值${val}`, `新的值${newval}`)
                    val = newval
                }
            })
        }
        arr.forEach((it, index) => {
            observeArr(arr, index, it)
        })

        arr.pop() //执行get函数 当前的key=3 当前的val值4
        // arr.shift()
        // 执行get函数 当前的key=0 当前的val值1
        // 执行get函数 当前的key=1 当前的val值2
        // 执行set函数 当前的key=0 当前的val值1 新的值2
        // 执行get函数 当前的key=2 当前的val值3
        // 执行set函数 当前的key=1 当前的val值2 新的值3
        // 执行get函数 当前的key=3 当前的val值4
        // 执行set函数 当前的key=2 当前的val值3 新的值4
        // arr.unshift(9)
        //  执行get函数 当前的key=3 当前的val值4
        //  执行get函数 当前的key=2 当前的val值3
        //  执行set函数 当前的key=3 当前的val值4 新的值3
        //  执行get函数 当前的key=1 当前的val值2
        //  执行set函数 当前的key=2 当前的val值3 新的值2
        //  执行get函数 当前的key=0 当前的val值1
        //  执行set函数 当前的key=1 当前的val值2 新的值1
        //  执行set函数 当前的key=0 当前的val值1 新的值9
        //arr.push(2)// ''
  • push:当push值的时候并没有执行set函数和get函数,并不能监听到数组的push方法的操作;
  • shift:能够触发getset
  • unshift:能够触发getset
  • push:无法触发,新增的索引并没有监听