Vue响应式原理剖析

676 阅读4分钟

响应式原理是Vue的核心思想,vue2.x使用的是Object.defineProperty(),3.x使用了Proxy代理。本文主要描述了defineProperty和Proxy的使用原理和特点。

defineProperty

名词解释

define: 定义
property: 属性
defineProperty: 定义属性的方法

用法

Object.defineProperty(obj, prop, description)

function defineProperty(){
  const _obj = {};
  Object.defineProperty(_obj, 'a', {
    value: 22
  })

  return _obj;

}

const obj = defineProperty()
console.log(obj);

function defineProperty2(){
  const _obj = {};
  Object.defineProperties(_obj, {
    a:{
      value: 22222
    },
    b:{
      value: 666
    }
  })

  return _obj;

}

const obj = defineProperty2()

直接使用value定义的属性,不可修改 不可枚举 不可删除

function defineProperty2(){
  const _obj = {};
  Object.defineProperties(_obj, {
    a:{
      value: 1
    },
    b:{
      value:2
    }
  })

  return _obj;

}

const obj = defineProperty2()

obj.a = 555;
console.log(obj);  //不可修改


for(var k in obj){
  console.log(obj[k]);   //不可枚举
}

delete obj.a;   //不可删除
console.log(obj);

defineProperty的配置项

function defineProperty2(){
  const _obj = {};
  Object.defineProperties(_obj, {
    a:{
      value: 1,
      writable: true, //默认为false
      enumerable: true, //默认false
      configurable: true //默认false  是否可删除
    },
    b:{
      value:2
    }
  })
  return _obj;
}

const obj = defineProperty2()

obj.a = 555;
console.log('修改后', obj);  //不可修改


for(var k in obj){
  console.log('枚举', obj[k]);   //不可枚举
}

delete obj.a;   //不可删除
console.log('删除后', obj);

defineProperty的set和get

function defineProperty(){
  const _obj = {};

  var a =1;

  Object.defineProperties(_obj, {
    a:{
      get(){
        console.log('get', a);
        
        return a;
      },
      set(newVal){
        
        a = newVal;
        console.log('set', a);
      }
    }
  })

  return _obj;
}


const ee = defineProperty()

注意:set get不能和valve writable一起用,一般都像上面这样,将值从外面复用进来,里面只写set get !

使用案例一:利用property实现一个对对象赋值,就可以添加到数组中的方法

function dataArray(){
  var _val = null,
      _arr = [];
  Object.defineProperty(this, 'val', {
    get(){
      return _val;
    },

    set(newVal){
      _val = newVal;
      _arr.push({'val': _val})
    }
  });

  this.getArr = ()=>{
    return _arr;
  }
}


var arr = new dataArray();
arr.val =1111;
arr.val =22222;
console.log(arr.getArr());

defineProperty的特点

1.defineProperty 是js比较底层的能力 官方应该是不太建议去使用的,之所以有它是为了弥补js在枚举,可写等的缺陷 对属性的控制不够彻底;
2.proxy是大的方向
3.defineProperty还有一个类似于bug的特点 就是:set get不能和valve writable一起用

案例二:demo-用defineProperty实现一个计算器

github.com/lijieJack/C…

defineProperty 数据劫持 给对象扩展属性 属性进行设置

proxy

proxy对对象、数组、方法的代理

// let obj = new Proxy(target, handler)

// target 目标对象   要处理的对象
// handler 容器  无数可以处理对象属性的方法


// 作用: 自定义对象属性的获取 赋值  枚举  函数调用等功能

const target = {
  a:1,
  b: 2
}

let proxy = new Proxy(target, {
  get(target, prop){
   console.log('this property:' + target[prop]);
   return target[prop];
  },

  set(target, prop, value){
    target[prop] = value;
    console.log('set ', target[prop]);
  }
})

console.log(target.a);
console.log(proxy.a);


proxy.b =3



// proxy可以代理 对象   数组   方法

// 代理数组
const arr = [
  {name: 'sss', age:18},
  {name: 'sss', age:18},
  {name: 'sss', age:18},
  {name: 'sss', age:18}
]

