第三章 数据结构:对象与数组
3.1 问题:Emily 姨妈家的猫
3.2 基本数据结构
3.2.1 属性
① 有些JavaScript 值有其他一些与其相关联的值,这些相关联的值称为属性。
② 可以采用两种方式访问属性:
a. 使用中括号 // 例:text["length"];
b. 使用点标记法 // 例:text.length;
③ 点标记法,只有在属性名是一个合法的变量名时才能使用(不包含任何空格或符号,且不以数字开头)。
④ 从null 和 undefined 中读取属性会产生错误。
⑤ 数字和布尔值都有属性,但没有任何意义。
3.2.2 对象值
① 对于大多数的值类型,若他们有属性,这些属性就是固定属性,不能进行修改。
② 对象的属性可以自由添加、删除及修改。
③ 对象的每个属性都用一个名称来标记,属性名可以是任意字符串,而不仅仅是合法的变量名。
④ =(赋值)操作符设置一个不存在的属性,将会给对象添加一个新属性。
⑤ 属性名若不是合法变量名,则不可用点标记法访问。
⑥ 创建对象时,除数字外,所有属性都需要用引号。
⑦ 操作符 in 判断一个对象是否有某个属性。 // 例:"xx" in XX;
⑧ 中括号内的部分可以为任意表达式,中括号会将表达式转化为字符串来判断是否有该属性的名称。
3.2.3 对象即集合
① 集合是指一组值,这组值中的每个值都只出现一次。
② 猫的名字作为属性名,要将一个名字添加到集合里,需要在对象里设置这个属性,同时设置属性的值。
③ 删除属性可以讲名字从集合里删除。
④ in 操作符用于判断某个名字是否存在于集合内。
例:
var set = {"Spot":true}; // 设置集合
set["White Fang"] = true; // 添加属性
delete set["Spot"]; // 删除属性
"Asoka" in set; // false 判断某个属性是否属于集合
3.2.4 易变性
① 对象的内容可以通过修改其属性来进行更改。
② 若两个数字,不管他们是否引用相同的物理位,都被认为是完全相同的数字。对象的两个引用和包含相同属性的两个不同对象是有区别的。
3.2.5 对象即集合:数组
① 对象值可以扮演很多不同的角色。集合知识其中一个角色。
② 猫问题的算法用集合比较复杂,数组更加便利。
③ 数组的length 属性表示数组里有多少值。
④ 新数组可用中括号[] 来创建:var mailArchive = ["mail one", "mail two"];
⑤ 数组的角标以0 开始。
⑥ 仅输入[] 就可以创建一个空数组。
⑦ 和对象一样,可以通过赋值属性给数组添加元素。(添加或删除元素时,数组的length 属性会自动更新。)
例:
function range(upto) {
var result = []
for (var i=0; i<=upto; i++)
result[i] = i;
return result;
}
range(4); // [0, 1, 2, 3, 4]
3.2.6 方法
① 除了length 属性以外,string 和array 还包含很多歌引用到函数值的属性。
var doh = "Doh";
typeof doh.toUpperCase; // "function"
doh.toUpperCase(); // "DOH"
② 包含函数的属性通常称为方法。
③ 每个字符串都有toLowerCase() 和 toUpperCase() 方法。
④ 数组对象的一些方法:
a. push 方法,在数组末尾插入新值。
b. pop 方法,将数组最后一个值删除并返回该值。
c. join 方法,将字符串数组转化为一个单字符串,传入的参数用于将数组的各个值连接起来。
例:
var mack = [];
mack.push("Mack");
mack.push("then");
makck.push("Knife")
mack; // ["Mack", "the", "Knife"]
mack.join(" "); // "Mack the Knife"
mack.pop(); // "Knife"
mack; // ["Mack", "the"]
3.3 解决关于Emily 姨妈家的猫的问题
3.3.1 分离段落
① split 方法:与数组的join 方法基本相反,它将一个字符串分解成一个数组,以给定的字符串作为参数来确定在什么位置分解字符串。
3.3.2 找出相关段落
① charAt 方法:用于从某个字符串获取指定的字符。 例如:x.charAt(0)给出第一个字符。
② slice 方法:用于从第一个参数所在的位置开始到第二个参数所在的位置结束拷贝出一部分字符串。例如:x.slice(0,4) // "born"
③ 如果charAt 或slice 获取不存在的字符串:
a. 若指定的位置没有字符,charAt 将返回空字符""。例如:"Pip".charAt(250); // ""
b. 若slice 指定位置没有字符,则只是将不存在的内容忽略掉。例如:"Nop".slice(1,10); // "op"
3.3.3 提取猫的名字
① 字符串的indexOf 方法:找出字符串第一次出现的位置或者截取字符串中的子串。
② 若slice 只有一个参数,它将返回从指定位置一直到字符串结束位置之间的字符串。
3.3.4 完整算法
var livingCats = {"spot": true};
for (var mail=0; mail<ARCHIVE.length; mail++) {
var paragraphs = ARCHIVE[mail].split("\n");
for (var i=0; i<paragraphs.length; i++) {
var paragraph = paragraphs[i];
if(startsWith(paragraph, "born")) {
var names = catNames(paragraph);
for (var name=0; name<names.length; name++)
livingCats[names[name]] = true;
} else if (startsWith(paragraph, "died")) {
var names = catNames(paragraph);
for (var name=0; name<names.length; name++)
delete livingCats[names[name]];
}
}
}
function startsWith(string, pattern) {
return string.slice(0, pattern.length)==pattern;
}
function catNames(paragraph) {
var colon = paragraph.indexOf(":");
return paragraph.slice(colon+2).split(", ");
}
a. 判断某只猫是否还活着:
if ("Spot" in livingCats)
print("Spot lives!");
else
print("Good old Spot, may she rest in peace.");
b. 列出所有活着的猫:
for (var cat in livingCats)
print(cat);
3.3.5 清理代码
var livingCats = {Spot: true};
for (var mail=0; mail<ARCHIVE.length; mail++) {
var paragraphs = ARCHIVE[mail].split("\n");
for (var i=0; i<paragraphs.length; i++) {
var paragraph = paragraphs[i];
if (startsWith(paragraph, "born"))
addToSet(LivingCats, catNames(paragraph));
else if (startsWith(paragraph, "died"))
removeFromSet(livingCats, catNames(paragraph));
}
}
function addToSet(set, values) {
for (var i=0; i<values.length; i++)
set[values[i]]=true;
}
function removeFromSet(set, values) {
for (var i=0; i<values.length; i++)
delete set[values[i]];
}
function startsWith(string, pattern) {
return string.slice(0, pattern.length)==pattern;
}
function catNames(paragraph) {
var colon = paragraph.indexOf(":");
return paragraph.slice(colon+2).split(", ");
}
a. addToSet 和 RemoveFromSet 为何要接收set 集合作为参数?因为通过这种方式,它就是一个更加通用的工具。
b. 即使这些函数不再被其他项目调用,这样编写也是很有用的。因为他们是“自给自足”的。不需要了解livingCats 外部变量的情况,就可以被程序读取和理解。
c. 继续将整体算法拆成不同的部分:
functionfinLivingCatskill() {
varlivingCatskill= {"Spot": true};
function handleParagraph(paragraph) {
if(startsWith(paragraph, "born"))
addToSet(LivingCats, catNames(paragraph));
else if (startsWith(paragraph, "died"))
removeFromSet(livingCats, catNames(paragraph));
}
for (var mail=0; mail<ARCHIVE.length; mail++) {
var paragraphs = ARCHIVE[mail].split("\n");
for (var i=0; i<paragraphs.length; i++)
handleParagraph(paragraphs[i]);
}
return livingCats;
}
d. 现在整个算法被封装成一个函数,这意味着它运行之后不会导致混乱(livingCats 在函数中是局部变量)。
3.3.6 日期表示
① JavaScript 提供了一种存储时间的对象,可用关键字 new 来创建。 var when = new Date(1980,1,1);
② 就像之前的花括号和分号 {}; 法,new 也是创建对象值的一种方式。它不是指定所有的属性名和值,而是用函数来创建对象。这样就可以定义一种创建对象的标准过程。这种函数称为构造函数。
new Date();
new Data(1980,1,1);
new Date(2007,2,30,8,20,30); // 2007年3月30日,8点20分30秒
注意:月份数字是从 0 到 11
③ 如果不传任何参数,就创建表示当前时间和日期的对象。
④ 参数用于请求特定的时间和日期,顺序是:年、月、日、小时、分钟、秒、毫秒。最后4个是可选的,不特别指定时默认为0。
⑤ Date 对象的方法:
a. getFullYear() setFullYear()
b. getMonth() setMonth()
c. getDate() setDate()
d. getHours() setHours()
e. getMinutes() setMinutes()
f. getSeconds() setSeconds()
g. getDay() // 获取当天是当前周的第几天
h. getTime()
⑥ 在对象内部,日期被描述成从1970年1月1日到当前时间的毫秒数。这是个非常大的数字。
⑦ Date 对象的getTimezoneOffset 函数用于找出当前时间和格林威治时间(GMT)相差多少分钟。
3.3.7 日期提取
编写一个函数extractDate ,传入一个段落,返回一个日期对象:
function extractDate(paragraph) {
function numberAt(start, length) {
return Number(paragraph.slice(start, start+length));
}
return new Date(numberAt(11,4), numberAt(8,2)-1,numberAt(5,2));
}
3.3.8 收集更多信息
从现在开始存储猫的方式就不同了。现在不仅是存储一个true到集合里而是存储一个关于猫信息的对象。
这意味着addToSet 函数和 removeFormSet 函数已经没有用处了。
① 更新代码:
function catRecord(name, birthdate, mother) {
return {name: name, birth: birthdate, mother: mother};
}
function addCats(set, names, birthdate, mother) {
for (var i=0; i<names.length; i++)
set[names[i]] = catRecord(names[i], birthdate, mother);
}
function deadCats(set, names, deathdate) {
for (var i=0; i<names.length; i++)
set[names[i]].death = deathdate;
}
② catRecord 是一个单独的函数,用于创建这些存储对象。

浙公网安备 33010602011771号