【Vue知识点】——Vue2和Vue3的数据劫持

发布于:2022-10-21 ⋅ 阅读:(809) ⋅ 点赞:(0)

数据劫持

   上一次面试中,考官有问我对Vue当中双向绑定(v-model)的理解,我当时回答的好像很片面,只是解释了这个内置指令的含义,当时面试官听完我的回答,也并没有对我的回答做出任何反应。我就明白了这应该是我的回答可能是错的或者理解的不够深刻。于是我一有空,就对Vue当中的双向绑定进行了更深刻的钻研。

v-model理解

  • v-model是Vue的内置指令,本质上是一种语法糖。主要是负责监听用户的输入事件来更新数据
  • v-model指令可以在表单 input、textarea以及select元素上创建双向数据绑定它会根据控件类型自动选取正确的方法来更新元素。
  • v-model本质上还是单向数据流,通过v-bind绑定value值,此时只能通过在data中修改输入框的显示值,即数据只能从data中流向视图,而不能获取用户输入的数据从而修改data中的值;此时需要绑定一个@input事件来实现获取用户输入的数据从而修改data中的值。从而实现数据的双向绑定:数据向下,事件向上

v-model其实是简化了两个操作:
   1.v-bind绑定value属性的值;

   2.v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;

<input type="text" v-model="textDetail">
<!--等价于-->    
<input type="text" :value="textDetail" @input="textDetail=$event.target.value">

   此时我对data中定义的textDetail挺好奇的:该属性是如何绑定到页面中的?该属性是如何进行一次次修改的?我通过打印输出该数据得到:
   大概意思是在获取data数据至页面应该是调用了get方法,修改该属性应该是调用了set方法。那么问题来了!!!
data中的数据

为什么在data中定义的数据会自动携带get、set属性方法呢?

  • 其实此刻正文才刚刚开始
    哈哈哈

数据劫持

数据劫持,其实就是数据代理
指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。

data与_data

我们通过全局输出this可以得到:
全局this
将data展开得到:
_data
   _data中的数据与data中的数据一致,只不过多了对数据的get和set方法还有一个Observer对象,Vue就是通过_data来实现对data的数据劫持,通过调用_data中的get和set方法去修改和展现数据。

__ob__: Observer对象是vue这个框架对数据设置的监控器,监控数据的变化,一般都是不可枚举的。

原来是_data上的数据携带get和set方法,那问题还是没解决,_data上存在get和set方法是怎么来的?

Object.DefineProperty

   Vue2中是通过Object.DefineProperty实现数据劫持,作用是往data中的一个对象中添加属性,get()和set()方法就是通过Object.DefineProperty添加的,从而实现当外部想要修改data中的值。

Object.DefineProperty使用规范为

  • Object.defineProperty(对象,要添加得键,{配置对象})

配置对象的主要属性有:

  • value:20 //添加的属性的value
  • enumerable:true //是否可以被枚举获取到 默认:false
  • writeable:true //value是否可以被修改 默认:false
  • configurable:true //是否可以被删除 默认:false
  • get(){} //当这个属性被获取的时候调用
  • set(){} //当这个属性被修改的时候调用

Object.DefineProperty的实际应用

const user={
            name:"jack",
        }
        var name=user.name;
        Object.defineProperty(user, "name", {
            enumerable: true,
            writeable: true,
            configurable: true,

            get() { 
                return name
             }, 
            set(e) { 
                name=e
             } 
        })

可以在浏览器控制台中输出name以及修改name:
控制台
Object.DefineProperty就是这样把data中的数据全部加工了一遍,添加上了get和set方法,从而实现了数据的劫持。

Object.DefineProperty的缺点

1.**对象上定义新属性时,Object.defineProperty监听不到。**因为Object.DefineProperty只能对对象的单个属性劫持,如果要对对象进行劫持,需要遍历。

		const user = {
            name: "jack",
        }
        var name = user.name;
        Object.defineProperty(user, "name", {
            enumerable: true,
            writeable: true,
            configurable: true,

            get() {
                return name
            },
            set(e) {
                name = e
            }
		})
        user.age = 20;  // 并没有触发set
        console.log(user);
        delete user.age;  //并没有触发set
        console.log(user);

2.Object.defineProperty无法监控到数组下标的变化

		let arr = [1, 2, 3]
        let obj = {}
        //把arr作为obj的属性监听
        Object.defineProperty(obj, 'arr', {
            get() {
                console.log('触发了get')
                return arr
            },
            set(newVal) {
                console.log('触发了set', newVal)
                arr = newVal
            }
        })
        console.log(obj.arr) //触发了get [1,2,3] 
        obj.arr = [1, 2, 3, 4] //触发了set [1,2,3,4] 
        obj.arr.push(5) //触发了get
        obj.arr.unshift() //触发了get
        obj.arr.pop() // //触发了get
        obj.arr.shift() //触发了get
  • 只要不是重新赋值一个新的数组对象,任何对数组内部的修改都不会触发set方法的执行。

3.**object.defineProperty 只能劫持对象的属性。**从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。

const data = {
      name: 'jack',
      age: 20,
      children: {
        name: 'john'
      }
    }
