Object.defineProperty和Proxy

一、Object.defineProperty()

  它可以用来给一个对象定义一个属性或者修改一个现有的属性,并且返回这个对象。

Object.defineProperty(obj,props,descripter)

   descripter: 需要定义或修改的属性描述符

Object.defineProperty(obj, 'name', {
  // configurable: false,  //默认为false,定义了属性描述符能否被修改以及该属性能否被删除
  // enumerable: false,  //默认为false,定义了该属性能否被枚举
  value: 'kongzhi',
  // writable: false,/默认为false
  // get(){},
  // set(v){}
});

   属性描述符主要分为两大类:数据描述符和存取描述符。这两个描述符只能存在一个,不能同时存在。它们共有的属性有:configurable,enumerable,数据描述符只有value和writable,存取描述符只有set和get。

小栗子:

const obj = {};
Object.defineProperty(obj, 'name', {
  // configurable: false,//默认为false
  // enumerable: false,//默认为false
  value: 'kongzhi',
  // writable: false,/默认为false
  // get(){},
  // set(v){}
});
console.log(obj.name); // 输出 kongzhi

// 改写obj.name 的值
obj.name = 111;
console.log(obj.name); // 还是打印出 kongzhi,因为writable默认为false
var o = {};

o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
  value: 1,
  writable: true,
  configurable: true,
  enumerable: true
});


// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
  value: 1,
  writable: false,
  configurable: false,
  enumerable: false
});

   注意两个例子的区别,如果直接定义o.a=1,那么默认writable,configurable,enumerable都为true,如果是通过defineProperty定义的属性值,那么这些属性值都为false.

不能监听数组的变化

  数组的这些方法是无法触发set的:push, pop, shift, unshift,splice, sort, reverse. Vue 把会修改原来数组的方法定义为变异方法 (mutation method)
  非变异方法 (non-mutating method):例如 filter, concat, slice 等,它们都不会修改原始数组,而会返回一个新的数组。
  Vue 的做法是把这些方法重写来实现数组的劫持

const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];
aryMethods.forEach((method)=> {

  // 这里是原生 Array 的原型方法
  let original = Array.prototype[method];

  // 将 push, pop 等封装好的方法定义在对象 arrayAugmentations 的属性上
  // 注意:是实例属性而非原型属性
  arrayAugmentations[method] = function () {
    console.log('我被改变啦!');

    // 调用对应的原生方法并返回结果
    return original.apply(this, arguments);
  };
});
let list = ['a', 'b', 'c'];
// 将我们要监听的数组的原型指针指向上面定义的空数组对象
// 这样就能在调用 push, pop 这些方法时走进我们刚定义的方法,多了一句 console.log
list.__proto__ = arrayAugmentations;
list.push('d');  // 我被改变啦!
// 这个 list2 是个普通的数组,所以调用 push 不会走到我们的方法里面。
let list2 = ['a', 'b', 'c'];
list2.push('d');  // 不输出内容

缺点:

 a.不能拦截数组

 b.必须挨个遍历对象的属性才能进行拦截

 c.必须递归遍历对象的属性,防止对象的属性还是对象

二、Proxy

   理解为在target对象前设置一层拦截,每次对target访问都得通过这层拦截,就可以对target进行一些改写;而handler就是一个装有改写target函数的对象,target就是需要被代理的对象。

const p = new Proxy(target,handler)

  handler是一个对象,里面的属性都是函数(一些捕捉器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 操作符的捕捉器。

其中:get() , set() , apply() , has() , contruct() ps: 用于拦截new操作

小栗子:

Proxy可以代理数组

let arr = [1,2,3]
let proxy = new Proxy(arr, {
    get (target, key, receiver) {
        console.log('get', key)
        return Reflect.get(target, key, receiver)
    },
    set (target, key, value, receiver) {
        console.log('set', key, value)
        return Reflect.set(target, key, value, receiver)
    }
})
proxy.push(4)
// 能够打印出很多内容
// get push     (寻找 proxy.push 方法)
// get length   (获取当前的 length)
// set 3 4      (设置 proxy[3] = 4)
// set length 4 (设置 proxy.length = 4)

Proxy也只代理外面一层属性,需要递归代理属性嵌套的对象属性

let obj={a:1,b:{c:2}};
let handler={
  get:function(obj,prop){
    const v = Reflect.get(obj,prop);
    return v; // 返回obj[prop]
  },
  set(obj,prop,value){
    return Reflect.set(obj,prop,value);//设置成功返回true
  }
};
let p=new Proxy(obj,handler);

p.a//会触发get方法
p.b.c//会触发get方法获取p.b,不会触发.c的set,因为c没被代理。
let obj={a:1,b:{c:2}};
let handler={
  get:function(obj,prop){
    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){
    return Reflect.set(obj,prop,value);//设置成功返回true
  }
};
let p=new Proxy(obj,handler);

p.a//会触发get方法
p.b.c//会先触发get方法获取p.b,然后触发返回的新代理对象的.c的set。

 虽然Proxy和Object.defineProperty一样都需要递归拦截属性的get,set,但是Proxy只在调用时递归,Object.defineProperty在一开始就递归,所以Proxy性能更高。

面试题:

1、什么样的 a 可以满足 (a === 1 && a === 2 && a === 3) === true 呢?(注意是 3 个 =,也就是严格相等)
解决:每次访问 a 返回的值都不一样,那么肯定会想到数据劫持(有其它解法)

let current = 0
Object.defineProperty(window, 'a', {
  get () {
    current++
    console.log(current)
    return current
  }
})
console.log(a === 1 && a === 2 && a === 3) // true

2、实现多继承

Javascript 通过原型链实现继承,正常情况一个对象(或者类)只能继承一个对象(或者类)。但通过这两个方法允许一个对象继承两个对象。

let foo = {
  foo () {
    console.log('foo')
  }
}
let bar = {
  bar () {
    console.log('bar')
  }
}
// 正常状态下,对象只能继承一个对象,要么有 foo(),要么有 bar()
let sonOfFoo = Object.create(foo);
sonOfFoo.foo();     // foo
let sonOfBar = Object.create(bar);
sonOfBar.bar();     // bar
// 黑科技开始
let sonOfFooBar = new Proxy({}, {
  get (target, key) {
    return target[key] || foo[key] || bar[key];
  }
})
// 我们创造了一个对象同时继承了两个对象,foo() 和 bar() 同时拥有
sonOfFooBar.foo();   // foo 有foo方法,继承自对象foo
sonOfFooBar.bar();   // bar 也有bar方法,继承自对象bar

总结:

1、Proxy代理了整个对象,Object.defineProperty()只代理对象上的某个属性

2、Proxy在调用时递归,Object.defineProperty()在一开始就全部递归,Proxy性能更优

3、Proxy可以代理数组,数组的一些增删改查它可以拦截掉,Object.defineProperty()不可以

4、对象新添加属性Proxy可以监听到,Object.defineProperty()不可以

5、Proxy不兼容IE,Object.defineProperty()不兼容IE8及以下

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值