《You Dont Know JS上卷》(八) ---对象

84 阅读7分钟

对象

数据类型

js中一共七种数据类型:

  • string
  • number
  • boolean
  • null
  • undefined
  • symbol
  • object

其中前六种为简单数据类型,(typeof null返回object是因为typeof的判断机制的原因,不同对象在底层都表示为二进制,js中二进制的前三位为0的话会被判断为object类型,null底层二进制表示全为0,所以也会返回object),最后一种为复杂数据类型

对于function array他们都是object的子类型,具备一些额外的行为.

内置对象

js中object还有一些子类型,称为内置对象,他们有的名字看起来和简单基础类型一样:

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

这些内置对象实际上是一些内置函数,可以当作构造函数使用,从而构造出对应子类型的新对象(null和undefined没有构造函数),也就是构造形式

const str = new String('ooo')

除了使用构造函数创建之外,还可以通过文字形式:

const str = 'ooo'

在内置对象的初始化中,使用构造形式`文字形式不同的数据类型是有区别的,简单来说就是:

  • 对于String,Number,Boolea类型的数据,既可以通过构造形式也可以通过文字形式声明,其文字形式只是一个字面量,直接执行操作js会隐式将数据包装为对应的内置对象类型,
  • 对于Object,Array,Function,RegExp既可以通过构造形式也可以通过文字形式声明,其文字形式就是都是对象,不是字面量,可以直接操作
  • Date只有构造形式没有文字形式的声明
  • Error一般在抛出异常时自动创建,也可以通过构造形式创建
const str = 'kkkk'
const num = 0
const boolean = true
const date = new Date()
const reg = /[12]/
console.log(obj4 instanceof Object) // true  
console.log(str instanceof String) // false
console.log(num instanceof Number) //false
console.log(boolean instanceof Boolean) //false
console.log(date instanceof Date)//true
console.log(reg instanceof RegExp)//truestring,number,boolean由于其文字声明形式只是字面量,因此其不是对应内置对象的实例,使用instanceof返回false

内容

对象的内容是一些存储在特定命名位置的任意类型的值,称为属性.

为什么说是存储在特定命名位置呢?

因为实际上这些值并不存储在对象内部,存储在对象内部的是这些属性的名称,他们引用了真正的存储位置,

对象的属性可以通过两种方式进行访问:

  • .操作符
  • []操作符

两者的区别在于[]操作符可以动态构造出属性名

[[Get]]和[[Put]]

访问一个对象的属性如obj.a看似仅仅是在obj中查找名字为a的属性,其实并没有这么简单,在语言规范中obj.a实际上是在obj上进行 [[Get]] 操作,在访问对象属性的时候执行,过程如下:

  1. 首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性值
  2. 如果没找到就遍历可能存在的[[prototype]]链,还是没有找到就会返回undefined

对应的也有设置操作: [[Put]] ,是在设置对象属性时执行,过程如下(存在这个属性的前提下):

  1. 属性是否有访问描述符? 如果是并存在setter就调用setter(下面会说到)
  2. 属性的属性描述符中writable是否是false? 如果是,在给严格模式下静默失败,严格模式下抛出异常
  3. 如果都不是,将该值设置为属性值

在对象中不存在这个属性[[Put]]操作会更复杂,之后会在原型章节继续讨论

属性描述符

对象的每个属性都有其对应的描述符,用来表示该属性的行为,通过Object.defineProperty(obj,'属性名',{配置项})进行设置,通过Object.getOwnPropertyDescriptor()进行查看:

const obj = {
    a: 1
}
console.log(Object.getOwnPropertyDescriptor(obj,'a'))
输出:
{
    value: 1, 
    configurable: true, // 默认值
    enumerable: true, // 默认值
    writable: true, // 默认值
}
  1. writable属性

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

  2. configurable

    决定是否可以使用definedProperty修改属性描述符和是否可删除该属性

  3. enumerable

    决定改属性是否可枚举,设置为false改属性将不会出现在遍历中

访问描述符

对象默认的[[Put]],[[Get]]操作分别对应属性值的设置和获取,同时可以通过访问描述符改写这些默认操作.访问描述符有两种:

  • getter

    • 一个函数,访问属性时触发,对应[[Get]]操作,其返回值便是访问结果
  • setter

    • 一个函数,设置属性值时触发,对应[[Put]]操作,其返回值便是设置结果

访问描述符定义方式有两种:

  1. // 直接在对象内部定义,下面定义一个a属性的getter和setter
    const o = {
      get a(){  // getter
        console.log("访问了a属性")
        return 1
      },
      set a(val){ //setter
        console.log(val)
        return 4
      }
    }
    o.a  // 访问了a属性
    o.a = 8 // 8
    
  2. 通过Object.definePerproty

    Object.defineProperty(o,'a',{
      get(){
        return 8
      },
      enumerable:true,
      configurable:true,
      set(val){
        return 4
      },
      // value:3,writable: true属性不能和get,set一起设置
    })
    上述代码执行后,每次访问a属性都会返回8,每次设置a属性都会设置为4(不过这里设置为多少已经无所谓了,无论怎么设置,访问的时候总是返回8)
    

属性的不变性

当希望对象的属性是不可变的时候,可以使用如下方法,首先要说明的是所有方法创建的不变性都是浅不变性,只影响目标对象,不会对目标对象引用的其他对象产生影响

  1. 禁止修改

    使用defineProperty设置writable描述符为false

  2. 禁止扩展

    使用Object.preventExtensions可以禁止对象添加属性并且保留已有属性

    const obj = {a:1}
    Object.preventExtensions(obj)
    obj.b = 1
    objs.b // undefined
    在严格模式下会报错,在非严格模式下静默失败
    
  3. 密封

    使用Object.seal()使对象不能扩展,也不能重新配置属性描述符,或者删除现有属性,实际上该方法就是调用Object.preventExtensions方法,并且将所有属性的configurable描述符设置为false

  4. 冻结

    Object.freeze()方法可以禁止对象扩展,删除,修改,配置属性,这个方法是能用在对象上级别最高的不可变性,在vue中可以使用这种方式将不需要修改的数据源冻结,从而告诉vue,该数据不用做响应式处理,从而提高性能

属性的存在性

判断属性是否存在于对象中有一些可以直接使用方法:

  • in 该操作符会检查属性是否在对象或其原型链上
  • hasOwnProperty该方法只会检查属性是否在对象中,不会检查原型链
  • propertyIsEnumerable该方法会检查属性是否存在于对象中且其属性描述符是否为enumerable: true

属性的遍历

js中的object没有实现内置Symbol.itertor属性,因此无法使用for of直接迭代,但提供了for in方法可以直接迭代对象,需要注意的是该循环会遍历出原型链上的属性:

const obj = {
    a: 1
}
Object.prototype.b = 2for(let key in obj){
    console.log(key)
}
​
// a b  => 原型上的b属性也遍历出来了,
​
通常需要配合hasOwnProperty一起使用
for(let key in obj){
    if(obj.hasOwnProperty(key)){
        // do something
    }
}

也可以手动实现Symbol.iterator属性,返回一个带有next属性的迭代器,将每个对象直接变成可迭代对象:

const obj = {}
Object.defineProperty(obj,Symbol.iterator,{
    wirtable: false,
    configurable: true,
    enumerable: false,
    value: function() {
        let idx = 0
        let keys = Object.keys(this)
        return {
            next: () => {
                return {
                    value: this[keys[idx++]],
                    done: idx > keys.length
                }
            }
        }
    }
})
obj.a = 1
obj.b = 2
for(let value of obj11){
  console.log(value) // 1 2
}

使用for ..of结合自定义迭代器可以实现非常强大的对象操作