function observer(target) {
      if (target === null || typeof target !== 'object') {
        return target
      }
      for (let key in target) {//target[key]作为value
        let value = target[key]
        observer(value)//调用自身,监控内部对象,如果不加遍历不到嵌套对象中的数据,无法修改
        Object.defineProperty(target, key, {
          get() {
            console.log('Object.defineProperty触发了get');
            return value
          },
          set(newValue) {
            console.log('Object.defineProperty触发了set');
            if (newValue !== value) {
              value = newValue
              //updateView()
            }
          }
        })
      }
    }
	observer(data)
    data.children.name = 'marray'//此时会调用get和set方法修改数据

Proxy

   在Vue3中则是使用Proxy来进行数据劫持,Proxy不同于Object.defineProperty的是,它是对整个数据对象进行数据劫持,而Object.defineProperty是对数据对象的某个属性进行数据劫持(如果是多层需要循环绑定)。

   Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。IE不兼容。基本格式为:

  • const p = new Proxy(target, handler)

参数:
   target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
   handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap):

  • handler.getPrototypeOf()
  • handler.setPrototypeOf()
  • handler.isExtensible()
  • handler.preventExtensions()
  • handler.getOwnPropertyDescriptor()
  • handler.defineProperty()
  • handler.has()//in 操作符的捕捉器。
  • handler.get(target, property)
  • handler.set(target, property, value)
  • handler.deleteProperty()//delete 操作符的捕捉器。
  • handler.ownKeys()
  • handler.apply()
  • handler.construct()//new 操作符的捕捉器。

Proxy的应用——监控数组下标变化

let arr=[1,2,3]
        let handler={
            get(target, key, receiver) {
                console.log('get的key为 ===>' + key);
                return Reflect.get(target, key, receiver);
            },
            set(target, key, value, receiver){
                console.log('set的key为 ===>' + key, value);
                return Reflect.set(target, key, value, receiver);
            }
        }
        let p=new Proxy(arr,handler);
        
        console.log(p[0]);
        //get的key为 ===>0
		//1
        console.log(p.push(4));
        //get的key为 ===>push
		//get的key为 ===>length
		//set的key为 ===>3 4
		//set的key为 ===>length 4
		//4
        console.log(p);
        //Proxy {0: 1, 1: 2, 2: 3, 3: 4}
        console.log(p.shift());
        //get的key为 ===>shift
		// get的key为 ===>length
		// get的key为 ===>0
		// get的key为 ===>1
		// set的key为 ===>0 2
		// get的key为 ===>2
		// set的key为 ===>1 3
		// get的key为 ===>3
		// set的key为 ===>2 4
		// set的key为 ===>length 3
		// 1
        console.log(p);
        //Proxy {0: 2, 1: 3, 2: 4}

reflect也是es6的语法,再proxy使用中常用到reflect,reflect有14中方法,
对应proxy是一样的,但是这俩其实没关系,只是搭配着使用,proxy用来拦截,reflect用来操作。

首先我们可以看到Proxy可以监控到数组下标的变化。
在使用push时先将push的值添加到数组中,再更新数组长度,所以才会有两次get和set。
在使用shift时首先要获取到要删除的元素下标,并删除,再更新每个下标的元素,再更新数组长度。

Proxy的应用——监控对象属性的变化

const user = {
            name: "jack",
        }
        let handler={
            get(target, key, receiver) {
                console.log('get的key为 ===>' + key);
                return Reflect.get(target, key, receiver);
            },
            set(target, key, value, receiver){
                console.log('set的key为 ===>' + key, value);
                return Reflect.set(target, key, value, receiver);
            }
        }
        let p=new Proxy(user,handler);
        console.log(p.age=10);
        // set的key为 ===>age 10
		// 10
        console.log(p);
        //Proxy {name: 'jack', age: 10}

Proxy的应用——监控嵌套对象的数据变化

let obj={name: "jack",phone:{main:10086,else:110}};
        let handler={
          get:function(obj,prop){
            console.log('proxy触发了get');
            const v = Reflect.get(obj,prop);
            if(v !== null && typeof v === 'object'){
              return new Proxy(v,handler);//代理内层
            }else{
              return v; // 返回obj[prop]
            }
          },
          set(obj,prop,value){
            console.log('proxy触发了set');
            return Reflect.set(obj,prop,value);//设置成功返回true
          }
        };
        let p=new Proxy(obj,handler);

        console.log(p.name);//会触发get方法
        //proxy触发了get
		//jack
        console.log(p.phone.main);//会先触发get方法获取p.b,然后触发返回的新代理对象的.c的set。
        //proxy触发了get
		//proxy触发了get
		//10086

   由此我们可以看到如果我们想要获取到内部N层的数据,proxy就会调用N次get。且proxy在实现监控嵌套对象仅需要一个递归即可实现。

总结Proxy和Object.defineProperty的区别

1.Proxy性能优于Object.defineProperty。 Proxy代理的是整个对象Object.defineProperty只代理对象上的某个属性,如果是多层嵌套的数据需要循环递归绑定;

2.对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到。

3.数组的某些方法(push、unshift和splice)Object.defineProperty监听不到,Proxy可以监听到。

4.Proxy在ie浏览器存在兼容性问题

本文含有隐藏内容,请 开通VIP 后查看