likes
comments
collection
share

JS-对象详细解析

作者站长头像
站长
· 阅读数 0

对象详细解析

对象

创建一个对象

大概有两种方式

  1. 文字语法

    var myObj = {
      key:value
      ...
    }
    
  2. 构造形式

    var myObj = new Object();
    myObj.key = value;
    

两种方式唯一的区别:在文字声明中可以添加多个key/value,而在构造形式中只能添加一个

因此,在JS社区中,大多数人都认为能使用文字声明就不要使用构造形式

对象的内容

对象的内容是由一些存储在特定命名位置的值组成的,我们称之为属性

在引擎内部,这些值的存储方式是多种多样的,一般不会在对象容器的内部。存储在对象容器内部的是这些属性的名称,他们就像指针一样,指向真正存储的位置

访问属性的值,我们有两种方式:.[""]

var obj = {
  a:2,
}

obj.a;
obj['a']

值得注意的一点是,使用[""]时,一定是以字符串的形式访问,并且可以接受任意UTF-8字符串作为属性名

在对象中,属性名永远都是字符串

数组

数组也支持使用[]访问,不过数组期望的是下标,也就是值存储的位置->整数

但是,数组也是对象,你仍然可以给数组添加属性

var myArr = ['foo',42,'bar'];
myArr.baz = "baz";
myArr.length; // 3
myArr.baz; // "baz"

可以看到,给数组添加属性后length并不会改变,但是我们可以访问到该属性

但是,这种语法并不提倡,数组和普通的对象都根据其对应的行为和用途进行了优化,所以最好还是只用对象存储key/value,只用数组存储index/value

值得注意的一点是:当你试图向数组添加一个属性,但是属性名看起来下一个数字,那么他就会变成一个数值下标,因此会修改数组的内容,而不是添加一个属性

复制对象

function anotherFunction(){}

var anotherObject = {
	c:true,
}

var anotherArray = [];

var myObject = {
  a:2,
  b:anotherObject,
  c:anotherArray,
  d:anotherFunction
}

两种复制:

  1. 浅拷贝,复制出的新对象的a的值会复制旧对象中a的值,也就是2,但是对于b,c,d四个属性来说,他们只是引用,他们与旧对象中的b,c,d引用的是同一个对象

    ES6中定义了Object.assign()实现浅拷贝

    var newObj = Object.assign({},myObject)
    
  2. 深拷贝,与浅拷贝不同的是,除了复制myObject之外,还会复制anotherObjectanotherArray

    var newObj = JSON.parse(JSON.stringify(someObj))
    

属性描述符

从ES5开始,所有属性都具备了属性描述符

var myObject = {
  a:2
}

Object.getOwnPropertyDescriptor(myObject,"a");
// { value: 2, writable: true, enumerable: true, configurable: true }

可以看到属性a不止有value,还多了另外三个特性:writable(可写),enumerable(可枚举),configurable(可配置)

在创建普通属性时,属性描述符会使用默认值(全都是true)。当然,我们可以使用Object.defineProperty(..),来添加一个新属性,或者修改一个已有属性

var myObject = {}
Object.defineProperty(myObject,"a",{
  value:2,
  writable:true,
  configurable:true,
  enumerable:true,
})
console.log(myObject.a) // 2
  1. writable

    决定是否可以修改属性的值

    var myObject = {}
    Object.defineProperty(myObject,"a",{
      value:2,
      writable:false, // 此处为false
      configurable:true,
      enumerable:true,
    })
    console.log(myObject.a) // 2
    myObject.a = 3;
    console.log(myObject.a) // 2 修改失败!!!
    
  2. enumerable

    这个描述符控制的是属性是否会出现在属性枚举中,比如说for..in循环中,虽然仍能正常访问他,但是已经无法在枚举中出现

    var myObject = {
      a:2,
      b:3,
      c:4
    }
    Object.defineProperty(myObject,"a",{
      value:2,
      writable:true,
      configurable:true,
      enumerable:false,
    })
    
    for (key in myObject){
      console.log(key,myObject[key])
    }
    // b 3
    // c 4
    
  3. configurable

    只要属性是可配置的,就可以使用defineProperty(..)修改属性描述符

    也就是说,configurable修改成false是单向操作,无法撤销

    除了无法修改之外,还会禁止删除这个属性

不可变性

  1. 结合writable:falseconfigurable:false就可以创建一个真正的常量属性

  2. 可以使用Object.preventExtensions(..)禁止一个对象添加新属性并且保留已有属性

  3. 可以使用Object.seal(..)创建一个密封对象,会在一个现有对象上调用Object.preventExtensions,并且把所有属性标记为configurable:false,即不能添加新属性也不能重新配置或者修改现有属性(但是可以修改属性的值

  4. 可以使用Object.freeze(..)冻结一个对象,堆在seal的基础上再把所有属性的writable标记为false,这样就无法修改属性的值了

    但是,这个只是禁止对于对象本身及其任意直接属性的修改(你仍然可以修改对象引用的其他对象

Getter&Setter

在ES5中,可以使用getter和setter部分改写默认操作,但是只能应用在某个属性上,无法应用在整个对象上

对于访问描述符来说,JS会忽略他们的value和writable,取而代之的是关心getset

var myObject = {
  get a(){
    return this._a_;
  },
  set a(val){
    this._a_ = val*2;
  },
  get b(){
    return this._a_*2;
  }
}

myObject.a = 3
console.log(myObject.a); // 6
console.log(myObject.b); // 12

可以看到上面的代码中运行结果与我们平时的不太一样,我们设置了a=3,却获取到了a=6,这是因为我们在set中,定义了this._a_ = val*2