JavaScript Algorithms and Data Structures(一)

freeCodeCamp —- JavaScript Algorithms and Data Structures


Basic JavaScript

1. 添加注释

被注释的代码块在 JavaScript 之中是不会执行的。 在代码中写注释,是一个可以让你自己和以后的其他人理解代码作用的好方法。

JavaScript有两种写注释的方法。

  • 使用 // 注释掉当前行的代码。 这是一个行内注释:
1
// This is an in-line comment.
  • 使用多行注释来注释代码,使用 /* 开始, */ 结束。 这是一个多行注释:
1
/* This is amulti-line comment */

最佳实践当你写代码的时候,你应该时不时的添加注释来解释你写的代码的作用。 适当的注释能让别人你未来的自己更容易看懂代码。

2. 声明变量

JavaScript 提供八种不同的数据类型,

  • undefined(未定义)

  • null(空)

  • boolean(布尔型)

  • string(字符串)

  • symbol

  • number(数字)

  • bigint(可以表示任意大的整数)

  •  object(对象)。

变量允许计算机以一种动态的形式来存储和操作数据, 即通过操作指向数据的指针而不是数据本身来实现。 以上八种数据类型中的任何一种都可以存储到一个变量中。

通过在变量前面使用关键字 var,声明一个变量,例如:

1
var ourName;

上面代码的意思是创建一个名为 ourName 的变量。 在 JavaScript 中我们以分号结束语句。 变量名称可以由数字、字母、美元符号 $ 或者下划线 _ 组成,但是不能包含空格或者以数字为开头。

3. 使用赋值运算符储存值

可以使用赋值(assignment)运算符 (=)将值存储在变量中。

1
myVariable = 5;

这条语句把 Number 类型的值 5 赋给变量 myVariable

在将值赋给运算符左侧的变量之前,将先执行 = 运算符右侧的所有运算。

1
2
var myVar;
myVar = 5;

首先,此代码创建一个名为 myVar 的变量。 然后,数值 5 被赋给变量 myVar。 现在,如果 myVar 再次出现在代码中,程序将会将它视为 5


将一个变量的值赋予另一个

在使用赋值运算符赋予变量某个值后,你可以使用赋值运算符将该变量的值赋给另一个变量。

1
2
3
4
var myVar;
myVar = 5;
var myNum;
myNum = myVar;

以上代码声明了一个没有初始值的变量 myVar,然后给它赋值为 5。 紧接着,又声明了一个没有初始值的变量 myNum。 然后,变量 myVar 的内容(也就是 5)被赋给了变量 myNum。 现在,变量 myNum 的值也为 5


使用赋值运算符初始化变量

通常在声明变量的时候会给变量初始化一个初始值。

1
var myVar = 0;

创建一个名为 myVar 的变量,并指定其初始值为 0。    


声明字符串变量

之前,你使用以下代码声明变量:

1
var myName;

但是你也可以像这样声明一个字符串变量:

1
var myName = "your name";

"your name" 被称为 string literal。 字符串文字或字符串是用单引号或双引号括起来的一系列零个或多个字符。


未初始化的变量

当 JavaScript 中的变量被声明的时候,程序内部会给它一个初始值 undefined。 当你对一个值为 undefined 的变量进行运算操作的时候,算出来的结果将会是 NaN,它的意思是 “Not a Number”。 如果你用 undefined 变量连接一个字符串,你将得到一个 undefined 的 字符串。


变量名区分大小写

在 JavaScript 中所有的变量和函数名都是大小写敏感的。 要区别对待大写字母和小写字母。

使用驼峰命名法(camelCase)来书写一个 Javascript 变量。

  • 变量名的第一个单词的首写字母小写,后面的单词的第一个字母大写

示例:

1
2
3
var someVariable;
var anotherVariableName;
var thisVariableNameIsSoLong;
4. 声明变量

使用 var 关键字声明变量的最大问题之一是你可以轻松覆盖变量声明:

1
2
3
var camper = "James";
var camper = "David";
console.log(camper);

在上面的代码中,camper 变量最初声明为 James,然后被覆盖为 David。 然后控制台显示字符串 David。在小型应用程序中,你可能不会遇到此类问题。 但是随着你的代码库变大,你可能会意外地覆盖一个你不打算覆盖的变量。 由于此行为不会引发错误,因此搜索和修复错误变得更加困难。

ES6 中引入了一个名为 let 的关键字,这是对 JavaScript 的一次重大更新,以解决与 var 关键字有关的潜在问题。

如果将上面代码中的 var 替换为 let ,则会导致错误:

1
2
let camper = "James";
let camper = "David";

该错误可以在你的浏览器控制台中看到。

所以不像 var,当你使用 let 时,同名的变量只能声明一次


使用const关键词声明只读变量

在 ES6 中,可以使用 const 关键字声明变量。

const 具有 let 的所有出色功能,另外还有一个额外的好处,即使用 const 声明的变量是只读的。 它们是一个常量值,这意味着一旦一个变量被赋值为 const,它就不能被重新赋值:

1
2
const FAV_PET = "Cats";
FAV_PET = "Dogs";

由于重新分配 FAV_PET 的值,控制台将显示错误。

你应该始终使用 const 关键字命名不想重新分配的变量。 这有助于避免给一个常量进行额外的再次赋值。

注意: 通常,开发者会用大写字母作为常量标识符,用小写字母或者驼峰命名作为变量(对象或数组)标识符。

