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]; |
第一部分结束