Object.defineProperty和Proxy

248 阅读4分钟

原文:github.com/mqyqingfeng…

Object.defineProperty

注意:

  • 不可以在set,get内部使用obj,name,因为这样也是执行了get操作,会造成循环调用,陷入死循环
  • 定义一个变量,将,set,get方法都代理到此变量,所以对象所指地址的值未发生变化,发生变化的是代理的value

ES5提供了Object.defineProperty方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象

基础

  1. 语法 Oject.deineProperty(obj, prop, descriptor)

  2. 参数

obj:要在其上定义属性的对象 prop:要定义或修改的属性的名称 descroptor: 将被定义或修改的属性的描述符

var obj = {};
Object.defineProperty(obj, "num", {
    value : 1,
    writable : true,
    enumerable : true,
    configurable : true
});
//  对象 obj 拥有属性 num,值为 1
  1. 第三个参数:数据描述符 存取描述符 ame | 数据描述符 | 存取描述符 |意义 -|-|-|- configurable | configurableconfigurable不可再修改除writable之外的特性,经过试验,实际情况并非如此,此时,若writable原来为true,仍然可以改为false;但是如果writable原为false,则不可再修改为true,同时该属性也能从对应的对象上被删除enumerableconfigurable | configurable |不可再修改除writable之外的特性,经过试验,实际情况并非如此,此时,若writable原来为true,仍然可以改为false;但是如果writable原为false,则不可再修改为true,同时该属性也能从对应的对象上被删除| enumerable | enumerable | enumerable |属性是否可被便利| value | valuewritablevalue | | writable | writable | |是否可写| get | | get | set | | set |

属性描述符必须是数据描述符或者存取描述符两种形式之一,不能同时是两者

可以

Object.defineProperty({}, "num", {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
});

//或者

var value = 1;
Object.defineProperty({}, "num", {
    get : function(){
      return value;
    },
    set : function(newValue){
      value = newValue;
    },
    enumerable : true,
    configurable : true
});

//但是不可以
// 报错
Object.defineProperty({}, "num", {
    value: 1,
    get: function() {
        return 1;
    }
})

此外,所有的属性描述符都是非必需的,但是descriptor这个字段是必须的,如果不进行配置,可以

var obj = Object.defineProperty({}, "num", {});
console.log(obj.num); // undefined

setters和getters

存储描述符中的get和set,这两种方法又被称作setter和getter。

由getter和setter定义的属性称作‘存取器属性’

当查询存储器属性时,调用getter方法,这个方法的返回就是属性存取表达式的值

当设置存储器属性时,调用setter方法,将赋值表达式右边的值当作参数传入setter。可以忽略setter方法的返回值

例子:数据绑定例子

function myProtype() {
    var value = 1;
    Object.defineProperty(this, 'num', {
        get: function () {
            console.log('取')
            console.log(value);
        },
        set: function (newValue) {
            console.log('存');
            value = newValue;
        }
    })
}

let obj = new myProtype();
obj.num;
obj.num = 2;
obj.num;

多个属性绑定的例子

function myProtype(obj) {
    for (let key in obj) {
        let value;
        Object.defineProperty(this, key, {
            get: function () {
                console.log('取get', value);
            },
            set: function (newValue) {
                value = newValue;
                console.log('存set', value);
            },
        });
        this[key] = obj[key];
    }
}

let obj = { age: 12, name: 'li' };
let obj2 = new myProtype(obj);
obj2.age;
obj2.age = 2;
obj2.age;

obj2.name;
obj2.name = 'liu';
obj2.name;

watch API

tips:自执行函数的好处

(function () {
  var num = 1;

  function a() {
    console.log(num);
  }

  function b() {
    console.log(num);
  }

  a();
  b();
})();

没有定义任何一个全局变量,也不用每次使用都调用外面函数获取里面的内容 同时若不考虑以上因素,可以简写为

function watch(object, name, fn){
    let value = object[name];
    Object.defineProperty(obj, name, {
        get: function(){
            return value;
        },
        set: function(newValue){
            console.log(newValue);
            value = newValue;
            fn(value);
        }
    });
}

object.defineProperty和object.creat的区别

Object.create()是继承于某个对象创建的新对象,Object.defineProperties()是对对象属性的扩展。

proxy

defineProperty只能重新定义函数的读取,但是到了ES6,提供了Proxy,可以重定义更多的行为

基础

表示代理某些操作,ES6提供Proxy构造函数,用来生成Proxy实例

  1. 语法
var proxy = new Proxy(target, handler);

new Proxy表示生成一个Proxy实例 target是要拦截的目标对象 handler参数也是一个对象,用来定制拦截行为

var proxy = new Proxy({},{
    get: function (obj, prop) {
        console.log('取操作');
        return obj[prop];
    },
    set: function (obj, prop, value) {
        console.log('赋值操作')
        obj[prop] = value;
    }
})

proxy.time = 35; // 设置 set 操作

console.log(proxy.time); // 设置 get 操作 // 35
  • 除了get和set外,proxy可以拦截13种操作,比如has,可以拦截propKey in proxy的操作,返回一个布尔值
// 使用 has 方法隐藏某些属性,不被 in 运算符发现
var handler = {
  has (target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
console.log('_prop' in proxy); // false
  • apply方法拦截函数的调用、call和apply操作:拦截Proxy实例作为函数的调用,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)

apply方法可以接受三个参数,分别是目标对象,目标对象的上下文对象(this),目标对象的参数数组

var twice = {
    //目标对象,目标对象的上下文对象,目标对象的参数数组
    apply:function (target,ctx,args) {
    //return console.log("target",target,"ctx",ctx,"args",args);
    return Reflect.apply(...arguments)*2;
}
};
function sum (left,right) {
    return left*right;
};
var proxy = new Proxy(sum,twice);
console.log("proxy1",proxy(1,2));
console.log("proxy.call",proxy.call(null,5,6));
console.log("proxy,apply",proxy.apply(null,[7,8]));
console.log("proxy,apply",Reflect.apply(proxy,null,[7,8]));
// proxy1 4
// proxy.call 60
// proxy,apply 112
// proxy,apply 112
  • ownKeys方法拦截对象自身属性的读取操作 拦截以下操作: Object.getOwnPropertyNames() Object.getOwnProppertySymbols() Object.Keys() 拦截第一个字符为下划线的属性名,不让它被for of遍历到
let target = {
  _bar: 'foo',
  _prop: 'bar',
  prop: 'baz'
};

let handler = {
  ownKeys (target) {
    return Reflect.ownKeys(target).filter(key => key[0] !== '_');
  }
};

let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
  console.log(target[key]);
}
// "baz"

总结

definePorperty

  1. 并没有修改原对象所指地址的值,修改的是被代理的值(value),这样读取都是这个值,不再读取原对象的值
  2. 不可以在内部使用obj.prop来直接修改值,这样会造成自身调用自身,造成堆栈溢出,修改的都是被代理的值(value)

Proxy

  1. 操作的是new Proxy返回的对象,而不是原对象,如使用Proxy方法实现的watch方法(上面),如果后来点击事件,是obj.value += 2;则不起效,操作的是watch返回的Proxy对象,即Obj.value += 2;