5.运算

加法运算

  • Number 是 JavaScript 中的一种数据类型,用来表示数值。

  • 通过符号 + 来进行加法运算

减法运算

  • 使用 - 来做减法运算

乘法运算

  • 使用 * 符号表示两数相乘

除法运算

  • 使用 / 符号做除法运算

求余运算

  • 使用% 返回两个数相除得到的余数

  • 在数学中,判断一个数是奇数还是偶数,只需要判断这个数除以 2 得到的余数是 0 还是 1。 如果是偶数,余数是 0,而如果是奇数,余数是 1

    17 % 2 = 1
    48 % 2 = 0

    提示 余数运算符(remainder)有时被错误地称为“模数”运算符。 它与模数非常相似,但不能用于负数的运算。

复合赋值

  •  += 运算符,-= 运算符,*= 运算符,/= 运算符

数字递增

  • 使用 ++,我们可以很容易地对变量进行自增或者 +1 运算。
    1
    i++;
    等效于:
    1
    i = i + 1;
    注意:i++; 这种写法省去了书写等号的必要。

数字递减

  • 使用自减符号 --,你可以很方便地对一个变量执行自减或者 -1 运算。
    1
    i--;
    等效于:
    1
    i = i - 1;
    注意:i--; 这种写法省去了书写等号的必要。

6. 转义字符

在 JavaScript 中,可以通过在引号前面使用反斜杠(\)来转义引号。

引号不是字符串中唯一可以被转义(escaped)的字符。 转义字符允许你使用可能无法在字符串中使用的字符。

代码 输出
\' 单引号
\" 双引号
\\ 反斜杠
\n 换行符
\t 制表符
\r 回车
\b 退格符
\f 换页符

请注意,反斜线本身必须被转义,才能显示为反斜线。

7. 字符串

用加号运算符连接字符串

在 JavaScript 中,当 + 操作符被用于一个 String 类型的值的时候,它被称作拼接操作符。 你可以通过拼接其他字符串来创建一个新的字符串。

例如:

1
'My name is Alan,' + ' I concatenate.'

提示: 注意空格。 拼接操作不会在两个字符串之间添加空格。所以,如果想加上空格的话,你需要自己在字符串里面添加。


用 += 运算符连接字符串

可以使用 += 运算符来拼接字符串到现有字符串变量的结尾。 对于那些被分割成几段的长的字符串来说,这一操作是非常有用的。

提示: 注意空格。 拼接操作不会在两个字符串之间添加空格,所以,如果想要加上空格的话,你需要自己在字符串里面添加。

例如:

1
2
let ourStr = "I come first. ";
ourStr += "I come second.";

ourStr 的值为字符串 I come first. I come second.


用变量构造字符串

通过使用连接运算符(+),你可以插入一个或多个变量来组成一个字符串。

例如:

1
2
const ourName = "freeCodeCamp";
const ourStr = "Hello, our name is " + ourName + ", how are you?";

ourStr 值为 Hello, our name is freeCodeCamp, how are you?


将变量追加到字符串

使用加且赋值(+=)运算符将字符串追加到字符串的末尾。

示例:

1
2
3
const anAdjective = "awesome!";
let ourStr = "freeCodeCamp is ";
ourStr += anAdjective;

ourStr 值为 freeCodeCamp is awesome!


查找字符串长度

通过在字符串变量或字符串后面写上 .length 来获得 String 的长度。

1
console.log("Alan Peter".length);

值 10 将显示在控制台中。 请注意,“Alan” 和 “Peter” 之间的空格字符也被计算在内。

例如,如果我们创建了一个变量 const firstName = "Ada",我们可以通过使用 firstName.length 找出字符串 Ada 的长度属性。


使用方括号查找字符串中的第n个字符

方括号表示法(Bracket notation)是一种在字符串中的特定 index(索引)处获取字符的方法。

大多数现代编程语言,如 JavaScript,不同于人类从 1 开始计数。 它们是从 0 开始计数。 这被称为基于零(Zero-based)的索引。

  • 例如,单词 Charles 的索引 0 的字符是 C。 所以如果 const firstName = "Charles",你可以通过 firstName[0] 得到字符串第一个字母的值。

    示例:

1
2
const firstName = "Charles";
const firstLetter = firstName[0];

    firstLetter 值为字符串 C 。

查找最后一个字符需要获取字符串的长度

  • 要获取字符串的最后一个字符,可以用字符串的长度减 1 的索引值。

  • 例如,如果 const firstName = "Ada",则可以使用 firstName[firstName.length - 1] 获取字符串最后一个字母的值。

示例:

1
2
const firstName = "Ada";
const lastLetter = firstName[firstName.length - 1];

lastLetter 值为字符串 a


字符串的不变性

在 JavaScript 中,字符串(String)的值是不可变的(immutable),这意味着一旦字符串被创建就不能被改变。

例如,以下代码将产生错误,因为字符串 Bob 中的字母 B 不能更改为字母 J

1
2
let myStr = "Bob";
myStr[0] = "J";

请注意,这 意味着无法重新分配 myStr。 更改 myStr 的唯一方法是为其分配一个新值,如下所示:

1
2
let myStr = "Bob";
myStr = "Job";
8. 数组

使用 JavaScript 数组将多个值存储在一个变量中

使用数组(array),可以在一个地方存储多个数据。

以左方括号开始定义一个数组,以右方括号结束,里面每个元素之间用逗号隔开,例如:

