重写不带defineProperty的对象getter

Overriding object getter without defineProperty

本文关键字:对象 getter defineProperty 重写      更新时间:2023-09-26

我已经使用Object.defineProperty用getter定义了一个属性,我希望能够在不需要使用defineProperty的情况下覆盖它,以防止那些将使用它但可能不知道它不是"正常"属性的同事感到困惑。我尝试过writeableconfigurable选项,但都没有成功。

var fixture = {
  name: 'foo',
  color: 'green'
};
Object.defineProperty(fixture, 'email', {
  get: function () {
    return 'test+' + Date.now() + '@gmail.com';
  },
  configurable: true,
  writeable: true
});
console.log(fixture.email); // test+<date>@gmail.com
fixture.email = 'bob'; // consumer attempts to overwrite
console.log(fixture.email); // test+<date>@gmail.com T_T
// this _will_ modify the property
Object.defineProperty(fixture, 'email', {
  value: 'overwritten'
});
console.log(fixture.email);

我尝试过可写和可配置的选项,但都没有用。

writable仅适用于数据属性,即具有value而非getter和setter的属性。

configurable只是允许删除和重新定义,而不是使属性可设置。

如果希望fixture.email = 'bob';不抛出错误,则必须在attributes对象上提供set方法。这种方法现在可以:

  • 忽略新值
  • 将新值存储在一个不同的属性或闭包变量中,这样getter就可以在随后的访问中生成它(@Icepicke在他的回答中有一些例子)
  • 将访问者属性转换回"正常"属性

最后一种可能是最简单、最有回报的选择:

var fixture = {
  name: 'foo',
  color: 'green'
};
Object.defineProperty(fixture, 'email', {
  get: function() {
    return 'test+' + Date.now() + '@gmail.com';
  },
  set: function(val) {
    Object.defineProperty(this, 'email', {
      value: val,
      writable: true
    });
  },
  configurable: true,
  enumerable: true
});
console.log(fixture.email); // test+<date>@gmail.com
fixture.email = 'bob'; // consumer attempts to overwrite
console.log(fixture.email); // bob

虽然我个人不会使用它,但我想你可以绕过这个问题,只在值特定于你的期望时设置它,如果它不是正确的值,就抛出某种错误?

当然,不利的一面是,一旦他们看到你把代码放在代码中的地方,他们也可以这样做,但我想他们那时已经不那么困惑了;)实际上,我更喜欢我在底部提供的第二个选项,它只是提供了一个额外的setEmail方法来自行设置,或者通过内部更新"DataContainer"中的props.email属性

var data = {
  foo: 'foo',
  bar: 'bar'
};
Object.defineProperty(data, 'email', {
  get: function() {
    if (typeof this._props === 'undefined') {
      this._props = {};
    }
    return this._props.email;
  },
  set: function(val) {
    if (!val || !val.private) {
      throw 'no access exception';
    }
    if (this.email === val) {
      return;
    }
    this._props.email = val.email;
  },
  configurable: false
});
console.log(data.email);
data.email = {
  private: 1,
  email: 'hey.you@somewhere.hi'
};
console.log(data.email);
try {
  data.email = 'not allowed to set';
} finally {
  console.log(data.email);
}

另一种方法可能只是将逻辑构建到类构造函数中,并在函数(或变量内部)上添加一个潜在值设置器

function DataContainer(options) {
  var createProp = function(obj, propertyName, propertyHolder, isReadOnly) {
      Object.defineProperty(obj, propertyName, {
        get: function() {
          return propertyHolder[propertyName];
        },
        set: function(val) {
          if (isReadOnly) {
            return;
          }
          propertyHolder[propertyName] = val;
        },
        configurable: false
      });
    
      if (isReadOnly) {
        obj['set' + propertyName[0].toUpperCase() + propertyName.substr(1)] = function(val) {
           //internal setter
          propertyHolder[propertyName] = val;
        };
      }
    },
    prop = {},
    fieldProp, fieldVal, ro;
  if (options && options.fields) {
    for (fieldProp in options.fields) {
      if (options.fields.hasOwnProperty(fieldProp)) {
        fieldVal = options.fields[fieldProp];
        ro = false;
        if (typeof fieldVal === 'object' && fieldVal.value) {
          ro = fieldVal.readOnly || false;
          prop[fieldProp] = fieldVal.value;
        } else {
          prop[fieldProp] = fieldVal;
        }
        createProp(this, fieldProp, prop, ro);
      }
    }
  }
}
var data = new DataContainer({
  fields: {
    foo: 'foo',
    bar: 'bar',
    email: {
      value: 'test@something.com',
      readOnly: true
    }
  }
});
console.log(data);
data.email = 'test@test.com';
console.log(data.email);
data.setEmail('test@test.com');
console.log(data.email);