JavaScript学习笔记7--对象

  |  

前言

万物皆对象


17



语法

对象可以通过两种方式来定义:声明形式和构造形式

声明形式:

1
2
3
4
var Obj = {
key: value,
....
}

构造形式:

1
2
3
var Obj = new Object();
Obj.key = value;
....

构造形式和文字形式生成的对象是一样的额,唯一的区别在于,声明形式可以直接添加多个键值对,而构造形式需要逐个添加对象属性

类型

对象是JavaScript的主要类型,除此之外JavaScript还有许多特殊的对象子类型,称之为复杂基本类型。

函数就是对象的一个子类型(可调用对象)

数组也是对象的一种类型,数组中的内容的组织方式比一般的对象要复杂一些。

内置对象

JavaScript中还有一些对象子类型,成为内置对象,包括:

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

这些内置对象看起来像是传统面向对象语言中的类,但是其实它们只是一些内置的构造函数,可以通过new操作符来调用生成一个对应子类型的新对象。

Date只有构造,没有文字形式

Object、Array、Function和RegExp无论使用文字形式还是构造形式,它们都是对象而不是字面量。

Error对象很少在代码中显示创建,一般只在抛出异常时自动创建

内容

对象的内容是由一些存储在特定命名空间的值组成的,称之为属性。

在引擎内部,对象容器只存储了属性的名称,这种名称是一种引用,指向真正的存储位置

属性可以通过”.”操作符和”[]”操作符访问。两者的区别在于点操作符要求访问的属性名满足标识符的命名规范,而”[]”操作符可以接收任意UTF-8/Unicode字符串作为属性名。

属性名永远都是字符串,如果使用string(字面量)以外的其它属性名,会被先转换为字符串。

可计算属性名

ES6标准增加了可计算属性名,可以在文字行驶中使用[]包裹一个表达式作为属性名:

1
2
3
4
5
6
var prefix = "foo";
var Obj = {
[prefix + "bar"]: "Hello";
[prefix + "baz"]: "World";
}
Obj[prefix + "bar"]; //Hello

属性与方法

如果访问的对象属性是一个函数,很多人称之为方法,但这种说法并不严谨。在JavaScript中,哪怕以文字形式声明对象的方法为一个函数,这个函数仍然不会”属于“这个对象,只能说这个对象持有一个对该函数的引用

数组

数组也支持[]访问形式,数组拥有一套更加结构话的值存储方式(不过它并不限制值的类型)。数组期望的是数值下标,也就是值的存储位置。但数组仍然是一个对象,准确来说,是一个期望属性名为数字的对象,所以我们仍然可以给数组台添加属性。

复制对象

浅复制较为简单,对于值是值复制,对于引用类型(对象,方法, 数组)来说,复制的是引用。也就是说原对象和被浅复制的新对象,保持的都是同样的引用。

如何深复制,对于一个JSON安全(可以被序列化为一个JSON字符串,并能被正确解析的对象),可以使用下面这个语法糖来进行深复制:

1
var newObj = JSON.parse(JSON.stringify(oldObj));

ES6定义了Object.assign()方法来进行对象的浅复制。Object.assign()的第一个参数为目标对象,之后可以跟一或多个源对象,方法会遍历源对象的可枚举的自有键并把它们复制(使用=)到目标对象,最后返回目标对象。

由于Object.assign()方法使用的是=赋值,所以源对象的一些特性并不会被复制到目标对象。

属性描述符

可以通过getOwnPropertyDescriptor(对象, “属性名”)来获得一个对象的特定属性的属性描述符。

创建普通对象的属性时属性描述符使用默认值,可以通过Object.defineProperty()和Object.defineProperties()方法来添加一个新属性并设置它的属性描述符或修改一个已有属性的属性描述符。

使用delete操作符来删除一个不可配置的属性时会失败。除此之外,如果对象的某个属性是某个对象的最后一个引用者,当这个属性被delete后,这个未引用的对象会被垃圾回收。

不变性

ES5可以通过多种方法来实现对象属性的不可变,但是所有的方法只能实现浅不变形,也就是说它只能保证目标对象、对象的直接属性和对象属性的引用不变,引用的对象仍可改变。

JavaScript程序中很少需要深不可变性。可能存在特殊的情况需要使用深不可变性。但根据通用的设计模式来说,如果你发现一个对象需要深不可变性,那么可能需要重新考虑程序的设计。

1.对象常量:

通过将对象属性的属性描述符的writable和configurable设置为false,即可实现一个常量属性:

1
2
3
4
5
6
var Obj = {};
Object.defineProperty(Obj, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false
})

2.禁止扩展:
如果想要禁止一个对象扩展它的属性,可以通过调用内部方法Object.preventExtensions():

1
2
3
4
var Obj = {
a: 2
}
Object.preventExtensions(Obj);

在非严格模式下扩展这个对象将失败,在严格模式下将会导致TypeError错误。

3.密封:

可以使用Object.seal()方法来创建一个密封对象,这个方式实际上会在一个现有对象上调用Object.preventExtensions()并把对象的所有属性标记为configurable: false。密封之后,对象无法扩展,且无法重新配置删除现有属性(但仍能改变属性的值)。

4.冻结:
Object.freeze()会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal()并把所有数据访问属性标记为writable: false,这样就无法修改对象属性的值。

冻结是最高级别的不可变性,他会禁止对于对象本身及其任意直接属性的修改(同样的,并不影响对象的引用对象的修改,但影响对象引用的修改)

[[Get]]

属性访问存在一个难以被注意到的细节。当我们访问一个对象的属性时,其实实在属性上实现了[[Get]]操作。对象默认的内置[[Get]]操作首先会在杜希昂中查找是否有名称相同的属性,如果有返回。如果没有,按照[[Get]]算法的定义,他将开始遍历对象的原型链,如果有,返回。如果仍没有,返回undefined。

[[Put]]

既然存在Get就会存在对应的Put操作,[[Put]]被触发时取决于许多因素:

  • 属性是否是访问器属性,是则检测setter,存在则调用setter
  • 属性数据描述符的writable是否为false,是则在非严格模式下静默失败,在严格模式下抛出TypeError错误
  • 如果都不是,则将该值设置为属性的值

上述判断都是在对象存在对应属性的情况下的。

存在性

当我们访问一个属性得到undefined的返回值时,我们怎么判断是它的值为undefined还是没有找到。或者说,我们如何判断一个属性是否存在要给对象中。

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

hasOwnProperty()方法只会检查属性是否在对象中,而并不会检查原型链。

遍历

for…in循环可以遍历对象的可枚举属性列表(包括原型链)。

对于数值索引的数组来说,可以使用标准for循环,但是它是通过遍历下标来指向值。

ES5增加了一些数组迭代器,包括forEach()、every()和some()。每种辅助迭代器都可以设置一个回调函数并把他应用在数组的每个元素上,唯一的区别就是它们对于回调函数返回值的处理方式不同。

  • forEach()会遍历数组中的所有值并忽略回调函数的返回值
  • every()会一直运行到回调函数返回false
  • some()会一直运行到回调函数返回true

ES6增加了一种用来遍历数组的for..of循环语法可以直接遍历值

文章目录
  1. 1. 语法
  2. 2. 类型
    1. 2.1. 内置对象
  3. 3. 内容
    1. 3.1. 可计算属性名
    2. 3.2. 属性与方法
    3. 3.3. 数组
    4. 3.4. 复制对象
    5. 3.5. 属性描述符
    6. 3.6. 不变性
    7. 3.7. [[Get]]
    8. 3.8. [[Put]]
    9. 3.9. 存在性
  4. 4. 遍历