1
const sandwich = ["peanut butter", "jelly", "bread"];

将一个数组嵌套在另一个数组中

可以在其他数组中嵌套数组,如:

1
const teams = [["Bulls", 23], ["White Sox", 45]];

这也叫做多维数组(multi-dimensional array)。

通过索引访问数组中的数据

可以使用索引(indexes)来访问数组中的数据。

数组索引与字符串一样使用方括号来表示,不同的是,它们不是指定字符,而是指定数组中的一个条目。 数组索引与字符串索引一样是从 0 开始(zero-based)的,所以数组中第一个元素的索引编号是 0

示例

1
2
3
const array = [50, 60, 70];
console.log(array[0]);
const data = array[1];

console.log(array[0]) 打印 50data 的值为 60

通过索引修改数组中的数据

与字符串不同,数组的条目是可变的 并且可以自由更改,即使数组是用 const 声明的。

示例

1
2
const ourArray = [50, 40, 30];
ourArray[0] = 15;

ourArray 值为 [15, 40, 30]

注意: 数组名与方括号之间不应该有任何空格,比如 array [0] 。

使用索引访问多维数组

可以把多维数组看作成是数组中的数组。 当你使用括号访问你的数组时,第一组括号指的是最外层(第一层)数组中的条目,而每一对额外的括号指的是里面下一层的条目。

例如:

1
2
3
4
5
6
7
8
9
10
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];

在这个例子中,subarray 的值为 [[10, 11, 12], 13, 14], nestedSubarray 的值为 [10, 11, 12]element 的值为 11 。

注意: 数组名与方括号之间不应该有任何空格,比如 array [0][0] 甚至是 array [0] [0] 都是不允许的。


操作数组

  1. push 方法

一个将数据添加到数组末尾的简单方法是 push() 函数。

.push() 接受一个或多个参数(parameters),并把它压入到数组的末尾。

示例:

1
2
3
4
5
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"]]

  1. pop 方法

改变数组中数据的另一种方法是用 .pop() 函数。

.pop() 函数用来弹出一个数组末尾的值。 我们可以把这个弹出的值赋给一个变量存储起来。 换句话说就是 .pop() 函数移除数组末尾的元素并返回这个元素。

数组中任何类型的元素(数值,字符串,甚至是数组)都可以被弹出来 。

1
2
3
4
const threeArr = [1, 4, 6];
const oneDown = threeArr.pop();
console.log(oneDown);
console.log(threeArr);

第一个 console.log 将显示值 6,第二个将显示值 [1, 4]

  1. shift方法

 .shift()的工作原理就像 .pop(),但它移除的是第一个元素,而不是最后一个。

示例:

1
2
const ourArray = ["Stimpson", "J", ["cat"]];
const removedFromOurArray = ourArray.shift();

removedFromOurArray 值为 StimpsonourArray 值为 ["J", ["cat"]]

  1. unshift方法

 unshift(移入)一个元素到数组的头部。

.unshift() 函数用起来就像 .push() 函数一样,但不是在数组的末尾添加元素,unshift() 在数组的头部添加元素。

示例:

1
2
3
const ourArray = ["Stimpson", "J", "cat"];
ourArray.shift();
ourArray.unshift("Happy");

在 shiftourArray 后值为 ["J", "cat"]。 在 unshiftourArray 后值为 ["Happy", "J", "cat"]

9.函数

可以把代码的重复部分抽取出来,放到一个函数 (functions)中。

举个例子:

1
2
3
function functionName() {
console.log("Hello World");
}

你可以通过函数名加上后面的小括号来调用(invoke)这个函数,就像这样: functionName(); 每次调用函数时,它都会在控制台上打印消息 Hello World。 每次调用函数时,大括号之间的所有代码都将被执行。


将值传递给带有参数的函数

函数的参数 (parameters)在函数调用中充当传入函数的输入占位符(也叫形参)。 函数调用时,参数可以为一个或多个。 调用函数时输入(或传递 “passed”)的实际值被称为参数(arguments)。

这是带有两个参数的函数,param1 和 param2

1
2
3
function testFun(param1, param2) {
console.log(param1, param2);
}

然后我们可以调用 testFun,就像这样: testFun("Hello", "World");。 我们传入了两个字符串参数, Hello 和 World。 在函数中,param1 等于字符串 Hello 以及 param2 等于字符串 World。 请注意,testFun 函数可以多次调用,每次调用时传递的参数会决定参数的实际值。


使用 return 给函数返回值

可以通过函数的参数(arguments)把值传入函数, 也可以使用 return 语句把数据从一个函数中传出来。

示例

1
2
3
4
5
function plusThree(num) {
return num + 3;
}

const answer = plusThree(5);

answer 的值为 8

plusThree 带有一个参数(argument)num,并返回(return)一个等于 num + 3 的值。

9.1 全局作用域和函数

在 JavaScript 中,作用域涉及到变量的作用范围。 在函数外定义的变量具有 全局 作用域。 这意味着,具有全局作用域的变量可以在代码的任何地方被调用。

未使用 let 或 const 关键字声明的变量会在 global 范围内自动创建。 当在代码其他地方无意间定义了一个变量,刚好变量名与全局变量相同,这时会产生意想不到的后果。 你应该总是用 let 或 const 声明你的变量。

9.2 局部作用域和函数

