JavaScript 学习笔记 (一)
前言
本文是通过 廖雪峰的 JavaScript 教程 学习而来. 全文内容几乎与教程无差别, 即本人跟着教程文字和代码均手敲一边, 遂产生此笔记. 笔记文件太大, 遂拆分成多个部分, 这是第一部分, 主要学习的是 JavaScript 基础语法, 包括数据类型、条件判断、循环、Map 和 Set 等.
1. 基础语法
1.1 数据类型与变量
计算机可以处理各种类型(文本、图形、音频、视频等)的数据,不同的数据需要用不同的类型定义,JavaScript 中定义了以下几种数据类型.
1.1.1 Number
JavaScript 不区分整型和浮点型,统一使用 Number 表示。
| 1 | 123; 整数 123 | 
Number可以直接做四则运算,规则与数学一致:
| 1 | 1 + 2; // 3 | 
1.1.2 字符串
字符串是以单引号或者双引号括起来的任意文本,比如 'abc' ,'xyz' 等等。
1.1.3 布尔值
布尔值和布尔代数表示完全一致,布尔值只有 true,false 两种值。
1.1.4 BigInt
用来表示比 2^53 还有大的整数,可以用内置的 BigInt 类型,它的表示方法是在整数后加一个 n ,例如 9223372036854775808n。不能将一个 BigInt 和一个 Number 放在一起运算,需要将 Number 转换成 BigInt。
1.1.5 null 和 undefined
null 表示一个空值,它和 0 以及空字符串 '' 不同,0 是一个数值,'' 表示长度为0的字符串,而 null 表示”空”。
undefined 表示未定义,区分两者的意义并不大,一般都用 null ,undefined 仅仅在判断函数参数是否传递的情况才有用。
1.1.6 数组
数组是一组按顺序排列的集合,集合的每个值称为元素。JavaScript 的数组中可以包括任意数据类型的元素。例如:
| 1 | [1, 3.14, 'Hello', null, true] | 
上述是通过字面量的方式创建数组,还可以用 Array() 函数定义数组:
| 1 | new Array(1, 2, 3); | 
1.1.7 对象
JavaScript 的对象是由一组键值对(key-value)组成的 无序集合 ,例如:
| 1 | const person = { | 
获取对象的属性,通过 对象变量.属性名 的方式:
| 1 | person.name; | 
1.1.8 变量
变量基本和初中代数中的方程变量概念一致,只是计算机中,变量不仅可以是数值,还可以是任意数据类型。变量在 JavaScript 中就是一个变量名表示,变量名是大小写、数字、$、_ 的组合,但不能是数字开头。变量名也不能是 JavaScript 的保留字,if、while 等。申明变量用 var 语句,例如:
| 1 | var a; | 
JavaScript 中变量名也可以是中文,但没必要麻烦。使用 = 为变量赋值,可以重复赋值,但不能重复申明。JavaScript 中变量本身类型不固定,这样的语言称为动态语言,而声明变量时必须指定类型的是静态语言,例如 Java,赋值语句如下:
| 1 | int a = 123; | 
不要把 = 等同于数学的等号,如下例子:
| 1 | var x = 2; | 
1.1.9 strict 模式
JavaScript 设计之处,并不强制使用 var 声明变量, 产生了严重的后果,如果一个变量没有通过 var 声明就使用,将变成全局变量:
| 1 | i = 10 | 
在同一个页面不同的 JavaScript 文件中,如果都不用 var 声明,而恰好都使用了变量 i ,将造成变量 i 互相影响,产生很难调试的 BUG。使用 var 声明,则不是全局变量,它的范围被限制在该变量声明的函数体内,同名变量在不同的函数体内不会冲突。为了修补这个设计缺陷,ECMA 在后续推出了 strict 模式,在 strict 模式中运行的 JavaScript 代码,强制通过 var 声明变量,未使用 var 声明变量的,将导致运行错误。
启用 strict 模式的方法是在 JavaScript 代码第一行写上:
| 1 | ; | 
这是一个字符串,不支持 strict 模式的浏览器会把它当作字符串语句运行,支持 strict 模式的浏览器才会开启 strict 模式运行 JavaScript。
另一种声明变量的方式是 let ,这是现代 JavaScript 推荐的方式:
| 1 | let s = 'Hello'; | 
1.2 字符串
JavaScript 的字符串就是用 '',"" 包裹的字符表示。
如果 ' 本身是一个字符,就需要用 "" 包裹,比如 I'am OK! ,如果 " 也是一个字符,就需要用转义字符 \ 表示,比如:
| 1 | 'I\'am \"OK\"!'; | 
转义字符可以表示很多字符,如下:
| 1 | '\n'; 表示换行 | 
1.2.1 多行字符串
由于多行字符串用 \n,写起来费劲,最新的 ES6 标准新增了一种多行字符串表示方法,用反引号`…` 表示:
| 1 | `这是一个 | 
1.2.2 模板字符串
要把多个字符串连接起来,可以使用 + 号连接:
| 1 | let name = 'zhang san'; | 
显然这种方法很麻烦,ES6 新增了一种模板字符串,表示方法和上面的多行字符一样,但是它会自动替换字符串中的变量:
| 1 | let name = 'zhang san'; | 
1.2.3 操作字符串
字符串常规操作如下:
| 1 | let s = 'Hello World!'; | 
要获取字符串某个指定位置的字符, 使用类似 Array 的下标操作, 索引号从 0 开始:
| 1 | let s = 'Hello, World!'; | 
toUpperCase
toUpperCase() 方法是把一个字符串全部变为大写:
| 1 | let s = 'Hello'; | 
toLowerCase
toLowerCase() 方法是把一个字符串全部变为小写:
| 1 | let s = 'World'; | 
indexOf
indexOf() 方法是搜索指定字符的出现位置:
| 1 | let s = 'Hello, World!'; | 
substring
substring() 返回指定索引区间的子串:
| 1 | let s = 'Hello, World'; | 
1.3 数组
JavaScript 的数组元素可以是任意数据类型, 并通过索引访问每个元素.  要取得 Array 的长度,  直接访问 length 属性:
| 1 | // Array.length | 
如果直接给 Array.length 赋一个新的值,  Array 的长度会发生变化:
| 1 | let arr = ['a', 'b', 'c']; | 
Array 通过索引修改元素的值, 如果索引超过 Array 长度, Array 长度也会发生变化:
| 1 | let arr = ['a', 'b', 'c']; | 
1.3.1 indexOf
与 String 类似,  Array 也可以通过 indexOf() 来搜索一个指定的元素的位置:
| 1 | let arr = [10, 20, '30', 'xyz']; | 
1.3.2 slice
slice() 就是对应 String 的 substring() 版本,  它截取 Array 的部分元素,  然后返回一个新的 Array:
| 1 | let arr = ['a', 'b', 'c', 'd']; | 
1.3.3 push 和 pop
push() 向 Array 的末尾添加 若干 元素,  pop() 则把 Array 的最后一个元素删除掉:
| 1 | let arr = [1, 2]; | 
1.3.4 unshift 和 shift
unshift() 用于向 Array 头部添加若干元素,  shift() 用于删除 Array 的头部元素:
| 1 | let arr = [1, 2]; | 
1.3.5 sort
sort() 可以对当前 Array 进行排序,  它会直接修改当前 Array 的元素位置,  直接调用时,按照默认顺序排序:
| 1 | let arr = ['b', 'c', 'a']; | 
后续函数中介绍指定顺序排序.
1.3.6 reverse
reverse() 把整个 Array 元素给调个个,  也就是反转:
| 1 | let arr = ['one', 'two', 'three']; | 
1.3.7 splice
splice() 方法是修改 Array 的万能方法,  它可以从指定的索引开始删除若干元素,  然后再从该位置添加若干元素:
| 1 | let arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Oracle']; | 
1.3.8 concat
concat() 方法把当前的 Array 和另一个 Array 连接起来,  并返回一个新的 Array:
| 1 | let arr = ['a', 'b', 'c']; | 
实际上 concat() 可以接受任意个 元素和 Array , 并且自动把 Array 拆分,  全部添加到新的 Array 中去.
| 1 | let arr = ['a', 'b', 'c']; | 
1.3.9 join
join() 方法是一个实用的方法,  它把当前 Array 的每一个元素都用指定的字符串连接起来,  然后返回连接后的字符串:
| 1 | let arr = ['a', 'b', 'c', 1, 2]; | 
1.3.10 多维数组
一个 Array 的元素中也有 Array, 就形成了多维数组, 例如:
| 1 | let arr = [1, 2, 3, [4, 5]] | 
1.4 对象
JavaScript 的对象是一种无序的集合数据类型, 它由若干键值对组成. JavaScript 的对象用于描述现实世界中的某个对象, 例如, 描述 “小明” 这个小朋友:
| 1 | let xiaoming = { | 
JavaScript 用 {~~~} 表示一个对象,  键值对用 xxx: xxx 声明,  用 , 隔开.  上述对象声明了一个 name 属性, 值是 小明,  birth 属性值是 2005,  以及其他属性.  最后把这个对象赋值给变量 xiaoming 后,  就可以通过变量 xiaoming 来获取小明的属性了.
| 1 | xiaoming.name; | 
访问属性是通过 . 操作符完成的,  但这要求属性名必须是一个有效的变量名,  如果属性名包含特殊字符,就必须用 '' 括起来, 访问这种属性, 必须使用 ['xxx'] 来访问:
| 1 | let xiaohong = { | 
也可以用 xiaohong['name'] 来访问 xiaohong 的 name 属性, 不过 xiaohong.name 写法更简洁.  写 JavaScript 代码时, 属性名尽可能使用标准的变量名,  这样就可以直接通过 Object.prop 的形式访问属性了.
实际上 JavaScript 对象的所有属性都是字符串,  不过属性对应的值可以是任意属性. 如果访问一个不存在的属性,  不会报错,  返回 undefined.
| 1 | let obj = { | 
因为 JavaScript 的对象属性是动态的, 可以自由地给一个对象添加或者删除属性:
| 1 | let xiaoming = { | 
要检测一个对象中是否有一个特定的属性, 可以使用 in 操作符:
| 1 | let xiaoming = { | 
但是用 in 判断一个属性是否存在时,  这个属性不一定是这个对象定义的,  可以是继承的:
| 1 | 'toString' in xiaoming; // true | 
因为 toString 定义在 object 对象中,  而所有对象都最终在原型链上指向 object , 所以 xiaoming 也拥有 toString 属性. 要判断是不是对象自己本身的,  可以用 hasOwnProperty() 方法:
| 1 | let xiaoming = { | 
1.5 条件判断
JavaScript 使用 if () {~~~} else {~~~} 进行条件判断, 例如,根据年龄显示不同内容:
| 1 | let age = 20; | 
省略 {} 的危险在于, 如果后来想添加一些语句,  却忘了写 {} 就改变了 if ~~~ else ~~~ 的语义:
| 1 | let age = 20; | 
1.5.1 多行条件判断
需要更细致的判断条件,  可以使用多个 if ~~~ else ~~~ 的组合:
| 1 | let age = 3; | 
注意 : if ~~~ else ~~~ 语句执行特点是二选一,  在多个 if ~~~ else ~~~ 语句中,  如果某个执行条件成立,  后续就不再继续判断了, 例如, 下面的代码输出 teenager:
| 1 | let age = 20; | 
JavaScript 把 null ,undefined,0,NaN 和空字符串 '' 视为 false,  其他值一概视为 true.
1.6 循环
要让计算机计算上千上万次的重复运算,  比如从 1 加到 100, 我们就需要循环语句. JavaScript 循环语句有两种,  一种是 for 循环, 通过初始条件,结束条件和递增条件来循环执行语句块:
| 1 | let s = 0; | 
for 循环最常用的是通过索引来遍历数组:
| 1 | let arr = ['Apple', 'Google', 'Microsoft']; | 
for 循环的三个条件都是可以省略的,  如果没有退出循环的判断条件,  就必须使用 break 退出循环,  否则就是死循环:
| 1 | let x = 0; | 
1.6.1 for … in
for 循环的一个变体是 for ... in 循环, 它可以把一个对象的所有 属性 (不是属性值) 依次循环出来:
| 1 | let obj = { | 
要过滤掉对象继承的属性, 使用 hasOwnProperty() 方法实现:
| 1 | let obj = { | 
由于 Array 也是对象,  而它的每个元素的索引被视为对象的属性,  因此 for ... in 同样可以循环出 Array 的索引:
| 1 | let a = ['a', 'b', 'c']; | 
注意 : for ... in 对 Array 的循环得到的索引是 String 类型, 而不是 Number 类型.
1.6.2 while
for 循环在已知循环的初始状态和结束条件时非常有用.  而上述忽略了条件的 for 循环容易让人看不清逻辑,  此时用 while 循环更好.  while 循环只有一个判断条件, 条件满足,  就不断循环,  条件不满足就退出循环.  比如用 while 循环计算 1~100 以内所有奇数和:
| 1 | let s = 0; | 
1.6.3 do … while
最后一种循环是 do { ... } while () 循环,  它和 while 循环的区别在于,  不是在每次循环开始时候进行判断条件,  而是在每次循环完成判断条件:
| 1 | let n = 0; | 
注意 : 使用 do { ... } while() 循环要小心,  循环体最少会执行一次,  而 for 和 while 循环则可能一次都不执行.
1.7 Map and Set
JavaScript 的默认对象表示方式 {} 可以视为其它语言中的 Map 或者 Dictionary 的数据结构,  即一组键值对.  但是 JavaScript 的对象有个问题,  即使键必须是字符串,  但实际上 Number 或者其他数据类型作为键也是非常合理的.  为了解决这个问题,  最新的 ES6 规范引入了新的数据类型, Map . 
1.7.1 Map
Map 是一组键值对结构, 具有极快的查找速度. 假设要根据同学的名字查找对应的成绩, 如果用 Array 实现, 就需要两个 Array:
| 1 | let names = ['Michael', 'Bob', 'Tracy']; | 
给定一个名字, 要查找对应的成绩, 就需要先在 names 中找到对应的位置, 再从 scores 取出对应的成绩, Array 越长, 查询越慢.
如果使用 Map 实现, 只需要一个 "名字"-"成绩" 的对照表,  直接根据名字查找成绩,  无论这个表有多大,  查找速度都不会变慢:
| 1 | let m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); | 
初始化 Map 需要一个二维数组, 或者直接初始化一个空 Map. Map 具体有以下方法:
| 1 | let m = new Map(); | 
1.7.2 Set
Set 和 Map 类似, 也是一组 key 的集合, 但是不存储 value. 由于 key 不能重复, 所以, 在 Set 中, 没有重复的 key.
我认为, Set 存储的是不重复的 value 而不是 key
要创建一个 Set , 需要提供一个 Array 作为输入, 或者创建一个空 Set:
| 1 | let s1 = new Set(); | 
重复元素在 Set 中被过滤掉:
| 1 | let s = new Set([1, 2, 3, 3, '3']); | 
通过 add(key) 方法可以添加元素到 Set 中, 可以重复添加, 但不会有效果:
| 1 | s.add(4); | 
通过 delete(key) 方法删除元素:
| 1 | let s = new Set([1, 2, 3]); | 
1.8 iterable
遍历 Array 可以采用下标循环, 便利 Map 和 Set 就无法使用下标. 为了统一集合类型, ES6 标准引入了新的 iterable 类型, Array、Map 和 Set 都属于 iterable 类型.
具有 iterable 类型的集合可以通过 for ... of 循环来遍历.
用 for ... of 循环遍历集合, 用法如下:
| 1 | let a = ['a', 'b', 'c']; | 
for ... in 和 for ... of 有什么区别 :
for ... in  循环由于是历史遗留问题,  它遍历的是对象的属性名称,  一个 Array 数组实际上也是一个对象,  它的每个元素的索引被视为一个属性.  当我们手动给 Array 对象添加了额外的属性后,  for ... in 循环将带来意想不到的效果:
| 1 | let a = ['a', 'b', 'c']; | 
for ... in 循环把 name 包括在内,  但 Array 的 length 属性却不包括在内. for ... of 循环则完全修复了这个问题, 它只循环集合本身的元素:
| 1 | let a = ['a', 'b', 'c']; | 
这就是为什么要引入 for ... of 循环.  然而,  更好的方式是直接使用 iterable 内置的 forEach 方法, 它接受一个函数,  每次迭代就自动回调该函数, 以 Array 为例:
| 1 | let a = ['a', 'b', 'c']; | 
Set 与 Array 类似, 但是 Set 没有索引, 因此回调函数的 element 和 index 都是元素本身:
| 1 | let s = new Set(['a', 'b', 'c']); | 
Map 的回调函数参数依次为 value、key 和 map 本身:
| 1 | let m = new Map([ [1, 'x'], [2, 'y'], [3, 'z'] ]); | 
如果不需要某些参数, 由于 JavaScript 的函数调用不要求参数必须一致, 因此可以忽略. 例如, 只需要获取 Array 的 element:
| 1 | let a = [1, 2, 3]; | 
第一部分结束
