前言
本篇介绍 JavaScript 中的对象。在第一篇博客中已经说到 JavaScript 是一种‘’对象模型‘’语言。所以可以这样说,对象是 JavaScript 语言的核心概念,也是最重要的数据类型。
概述
生成方法
在 JavaScript 中声称对象相当方便,直接定义一个空字典就 ok。想要添加属性或者方法的话可以在定义结束之后动态添加。注意:对象时无序的复合数据集合。
上面代码中,大括号就可以直接定义一个对象,被赋值给变量 a,所以 a 就指向一个对象。该对象为一个空对象,但是会有一些默认的方法,像 constructor 是构造方法,想要动态的添加属性和方法就是这个方法的功劳。
在这里添加了一个属性为name,那么 name 是键名(成员名称),字符串 musibii 是键值(成员的值)。键名与键值之间用冒号分隔。如果再添加一个属性,那么属性之间使用逗号分隔。
键名
对象的所有键名都是字符串(ES6又引入了 Symbol 值也可以作为键名:还没了解过),所以加不加引号都可以。如果键名是数值,会被自动转为字符串。如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。
对象的每一个键名又称为‘’属性‘’,它的键值可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为方法,调用方法和函数一样。
特别的如果属性的值指向的还是一个对象,那么就行成了链式引用。对象的属性之间用逗号分隔,最后一个属性后面可以加逗号,也可以不加。
对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有的引用。和 JavaScript 中的基本数据类型不一样,复合数据类型是传址传递。
a 和 b指向同一个对象,因此为其中任何一个变量添加属性,另一个变脸都可以读写该属性。如果取消某一个对象的引用,不会影响到其他变量。
这种引用只局限于对象,在之前的博客也提到,两个变量指向同一个原始类型(基本数据类型)的值,那么变量只是对值得拷贝(传值传递)。
属性的操作
属性的读取
读取对象的属性,有两种方法,一种是使用点运算符;另一种是使用方括号运算符。(在 python 中,字典只能通过方括号取值;对象只可以通过点运算符取值。不过可以通过自定义字典类改写 getattr 魔术方法改变。)
注意:如果使用方括号运算符,键名必须放在引号里面,否则会被当做变量处理。
var foo = 'bar';var obj = { foo: 1, bar: 2}; obj.foo // 1obj[foo]// 2
上面代码中,引用对象obj 的 foo 属性时,如果使用点运算符,foo 就是字符串;如果使用方括号运算符,但是不使用引号,那么 foo 就是一个变量,指向字符串 bar。
方括号运算符内部还可以使用表达式:
obj['hello' + 'world'] obj[3 + 3]
数字键可以不加引号,因为会自动转为字符串。
var obj = { 0.7: 'hello world'}; obj['0.7'] // 'Hello World'obj[0.7] // 'Hello World'
上面代码对象的数字键0.7加不加引号都可以,因为会自动转为字符串。
var obj = { 123: 'Hello musibii'}; obj.123 // 报错obj[123] // 'Hello musibii'
如果对数值键名123使用点运算符,会报错,使用方括号运算符才是正确的方式。
属性的赋值
点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。
JavaScript 允许属性的后绑定,也就是说可以在任意时刻新增属性,没必要在定义对象的时候就把属性全都定义好。
属性的查看
查看一个对象的所有属性使用 Object.keys 方法。
var obj = { key1: 1, key2: 2};Object.keys(obj); // [key1, key2]
属性的删除
delete 命令用于删除对象的属性,删除成功后返回 true。
var obj = { p: 1; }delete obj.p; // trueobj.p // undefinedObject.keys(obj) // []
上述代码中,delete 命令删除对象 obj 的 p 属性。删除后,再读取 p 属性就会返回 undefined,而且 Object.keys 方法的返回值也不再包括该属性。
注意:删除一个不存在的属性,delete 不会报错而是返回 true。因此不能根据 delete 命令的结果认为某个属性的存在。(那么到底哪种方式才可以证明某个属性的存在与否)
如果删除属性时返回 false那就说明该属性存在,但是不可以删除。
var obj = Object.defineProperty({}, 'p', { value: 'musibii', configurable: false}); obj.p // 'musibii'delete obj.p // false
上述代码中,通过 Object 的defineProperty方法给对象 obj创建了一个属性,属性的configurable(可配置) 的值为 false,这样的一个属性就是不可以删除的。
另外需要注意的是,delete 命令只能删除对象本身的属性,无法删除继承的属性。
可以看出虽然 delete 命令返回 true,但是删除的属性依然存在。但是如果通过 proto 删除的话就可以删除。
判断属性的存在
in 运算符用于检出对象是否包含某个属性(注意,检查的是键名,不是键值)。如果包含就返回 true,否则就返回 false。它的左边是一个字符串,表示属性名,右边则是一个对象。
var obj = {p: 1};'p' in obj //true'toString' in obj // true
拿上面删除的 constructor 来说:
in 运算符的一个问题是,它不能识别哪些属性时对象自身的,哪些属性是继承的。就像上面,对象 obj 本身并没有 toString 属性,但是 in 运算符会返回 true,因为这个属性是继承的。
这时可以通过对象的 hasOwnProperty 方法判断,是否为对象自身属性
var obj = {};if ('toString' in obj) { console.log(obj.hasOwnProperty('toString')); // fasle}
属性遍历
for...in 循环用来遍历一个对象的所有属性。
for...in 循环有两个注意点;
它遍历的是对象所有可遍历的属性,会跳过不可遍历的属性;
它不仅遍历对象自身的属性,还遍历继承的属性。
如果继承的属性是可遍历的,那么就会被 for...in 循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用 for...in 的时候,应该结合使用 hasOwnProperty 方法,在循环内部判断一下,某个属性是否为对象自身的属性。
var person = {name: '老张'};for (var key in person) { if (person.hasOwnProperty(key)) { console.log(key); } } // name
with 语句
with 语句的格式如下:
with (对象) { 语句; }
它的作用是操作同一个对象的多个属性时,提供一些书写的方便。
注意:如果 with 区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。
var obj = {};with (obj) { p1 = 4, p2 = 5} obj.p1 // undefinedp1 // 4
上面代码中,对象 obj 并没有 p1属性,对 p1赋值等于创造了一个全局变量 p1.正确的写法应该是,先定义对象 obj 的属性 p1,然后在 with 区块内操作它。
这是因为 with 区块没有改变作用域,它的内部依然是当前作用域。这造成了with 语句的一个很大的弊病,就是绑定对象不明确。
with (obj) { console.log(x); }
单纯从上面的代码块,根本无法判断 x 到底是全局变量,还是对象 obj 的一个属性。这非常不利于代码的除错和模块化,编译器也无法对这段代码进行优化,只能留到运行时判断,这就拖慢了运行速度。因此,建议不要使用 with 语句,可以考虑用一个临时变量代替 with。
with (obj1.obj2.obj3) { console.log(p1 + p2); }// 可以写为var temp = obj1.obj2.obj3;console.log(temp.p1 + temp.p2);