了解JavaScript对象的特殊属性

理解对象

面向对象最常见的方式就是类,定义一个类之后,由它创建的对象都拥有从类继承而来的方法与属性。然而 JavaScript 里面,至少在 ES6 之前是没有 class的概念的。所以它的对象与传统类的对象还是有区别的。

Js 的对象可以说是一组无序值的集合,可以包括基本类型值、引用类型值、函数

通常采用字面量的方式或者 new Object() 的方式来创建;创建时除定义的值外,还具有一些特殊值,用来定义对象的各种行为。

属性类型

JS 规定了许多属性值用于给引擎使用,但是不能直接访问他们,通常用 [[Value]] 的方式放置

数据属性

四个值,用来描述行为

  • [[Configurable]]:能否用 delete 删除某个属性,是否可以修改属性的特性,能否改为访问器属性,字面量创建的对象默认值为 true
  • [[Enumerable]]:能否通过 for-in 遍历属性名字,默认为 true
  • [[Writable]]:能否直接修改某个属性的值,默认为 true
  • [[Value]]:读数据时从这读取,写入时放在这里,默认为 undefined

Object.defineProperty()

该方法可以设置上述的特殊值,接受三个参数,参数1 为要修改的对象,参数2 为修改的对象,参数3 可以指定多个特殊值的值

  • 如果是对已有属性操作,则改变相应的特殊值就行
  • 如果没有该属性,则认为是通过该方法添加新属性,此时应该显式的定义各项值,否则就会默认为 false

看实例,允许直接通过该方法定义属性并直接指定对应的特殊值,若没指定的特殊值则按false ,要想让默认值为 true,需要用字面量或 new Object() 来创建

  • 修改 Writable 属性,在严格模式下, writable 值为false时,修改属性值会报错
var great = {}
var x = {
  name: 'Great'
}
Object.defineProperty(great,'name',{
  writable: false,//设置 name 属性值不可修改
  value: "Greatiga"
});
console.log(x.name,great.name);//Great Greatiga
x.name = "yes";
great.name = "no";
console.log(x.name,great.name);//yes Greatiga
  • 修改 Configurable 属性
var great = {}
Object.defineProperty(great,'name',{
  configurable: false,//设置 name 属性值不可删除
  value: "Greatiga"
});
console.log(great.name);//Greatiga
delete great.name;
console.log(great.name);//Greatiga

但是,Configurable 属性一旦被定义为 false,就不能再变为 true 了,同时 Enumberable 属性也不可修改

var great = {}
Object.defineProperty(great,'name',{
  configurable: false,//设置 name 属性值不可删除,
  enumerable: true,
  writable: true,
  value: "Greatiga"
});

Object.defineProperty(great,'name',{
  writable: false
});
//正常不会报错

Object.defineProperty(great,'name',{
  configurable: true
});
//报错
//Cannot redefine property: name
//   at Function.defineProperty (<anonymous>)
//   at <anonymous>:6:8

Object.defineProperty(great,'name',{
  enumerable: false
})
//报错
//Uncaught TypeError: Cannot redefine property: name
//  at Function.defineProperty (<anonymous>)
//  at <anonymous>:11:8

访问器属性

访问器属性只能通过 Object.defineProperty() 定义,通过字面量定义不是

四个值

  • [[Configurable]]:能否用 delete 删除某个属性,是否可以修改属性的特性,能否改为访问器属性,字面量创建的对象默认值为 true
  • [[Enumerable]]:能否通过 for-in 遍历属性名字,默认为 true
  • [[Get]]:读取数据时调用的函数,默认为 undefined
  • [[Set]]:写入数据时调用的函数,默认为 undefined

Object.defineProperty()

  • 但一个属性添加了 get 和 set 方法后,该属性就是一个访问器属性,读取时触发 get ,设置值时触发 set

  • set 指向了 setter方法,get 指向了 getter 方法

  • 约定属性名前面加上 _ 作为私有变量,即外部不可以直接访问,需要通过 get 与 set 来访问,(事实上也是可以直接访问的,因为都是普通变量,但是既然约定,那么我们在编写我们的对象时,就应该遵守约定,哪些可以给外部看到,哪些对于外部隐藏)

var Great = {
  _age: 20
}
Object.defineProperty(Great,"age",{
  get: function() {
    console.log('get');
    return this._age;
  },
  set: function(s) {
    console.log('set');
    this._age += s;
  }
});
Great._age = 25;//同样可以通过直接访问属性改变值,但是不要这样,要遵守规定
console.log(Great.age)//25
Great.age = 1;
console.log(Great.age)//26

