美文网首页
JavaScript 的 setter、getter 和 pro

JavaScript 的 setter、getter 和 pro

作者: VioletJack | 来源:发表于2019-02-20 15:28 被阅读26次

    今天来学习下 JavaScript 的对象中的 setter、getter 和 proxy。

    对象属性值的 [[Get]] 和 [[Put]] 操作

    我们对于对象属性值的常用操作无非就是创建、修改和读取(删除操作想必都用的不多)。而对象属性值获取其实是对象属性值的 [[Get]] 操作,对象属性值的创建和修改是对象属性值的 [[Put]] 操作。

    [[Get]]

    获取对象属性值就是一次属性访问,访问具体步骤为:

    1. 在语言规范中,对象实际上是实现了 [[Get]] 操作的。对象默认内置的 [[Get]] 操作会在对象中查找是否有名称相同的属性,并返回这个属性的值。(这里其实还要注意 getter)
    2. 如果当前对象中并没有找到属性,就根据原型链向下查找。如果在原型链中找到同名属性则返回属性的值(找原型对象属性的方式还是用 [[Get]] 操作),如果找到原型链底端都没有找到则返回 undefined。

    [[Put]]

    与 [[Get]] 操作相对,[[Put]] 操作一般用于设置或创建对象的属性。但是 [[Put]] 操作要比 [[Get]] 操作更加麻烦一些:

    1. 检查对象属性是否设置了 setter,如果是就调用 setter。
    2. 检查对象属性的属性描述符中的 writable 是否为 false,如果是则无法进行修改。
    3. 检查对象中是否存在这个属性,如果存在则设置属性的值。
    4. 遍历原型链,检查对象的原型链对象中是否有这个属性。如果原型链中没有,则在对象上创建这个属性。
    5. 如果对象的原型链对象中有这个属性,就比较麻烦了。它分为三种情况:
      1. 如果在对象的原型链对象上存在同名属性,且属性标识符是可写的(writable:true),那就会在对象上创建这个属性,如此对象上的属性将屏蔽原型链对象上属性,称为屏蔽属性
      2. 如果在对象的原型链对象上存在同名属性,且属性标识符是只读的(writable:false),那么就无法继续赋值,在严格模式下还会报错。
      3. 如果在对象的原型链对象上存在同名属性,且属性是一个 setter,那么只会调用这个 setter。

    setter 和 getter

    上面提到了一些 setter 和 getter 的知识,那它们到底是什么呢?

    在 ES5 中,可以使用 getter 和 setter 改写对象属性的默认操作。getter 会在获取属性值时调用,setter 会在设置属性值时调用。getter 和 setter 都是隐藏函数。

    下面是 setter 和 getter 的两种定义方式:

    // 方式 1
    var obj = {
      get a() {
        return this.__a__ + 100
      },
      set a(val) {
        this.__a__ = val
      }
    };
    // 方式 2
    Object.defineProperty(obj, "b", {
      get: function() {
        return this.__b__ * -1
      },
      set: function (val) {
        this.__b__= val / 2
      }
    });
    // setters
    obj.a = 12
    obj.b = 20
    // getters
    console.log(obj.a) // 112
    console.log(obj.b) // -10
    
    console.log(obj) // { __a__: 12, __b__: 10 }
    

    可以看到我们传入的值分别为 12 和 20,但是在保存到对象变量前由于 setter 的计算,最后保存的值为 12 和 10。而在读取 a 和 b 时,获取到了 getter 的计算结果。

    注意:当对象属性使用访问描述符(setter & getter)后,JavaScript 将忽略 value 和 writable 属性描述符。

    var obj = {
      b: 123
    } // { b: 123 }
    
    Object.defineProperty(obj, "b", {
      get: function() {
        return this.__b__ * -1
      },
      set: function (val) {
        this.__b__= val / 2
      }
    });
    
    obj // {}
    
    Object.getOwnPropertyDescriptor(obj, "b")
    
    // {
    //   onfigurable: true,
    //   enumerable: true
    //   get: f (),
    //   set: f (val)
    // }
    

    Proxy

    Proxy 用于封装一个普通对象,并返回一个新对象。它接收两个参数,第一个参数是被代理的普通对象,第二个参数是代理行为对象。

    var obj1 = {
      a: 1
    } // { a: 1 }
    
    var obj2 = new Proxy(obj1, {
      get: function (target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
      },
      set: function (target, key, value, receiver) {
        console.log(`setting ${key}!`);
        return Reflect.set(target, key, value, receiver);
      }
    }) // Proxy { a: 1 }
    
    obj2.a
    // getting a!
    obj2.a++
    // getting a!
    // setting a!
    obj1 // { a: 2 }
    

    上面例子中使用 Proxy 代理了 obj1 对象的 setter 和 getter 行为,当 obj1 有 setter 或 getter 行为时都会先经过 proxy 中的 getter 和 setter 方法。本例中的代理方法使用 Reflect.set(...) 和 Reflect.get(...) 来设置和获取对象中的属性,所以 obj1 的属性随之改变。

    下面是 Proxy 中实例方法的整理

    • get 方法拦截属性的读取操作。
    • set 方法拦截属性的赋值操作。
    • apply 方法拦截函数的调用。
    • has 方法拦截 HasProperty 操作,即查找对象中是否有某属性。可用来隐藏一些属性不被 in 运算符发现。
    • construct 方法拦截 new 指令。即在 new 指令创建实例的时候可以对对象中的参数进行一些初始化修改操作。
    • deleteProperty方法拦截 delete 指令,可用来保护某些对象属性无法被删除。
    • defineProperty方法拦截了Object.defineProperty操作。
    • getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined
    • getPrototypeOf方法主要用来拦截获取对象原型。
    • isExtensible方法拦截Object.isExtensible操作。
    • ownKeys方法用来拦截对象自身属性的读取操作。
    • preventExtensions方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
    • setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法。

    apply方法的使用

    var target = function () { return 'I am the target'; };
    var handler = {
        apply: function () {
            return 'I am the proxy';
        }
    };
    
    var p = new Proxy(target, handler);
    
    console.log(p())
    // "I am the proxy"
    

    has方法的使用

    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
    

    construct方法的使用

    var p = new Proxy(function () {}, {
        construct: function(target, args) {
            console.log('called: ' + args.join(', '));
            return { value: args[0] * 5 + 12 };
        }
    });
    
    console.log(new p(1))
    console.log(new p(1).value)
    
    // call: 1
    // { value: 17 }
    // call: 1
    // 17
    

    代理在某些处理对象属性的场景下是非常好用的,这个之后我们可以继续探讨下~

    参考资料

    相关文章

      网友评论

          本文标题:JavaScript 的 setter、getter 和 pro

          本文链接:https://www.haomeiwen.com/subject/atifyqtx.html