在一个函数内声明的变量,以及该函数的参数都具有局部(local)作用域。 这意味着它们只在该函数内可见。

这是在函数 myTest 内声明局部变量 loc 的例子:

1
2
3
4
5
6
7
function myTest() {
const loc = "foo";
console.log(loc);
}

myTest();
console.log(loc);

myTest() 函数调用将在控制台中显示字符串 foo。 console.log(loc) 行(在 myTest 函数之外)将抛出错误,因为 loc 未在函数之外定义。


函数中的全局作用域和局部作用域

一个程序中有可能具有相同名称的局部变量 和全局变量。 在这种情况下,局部变量将会优先于全局变量。

下面为例:

1
2
3
4
5
6
const someVar = "Hat";

function myFun() {
const someVar = "Head";
return someVar;
}

函数 myFun 将会返回字符串 Head,因为局部变量的优先级更高。


函数也可以返回 undefined

函数一般用 return 语句来返回值,但这不是必须的。 在函数没有 return 语句的情况下,当你调用它时,该函数会执行内部代码,返回的值是 undefined

示例

1
2
3
4
5
6
7
let sum = 0;

function addSum(num) {
sum = sum + num;
}

addSum(3);

addSum 是一个没有 return 语句的函数。 该函数将更改全局变量 sum,函数的返回值为 undefined

可以获取函数的返回值,并将其赋值给一个变量。

假设我们有一个预先定义的函数 sum ,它将两个数相加。

1
ourSum = sum(5, 12);

调用 sum 函数,参数为 5 和 12,生成的返回值为 17。 将返回值赋给 ourSum 变量。

10. 队列

队列(queue)是一个抽象的数据结构(Data Structure),队列中的条目都是有秩序的。 新的条目会被加到队列的末尾,旧的条目会从队列的头部被移出。

11. 布尔值

数据类型布尔(Boolean)

布尔值只能是两个值中的一个:true 或者 false。 它非常像电路开关,true 是 “开”,false 是 “关”。 这两种状态是互斥的。

注意: 布尔值是不带引号的。 字符串 "true" 和 "false" 不是布尔值,在 JavaScript 中也没有特殊含义。


从函数返回布尔值

所有比较操作符都会返回布尔值:要么是true,要么是false

有时人们通过 if/else 语句来做比较,像这样。

1
2
3
4
5
6
7
function isEqual(a, b) {
if (a === b) {
return true;
} else {
return false;
}
}

但有更好的方式来达到相同的效果。 既然 === 返回 true 或 false 我们可以直接返回比较结果:

1
2
3
function isEqual(a, b) {
return a === b;
}
12. if 语句

if 语句用于在代码中做出决定。 关键字 if 告诉 JavaScript 在小括号中的条件为真的情况下去执行定义在大括号里面的代码。 这种条件被称为 Boolean 条件,因为他们只可能是 true(真)或 false(假)。

当条件的计算结果为 true,程序执行大括号内的语句。 当布尔条件的计算结果为 false,大括号内的代码将不会执行。

伪代码

if(条件为真){
语句被执行
}

示例

1
2
3
4
5
6
7
8
9
function test (myCondition) {
if (myCondition) {
return "It was true";
}
return "It was false";
}

test(true);
test(false);

test(true) 返回字符串 It was truetest(false) 返回字符串 It was false

当 test 被调用,并且传递进来的参数值为 true 时,if 语句会计算 myCondition 的结果,看它是否为 true。 如果条件为 true,函数会返回 It was true。 当 test 被调用,并且传递进来的参数值为 false 时,myCondition  为 true,并且不执行大括号后面的语句,函数返回 It was false


12.1 运算符

在 JavaScript 中,有很多 相互比较的操作。 所有这些操作符都返回一个 true 或 false 值。

最基本的运算符是 相等运算符==。 相等运算符比较两个值,如果它们是相等,返回 true,如果它们不相等,返回 false。 值得注意的是相等运算符不同于赋值运算符(=),赋值运算符是把等号右边的值赋给左边的变量。

1
2
3
4
5
6
function equalityTest(myVal) {
if (myVal == 10) {
return "Equal";
}
return "Not Equal";
}

如果 myVal 等于 10,相等运算符会返回 true,因此大括号里面的代码会被执行,函数将返回 Equal。 否则,函数返回 Not Equal。 在 JavaScript 中,为了让两个不同的数据类型(例如 numbers 和 strings)的值可以作比较,它必须把一种类型转换为另一种类型。 这叫作 “类型强制转换”。 转换之后,可以像下面这样来比较:

1
2
3
4
1   ==  1  // true
1 == 2 // false
1 == '1' // true
"3" == 3 // true

严格相等运算符===)是相对相等操作符(==)的另一种比较操作符。 与相等操作符转换数据两类型不同,严格相等运算符不会做类型转换。

如果比较的值类型不同,那么在严格相等运算符比较下它们是不相等的,会返回 false 。

示例

1
2
3 ===  3  // true
3 === '3' // false

在第二个例子中,3 是一个 Number 类型,而 '3' 是一个 String 类型。

 JavaScript 中,你可以使用 typeof 运算符确定变量或值的类型


不相等运算符!=)与相等运算符是相反的。 这意味着不相等并返回 false 的地方,用相等运算符会返回 true反之亦然。 与相等运算符类似,不相等运算符在比较的时候也会转换值的数据类型。

例如

1
2
3
4
5
1 !=  2    // true
1 != "1" // false
1 != '1' // false
1 != true // false
0 != false // false