getter 与 setter 不一定都要定义,只定义了 get 表示只能读,反之表示只能写

var Great = {
  _age: 20
}
Object.defineProperty(Great,"age",{
  get: function() {
    console.log('get');
    return this._age;
  }
});
Great._age = 25;
console.log(Great.age)//25
Great.age = 21;
console.log(Great.age)//25 显然上面这一步没有赋值成功,因为没有定义 set

IE8 对 Object.defineProperty() 的实现并不全面,建议不要使用在这个版本

defineGetterdefineSetter

另一种定义访问器属性的方式

例子

var Great = {
  _age: 20
}
Great.__defineGetter__("age", function() {
  console.log('get');
  return this._age;
});

同时定义多个属性

Object.defineProperties() 方法

var Great = {}
Object.defineProperties(Great,{
  name: {
    writable: true,
    enumerable: true,
    configurable: true,//允许修改另外两个属性,因为他们默认为 false
    value: 'Greatiga'
  },
  _time: {
    configurable: true,//允许修改另外两个属性,因为他们默认为 false
    value: 2020
  },
  time: {
    configurable: true,//允许修改 set 和 get,因为他们默认为 false
    get: function() {
      return this._time
    },
    set: function(time) {
      this._time = time;
    }
  }
});
Great.name = 'Link';
Great.time = 1999;
console.log(Great.name,Great.time);//Link 2020 
//很显然_time的值并未修改,因为默认都为 false,writable 为 false;所以不许修改其值

Object.defineProperties(Great,{
  name: {
    writable: false, //将此属性设为不可更改
    enumerable: false //禁止遍历
  },
  _time: {
    writable: true, //允许修改
    enumerable: true //允许遍历
  },
  time: {
    enumerable: true, //允许遍历
    set: function(time) { //重写 set 方法
      this._time = time + 10;
    }
  }
});

Great.name = "GG";
Great.time = 1999;
console.log(Great.name,Great.time);//Link 2009
//此时 _time 可以修改了,但是 name 被我们禁止修改了

警惕: 上面的例子中,如果一开始没有设置 configurable 为 true,那么后面的步骤除了修改 writable 以外,修改其他特殊属性以及重写 set 方法都会报错,因为这个 configurable 就是规定每个属性在第一次设置之后是否可以再次修改

Uncaught TypeError: Cannot redefine property: 属性名 -> 这是通常的报错信息,表示不能重新定义特殊属性

获取对象属性的特殊属性值

Object.getOwnPropertyDescriptor()

接受两个参数,参数1位对象,参数2为属性值。返回一个对象,里面包括了之前介绍的各种 property 值

var Great = {}
Object.defineProperties(Great,{
  name: {
    writable: true,
    configurable: true,
    value: 'Greatiga'
  },
  _time: {
    configurable: true,
    value: 2020
  },
  time: {
    configurable: true,
    get: function() {
      return this._time
    }
  }
});
var t1 = Object.getOwnPropertyDescriptor(Great,'name');
var t2 = Object.getOwnPropertyDescriptor(Great,'time');
console.log(t1.writable,t1.enumerable,t1.set);//true false undefined
console.log(t2.writable,t2.configurable,typeof t2.get);//undefined true "function"

总结

首先来看看数据属性与访问器属性是否可以同时定义

var test = {}
Object.defineProperty(test,'name',{
  writable: true,
  configurable: true,
  get: function() {
    return this.name;
  }
})
//报错
//Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute,

这样一看就明白了,数据属性是定义某个属性的读取写入功能的,而访问器属性则是用来间接读取写入对象中的属性

所以这很像 公有变量与私有变量,如果要在对象中定义对外开放的变量,此时可以用数据属性来规定它,如果你想定义一个不对外公开的变量,就用访问器属性规定它

本文为原创文章,若文章内容出现抄袭雷同,请联系文章发布人或者网站管理员,我们将认真核实并及时删除。 除非另有说明,否则此博客中的所有文章均根据CC BY-NC-SA 4.0许可。如需转载请标明出处,谢谢配合!

END--感谢阅读

来发表你的感想吧~

  • 湖里区的全栈哦
    • 努力向前
      回复
      小何
      哈哈,欢迎湖里区的全栈大佬!常来光顾小站哟~