Object.defineProperty详解

99 篇文章 12 订阅
11 篇文章 1 订阅

前置知识

对象中方法的定义

看如下代码对象中的方法定义是可以简写的

const obj = {
  fnn () {
    return 'fn-->fnn';
  },
  testOne: function () {
    return 'fn-->testOne';
  },
  testTwo: () => {
    return 'fn-->testTwo';
  },
  async fnns () {
    return 'fn-->fnns';
  },
  testOnes: async function () {
    return 'fn-->testOnes';
  },
  testTwos: async () => {
    return 'fn-->testTwos';
  },
  ['test' + 'three'] () {
    return `fn-->${['test' + 'three']}`;
  },
};

console.log('fnn:', obj.fnn());
console.log('testOne:', obj.testOne());
console.log('testTwo:', obj.testTwo());
console.log('fnns:', obj.fnns());
console.log('testOnes:', obj.testOnes());
console.log('testTwos:', obj.testTwos());
console.log('test' + 'three', obj['test' + 'three']());
console.log('test' + 'three', obj.testthree());

getter 与 setter

get:将对象属性绑定到查询该属性时将被调用的函数。

  • 语法:get [prop]() { ... }
  • prop: 要绑定到给定函数的属性名。

set:将对象属性绑定到要调用的函数。

  • 语法:set [prop](val) { ... }
  • prop: 要绑定到给定函数的属性名。
  • val: 用于保存尝试分配给 prop 的值的变量的一个别名。

使用示例

const obj = {
  a: '我是 a 初始的值,稍后我将改变',
  // 相当于一个普通函数
  get () {
    console.log('调用了此对象的 get 方法');
    return 'get'
  },
  // getter 绑定了 test 属性
  get testGetter () {
    return this.a
  },
  set testGetter (name) {
    this.a = name
  },
};

console.log('第一次获取 testGetter:', obj.testGetter);
obj.a = '第一次获取 testGetter 后 a 的值变成此文字'
console.log('第二次获取 testGetter:', obj.testGetter);
obj.testGetter = '这是一个新值'
console.log('第三次获取 testGetter:', obj.testGetter);

Object.defineProperty 作用

直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

语法: Object.defineProperty(obj, prop, descriptor)

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

返回值: 被传递给函数的对象。

属性描述符

  • 对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符

  • 数据描述符和存取描述符均具有以下可选键值

    • configurable:当且仅当该属性的 configurabletrue 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false
    • enumerable:当且仅当该属性的 enumerabletrue 时,该属性才能够出现在对象的枚举属性中。默认为 false
  • 数据描述符同时具有以下可选键值:

    • value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
    • writable:当且仅当该属性的 writabletrue 时,value 才能被赋值运算符改变。默认为 false
  • 存取描述符同时具有以下可选键值:

    • get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入 this 对象(由于继承关系,这里的 this
      并不一定是定义该属性的对象)。默认为 undefined
    • set:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined
  • 如果一个描述符不具有 value, writable , getset 任意一个关键字,那么它将被认为是一个数据描述符。

  • 如果一个描述符同时有(valuewritable)和(getset)关键字,将会产生一个异常。

  • 拥有布尔值的字段的默认值都是falsevaluegetset字段的默认值为undefined

  • 当描述符中省略某些字段时,这些字段将使用它们的默认值。

存储描述符示例

const obj = {
  firstName: 'A',
  lastName: 'B'
}

Object.defineProperty(obj, 'fullName', {
  // 当读取对象此属性值时自动调用, 将函数返回的值作为属性值, this 为 obj
  get () {
    return this.firstName + '-' + this.lastName
  },
  // 当修改了对象的当前属性值时自动调用, 监视当前属性值的变化, 修改相关的属性, this 为 obj
  set (value) {
    const names = value.split('-')
    this.firstName = names[0]
    this.lastName = names[1]
  }
})
console.log(obj.fullName) // A-B
obj.fullName = 'w-fly'
console.log('fullName:', obj.fullName) // w-fly
console.log('firstName:', obj.firstName) // w
console.log('lastName:', obj.lastName) // fly

