语法

对象有两种定义形式:声明形式和构造形式

声明形式:

1
    var obj = {}

构造形式:

1
    var obj = new Object()

类型

在JavaScript中共有6种基本类型(现在是7种)

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

还有一种symbol类型

简单基本类型并不是对象,虽然typeof null === 'object',但是这个语言的一个bug

原理是这样的,不同的对象在底层都表示为二进制,在JavaScript中二进制前3位为0的话会被判定为object类型,null的二 进制全是0,自然前三位也是0,所以执行typeof时会返回"object"

内置对象

JavaScript中还有一些对象子类型,通常被称为内置对象

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

这些对象从表现形式来说很像其他语言中的类型或者类,但在JavaScript中,只是一些内置函数,这些内置函数 可以当做构造函数来使用。

装箱和拆箱操作

把字面量转换成对象的操作称作"装箱",把对象转换成字面量的操作称作"拆箱"

“装箱”

1
2
    var obj = new String('hello')
    typeof obj // "object"

“拆箱”

1
2
3
4
    var obj = new String('hello')
    var str = obj.toString()

    typeof str // "string"

“自动装箱”

1
2
    var str = 'hello'
    str.length

属性

对象内的属性不是真正存储在对象内部,而是像指针一样,指向这些值的真正的存储位置

属性描述符

从ES5开始,所有属性都具备了属性描述符,可以使用defineProperty来显式指定一些特性

  1. writable

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

    如果writable设为false,修改属性的值在非严格模式下静默失败,严格模式下抛出TypeError

  2. configurable

    configurable决定是否可以通过defineProperty 来修改属性描述符

    如果configurable设为false,不管是否是严格模式,再次使用defineProperty都会抛出TypeError, 因此把configurable设为false是单向操作,无法撤销。

    除了无法修改,configurable设为false还会禁止删除这个属性。

  3. enumerable

    enumerable决定属性是否会出现在对象的属性枚举中,比如for..in

    如果设为false,属性可以正常访问,但不会出现在枚举中。

可计算属性名

ES6中增加了可计算属性名,可以在文字形式中使用[]包裹一个表达式当做属性名

1
2
3
4
5
6
    var prefix = 'foo'

    var myObject = {
        [prefix + 'bar']: 'hello'
        [prefix + 'baz']: 'world'
    }

关于数组

如果试图向数组添加一个属性,但是属性名"看起来"像一个数组,那它会变成数值下标,因此会修改数组的内容而不是添加一个属性

1
2
3
4
    var arr = ['hello']
    arr['1'] = 'hi'

    arr[1] // 'hi'

不变性

所有方法创建的都是浅不变性,也就是说它们只会影响目标对象的直接属性,如果目标对象引用了其他对象,其它对象的内容不受影响。

实现不变性的几种方法

  1. 对象常量

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

  2. 禁止拓展

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

  3. 密封

    Object.seal(...)会创建一个密封对象,这个方法实际上会调用Object.preventExtensions,并将现有 对象的属性标记为configurable: false

  4. 冻结

    Object.freeze(...)会创建一个冻结对象,这个方法实际上会调用Object.seal, 并将现有对象的属性标记为writable: false

[[GET]] 和 [[PUT]]

[[GET]]操作

对于属性的访问实际上是实现[[GET]]操作,默认的[[GET]]操作实际上是首先在当前对象中查找是否有名称相同的属性,如果找到就返回这个属性的值,否则 沿着原型链查找,如果还没有找到,返回undefined

[[PUT]]操作

给对象的属性赋值会触发[[PUT]]操作,如果对象已经存在这个属性,[[PUT]]算法大致会检查下面这些内容

  1. 属性是否是访问描述符,如果是并且存在setter就调用setter
  2. 属性描述符中writable是否是false,如果是,在非严格模式下静默失败,在严格模式下抛出TypeError异常
  3. 如果都不是,将该值设为属性的值

如果对象中不存在这个属性,,,之后再叙述,待补充

Getter 和 Setter

当给一个对象的属性定义getter、setter或者两者都有时,这个属性会被定义为"访问描述符",对于访问描述符来说, JavaScript会忽略它们的valuewritable特性,取而代之的是关心setget特性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    var myObject = {
        get a() {
            return 2
        }
    }

    Object.defineProperty(
        myObject,
        'b',
        {
            get: function() {
                return this.a * 2
            }
            enumerable: true
        }
    )

    myObject.a  // 2
    myObject.b  // 4

注意,由于只定义了agetter,所以对a进行赋值操作会被忽略。

属性的存在性

in

in操作符会检查属性是否在对象及其原型链中

hasOwnProperty

hasOwnProperty只会检查属性是否在本对象中

注意hasOwnProperty是存在于Object.prototype中的方法,所以通过Object.create(null)创建的对象调用会失败。

遍历

ES6中的for..of可以用来直接遍历值, for..of首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有返回值。

数组有内置的@@iterator,因此for..of可以直接应用在数组上。

1
2
3
4
5
6
7
8
    var a = [1,2,3]
    var it = a[Symbol.iterator]()

    it.next() // {value: 1, done: false}
    it.next() // {value: 2, done: false}
    it.next() // {value: 3, done: false}
    it.next() // {value: undefined, done: true}
    it.next() // {value: undefined, done: true}

因此我们我们可以自己定义迭代器,只要迭代器拥有next方法,并且返回值符合{value: ..., done: true/false}规范,就可以使用 for..of来迭代遍历。