const arrProxy = new Proxy(arr, {
  get(arr, prop){
    console.log('prop:',prop);
    
    return arr[prop];
  },

  set(arr, prop, value){
    arr[prop] = value;
  }
})

console.log('arrProxy[1]', arrProxy[5]);
arrProxy[2] = {bb:111}
console.log('arrProxy', arrProxy, 'arr', arr);


//代理方法
function func (){
  console.log(111);
  
}
func.a = 888;

const funcProxy = new Proxy(func,{
  get(func, prop){
    return func[prop]
  },
  set(func, prop, value){
    func[prop] = value
  }
})

console.log(funcProxy.a);
funcProxy.a = 7777
console.log(funcProxy.a);
console.log('func', func, 'funcProxy', funcProxy);

Proxy实现的剖析

Proxy是浏览器基本的api实现,已有很好的兼容性。目前主要IE不支持,我们使用Object.defineProiperty() 来实现一个Proxy

//手动实现一个Proxy
// Proxy 是浏览器自带的api功能  当浏览器不支持时 如IE, 可以自己手动构建实现一个

function myProxy(target, handle) {
  const _target = deepClone(target);

  Object.keys(_target).forEach((key)=>{
    console.log('key', key);
    
    Object.defineProperty(_target, key, {

      get(){
        console.log('defineProperty get:', key);
        return handle.get && handle.get(target, key);
      },
      set(newVal){
        handle.set && handle.set(target, key, newVal);
      }
    })

  })
  return _target;


  // 深拷贝
  function deepClone(org, tar){
    tar = tar || {};
    for(let key in org){

      if(org.hasOwnProperty(key)){
        if( typeof org[key] === 'object' && org[key]!==null ){
          if(Object.prototype.toString.call(org[key]) === '[object Array]'){
            //是数组
            tar[key] = [];
          }else{
            tar[key] = {};
          }
          deepClone(org[key], tar[key])
        }else{
          tar[key] = org[key]
        }
      }

    }

    return tar;
  }
}



const target = {
  a: {
    s:333
  },
  b: 2
}

const proxyTarget = new myProxy(target, {
  
  
  get(target, prop){
    console.log('proxy target:',target, prop);
    console.log(222222222);
    
    return target[prop]
  },
  set(target, prop, value){
    target[prop] = value;
  }
})

Proxy其他可操作的方法

除了get set 还有其他如:has,deleteProperty等共十四种操作对象的方法。

defineProperty 与 Proxy的对比:

defineProperty原则上是给对象添加属性时使用的,在修改数组的长度,用索引去设置元素的值的时候 ,数组的push pop时 是无法触发defineProperty的set方法的。vue2.0对数组的操作是vue自己重新写的,而不是原生写的,这样导致vue2.0的代码很重。
Proxy 没有这个问题,功能更加强大。
defineProperty是拦截
Proxy是代理

为什么在vue2.0没有使用proxy?

Proxy是ES6的语法 是一个构造函数,当时考虑到兼容性 在2.x版本就没用Proxy 3.0时 es6的支持更好,Vue在代码的编译过程中做了代码的转换,proxy转化为DefineProperty,解决兼容问题。Proxy自身支持IE9以上。

在2.x中data:

data(){
    return {
        
    }
}

设计为函数返回值的形式是为了: 1.防止引用的时候被篡改
2.直接返回一个对象 这个对象直接用来做defineProperty处理

3.x中

data:{
    
}

是用来被代理操作的 不直接操作它,故不用函数返回值的形式。

使用Reflect

使用Reflect实现 函数式的方法操作 而不是,声明式的操作,会更方便维护和使用。

Reflect的好处:

1.js受原来一切都是对象影响,很多方法挂在Object(object.prototype.constract)上设计不完善.很多方法将移到Reflect。
2.Reflect有函数操作的返回值,true false更方便操作
3.兼容性:es6
4.handle中的方法都可以用Reflect实现。
5.全局可用。

下面是Reflect下面的方法:

总结

本文分别描述了Vue双向绑定的核心:Object.defineProperty()和Proxy的用法,如何手动自己去实现一个Proxy,对两者进行了对比,最好描述了使用Reflect的一些好处。

如果对你有帮助 欢迎交流~