严格不相等运算符!==)与全等运算符是相反的。 这意味着严格不相等并返回 false 的地方,用严格相等运算符会返回 true反之亦然。 严格不相等运算符不会转换值的数据类型。

示例

1
2
3
3 !==  3  // false
3 !== '3' // true
4 !== 3 // true

使用大于运算符>)来比较两个数字。 如果大于运算符左边的数字大于右边的数字,将会返回 true。 否则,它返回 false

与相等运算符一样,大于运算符在比较的时候,会转换值的数据类型

例如:

1
2
3
4
5   >  3  // true
7 > '3' // true
2 > 3 // false
'1' > 9 // false

使用大于等于运算符>=)来比较两个数字的大小。 如果大于等于运算符左边的数字比右边的数字大或者相等,会返回 true。 否则,会返回 false

与相等运算符相似,大于等于运算符在比较的时候会转换值的数据类型

例如:

1
2
3
4
6   >=  6  // true
7 >= '3' // true
2 >= 3 // false
'7' >= 9 // false

使用小于运算符<)来比较两个数字。 如果小于运算符左边的数字比右边的数字小,它会返回 true。 否则会返回 false。 与相等运算符类似,小于运算符在做比较的时候会转换值的数据类型

例如:

1
2
3
4
5
2   < 5 // true
'3' < 7 // true
5 < 5 // false
3 < 2 // false
'8' < 4 // false

使用小于等于运算符<=)比较两个数字的大小。 如果在小于等于运算符左边的数字小于或者等于右边的数字,它会返回 true。 如果在小于等于运算符左边的数字大于右边的数字,它会返回 false。 与相等运算符类似,小于或等于运算符会转换数据类型

例如

1
2
3
4
5
4   <= 5 // true
'7' <= 7 // true
5 <= 5 // true
3 <= 2 // false
'8' <= 4 // false

当且仅当运算符的左边和右边都是 true,逻辑与运算符&&)才会返回 true

可以通过在一个 if 语句中嵌套另一个 if 语句来实现同样的效果。

1
2
3
4
5
6
if (num > 5) {
if (num < 10) {
return "Yes";
}
}
return "No";

当 num 的值大于 5 并且小于10,代码会返回 Yes。 可以使用逻辑 and 操作符写出相同的逻辑。

1
2
3
4
if (num > 5 && num < 10) {
return "Yes";
}
return "No";

只要逻辑或运算符(||)两边的任何一个运算的结果是 true,则返回 true。 否则,返回 false

逻辑或运算符由两个竖线(||)组成。 这个按键位于退格键和回车键之间。

下面这个模式看起来应该很熟悉:

1
2
3
4
5
6
7
if (num > 10) {
return "No";
}
if (num < 5) {
return "No";
}
return "Yes";

如果 num 在 5 和 10 之间(包括 5 和 10),这段代码将返回 Yes。 可以使用逻辑 or 操作符写出同样的逻辑。

1
2
3
4
if (num > 10 || num < 5) {
return "No";
}
return "Yes";

当 if 语句的条件为真,会执行大括号里的代码。 那如果条件为假呢? 正常情况下什么也不会发生。 使用 else 语句,可以执行当条件为假时相应的代码。

1
2
3
4
5
if (num > 10) {
return "Bigger than 10";
} else {
return "10 or Less";
}

12.2 if else语句

如果你有多个条件语句,你可以通过 else if 语句把 if 语句链起来。

1
2
3
4
5
6
7
if (num > 15) {
return "Bigger than 15";
} else if (num < 5) {
return "Smaller than 5";
} else {
return "Between 5 and 15";
}

ifelse if 语句中的代码顺序是很重要的。

在条件判断语句中,代码的执行顺序是从上到下,所以你需要考虑清楚先执行哪一句,后执行哪一句。

这有两个例子。

第一个例子:

1
2
3
4
5
6
7
8
9
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";
}
}

第二个例子更改了代码的执行顺序:

1
2
3
4
5
6
7
8
9
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";
}
}

这两个函数看起来几乎一模一样,我们传一个值进去看看它们有什么区别。

1
2
foo(0)
bar(0)

foo(0) 将返回字符串 Less than onebar(0) 将返回字符串 Less than two


if/else 语句串联在一起可以实现复杂的逻辑。 这是多个 if / else if 语句串联在一起的伪代码:

1
2
3
4
5
6
7
8
9
10
if (condition1) {
statement1
} else if (condition2) {
statement2
} else if (condition3) {
statement3
. . .
} else {
statementN
}
13. switch语句

switch 语句将值与定义各种可能的值的 case 语句比较。 任何有效的 JavaScript 语句都可以在 case 块中执行,并且将从第一个匹配的 case 的值开始运行,直到遇到 break

这是 switch 语句的示例:

1
2
3
4
5
6
7
8
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 ,下一个语句将会被执行。


在 switch 语句中添加默认选项

在 switch 语句中,你可能无法用 case 枚举出所有可能的值。 相反,你可以添加 default 语句,它会在找不到相匹配的 case 语句之后执行。 你可以把它看作是 if/else 链中最后的那个 else 语句。

default 语句应该被放到最后。

1
2
3
4
5
6
7
8
9
10
11
12
switch (num) {
case value1:
statement1;
break;
case value2:
statement2;
break;
...
default:
defaultStatement;
break;
}

添加多个相同选项

