《JavaScript权威指南》读书笔记(9)-对象
JavaScript对象
对象是JavaScript的基本数据类型,是一个复合值,将很多值聚合在一起,可以通过名字访问这些值,可以看做是属性的无序集合,每个属性都是一个名/值对(键/值对)。属性名是字符串,可以把对象看成是从字符串到值的映射,即称为“散列”(hash)、“散列表”(hashtable)、“字典”(dictionary)、“关联数组”(associative array)。但并不是从字符串到值的映射,它具有对象的特性,可以从一个称为原型的对象继承属性。
JavaScript对象是动态的——可以新增属性也可以删除属性——常用来模拟静态对象即静态类型语言中的“结构体”,或是字符串的集合。
对象的常见用法是创建、设置、查找、删除、检测和枚举它的属性。
属性名可以是包含空字符串在内的任意字符串,但对象不能存在两个同名的属性。
每个属性有一些与之相关的值,即“属性特性”:
可写,表明是否可以设置该属性的值。
可枚举,表明是否可以通过for/in循环返回该属性。
可配置,表明是否可以删除或修改该属性。
对象还拥有三个相关的对象特性:
对象的原型(prototype)指向另外一个对象,本对象的属性继承自它的原型对象。
对象的类(class)是一个标识对象类型的字符串。
对象的扩展标记(extensible flag)指明了是否可以向该对象添加新属性。
常见术语:
内置对象:ECMAScript规范定义的对象或类。例如,数组、函数、日期和正则表达式都是内置对象。
宿主对象:JavaScript解释器所嵌入的数组环境定义的。客户端JavaScript中表示网页结构的HTMLElement对象均是宿主对象。宿主对象环境定义的方法可以当成普通JavaScript函数对象,也可以当成内置对象。
自定义对象:运行的JavaScript代码创建的对象。
自有属性:直接在对象中定义的属性。
继承属性:在对象的原型对象中定义的属性。
创建对象
对象直接量
可以通过对象直接量、关键字new和Object.create()函数来创建对象。
eg.
var point = {
x:0,
y:0
};
通过new创建对象
new运算符创建并初始化一个新对象。new后跟随一个函数调用,这样的函数称作构造函数,构造函数用以初始化一个新创建的对象。JavaScript语言核心中的原始类型都包含内置构造函数。eg.
var o = new Object();
var a = new Array();
var r = new RegExp();
原型
每一个JavaScript对象(null除外)都和另一个对象相关联,即原型,每一个对象都从原型继承属性。所有通过对象直接量创建的对象都具有同一个原型对象,并通过JavaScript代码Object.prototype获得对原型对象的引用。通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。
同使用{}创建对象一样,通过new Object()创建的对象也继承自Object.prototype。new Array()创建的对象的原型是Array.prototype()。
Object.prototype不继承任何属性,没有原型。所有的内置构造函数都具有一个继承自Object.prototype的原型。例如Date.prototype继承自Object.prototype,因此new Date()创建的Date对象的属性同时继承自Date.prototype和Object.prototype。这一系列链接的原型对象就是所谓的“原型链”。
Object.create()
Object.create()创建一个新对象,其中第一个参数是这个对象的原型。Object.create()提供第二个可选参数,暂无介绍。
eg.
var o1 = Object.create({x:1, y:2}); //创建一个继承了属性x和y的对象
var o2 = Object.create(null); //创建一个没有原型的对象
属性的查询和设置
//查询
var author = book.author;
var title = book["main title"];
//设置
book.edition = 6;
book["main title"] = "ECMAScript";
继承
JavaScript对象具有“自有属性”,也有一些属性是从原型对象继承而来的。
假设要查询对象o的属性x,如果o中不存在x,将会继续在o的原型对象中查询属性x,如果也没有,继续在这个原型对象的原型上执行查询,直到找到x或者查找到一个原型是null的对象为止,构成了一条链。
继承可以使用inherit()函数
eg.
var p = inherit(o); //p继承o和o的原型和o的原型的原型……
属性访问错误
查询一个不存在的属性并不会报错,而是会返回undefined。
如果对象不存在,那么查找这个不存在的对象的属性就会报错。
查询null/undefined值的属性也会报错。
给只读的对象重新赋值会赋值失败但在ECMAScript5严格模式之前并不会报错。
常见的避免错误的方法:
var len = book && book.subtitle && book.subtitle.length;
JavaScript中,&&运算符会先判断左值为真或假。如果左值为假,则整个表达式的值一定为假,即返回左值。如果左值为真,则将计算右值,并将其返回值作为整个表达式的计算结果。因此,上述代码可以实现book和book.subtitle的存在性判定,并且将book.subtitle.length赋值给len变量。
删除属性
delete book.author;
delete book["main title"];
delete只能删除自有属性,不能删除继承属性。(继承属性必须从定义这个属性的原型对象上删除他,而且这会影响到所有继承自这个原型的对象。)
错误示范:
a = {p:{x:1}};b = a.p;delete a.p;
执行这段代码,由于a.p仍然存在引用b,所以JavaScript并没有将a.p进行回收,所以可能导致内存泄漏。所以在销毁对象的时候,要遍历属性中的属性,依次删除,防止出现内存泄漏的情况。
检测属性
我们经常会检测集合中成员的所属关系——判断某个属性是否存在于某个对象中。可以通过in运算符、hasOwnPreperty()和propertyIsEnumerable()方法来完成这个工作,甚至仅通过属性查询也可以做到这一点。
in运算符:
var o = {x: 1};
"x" in o; //=> true
"y" in o; //=> false
"toString" in o //=> true o继承了Object的toString属性
hasOwnPreperty方法:
hasOwnPreperty()`方法会检测给定的名字是否为对象的自由属性,对于继承属性它将返回false。
eg.
o.hasOwnProperty("x"); //true
o.hasOwnProperty("toString") //false
propertyIsEnumerable是hasOwnPreperty()的增强版,只有检测到是自由属性且这个属性的可枚举性为true时他才返回true。
propertyIsEnumerable方法:
o.propertyIsEnumerable("x"); //true
o.prototype.propertyIsEnumerable("toString") //false,不可枚举
!==运算符:
var o = {x: 1 };
o.x !== undefined;
o.y !== undefined;
这种判断方法与in运算符类似,但并不等价。如果存在o.x为undefined,则in运算符判断为存在,而o.x !== undefined;将判断为false。
枚举属性
for/in循环:
for/in循环可以在循环体中遍历对象中所有可枚举的属性,包括自由属性和继承的属性,把属性名称赋值给循环变量。对象继承的内置方法是不可枚举的,但在代码中给对象添加到属性都是可枚举的(可以自行转换为不可枚举的)。
过滤继承的属性/方法:
for(p in o){
if(!o.hasOwnProperty(p)) continue; //跳过继承的属性
}
for(p in o){
if(typeof o[p] === "function") continue; //跳过方法
}
Object.keys()方法:
Object.keys()方法返回一个数组,这个数组由对象中可枚举的自有属性的名称组成。
Object.getOwnPropertyNames()方法
该方法比Object.keys()更强大,能返回所有的自有属性的名称,而不仅仅是可枚举的属性。
JavaScript中,对象的属性除了数值属性外,还存在存取器属性,即getter和setter。
属性getter和setter
JavaScript中的存取器类似于方法。eg.
var p = {
x: 1.0,
y: 1.0,
get r(){return ...},
set r(newValue){...},
get theta(){return ...}
}
属性的特性
可写、可枚举、可配置的特性。
JavaScript权威指南中,把存取器属性的getter和setter方法看成是属性的特性,并且把数据属性的值同样看做属性的特性。因此,可以认为一个属性包含一个名字和四个特性。数据属性的四个特性分别是他的值、可写性、可枚举性和可配置性。存取器不具有值特性和可写性,他们的可写性是由setter方法存在与否决定的。
获取属性的特性可以使用如下方法:
Object.getOwnPropertyDescriptor({x: 1}, "x");
设置属性的特性则需要调用Object.defineProperty()方法:
var o = {}
Object.defineProperty(o, "x", {
value: 1,
writeable: true,
enumerable: false,
configurable: true
});
Object.defineProperties({},{
x:{value: 1, writeable: true, enumerable: false, configurable: true},
y:{value: 1, writeable: true, enumerable: false, configurable: true},
r:{
get: function(){return 0;},
enumerable:true;
configurable:true
},
})
对不可扩展的对象新增属性会抛出类型错误异常,使用defineProperties()和defineProperty()方法一样会抛出错误。
- 如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性。
- 如果属性是不可配置的,则不能修改它的可配置性和可枚举性。
- 如果存取器属性是不可配置的,则不能修改其getter和setter方法,也不能将它转
换为数据属性。 - 如果数据属性是不可配置的,则不能将它转换为存取器属性。
- 如果数据属性是不可配置的,则不能将它的可写性从false修改为true,但可以从
true 修改为 false。 - 如果数据属性是不可配置且不可写的,则不能修改它的值。然而可配置但不可写属
性的值是可以修改的(实际上是先将它标记为可写的,然后修改它的值,最后转换
为不可写的)。
对象的三个属性
每一个对象都有三个属性,即原型(prototype)、类(class)和可扩展性(extensible attribute)。
原型属性
对象的原型属性是用来继承属性的。
Object.getPrototypeOf()可以查询对象的原型。
类属性
对象的类属性是一个字符串,用以表示对象的类型信息。ECMAScript5及之前都未提供设置这个属性的方法,可以使用对象默认的toString方法查询。
可扩展性
通过将对象传入Object.esExtensible()来判断对象是否可扩展。
如果想将对象转换为不可扩展的,需要调用Object.preventExtensions(),将待转换的对象作为参数传进去。一旦将对象转换为不可扩展的,就无法再将其转换为可扩展的了。但可以通过对该对象的原型进行扩展,该不可扩展的对象同样会继承这些属性。
Object.seal()和Object.preventExtensions()类似,除了能够将对象设置为不可扩展的,还可以将对象的所有自有属性都设置为不可配置的,即,该对象不可扩展,该对象的所有自由属性也不可配置或删除,但已有的可写属性依然可以设置。对于已经封闭(sealed)的对象是不能解封的。可以使用Object.isSealed()来检测对象是否封闭。
Object.freeze()将更严格的锁定对象——“冻结”(frozen)。除了将对象属性设置为不可扩展的和将其属性设置为不可配置的之外,还可以将它自有的所有数据属性设置为只读(setter方法不受影响,仍然可以通过给属性赋值调用它们)。使用Object.isFrozen()来检测对象是否冻结。
序列化对象(JSON)
对象序列化是指将对象的状态转换为字符串,也可将字符串还原为对象。
ECMAScript 5提供了内置函数JSON.stringify()和JSON.parse()用来序列化和还原JavaScript对象。这些方法都使用JSON作为数据交换格式。JSON即“JavaScript Object Notation”——JavaScript对象表示法,它的语法和JavaScript对象与数组直接量的语法非常相近。
JSON中,并不能表示JavaScript里的所有值。NaN、Infinity、-Infinity序列化的结果是null,日期对象序列化的结果是ISO格式的日期字符串,即Date.toJSON()函数的返回值。
JSON.stringify()只能序列化对象可枚举的自由属性。
JSON.stringify()和JSON.parse()都可以接收第二个可选参数,通过传入需要序列化或还原的属性列表来定制自定义的序列化或还原操作。
Object对象方法
toString方法
默认的toString方法的返回值带有的信息量很少,eg."[object Object]",大多数情况下会选择自定义toString方法(本章尚未提到如何自定义)。
toLocaleString方法
这个方法返回一个表示这个对象的本地化字符串。Object中默认的toLocaleString方法并不做任何本地化自身的操作,仅仅调用toString方法并返回对应值。而Date和Number类对toLocaleString方法做了定制,可以用它对数字、日期和时间做本地化的转换。
toJSON方法
Object.prototype实际上没有定义toJSON方法,但对于需要执行序列化的对象来说,JSON。stringify()方法会调用toJSON方法。如果在待序列化的对象存在这个方法,则调用它,返回值即是序列化的结果,而不是原始的对象。
valueOf方法
可自定义,尚未提及。

浙公网安备 33010602011771号