使用 Object.defineProperty 创建属性

如果对象中不存在指定的属性,Object.defineProperty() 就创建这个属性。

var o = {}; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
  value: 37,
  writable: true,
  enumerable: true,
  configurable: true
});

// 对象 o 拥有了属性 a,值为 37 
console.log(o);

// 在对象中添加一个属性与存取描述符的示例
var bValue;
Object.defineProperty(o, "b", {
  get: function () {
    return bValue;
  },
  set: function (newValue) {
    bValue = newValue;
  },
  enumerable: true,
  configurable: true
});

// 对象 o 拥有了属性 b,值为 38
// o.b 的值现在总是与 bValue 相同,除非重新定义 o.b
o.b = 38;

// 数据描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
  value: 'aaa',
  get: function () {
    return 'bbb';
  }
});
// throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors

修改属性

  • 如果属性已经存在,Object.defineProperty() 将尝试根据描述符中的值以及对象当前的配置来修改这个属性。
  • 如果旧描述符将其 configurable 属性设置为 false,则该属性被认为是“不可配置的”,并且没有属性可以被改变(除了单向改变 writablefalse)。
  • 当属性不可配置时,不能在数据和访问器属性类型之间切换。
  • 当试图改变不可配置属性(除了 valuewritable 属性之外)的值时会抛出 TypeError,除非当前值和新值相同。
  • Writable 属性:当 writable 属性设置为 false 时,该属性被称为“不可写”。它不能被重新分配。

如示例所示,试图写入非可写属性不会改变它,也不会引发错误。

var o = {}; // Creates a new object

Object.defineProperty(o, 'a', {
  value: 37,
  writable: false
});

console.log(o.a); // 37
o.a = 25; // 不会报错,但是修改不会生效
console.log(o.a); // 37

Enumerableenumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

var o = {};
Object.defineProperty(o, "a", { value: 1, enumerable: true });
Object.defineProperty(o, "b", { value: 2, enumerable: false });
Object.defineProperty(o, "c", { value: 3 }); // enumerable defaults to false
o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则这个属性的 enumerable 为 true

for (var i in o) {
  // 'a' 和 'd'
  console.log(i);
}

Object.keys(o); // ["a", "d"]

o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false

Configurable 特性:configurable 特性表示对象的属性是否可以被删除,以及除 valuewritable 特性外的其他特性是否可以被修改。如果 o.aconfigurable 属性为 true ,则不会抛出任何错误,并且该属性将在最后被删除。

var o = {}
Object.defineProperty(o, 'a', {
  get: function () {
    return 1
  },
  configurable: false
})
// throws a TypeError
Object.defineProperty(o, 'a', { configurable: true })
// throws a TypeError
Object.defineProperty(o, 'a', { enumerable: true })
// throws a TypeError (set was undefined previously) 
Object.defineProperty(o, 'a', {
  set: function () {
  }
})
// throws a TypeError (even though the new get does exactly the same thing) 
Object.defineProperty(o, 'a', {
  get: function () {
    return 1
  }
})
// throws a TypeError
Object.defineProperty(o, 'a', { value: 12 })
console.log(o.a) // 1
delete o.a // false
console.log(o.a) // 1

添加多个属性和默认值:考虑特性被赋予的默认特性值非常重要,通常,使用点运算符和 Object.defineProperty() 为对象的属性赋值时,数据描述符中的属性默认值是不同的,如下例所示。

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
});

一般的 SettersGetters:下面的例子展示了如何实现一个自存档对象。 当设置temperature属性时,archive数组会获取日志条目。

function Archiver () {
  var temperature = null;
  var archive = [];
  
  Object.defineProperty(this, 'temperature', {
    get: function () {
      console.log('get!');
      return temperature;
    },
    set: function (value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });
  
  this.getArchive = function () { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wflynn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值