JavaScript 算法和数据结构之——基础JavaScript 笔记
做整理是为了知识更加系统一些,遂记录
参考资料
js基础算法
JavaScript 字符串
可以用单引号或双引号
查找字符串长度
.length
空格符也会计算在内
使用方括号查找字符串中的第一个字符
方括号表示法(Bracket notation)是一种在字符串中的特定 index(索引)处获取字符的方法
xxx[0]
获取字符串第一个字符
字符串的不变性
字符串要变整个改掉,不能用xxx[0]
这样改
方括号查找字符串中的第 N 个字符
xxx[N-1]
即可
方括号查找字符串中的最后一个字符
xxx[xxx.length-1]
方括号查找字符串中的倒数第 N 个字符
xxx[xxx.length-N]
填词
字符串变量:myNoun
、myAdjective
、myVerb
和 myAdverb
字符串 const wordBlanks = "The"+" "+myAdjective+" "+myNoun+" "+myVerb+" "+myAdverb;
数组
js数组将多个值存储在一个变量中
const myArray = ["hi",3];
将一个数组嵌套在另一个数组中
const teams = [["Bulls", 23], ["White Sox", 45]];
也叫做多维数组(multi-dimensional array)
通过索引访问数组中的数据
和字符串时一样
通过索引修改数组中的数据
const myArray = [18, 64, 99];
myArray[0] = 45;
修改索引0的值
使用索引访问多维数组
可以把多维数组看作成是数组中的数组。
使用括号访问数组时,第一组括号指的是最外层(第一层)数组中的条目
通过每一对额外的括号指向里面下一层的条目
const arr = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[[10, 11, 12], 13, 14]
];
const subarray = arr[3];
const nestedSubarray = arr[3][0];
const element = arr[3][0][1];
注意:数组和方括号之间不允许加空格
使用 push 方法操作数组---数据添加到数组末尾
一个将数据添加到数组末尾的简单方法是 push()
函数
.push()
接受一个或多个参数(parameters),并把它压入到数组的末尾
const arr1 = [1, 2, 3];
arr1.push(4);
const arr2 = ["Stimpson", "J", "cat"];
arr2.push(["happy", "joy"]);
运行后arr1
值为 [1, 2, 3, 4]
,arr2
值为 ["Stimpson", "J", "cat", ["happy", "joy"]]
。
使用 pop 方法操作数组---弹出数组末尾的值并返回这个元素
.pop()
函数用来弹出一个数组末尾的值
可以把这个弹出的值赋给一个变量存储起来
换句话说就是 .pop()
函数移除数组末尾的元素并返回这个元素
数组中任何类型的元素(数值,字符串,甚至是数组)都可以被弹出来
使用 shift 方法操作数组---移除数组首项的值并返回这个元素
.shift()
,它的工作原理就像 .pop()
,但它移除的是第一个元素
const ourArray = ["Stimpson", "J", ["cat"]];
const removedFromOurArray = ourArray.shift();//注意需要定义变量
removedFromOurArray
值为 Stimpson
,ourArray
值为 ["J", ["cat"]]
使用 unshift 方法操作数组---在数组的头部添加元素
不仅可以移出,也可以 unshift
(移入)一个元素到数组的头部
const ourArray = ["Stimpson", "J", "cat"];
ourArray.shift();
ourArray.unshift("Happy");
在 shift
后ourArray
值为 ["J", "cat"]
。 在 unshift
后ourArray
值为 ["Happy", "J", "cat"]
。
购物清单---创建多位数组
创建多维数组,方法很多:
const myList = [["Chocolate Bar",15],["qingcai",3],["caihua",4],["guobaorou",10],["zhajitui",5]];
const myList = [];
myList.push(["Chocolate Bar",15]);
myList.push(["qingcai",3]);
myList.push(["caihua",4]);
myList.push(["guobaorou",10]);
myList.push(["zhajitui",5]);
函数
用函数编写可复用代码
可以把代码的重复部分抽取出来,放到一个函数 (functions)中
function functionName() {
console.log("Hello World");
}
每次调用functionName
时,都会运行大括号内的内容
将值传递给带有参数的函数
函数的参数 (parameters)在函数调用中充当传入函数的输入占位符(也叫形参)
function testFun(param1, param2) {
console.log(param1, param2);
}
调用函数的时候如果没有输入两个参数则会输出NaN
使用 return 给函数返回值
我们可以通过函数的参数(arguments)把值传入函数, 也可以使用 return
语句把数据从一个函数中传出来
function plusThree(num) {
return num + 3;
}
const answer = plusThree(5);
全局作用域和函数
作用域涉及到变量的作用范围
在函数外定义的变量具有 全局 作用域
也就是具有全局作用域的变量可以在代码的任何地方被调用
未使用 let
或 const
关键字声明的变量会在 global
范围内自动创建
当在代码其他地方无意间定义了一个变量,刚好变量名与全局变量相同,这时会产生意想不到的后果
你应该总是用 let
或 const
声明你的变量
const myGlobal=10;
function fun1() {
oopsGlobal=5;
}
fun1();
function fun2() {
let output = "";
if (typeof myGlobal != "undefined") {
output += "myGlobal: " + myGlobal;
}
if (typeof oopsGlobal != "undefined") {
output += " oopsGlobal: " + oopsGlobal;
}
console.log(output);
};
fun2()
输出myGlobal: 10 oopsGlobal: 5
局部作用域和函数
在一个函数内声明的变量,以及该函数的参数都具有局部(local)作用域
也就是它们只在该函数内可见
function myTest() {
const loc = "foo";
console.log(loc);
}
myTest();
console.log(loc);
外面的console.log
就会抛出错误
函数中的全局作用域和局部作用域---局部变量优先级更高
一个程序中有可能具有相同名称的局部变量 和全局变量
在这种情况下,局部变量将会优先于全局变量
const someVar = "Hat";
function myFun() {
const someVar = "Head";
return someVar;
}
函数 myFun
将会返回字符串 Head
函数也可以返回 undefined---没有return时
函数一般用 return
语句来返回值,但这不是必须的
在函数没有 return
语句的情况下,当你调用它时,该函数会执行内部代码,返回的值是 undefined
。
let sum = 0;
function addSum(num) {
sum = sum + num;
}
addSum(3);
返回值为undefined
使用返回值赋值---在return时计算
可以获取函数的返回值,并将其赋值给一个变量
let processed = 0;
function processArg(num) {
return (num + 3) / 5;
}
processed=processArg(7)
队列---新条目进旧条目出
把数字添加到数组的结尾,然后移出数组的第一个元素
function nextInLine(arr, item) {
arr.push(item);
return item=arr.shift();
}
// 设置
let testArr = [1, 2, 3, 4, 5];
// 显示代码
console.log("Before: " + JSON.stringify(testArr));
console.log(nextInLine(testArr, 6));
console.log("After: " + JSON.stringify(testArr));
理解布尔值
数据类型布尔(Boolean)
布尔值只能是两个值中的一个:true
或者 false
注意: 布尔值是不带引号的。 字符串 "true"
和 "false"
不是布尔值,在 JavaScript 中也没有特殊含义。
用 if 语句来表达条件逻辑
关键字 if
告诉 JavaScript 在小括号中的条件为真的情况下去执行定义在大括号里面的代码
这种条件被称为 Boolean
条件,因为他们只可能是 true
(真)或 false
(假)
//伪代码
if(条件为真){
语句被执行
}
function trueOrFalse(wasThatTrue) {
if(wasThatTrue){
return "Yes, that was true"
}
return "No, that was false"
}
如果参数 wasThatTrue
值为 true
,返回 Yes, that was true
,否则,返回No, that was false
运算符
相等运算符---==
在 JavaScript 中,有很多相互比较的操作
所有这些操作符都返回一个 true
或 false
值
最基本的运算符是相等运算符:==
相等运算符比较两个值,如果它们是相等,返回 true
,如果它们不相等,返回 false
严格相等运算符---===
严格相等运算符(===
)是相对相等操作符(==
)的另一种比较操作符
与相等操作符转换数据两类型不同,严格相等运算符不会做类型转换
如果比较的值类型不同,那么在严格相等运算符比较下它们是不相等的,会返回 false
3 === 3 // true
3 === '3' // false
比较不同值
如果要比较的值不是同一类型,相等运算符会先执行数据类型转换,然后比较值
而严格相等运算符只比较值,不会进行数据类型转换
不等运算符---!=
不相等运算符(!=
)与相等运算符是相反的
不相等并返回 false
的地方,用相等运算符会返回 true
,反之亦然
1 != 2 // true
1 != "1" // false
1 != '1' // false
1 != true // false
0 != false // false
严格不等运算符---!==
严格不相等运算符(!==
)与全等运算符是相反的
这意味着严格不相等并返回 false
的地方,用严格相等运算符会返回 true
,反之亦然
严格不相等运算符不会转换值的数据类型
3 !== 3 // false
3 !== '3' // true
4 !== 3 // true
大于运算符--->
大于运算符(>
)来比较两个数字
如果大于运算符左边的数字大于右边的数字,将会返回 true
否则,它返回 false
5 > 3 // true
7 > '3' // true
2 > 3 // false
'1' > 9 // false
大于或等于运算符--->=
使用大于等于运算符(>=
)来比较两个数字的大小
如果大于等于运算符左边的数字比右边的数字大或者相等,会返回 true
,否则,会返回 false
与相等运算符相似,大于等于运算符在比较的时候会转换值的数据类型
6 >= 6 // true
7 >= '3' // true
2 >= 3 // false
'7' >= 9 // false
小于运算符---<
使用小于运算符(<
)来比较两个数字
如果小于运算符左边的数字比右边的数字小,它会返回 true
, 否则会返回 false
与相等运算符类似,小于运算符在做比较的时候会转换值的数据类型
2 < 5 // true
'3' < 7 // true
5 < 5 // false
3 < 2 // false
'8' < 4 // false
小于或等于运算符---<=
使用小于等于运算符(<=
)比较两个数字的大小
如果在小于等于运算符左边的数字小于或者等于右边的数字,它会返回 true
。 如果在小于等于运算符左边的数字大于右边的数字,它会返回 false
。
与相等运算符类似,小于或等于运算符会转换数据类型。
4 <= 5 // true
'7' <= 7 // true
5 <= 5 // true
3 <= 2 // false
'8' <= 4 // false
逻辑与 运算符---&&
有时你需要在一次判断中做多个操作
当且仅当运算符的左边和右边都是 true,逻辑与运算符(&&
)才会返回 true
if (num > 5) {
if (num < 10) {
return "Yes";
}
}
return "No";
比如这个嵌套可以用逻辑 and 操作符写出相同的逻辑
if (num > 5 && num < 10) {
return "Yes";
}
return "No";
逻辑或 运算符---||
只要逻辑或运算符(||
)两边的任何一个运算的结果是 true
,则返回 true
,否则,返回 false
。
if (num > 10) {
return "No";
}
if (num < 5) {
return "No";
}
return "Yes";
用逻辑或就是
if (num > 10 || num < 5) {
return "No";
}
return "Yes";
语句
else 语句
当 if
语句的条件为真,会执行大括号里的代码
那如果条件为假呢? 正常情况下什么也不会发生
使用 else
语句,可以执行当条件为假时相应的代码
if (num > 10) {
return "Bigger than 10";
} else {
return "10 or Less";
}
else if 语句
如果你有多个条件语句,你可以通过 else if
语句把 if
语句链接起来
if (num > 15) {
return "Bigger than 15";
} else if (num < 5) {
return "Smaller than 5";
} else {
return "Between 5 and 15";
}
if else 语句中的逻辑顺序
if
、else if
语句中的代码顺序是很重要的。
在条件判断语句中,代码的执行顺序是从上到下,所以你需要考虑清楚先执行哪一句,后执行哪一句。
第一个例子:
function foo(x) {
if (x < 1) {
return "Less than one";
} else if (x < 2) {
return "Less than two";
} else {
return "Greater than or equal to two";
}
}
第二个例子更改了代码的执行顺序:
function bar(x) {
if (x < 2) {
return "Less than two";
} else if (x < 1) {
return "Less than one";
} else {
return "Greater than or equal to two";
}
}
这两个函数看起来几乎一模一样,我们传一个值进去看看它们有什么区别。
foo(0)
bar(0)
foo(0)
将返回字符串 Less than one
,bar(0)
将返回字符串 Less than two
。
范围需要由小变大
多个 if else 语句
if/else
语句串联在一起可以实现复杂的逻辑。 这是多个 if
/ else if
语句串联在一起的伪代码:
if (condition1) {
statement1
} else if (condition2) {
statement2
} else if (condition3) {
statement3
. . .
} else {
statementN
}
使用 Switch 语句在多个选项中选择
如果你需要将一个值与许多选项匹配,你可以使用 switch 语句
switch
语句将值与定义各种可能的值的 case 语句比较
任何有效的 JavaScript 语句都可以在 case 块中执行,并且将从第一个匹配的 case
的值开始运行,直到遇到 break
switch (fruit) {
case "apple":
console.log("The fruit is an apple");
break;
case "orange":
console.log("The fruit is an orange");
break;
}
测试 case
值使用严格相等(===
)运算符进行比较
break
告诉 JavaScript 停止执行 switch 语句。 如果遗漏了 break
,下一个语句将会被执行
function caseInSwitch(val) {
let answer = "";
// 只修改这一行下面的代码
switch(val){
case 1:
answer="alpha";
break;
case 2:
answer="beta";
break;
case 3:
answer="gamma";
break;
case 4:
answer="delta";
break;
}
// 只修改这一行上面的代码
return answer;
}
caseInSwitch(1);
在 switch 语句中添加默认选项---default
在 switch
语句中,可能无法用 case
枚举出所有可能的值
我们可以添加 default
语句,它会在找不到相匹配的 case
语句之后执行
可以把它看作是 if/else
链中最后的那个 else
语句。
default
语句应该被放到最后
switch (num) {
case value1:
statement1;
break;
case value2:
statement2;
break;
...
default:
defaultStatement;
break;
}
下面为字符串输入时
function switchOfStuff(val) {
let answer = "";
// 只修改这一行下面的代码
switch(val){
case "a":
answer="apple";
break;
case "b":
answer="bird";
break;
case "c":
answer="cat";
break;
default:
answer="stuff";
break;
}
// 只修改这一行上面的代码
return answer;
}
switchOfStuff("a");//输出apple
在 Switch 语句添加多个相同选项
如果忘了给 switch
的每一条 case
添加 break
,那么后续的 case
会一直执行,直到遇见 break
为止
如果想为 switch
中的多个不同的输入设置相同的结果,可以这样写
let result = "";
switch (val) {
case 1:
case 2:
case 3:
result = "1, 2, or 3";
break;
case 4:
result = "4 alone";
}
这样,1、2、3 都会有相同的结果
用一个 Switch 语句来替代多个 if else 语句
如果你有多个选项需要选择,switch
语句写起来会比多个串联的 if
/else if
语句容易些
if (val === 1) {
answer = "a";
} else if (val === 2) {
answer = "b";
} else {
answer = "c";
}
可以被下面替代:
switch (val) {
case 1:
answer = "a";
break;
case 2:
answer = "b";
break;
default:
answer = "c";
}
从函数返回布尔值
所有比较操作符都会返回布尔值:要么是true
,要么是false
有时人们通过 if/else
语句来做比较,像这样。
function isEqual(a, b) {
if (a === b) {
return true;
} else {
return false;
}
}
但有更好的方式来达到相同的效果。 既然 ===
返回 true
或 false
我们可以直接返回比较结果:
function isEqual(a, b) {
return a === b;
}
函数执行到 return 语句就结束
当代码执行到 return
语句时,函数返回一个结果就结束运行了,return 后面的语句不会执行
function myFun() {
console.log("Hello");
return "World";
console.log("byebye")
}
myFun();
将在控制台中显示字符串 Hello
并返回字符串 World
。 字符串 byebye
将永远不会在控制台中显示,因为函数在 return
语句处就退出了
直接写return
出的就是undefined
,undefined
是关键字不是字符串
21 点游戏
在 21 点游戏中,玩家可以通过计算牌桌上已经发放的卡牌的高低值来让自己在游戏中保持优势。 这被称为卡片计数。
牌桌上的大值的卡牌更多,对玩家有利。 根据下面的表格,每张卡牌都被分配了一个值。 如果卡牌的值大于 0,那么玩家应该追加赌注。 如果卡牌的值为 0 或负数,玩家应该追加少许赌注甚至不追加赌注。
计数 | 卡牌 |
---|---|
+1 | 2, 3, 4, 5, 6 |
0 | 7, 8, 9 |
-1 | 10, 'J', 'Q', 'K', 'A' |
请写一个函数实现 21 点算法。 它根据参数 card
的值(见表格,可能是数字或者字符串)来递增或递减全局变量 count
。 然后函数返回一个由当前 count(计数)和 Bet
(当 count > 0 时)或 Hold
(当 count <= 0 时) 拼接的字符串。 注意 count(计数)和玩家的决定(Bet
或 Hold
)之间应该有空格。
示例输出:-3 Hold
或者 5 Bet
提示:
当卡牌为 7、8、9 时,不要把 count
值重置为 0。 不要返回一个数组。
输出结果中不要包含单引号或双引号。
let count = 0;
function cc(card) {
// 只修改这一行下面的代码
switch(card){
case 2:
case 3:
case 4:
case 5:
case 6:
count++;
break;
case 10:
case "J":
case "Q":
case "K":
case "A":
count--;
break;
}
var holdBet="Hold"
if (count>0){
holdBet="Bet"
}
return count+" "+holdBet;
// 只修改这一行上面的代码
}
cc(2); cc(3); cc(7); cc('K'); cc('A');
对象
创建 JavaScript 对象
你之前可能听过 object
这个词。
对象和 arrays
(数组) 类似,区别在于数组使用索引来访问和修改数据,而对象中的数据是通过 properties
访问的。
对象非常适合用来存储结构化数据,可以表示真实世界中的物体,比如一只猫。
样本:
const cat = {
"name": "Whiskers",
"legs": 4,
"tails": 1,
"enemies": ["Water", "Dogs"]
};
在此示例中,所有属性都存储为字符串,例如 name
、legs
和 tails
。
也可以使用数字作为属性。 甚至可以省略单字字符串属性中的引号,如下所示:
const anotherObject = {
make: "Ford",
5: "five",
"model": "focus"
};
其实,如果对象有非字符串属性的话,JavaScript 会自动将它们转为字符串
通过点号表示法访问对象属性
和访问数组类似,访问对象属性有两种方式:点号表示法(.
)和方括号表示法([]
)
如果我们已经提前知道要访问的属性名,使用点号表示法是最方便的
这里是一个用点符号(.
)读取对象属性的示例:
const myObj = {
prop1: "val1",
prop2: "val2"
};
const prop1val = myObj.prop1;
const prop2val = myObj.prop2;
prop1val
的值将为字符串 val1
,并且prop2val
的值将为字符串 val2
使用方括号表示法访问对象属性
访问对象属性的第二种方式是方括号表示法([]
)。 如果你想访问的属性名中包含空格,就必须使用方括号表示法来获取它的属性值
当然,如果属性名不包含空格,也可以使用方括号表示法
一个使用方括号表示法读取对象属性的例子:
const myObj = {
"Space Name": "Kirk",
"More Space": "Spock",
"NoSpace": "USS Enterprise"
};
myObj["Space Name"];
myObj['More Space'];
myObj["NoSpace"];
myObj["Space Name"]
将会是字符串 Kirk
,myObj['More Space']
将会是字符串 Spock
,并且myObj["NoSpace"]
将会是字符串 USS Enterprise
。
注意:如果属性名中包含空格,就必须使用引号(单引号或双引号)将它们包裹起来。
通过变量访问对象属性
对对象上使用方括号表示法,还可以访问对象上作为变量值存储的属性。
当你需要遍历对象的所有属性,或者根据一个变量的值查找对应的属性值时,这种写法尤其适用。
一个使用变量来访问属性的例子:
const dogs = {
Fido: "Mutt",
Hunter: "Doberman",
Snoopie: "Beagle"
};
const myDog = "Hunter";
const myBreed = dogs[myDog];
console.log(myBreed);
字符串 Doberman
将会出现在控制台中。
注意:我们在使用变量名访问属性时,不要使用引号引起来,因为我们使用的是 值,而不是 属性名。
更新对象属性
创建了 JavaScript 对象后,可以随时更新它的属性,就像更新任何其他变量那样
可以使用点或中括号操作符来更新
例子:
const ourDog = {
"name": "Camper",
"legs": 4,
"tails": 1,
"friends": ["everything!"]
};
既然他是一个特别愉快的狗,让我们将他的名字更改为字符串 Happy Camper
。 这有两种方式来更新对象的 name 属性: ourDog.name = "Happy Camper";
或 ourDog["name"] = "Happy Camper";
。
更新后,ourDog.name
的值就不再是 Camper
,而是 Happy Camper
。
给 JavaScript 对象添加新属性
可以像更改属性一样给 JavaScript 对象添加属性。
这里展示了如何给 ourDog
添加一个属性 bark
:
ourDog.bark = "bow-wow";
或者
ourDog["bark"] = "bow-wow";
现在,当我们执行 ourDog.bark
时,就能得到他的叫声,bow-wow
。
例如:
const ourDog = {
"name": "Camper",
"legs": 4,
"tails": 1,
"friends": ["everything!"]
};
ourDog.bark = "bow-wow";
删除对象的属性---delete
我们同样可以删除对象的属性,例如:
delete ourDog.bark;
例如:
const ourDog = {
"name": "Camper",
"legs": 4,
"tails": 1,
"friends": ["everything!"],
"bark": "bow-wow"
};
delete ourDog.bark;
在上面代码的最后一行中,ourDog
是这样的:
{
"name": "Camper",
"legs": 4,
"tails": 1,
"friends": ["everything!"]
}
使用对象进行查找
对象和字典一样,可以用来存储键/值对。
如果数据是扁平的,你可以用对象来查找你想要的值,而不是链式使用 switch
或 if/else
语句。
当你知道你的输入数据在某个范围时,这种查找方式极为有效。
示例:
const article = {
"title": "How to create objects in JavaScript",
"link": "https://www.freecodecamp.org/news/a-complete-guide-to-creating-objects-in-javascript-b0e2450655e8/",
"author": "Kaashan Hussain",
"language": "JavaScript",
"tags": "TECHNOLOGY",
"createdAt": "NOVEMBER 28, 2018"
};
const articleAuthor = article["author"];
const articleLink = article["link"];
const value = "title";
const valueLookup = article[value];
目标:把 switch 语句转化为对象 lookup
调用。 使用它来查找 val
属性的值,并赋值给 result
变量。
function phoneticLookup(val) {
let result = "";
// 只修改这一行下面的代码
const lookup={
alpha:"Adams",
bravo:"Boston",
charlie:"Chicago",
delta:"Denver",
echo:"Easy",
foxtrot:"Frank"
};
result=lookup[val];
// 只修改这一行上面的代码
return result;
}
phoneticLookup("charlie");
测试对象的属性是否存在---.hasOwnProperty()
属性(property)
要检查某个对象是否具有一个属性,可以使用 .hasOwnProperty()
方法
根据对象是否具有该属性,someObject.hasOwnProperty(someProperty)
返回 true
或 false
。
示例:
function checkForProperty(object, property) {
return object.hasOwnProperty(property);
}
checkForProperty({ top: 'hat', bottom: 'pants' }, 'top'); // true
checkForProperty({ top: 'hat', bottom: 'pants' }, 'middle'); // false
目标:修改函数 checkObj
以检查传给函数参数的对象 obj
是否包含传给函数参数的属性 checkProp
。 如果在 obj
中找到传给 checkProp
的属性,则返回该属性的值。 如果没有,则返回 Not Found
。
function checkObj(obj, checkProp) {
// 只修改这一行下面的代码
if(obj.hasOwnProperty(checkProp)){
return obj[checkProp]
}else{
return "Not Found"
}
// 只修改这一行上面的代码
}
操作复杂对象---数组对象
我们会希望将数据存储在一个灵活的数据结构(Data Structure)中
JavaScript 对象是一种灵活的数据结构。
它可以储存字符串(strings)、数字(numbers)、布尔值(booleans)、数组(arrays)、函数(functions)和对象(objects)以及这些值的任意组合。
这是一个复杂数据结构的示例:
const ourMusic = [
{
"artist": "Daft Punk",
"title": "Homework",
"release_year": 1997,
"formats": [
"CD",
"Cassette",
"LP"
],
"gold": true
}
];
这是一个包含一个对象的数组。
该对象有各种元数据(metadata)。 它也有一个嵌套的 formats
数组。
对象将数据以一种键 - 值对的形式保存。
在上面的示例中,"artist": "Daft Punk"
有一个键为 artist
值为 Daft Punk
的属性。
提示:数组中有多个 JSON 对象的时候,对象与对象之间要用逗号隔开。
访问嵌套对象
可以通过连续使用点号表示法和方括号表示法来访问对象的嵌套属性。
这是一个嵌套对象:
const ourStorage = {
"desk": {
"drawer": "stapler"
},
"cabinet": {
"top drawer": {
"folder1": "a file",
"folder2": "secrets"
},
"bottom drawer": "soda"
}
};
ourStorage.cabinet["top drawer"].folder2;
ourStorage.desk.drawer;
ourStorage.cabinet["top drawer"].folder2
将会是字符串 secrets
,ourStorage.desk.drawer
将会是字符串 stapler
。
访问嵌套数组
访问嵌套数组与访问嵌套对象类似,数组的方括号可以用来对嵌套数组进行链式访问。
下面是访问嵌套数组的例子:
const ourPets = [
{
animalType: "cat",
names: [
"Meowzer",
"Fluffy",
"Kit-Cat"
]
},
{
animalType: "dog",
names: [
"Spot",
"Bowser",
"Frankie"
]
}
];
ourPets[0].names[1];
ourPets[1].names[0];
ourPets[0].names[1]
应该是字符串 Fluffy
, 并且 ourPets[1].names[0]
应该是字符串 Spot
。
记录集合
创建一个帮助维护音乐专辑集的函数。 这个集合是一个包含多个相册的对象,这些相册也是对象。 每张专辑在集合中以唯一的 id
作为属性名来表示。 在每个专辑对象中,有各种描述专辑信息的属性。 并非所有专辑都有完整的信息。
updateRecords
函数有 4 个参数,即以下参数:
records
- 一个包含多个专辑的对象id
- 一个数字,代表records
对象中特定的专辑prop
- 一个字符串,代表相册属性名称value
- 一个字符串,包含用来更新相册属性的信息
使用下面的规则完成函数来修改传递给函数的对象。
- 你的函数必须始终返回整个
records
对象。 - 如果
value
是空字符串,从专辑里删除指定的prop
。 - 如果
prop
不是tracks
,并且value
不是一个空字符串,将value
赋给那个专辑的prop
。 - 如果
prop
是tracks
并且value
不是一个空字符串,你需要更新专辑的tracks
数组。 首先,如果专辑没有tracks
属性,赋予它一个空数组。 然后添加value
作为专辑的tracks
数组的最后一个项目。
// 设置
const recordCollection = {
2548: {
albumTitle: 'Slippery When Wet',
artist: 'Bon Jovi',
tracks: ['Let It Rock', 'You Give Love a Bad Name']
},
2468: {
albumTitle: '1999',
artist: 'Prince',
tracks: ['1999', 'Little Red Corvette']
},
1245: {
artist: 'Robert Palmer',
tracks: []
},
5439: {
albumTitle: 'ABBA Gold'
}
};
// 只修改这一行下面的代码
function updateRecords(records, id, prop, value) {
return records;
}
updateRecords(recordCollection, 5439, 'artist', 'ABBA');
原题是这样的考察的内容还是比较多的:
我能写完前三个规则
function updateRecords(records, id, prop, value) {
if(value==""){
delete records[id][prop];
};
if(prop!="tracks"&&value!=""){
records[id][prop]=value
};
if(prop="tracks"&&value!=""){
//此时出错需要用prop=="tracks",否则会出现records[id][prop]出错
//第四个也用到了.push
//没有想到.hasOwnProperty所以再次用if的时候不知道如何判断
}
return records;
}
以下是推荐方案
function updateRecords(records, id, prop, value) {
if (value === "") {
delete records[id][prop];
} else if (prop !== "tracks" && value !== "") {
records[id][prop] = value;
} else if (prop === "tracks" && value !== "") {
if (records[id].hasOwnProperty("tracks") === false) {
records[id][prop] = [];
}
records[id][prop].push(value);
}
return records;
}
短路求值
方案2
function updateRecords(records, id, prop, value) {
if (value === '') {
delete records[id][prop];
} else if (prop === "tracks") {
records[id][prop] = records[id][prop] || []; // shortcircuit evaluation
records[id][prop].push(value);
} else {
records[id][prop] = value;
}
return records;
}
短路求值(short-circuit evaluation)的逻辑。
短路求值是一种布尔逻辑运算的行为,其中只有在必要时才对表达式的第二个部分进行求值。在这段代码中,records[id][prop] || []
是一个逻辑表达式,用于判断指定的属性(prop
)是否已经存在于记录(records[id]
)中。
如果属性已经存在(值不为假值),则不执行第二个部分的求值,直接将原有的属性值赋给records[id][prop]
。这里使用了短路求值的特性,因为逻辑运算符||
在左侧的操作数为真值时,会直接返回左侧的值,而不再进行右侧表达式的求值。
如果属性不存在(值为假值,例如null
或undefined
),则执行第二个部分的求值,即创建一个空数组[]
并赋值给records[id][prop]
。这样就确保了无论属性是否存在,records[id][prop]
都会是一个数组,以便后续可以向该数组中添加值。
总之,这段代码的作用是确保在更新记录时,如果属性不存在,则创建一个空数组,并将其赋值给指定属性,以便可以向数组中添加新的值。
循环
while 循环
使用循环多次执行相同的代码。
第一种类型的循环称为 while
循环,当 while 指定的条件为真,循环才会执行,反之不执行。
const ourArray = [];
let i = 0;
while (i < 5) {
ourArray.push(i);
i++;
}
在上面的代码里,while
循环执行 5 次把 0 到 4 的数字添加到 ourArray
数组里。
for循环
JavaScript 中最常见的循环就是 for
,它可以循环指定次数。
for 循环中的可选三个表达式用分号隔开:
for (a; b; c)
,其中a
为初始化语句,b
为条件语句,c
是最终的表达式。
初始化语句只会在执行循环开始之前执行一次。 它通常用于定义和设置你的循环变量。
循环条件语句会在每一轮循环的开始前执行,只要条件判断为 true
就会继续执行循环。 当条件为 false
的时候,循环将停止执行。 这意味着,如果条件在一开始就为 false,这个循环将不会执行。
终止循环表达式在每次循环迭代结束, 在下一个条件检查之前时执行,通常用来递增或递减循环计数。
在下面的例子中,先初始化 i = 0
,条件 i < 5
为 true 时,进入循环。 每次循环后 i
的值增加 1
,然后执行终止循环条件表达式 i++
。
const ourArray = [];
for (let i = 0; i < 5; i++) {
ourArray.push(i);
}
ourArray
现在的值为 [0, 1, 2, 3, 4]
。
使用 For 循环遍历数组的奇数
对于循环,一次不必递增一个。 通过更改我们的 final-expression
,我们可以用偶数来计数。
初始化 i = 0
,当 i < 10
的时候继续循环。 i += 2
让 i
每次循环之后增加 2。
const ourArray = [];
for (let i = 0; i < 10; i += 2) {
ourArray.push(i);
}
ourArray
现在将包含 [0, 2, 4, 6, 8]
。 改变计数器(initialization
) ,这样我们可以用奇数来递增。
使用 For 循环反向遍历数组
只要我们定义好合适的条件,for 循环也可以反向遍历。
为了让每次递减 2,我们需要改变 initialization、condition 和 final-expression。
设置 i = 10
,并且当 i > 0
的时候才继续循环。 我们使用 i -= 2
来让 i
每次循环递减 2。
const ourArray = [];
for (let i = 10; i > 0; i -= 2) {
ourArray.push(i);
}
ourArray
现在将包含 [10, 8, 6, 4, 2]
。
当我们改变初始值和最后的表达式,这样我们就可以按照奇数从后往前两两倒着数。
使用 For 循环遍历数组
JavaScript 中的一个常见任务是遍历数组的内容。 一种方法是使用 for
循环。 下面的代码将输出数组 arr
的每个元素到控制台:
const arr = [10, 9, 8, 7, 6];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
记住数组的索引从零开始的,这意味着数组的最后一个元素的下标是:length - 1
(数组的长度 -1)。 我们这个循环的条件是 i < arr.length
,当 i
的值为 length
的时候循环就停止了。 在这个例子中,最后一个循环是 i === 4
,也就是说,当 i
的值等于 arr.length - 1
时,结果输出 6
。 然后 i
增加到 5
,循环会终止,因为 i < arr.length
是 false
。
循环嵌套
如果我们有一个二维数组,可以使用相同的逻辑,先遍历外面的数组,再遍历里面的子数组。 下面是一个例子:
const arr = [
[1, 2], [3, 4], [5, 6]
];
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr[i].length; j++) {
console.log(arr[i][j]);
}
}
这里一次输出了 arr
中的每个子元素。 提示,对于内部循环,我们可以通过 arr[i]
的 .length
来获得子数组的长度,因为 arr[i]
本身就是一个数组。
function multiplyAll(arr) {
let product = 1;
// 只修改这一行下面的代码
for(let i=0;i<arr.length;i++){
for(let j=0;j<arr[i].length;j++){
product*=arr[i][j];
}
}
// 只修改这一行上面的代码
return product;
}
multiplyAll([[1, 2], [3, 4], [5, 6, 7]]);
注意:在function中使用的是arr.length不要错写成multiplyAll.length
do...while 循环
do...while
循环,是因为不论什么情况
它都会首先 do
(运行)循环里的第一部分代码,然后 while
(当)规定的条件被评估为 true
(真)的时候,它会继续运行循环。
const ourArray = [];
let i = 0;
do {
ourArray.push(i);
i++;
} while (i < 5);
上面的示例行为类似于其他类型的循环,由此产生的数组将看起来像 [0, 1, 2, 3, 4]
。 然而,do...while
不同于其他循环的地方,是第一次循环检查失败时的行为。 让我们看看代码示例。 这里是一个常规的 while
循环,只要 i < 5
,就会在循环中运行代码:
const ourArray = [];
let i = 5;
while (i < 5) {
ourArray.push(i);
i++;
}
这个例子中,定义了一个空数组 ourArray
以及一个值为 5 的 i
。 当执行 while
循环时,因为 i
不小于 5,所以循环条件为 false
,循环内的代码将不会执行。 ourArray
最终没有添加任何内容,因此示例中的所有代码执行完时,ourArray 仍然是[]
。 现在,看一下 do...while
循环。
const ourArray = [];
let i = 5;
do {
ourArray.push(i);
i++;
} while (i < 5);
在这里,和使用 while
循环一样,将 i
的值初始化为 5。 执行下一行时,没有执行循环检查,直接执行花括号内的代码。 数组会添加一个元素,并在进行条件检查之前递增 i
。 然后,在条件检查时因为 i
等于 6 不符合条件 i < 5
,所以退出循环。 最终 ourArray
的值是 [5]
。 本质上,do...while
循环确保循环内的代码至少运行一次。 让我们通过 do...while
循环将值添加到数组中。
使用递归代替循环
递归是函数调用自身的操作。 为了便于理解,有如下任务:计算数组内元素前 n
的元素乘积。 使用 for
循环, 可以这样做:
function multiply(arr, n) {
let product = 1;
for (let i = 0; i < n; i++) {
product *= arr[i];
}
return product;
}
下面是递归写法,注意代码里的 multiply(arr, n) == multiply(arr, n - 1) * arr[n - 1]
。 这意味着可以重写 multiply
以调用自身而无需依赖循环。
function multiply(arr, n) {
if (n <= 0) {
return 1;
} else {
return multiply(arr, n - 1) * arr[n - 1];
}
}
递归版本的 multiply
详述如下。 在 base case 里,也就是 n <= 0
时,返回 1。 在 n
比 0 大的情况里,函数会调用自身,参数 n 的值为 n - 1
。 函数以相同的方式持续调用 multiply
,直到 n <= 0
为止。 所以,所有函数都可以返回,原始的 multiply
返回结果。
注意: 递归函数在没有函数调用时(在这个例子是,是当 n <= 0
时)必需有一个跳出结构,否则永远不会执行完毕。
资料查找
我们有一个对象数组,里面存储着通讯录。
lookUpProfile
函数已经写好了参数,需要 name
和属性 (prop
) 参数。
函数将会检查通讯录中是否存在一个 firstName
与传入的 name
相同的联系人。 如果存在,那么还需要检查对应的联系人中是否存在 prop
属性。
如果它们都存在,函数返回 prop 属性对应的值。
如果 name
不对应于任何联系人,然后返回字符串 No such contact
。
如果 prop
属性在匹配 name
的联系人里不存在,返回 No such property
。
// 设置
const contacts = [
{
firstName: "Akira",
lastName: "Laine",
number: "0543236543",
likes: ["Pizza", "Coding", "Brownie Points"],
},
{
firstName: "Harry",
lastName: "Potter",
number: "0994372684",
likes: ["Hogwarts", "Magic", "Hagrid"],
},
{
firstName: "Sherlock",
lastName: "Holmes",
number: "0487345643",
likes: ["Intriguing Cases", "Violin"],
},
{
firstName: "Kristian",
lastName: "Vos",
number: "unknown",
likes: ["JavaScript", "Gaming", "Foxes"],
},
];
function lookUpProfile(name, prop) {
// 只修改这一行下面的代码
for(var i=0;i<contacts.length;i++){
if(contacts[i].firstName===name){
return contacts[i][prop] || "No such property"
}
}return "No such contact"
// 只修改这一行上面的代码
}
用到了短路求值
随机数、转换、递归
使用 JavaScript 生成随机分数---Math.random()
随机数非常适合用来创建随机行为。
在 JavaScript 中,可以用 Math.random()
生成一个在0
(包括 0)到 1
(不包括 1)之间的随机小数。 因此 Math.random()
可能返回 0
,但绝不会返回 1
。
提示:使用赋值运算符存储值这一节讲过,所有函数调用将在 return
执行之前结束,因此我们可以 return
(返回)Math.random()
函数的值。
使用 JavaScript 生成随机整数
可以用 Math.random()
生成随机的小数,但有时你需要生成随机的整数。 下面的流程将给你一个小于 20
的随机整数:
- 用
Math.random()
生成一个随机小数。 - 把这个随机小数乘以
20
。 - 用
Math.floor()
向下取整,获得它最近的整数。
记住 Math.random()
永远不能完全返回 1
,所以不可能实际得到 20
,因为你正在用 Math.floor()
四舍五入。 这个流程将给你一个从 0
到 19
的随机整数。
把操作连起来,代码类似于下面:
Math.floor(Math.random() * 20);
你将调用 Math.random()
,把结果乘以 20,然后把值传给 Math.floor()
,向下取整获得最近的整数。
生成某个范围内的随机整数
Math.floor()
:向下取整
可以在从零到给定数字的范围内生成随机整数。 你也可以为此范围选择一个不同的较小数字。
你将把最小的数字定义为 min
,把最大的数字定义为 max
。
这个公式将生成一个从 min
到 max
的随机整数。 仔细看看并尝试理解这行代码到底在干嘛:
Math.floor(Math.random() * (max - min + 1)) + min
使用 parseInt 函数---转换为数字
parseInt()
函数解析一个字符串返回一个整数。 下面是一个示例:
const a = parseInt("007");
上述函数将字符串 007
转换为整数 7
。 如果字符串中的第一个字符不能转换为数字,则返回 NaN
。
使用 parseInt 函数并传入一个基数
parseInt()
函数解析一个字符串并返回一个整数。 它还可以传入第二个参数,指定了字符串中数字的基数。 基数可以是 2 到 36 之间的整数。
函数调用如下所示:
parseInt(string, radix);
radix表示进制
这是一个示例:
const a = parseInt("11", 2);
变量 radix 表示 11
是在二进制系统中。 这个示例将字符串 11
转换为整数 3
。
使用三元运算符
条件运算符( conditional operator,)(也称为三元运算符( ternary operator))的就像写成一行的 if-else 表达式
语法是:a ? b : c
, where a
是条件,当条件返回 true
的时候运行代码 b
,当条件返回 false
的时候运行代码 c
。
以下函数使用 if/else
语句来检查条件:
function findGreater(a, b) {
if(a > b) {
return "a is greater";
}
else {
return "b is greater or equal";
}
}
这可以使用三元运算符重写:
function findGreater(a, b) {
return a > b ? "a is greater" : "b is greater or equal";
}
使用多个三元运算符
我们可以将多个运算符串联在一起以检查多种条件。
下面的函数使用 if
,else if
和 else
语句来检查多个条件:
function findGreaterOrEqual(a, b) {
if (a === b) {
return "a and b are equal";
}
else if (a > b) {
return "a is greater";
}
else {
return "b is greater";
}
}
以上函数可以使用多个三元运算符重写:
function findGreaterOrEqual(a, b) {
return (a === b) ? "a and b are equal"
: (a > b) ? "a is greater"
: "b is greater";
}
如上文所示,对多个三元运算符进行每个条件都是单独一行的格式化被认为是最佳做法。 使用多个三元运算符而没有适当的缩进可能会使您的代码难以理解。 例如:
function findGreaterOrEqual(a, b) {
return (a === b) ? "a and b are equal" : (a > b) ? "a is greater" : "b is greater";
}
使用递归创建一个倒计时
在上一个挑战-使用递归代替循环中,你学习了怎样用递归来代替 for
循环。
现在来学习一个更复杂的函数,函数返回一个从 1
到传递给函数的指定数字的连续数字数组。
正如上一个挑战提到的,会有一个 base case。 base case 告诉递归函数什么时候不再需要调用其自身。 这是简单 情况,返回得到的值。 还有 recursive call,继续用不同的参数调用自身。 如果函数无误,一直执行直到 base case 为止。
比如,如果想写一个递归函数,返回一个数字 1
到 n
的连续数组。 这个函数需要接收一个参数 n
代表最终数字。 然后会持续的调用自身,传入一个比 n
更小的值一直到传入的值是 1
为止。 函数如下:
function countup(n) {
if (n < 1) {
return [];
} else {
const countArray = countup(n - 1);
countArray.push(n);
return countArray;
}
}
console.log(countup(5));
值 [1, 2, 3, 4, 5]
将显示在控制台中。
起初,这似乎是违反直觉的,因为 n
的值递减,但是最终数组中的值却递增。 之所以发生这种情况,是因为在递归调用返回之后,才调用 push。 在将 n
pushed 进数组时,countup(n - 1)
已经调用赋值成功并返回了 [1, 2, ..., n - 1]
。
已经定义了一个函数 countdown
,函数有一个参数(n
)。 函数应该基于参数 n
递归调用返回 n
到 1
的连续数字的数组。 如果函数以小于 1 的参数调用,函数应该返回空数组。 比如,用 n = 5
调用函数应该返回数组 [5, 4, 3, 2, 1]
。 函数必需使用递归函数调用自身,不能使用任何形式的循环。
function countdown(n){
if(n<1){
return [];
}else{
const conutArray=countdown(n-1);
conutArray.unshift(n);
return conutArray;
}
}
.unshift
是将值加在数组的开头
也可以把.unshift(n)
替换成arr.splice(0, 0, n);
array.splice(start, deleteCount, item1, item2, ...);
start
: 必需,指定要修改的起始索引位置。deleteCount
: 可选,指定要删除的元素数量。如果为 0,则不删除任何元素。item1, item2, ...
: 可选,要添加到数组中的元素。可以添加任意数量的元素。
上面是在索引为0的位置添加
还有两种更优雅的方法
function countdown(n){
return n < 1 ? [] : [n].concat(countdown(n - 1));
}
concat()
方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
function countdown(n){
return n < 1 ? [] : [n, ...countdown(n - 1)];
}
使用递归来创建一个数字序列
已经定义好了 rangeOfNumbers
函数,包含两个参数。 函数应该返回一个连续数字数组,startNum
参数开始 endNum
参数截止。 开始的数字小于或等于截止数字。 函数必需递归调用自身,不能使用任意形式的循环。 要考虑到 startNum
和 endNum
相同的情况。
function rangeOfNumbers(startNum, endNum) {
if(startNum>endNum){
return [];
}else{
const countArray=rangeOfNumbers(startNum,endNum-1);
countArray.push(endNum);
return countArray
}
};
或者
function rangeOfNumbers(startNum, endNum) {
return endNum < startNum
? []
: rangeOfNumbers(startNum, endNum - 1).concat(endNum);
}
或者
function rangeOfNumbers(startNum, endNum) {
return endNum < startNum
? []
: [...rangeOfNumbers(startNum, endNum - 1), endNum];
}