不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为0的话会被判断为 object 类型,null的二进制表示是全0,自然前三位也是0,所以执行 typeof 时会返回 "object"
对象可以通过两种形式定义:字面量 和 构造形式。两者生成的对象是一样的,唯一的区别是字面量可以添加多个键/值对,但是在构造形式中必须逐个添加属性。
函数是对象的一个子类型,JavaScript中的函数是“一等公民”,因为它们本质上和普通的对象一样(只是可以调用),所以可以像操作其他对象一样操作函数
数组也是对象的一种类型,具备一些额外的行为。数组中内容的组织方式比一般对象要稍微复杂一些。
JavaScript中还有一些对象子类型,通常被称为内置对象。
这些内置对象可以当做构造函数来使用,从而可以构造一个对应子类型的新对象。
.
操作符和 []
操作符.
语法通常被称为「属性访问」,要求属性名满足标识符的命名规范
[]
语通常被称为「键访问」,可以接受任意 UTF-8/Unicode
字符串作为属性名。
在对象中,属性名用于都是字符串。如果使用string字面量以外的其它值作为属性名,那么它首先会被转换成一个字符串。
如果试图像数组添加一个属性,但是属性看起来像一个数字,那它会变成一个数值下标(因此会修改数组的内容而不是添加一个属性)
ES5开始,所有的属性都具备了属性描述符
definedProperty()
方法来修改属性描述符。注意:把 configurable
修改成 false 是单向操作,无法撤销!除了无法修改,configurable: false
还会禁止删除这个属性。结合 writable: false
和 configurable: false
就可以创建一个真正的常量属性。(不可修改、重定义或者删除)
如果你想禁止一个对象添加新属性并且保留已有属性,可以使用 Object.preventExtensions()
,非严格模式下,创建新属性会静默失败,在严格模式下,将会抛出 TypeError
错误。
Object.seal()
会创建一个密封对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions()
并把所有现在有属性标记为 configurable: false
所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。
Object.freeze()
会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal()
并把所有 “数据访问” 属性标记为 writable: false
,这样就无法修改它们的值。
这个方法是你可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意直接属性的修改(这个对象引用的其他对象是不受影响的)
你可以 “深度冻结” 一个对象,具体方法为,首先在这个对象上调用 Object.freeze()
然后遍历它引用的所有对象并在这些对象上调用 Object.freeze()
。但是一定要小心,因为这样做有可能会在无意中冻结其它(共享)对象。
获取属性值
访问属性时,引擎实际上会调用内部的默认[[get]]操作(在设置属性值时是[[put]]),[[get]]操作会检查对象本身是否包含这个属性,如果没找到的话还会查找[[prototype]]链。
设置属性值
[[put]]被触发时,实际的行为取决于许多因素,包括对象中是否已经存在这个属性(这是最重要的因素)
如果已经存在这个属性,[[put]] 算法大致会检查下面这些内容:
TypeError
异常。如果对象中不存在这个属性,[[put]] 操作会更加复杂。
在ES5中可以使用 getter 和 setter 部分改写默认操作,但是只能引用在单个属性上,无法应用在整个对象上。
getter是一个隐藏函数,会在获取属性值时调用。
setter也是一个隐藏函数,会在设置属性值时调用。
当你给一个属性定义 getter、setter或者两者都有时,这个属性会被定义为「访问描述符」(和数据描述符相对),对于访问描述符来说,JavaScript会忽略它们的value和writable特性,取而代之的是get和set。
setter会覆盖单个属性默认的[[put]]操作。
通常来说 getter 和 setter 是成对出现的(只定义一个的话,通常会产生意料之外的行为)
in
操作符会检查属性名是否在对象及其[[prototype]]原型链中。
hasOwnProperty()
只会检查属性是否在当前对象中,不会检查[[prototype]]链。
所有普通对象都可以通过对于 Object.prototype
的委托来访问 hasOwnProperty()