如果你忘了给 switch 的每一条 case 添加 break,那么后续的 case 会一直执行,直到遇见 break 为止。 如果你想为 switch 中的多个不同的输入设置相同的结果,可以这样写:

1
2
3
4
5
6
7
8
9
10
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 if 语句容易些。 譬如:

1
2
3
4
5
6
7
if (val === 1) {
answer = "a";
} else if (val === 2) {
answer = "b";
} else {
answer = "c";
}

可以被下面替代:

1
2
3
4
5
6
7
8
9
10
switch (val) {
case 1:
answer = "a";
break;
case 2:
answer = "b";
break;
default:
answer = "c";
}

函数执行到 return 语句就结束

当代码执行到 return 语句时,函数返回一个结果就结束运行了,return 后面的语句不会执行。

示例

1
2
3
4
5
6
function myFun() {
console.log("Hello");
return "World";
console.log("byebye")
}
myFun();

以上将在控制台中显示字符串 Hello 并返回字符串 World。 字符串 byebye 将永远不会在控制台中显示,因为函数在 return 语句处就退出了。

14. JavaScript对象

对象和 arrays 类似,区别在于数组使用索引来访问和修改数据,而对象中的数据是通过 properties 访问的。

对象非常适合用来存储结构化数据,可以表示真实世界中的物体,比如一只猫。

这里是一个猫对象的样本:

1
2
3
4
5
6
const cat = {
"name": "Whiskers",
"legs": 4,
"tails": 1,
"enemies": ["Water", "Dogs"]
};

在此示例中,所有属性都存储为字符串,例如 namelegs 和 tails。 然而,你也可以使用数字作为属性。 你甚至可以省略单字字符串属性中的引号,如下所示:

1
2
3
4
5
const anotherObject = {
make: "Ford",
5: "five",
"model": "focus"
};

然而,如果你的对象有非字符串属性的话,JavaScript 会自动将它们转为字符串。


和访问数组类似,访问对象属性有两种方式:点号表示法(.)和方括号表示法([]

如果我们已经提前知道要访问的属性名,使用点号表示法是最方便的。

  • 这里是一个用点符号(.)读取对象属性的示例:

    1
    2
    3
    4
    5
    6
    7
    const myObj = {
    prop1: "val1",
    prop2: "val2"
    };

    const prop1val = myObj.prop1;
    const prop2val = myObj.prop2;

    prop1val 的值将为字符串 val1,并且prop2val 的值将为字符串 val2

  • 访问对象属性的第二种方式是方括号表示法([])。 如果你想访问的属性名中包含空格,就必须使用方括号表示法来获取它的属性值。

    当然,如果属性名不包含空格,也可以使用方括号表示法。

    这是一个使用方括号表示法读取对象属性的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const myObj = {
    "Space Name": "Kirk",
    "More Space": "Spock",
    "NoSpace": "USS Enterprise"
    };

    myObj["Space Name"];
    myObj['More Space'];
    myObj["NoSpace"];

    myObj["Space Name"] 将会是字符串 KirkmyObj['More Space'] 将会是字符串 Spock,并且myObj["NoSpace"] 将会是字符串 USS Enterprise

    注意,如果属性名中包含空格,就必须使用引号(单引号或双引号)将它们包裹起来。

  • 对对象上使用方括号表示法,还可以访问对象上作为变量值存储的属性。 当你需要遍历对象的所有属性,或者根据一个变量的值查找对应的属性值时,这种写法尤其适用。

    以下是一个使用变量来访问属性的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const dogs = {
    Fido: "Mutt",
    Hunter: "Doberman",
    Snoopie: "Beagle"
    };

    const myDog = "Hunter";
    const myBreed = dogs[myDog];
    console.log(myBreed);

    字符串 Doberman 将会出现在控制台中。

    请注意,我们在使用变量名访问属性时,不要使用引号引起来,因为我们使用的是 ,而不是 属性名


在你创建了 JavaScript 对象后,你可以随时更新属性,就像更新任何其他变量那样。 你可以使用点或中括号操作符来更新。

举个例子,让我们看看 ourDog

1
2
3
4
5
6
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 对象添加属性。

这里展示了如何给 ourDog 添加一个属性 bark

1
ourDog.bark = "bow-wow";

或者

1
ourDog["bark"] = "bow-wow";

现在,当我们执行 ourDog.bark 时,就能得到他的叫声,bow-wow

例如:

1
2
3
4
5
6
7
8
const ourDog = {
"name": "Camper",
"legs": 4,
"tails": 1,
"friends": ["everything!"]
};

ourDog.bark = "bow-wow";

删除对象的属性,例如:

1
delete ourDog.bark;

例如:

1
2
3
4
5
6
7
8
9
const ourDog = {
"name": "Camper",
"legs": 4,
"tails": 1,
"friends": ["everything!"],
"bark": "bow-wow"
};

delete ourDog.bark;

在上面代码的最后一行中,ourDog 是这样的:

1
2
3
4
5
6
{
"name": "Camper",
"legs": 4,
"tails": 1,
"friends": ["everything!"]
}

对象和字典一样,可以用来存储键/值对。 如果数据是扁平的,你可以用对象来查找你想要的值,而不是链式使用 switch 或 if/else 语句。 当你知道你的输入数据在某个范围时,这种查找方式极为有效。

这是一个文章对象的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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];

articleAuthor 是字符串 Kaashan HussainarticleLink 是字符串 https://www.freecodecamp.org/news/a-complete-guide-to-creating-objects-in-javascript-b0e2450655e8/valueLookup 是字符串 How to create objects in JavaScript


要检查一个给定对象上的属性是否存在,你可以使用.hasOwnProperty()方法SomeObject.hasOwnProperty(someProperty)根据该对象上是否有该属性,返回真或假。

示例

1
2
3
4
5
6
function checkForProperty(object, property) {
return object.hasOwnProperty(property);
}

checkForProperty({ top: 'hat', bottom: 'pants' }, 'top'); // true
checkForProperty({ top: 'hat', bottom: 'pants' }, 'middle'); // false

The first checkForProperty function call returns true, while the second returns false.


 JavaScript 对象是一种灵活的数据结构。 它可以储存字符串(strings)、数字(numbers)、布尔值(booleans)、数组(arrays)、函数(functions)和对象(objects)以及这些值的任意组合。

这是一个复杂数据结构的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 对象的时候,对象与对象之间要用逗号隔开。


可以通过连续使用点号表示法和方括号表示法来访问对象的嵌套属性。

这是一个嵌套对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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


你将创建一个帮助维护音乐专辑集的函数。 这个集合是一个包含多个相册的对象,这些相册也是对象。 每张专辑在集合中以唯一的 id 作为属性名来表示。 在每个专辑对象中,有各种描述专辑信息的属性。 并非所有专辑都有完整的信息。

updateRecords 函数有 4 个参数,即以下参数:

  • records - 一个包含多个专辑的对象
  • id - 一个数字,代表 records 对象中特定的专辑
  • prop - 一个字符串,代表相册属性名称
  • value - 一个字符串,包含用来更新相册属性的信息

使用下面的规则完成函数来修改传递给函数的对象。

  • 你的函数必须始终返回整个 records 对象。
  • 如果 value 是空字符串,从专辑里删除指定的 prop
  • 如果 prop 不是 tracks,并且 value 不是一个空字符串,将 value 赋给那个专辑的 prop
  • If prop is tracks and value isn’t an empty string, you need to update the album’s tracks array. First, if the album does not have a tracks property, assign it an empty array. Then add the value as the last item in the album’s tracks array.

注意: 将 recordCollection 对象的副本用于测试。 你不应该直接修改 recordCollection 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

15. 循环
15.1while循环

当 while 指定的条件为真,循环才会执行,反之不执行。

1
2
3
4
5
6
7
const ourArray = [];
let i = 0;

while (i < 5) {
ourArray.push(i);
i++;
}

在上面的代码里,while 循环执行 5 次把 0 到 4 的数字添加到 ourArray 数组里。

15.2 for循环

for,它可以循环指定次数。

for 循环中的可选三个表达式用分号隔开:

for (a; b; c),其中a为初始化语句,b为条件语句,c 是最终的表达式。

初始化语句只会在执行循环开始之前执行一次。 它通常用于定义和设置你的循环变量。

循环条件语句会在每一轮循环的开始前执行,只要条件判断为 true 就会继续执行循环。 当条件为 false 的时候,循环将停止执行。 这意味着,如果条件在一开始就为 false,这个循环将不会执行。

终止循环表达式在每次循环迭代结束, 在下一个条件检查之前时执行,通常用来递增或递减循环计数。

在下面的例子中,先初始化 i = 0,条件 i < 5 为 true 时,进入循环。 每次循环后 i 的值增加 1,然后执行终止循环条件表达式 i++

1
2
3
4
5
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。

1
2
3
4
5
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。

1
2
3
4
5
const ourArray = [];

for (let i = 10; i > 0; i -= 2) {
ourArray.push(i);
}

ourArray 现在将包含 [10, 8, 6, 4, 2]。 让我们改变初始值和最后的表达式,这样我们就可以按照奇数从后往前两两倒着数。


使用 For 循环遍历数组

JavaScript 中的一个常见任务是遍历数组的内容。 一种方法是使用 for 循环。 下面的代码将输出数组 arr 的每个元素到控制台:

1
2
3
4
5
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


循环嵌套

如果你有一个二维数组,可以使用相同的逻辑,先遍历外面的数组,再遍历里面的子数组。 下面是一个例子:

1
2
3
4
5
6
7
8
9
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] 本身就是一个数组。

15.3 do…while 循环

 do...while 循环,不论什么情况,它都会首先 do(运行)循环里的第一部分代码,然后 while(当)规定的条件被评估为 true(真)的时候,它会继续运行循环。

1
2
3
4
5
6
7
const ourArray = [];
let i = 0;

do {
ourArray.push(i);
i++;
} while (i < 5);

上面的示例行为类似于其他类型的循环,由此产生的数组将看起来像 [0, 1, 2, 3, 4]。 然而,do...while 不同于其他循环的地方,是第一次循环检查失败时的行为。 让我们看看代码示例。 这里是一个常规的 while 循环,只要 i < 5,就会在循环中运行代码:

1
2
3
4
5
6
7
const ourArray = []; 
let i = 5;

while (i < 5) {
ourArray.push(i);
i++;
}

这个例子中,定义了一个空数组 ourArray 以及一个值为 5 的 i 。 当执行 while 循环时,因为 i 不小于 5,所以循环条件为 false,循环内的代码将不会执行。 ourArray 最终没有添加任何内容,因此示例中的所有代码执行完时,ourArray 仍然是[]。 现在,看一下 do...while 循环。

1
2
3
4
5
6
7
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 循环将值添加到数组中。

15.4 递归

使用递归代替循环

递归是函数调用自身的操作。 为了便于理解,有如下任务:计算数组内元素前 n 的元素乘积。 使用 for 循环, 可以这样做:

1
2
3
4
5
6
7
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 以调用自身而无需依赖循环。

1
2
3
4
5
6
7
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 时)必需有一个跳出结构,否则永远不会执行完毕。


使用递归创建一个倒计时

函数返回一个从 1 到传递给函数的指定数字的连续数字数组。

正如上一个挑战提到的,会有一个 base case。 base case 告诉递归函数什么时候不再需要调用其自身。 这是简单 情况,返回得到的值。 还有 recursive call,继续用不同的参数调用自身。 如果函数无误,一直执行直到 base case 为止。

比如,如果想写一个递归函数,返回一个数字 1 到 n 的连续数组。 这个函数需要接收一个参数 n 代表最终数字。 然后会持续的调用自身,传入一个比 n 更小的值一直到传入的值是 1 为止。 函数如下:

1
2
3
4
5
6
7
8
9
10
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]


资料查找

我们有一个对象数组,里面存储着通讯录。

lookUpProfile 函数已经写好了参数,需要 name 和属性 (prop) 参数。

函数将会检查通讯录中是否存在一个 firstName 与传入的 name 相同的联系人。 如果存在,那么还需要检查对应的联系人中是否存在 prop 属性。

如果它们都存在,函数返回 prop 属性对应的值。

如果 name 不对应于任何联系人,然后返回字符串 No such contact

如果 prop 属性在匹配 name 的联系人里不存在,返回 No such property

1
2
3
4
5
6
7
8
9
10
11
12
function lookUpProfile(name, prop) {
for (let x = 0; x < contacts.length; x++) {
if (contacts[x].firstName === name) {
if (contacts[x].hasOwnProperty(prop)) {
return contacts[x][prop];
} else {
return "No such property";
}
}
}
return "No such contact";
}
16.
16.1 随机数

随机数非常适合用来创建随机行为。

在 JavaScript 中,可以用 Math.random() 生成一个在0(包括 0)到 1(不包括 1)之间的随机小数。 因此 Math.random() 可能返回 0,但绝不会返回 1


可以用Math.random()生成随机小数,但有时你需要生成随机整数。下面的过程会给你一个小于20的随机整数:

  • 使用Math.random()生成一个随机的小数。

  • 将该随机小数乘以20。

  • 使用Math.floor()将这个数字四舍五入为最接近的整数。

  • 记住,Math.random()不可能完全返回1,所以不可能真正得到20,因为你是用Math.floor()四舍五入的。这个过程会给你一个在0到19范围内的随机整数。

把所有东西放在一起,这就是你的代码的样子:

1
Math.floor(Math.random() * 20);

你正在调用Math.random(),将结果乘以20,然后将该值传给Math.floor(),将该值四舍五入为最近的整数。


你可以在从零到给定数字的范围内生成一个随机整数。你也可以为这个范围挑选一个不同的下限数字。

你将把你的最小数称为min,最大数称为max。

这个公式给出了一个从min到max范围内的随机整数。仔细看看并尝试理解这行代码到底在干嘛:。

1
Math.floor(Math.random() * (max - min + 1)) + min

16.2 parseInt 函数

parseInt() 函数解析一个字符串返回一个整数。 下面是一个示例:

1
const a = parseInt("007");

上述函数将字符串 007 转换为整数 7。 如果字符串中的第一个字符不能转换为数字,则返回 NaN


parseInt() 函数解析一个字符串并返回一个整数。 它还可以传入第二个参数,指定了字符串中数字的基数。 基数可以是 2 到 36 之间的整数。

函数调用如下所示:

1
parseInt(string, radix);

这是一个示例:

1
const a = parseInt("11", 2);

变量 radix 表示 11 是在二进制系统中。 这个示例将字符串 11 转换为整数 3

16.3 三元运算符

条件运算符( conditional operator,)(也称为三元运算符( ternary operator))的就像写成一行的 if-else 表达式

语法是:a ? b : c, where a 是条件,当条件返回 true 的时候运行代码 b,当条件返回 false 的时候运行代码 c

以下函数使用 if/else 语句来检查条件:

1
2
3
4
5
6
7
8
function findGreater(a, b) {
if(a > b) {
return "a is greater";
}
else {
return "b is greater or equal";
}
}

这可以使用三元运算符重写:

1
2
3
function findGreater(a, b) {
return a > b ? "a is greater" : "b is greater or equal";
}

将多个运算符串联在一起以检查多种条件。

下面的函数使用 ifelse if 和 else 语句来检查多个条件:

1
2
3
4
5
6
7
8
9
10
11
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";
}
}

以上函数可以使用多个三元运算符重写:

1
2
3
4
5
function findGreaterOrEqual(a, b) {
return (a === b) ? "a and b are equal"
: (a > b) ? "a is greater"
: "b is greater";
}

如上文所示,对多个三元运算符进行每个条件都是单独一行的格式化被认为是最佳做法。 使用多个三元运算符而没有适当的缩进可能会使您的代码难以理解。 例如:

1
2
3
function findGreaterOrEqual(a, b) {
return (a === b) ? "a and b are equal" : (a > b) ? "a is greater" : "b is greater";
}