第3章 语言基础
# 语法
# 区分大小写
ECMAScript 中,一切都区分大小写。 typeof 和 Typeof 完全不同。
# 标识符
标识符,就是变量、函数、属性或函数参数的名称。
- 第一个字符必须是字母、
_
或$
- 剩下的其他字符可以 是字母、下划线、美元符号或数字
标识符推荐使用驼峰大小写形式,如 myName。
# 注释
单行注释
// 单行注释
1多行注释
/* 多行注释 */
1
2
3
# 严格模式
一般在脚本开头加上
'use strict'
不规范写法在严格模式下会被处理,不安全的活动会报错。
# 语句
建议以分号结尾。
if 之类的控制语句在执行多行语句时要求有代码块,即左右 花括号 包裹。
# 关键字和保留字
规定或建议,不能用关键字作为标识符或属性名。
# 变量
# var
var 声明的变量在当前作用域下生效。另外,变量的查找遵循变量搜索原则。
var 声明的变量会自动提升到函数作用域的顶部。
var 在全局作用域下声明的变量会成为 window 对象的属性。
# let
与 var 不同,let 声明的范围是块作用域,它是函数作用域的子集。
let 声明的变量不会提升。
let 在全局作用域中声明的变量不会成为 window 对象的属性。
不允许在 let 声明之前使用变量,也不允许在同一作用域中重复声明。
for 循环中的 let 声明,每个迭代循环会声明一个新的迭代变量,如
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, 0)
}
// 0,1,2,3,4
2
3
4
5
6
# const
基本语法和 let 相似,不同点是它声明的变量必须初始化,同时不允许再次赋值。
对于对象类型,它只是指向变量的引用,对象内部属性的修改是允许的。
# 最佳实践
不再使用 var。
优先使用 const,对于可预见会被修改的变量,可以使用 let。
# 数据类型
- 6 种简单数据类型
- Undefined
- Null
- Boolean
- Number
- String
- Symbol
- 复杂数据类型
- Object,包括 object,Array,function,内置对象等
判断类型的方式
typeof:可用来判断简单数据类型。typeof a
Instanceof:判断构造函数的原型是否在该对象的原型链上。 b instanceof Array
Object.prototype.toString.call(xxx)
# undefined
对于声明了却未初始化的变量,如下
let a
console.log(a) // undefined
console.log(typeof a) // 'undefined'
2
3
如果未定义一个变量,则不能直接访问,会报错;但可以使用 typeof 来执行
console.log(b) // ReferenceError: b is not defined
console.log(typeof b) // 'undefined'
2
# null
逻辑上讲,null 值表示一个空对象指针
let a = null
console.log(typeof a) // 'object'
2
所以,当声明一个对象类型的变量,但不确定值时,可以使用 null 来初始化。
# boolean
只有 true 和 false2 个值,在 if 等流程控制语句中会将其他类型转为 boolean 类型
// string
!!'1' === true
!!'0' === true
!!'' === false
// number
!!0 === false
!!NaN === false
!!1 === true
!!Infinity === true
// object
!!{} === true
!![] === true
!!null === false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# number
包括二进制、八进制、十进制、十六进制等
科学计数法,如 3.125e7===31250000
精度丢失,因为十进制转二进制计算时,二进制表示的数会出现 0 舍 1 入,导致精度丢失。经典示例如 0.1+0.2 !==0.3
值的范围:最大数 Number.MAX_VALUE,最小数 Number.MIN_VALUE,超出这个范围则为无穷大,可用 isFinite 判断一个数是否无穷大。
console.log(isFinite(1)) // true
console.log(isFinite(Number.MAX_VALUE)) // true
console.log(isFinite(Number.MAX_VALUE + Number.MAX_VALUE)) // false
2
3
NaN:Not a Number。
console.log(0 / 0) // NaN
console.log(+0 / 0) // NaN
console.log(+0 / -0) // NaN
console.log(Number('a')) // NaN
console.log(1 / 0) // Infinity
console.log(-1 / 0) // -Infinity
2
3
4
5
6
涉及 NaN 的操作始终返回 NaN;NaN 不等于任何值(包括 NaN)
console.log(NaN + 1) // NaN
console.log(NaN === NaN) // false
2
可用 IsNaN()函数判断是否 NaN,它会先尝试将参数转为数值类型
console.log(isNaN(NaN)) // true
console.log(isNaN('a')) // true
console.log(isNaN('')) // false
console.log(isNaN(10)) // false
console.log(isNaN(true)) // false
2
3
4
5
数值转换:有 3 个函数,Number(),parseInt(),parseFloat()
注意:parseInt('') 得到 NaN,另外可以传入第二个参数,表示进制。
# string
表示方式:'' 、""、``
除了 null 和 undefined 没有 toString 方法,其他类型都有。可以传入一个参数,表示进制。
如果要强制转换成 string,可以使用 String()
# Symbol
译为”符号“,Symbol 实例是唯一,不可变的,用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
# 基本用法
let foo1 = Symbol('foo')
let foo2 = Symbol('foo')
console.log(typeof foo1) // symbol
console.log(foo1 === foo2) // false
2
3
4
5
此外,Symbol 不能用作构造函数,即不能与 new 一起使用
# 全局符号注册表
由于 Symbol 实例具有唯一性,那么怎么共享 Symbol 呢,使用 Symbol.for()方法
// 局部!==全局
const localSymbol = Symbol('foo')
const globalSymbol = Symbol.for('foo')
console.log(localSymbol === globalSymbol) // false
// 全局可以共享
const globalSymbol2 = Symbol.for('foo')
console.log(globalSymbol === globalSymbol2) // true
2
3
4
5
6
7
8
此外可以使用 Symbol.keyFor()来查询全局注册表,入参为 Symbol 实例,返回对象的字符串键。
let s = Symbol.for('foo')
console.log(Symbol.keyFor(s)) // foo
2
# 常用内置符号
1.Symbol.iterator
表示一个方法,该方法返回对象默认的迭代器,由 for-of 语句使用,for-of 会利用这个函数执行迭代操作。
class Emitter {
constructor(max) {
this.max = max
this.idx = 0
}
*[Symbol.iterator]() {
while (this.idx < this.max) {
yield this.idx++
}
}
}
function count() {
let emitter = new Emitter(3)
for (let x of emitter) {
console.log(x)
}
}
count()
// 0
// 1
// 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
上述代码中,count 方法等同于下面的 count
function count() {
let emitter = new Emitter(3)
let iterator = emitter[Symbol.iterator]()
let progress = iterator.next()
console.log(progress.value)
progress = iterator.next()
console.log(progress.value)
progress = iterator.next()
console.log(progress.value)
}
2
3
4
5
6
7
8
9
10
11
12
2.Symbol.asyncIterator
与 Symbol.iterator 类似,不过这个表示实现异步迭代的函数,该方法返回对象默认的 AsyncIterator,由 for-await-of 使用。
class Emitter {
constructor(max) {
this.max = max
this.asyncIdx = 0
}
async *[Symbol.asyncIterator]() {
console.log('start')
while (this.asyncIdx < this.max) {
yield new Promise((resolve) => resolve(this.asyncIdx++))
}
}
}
async function asyncCount() {
let emitter = new Emitter(5)
console.log('emitter', emitter)
for await (const x of emitter) {
console.log(x)
}
// 等同于下面的调用
// let progress = emitter[Symbol.asyncIterator]()
// let a = await progress.next()
// console.log(a.value)
// a = await progress.next()
// console.log(a.value)
// a = await progress.next()
// console.log(a.value)
// a = await progress.next()
// console.log(a.value)
// a = await progress.next()
// console.log(a.value)
}
asyncCount()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
3.Symbol.hasInstance
可以理解为将 instanceof 转为函数行为
function Foo() {}
let foo = new Foo()
console.log(foo instanceof Foo) // true
console.log(Foo[Symbol.hasInstance](foo)) // true
2
3
4
5
6
此外还有 Symbol.match、Symbol.replace、Symbol.search、Symbol.split 等,这里不再列举。
# Object
对象是一组数据和功能的集合。Object 实例包括如下属性和方法。
constructor:创建当前对象的函数。
hasOwnProperty(propertyName):当前对象实例上(不是原型)是否存在给定的属性。
isPrototypeof(Object):用于判断当前对象是否为另一个对象的原型。
propertyIsEnumerable(propertyName):判断给定属性是否可以用 for-in 枚举。
toLocaleString():返回对象的字符串表示,该字符串反映对象所在本地化的执行环境。
toString():返回对象的字符串。
valueOf():返回对象对应的字符串、数值或布尔值表示。
# 一元操作符
# 1.递增/递减操作符
- 前缀版 ++age --age
- 后缀版 age++ age--
let age = 20
let newAge = age++
console.log(age) // 21
console.log(newAge) // 20
2
3
4
let age = 20
let newAge = ++age
console.log(age) // 21
console.log(newAge) // 21
2
3
4
递减类似,在之前的值减 1。
tips:这 4 个操作符可作用于任何值,可能涉及到数据类型转换。
# 2.一元加和减
常用于将值转换为数值类型。例如
let a = '1.2'
let b = false
console.log(+a) // 1.2
console.log(-b) // -0
2
3
4
# 位操作符
正值在计算机中以二进制格式存储,负值以二补数的二进制存储,过程如下
(1)确定绝对值的二进制表示,如-18 的绝对值为 18;
(2)找到数值的补数,即 0 变为 1,1 变为 0;
(3)给结果加 1。
18
二进制如右 0000 0000 0000 0000 0000 0000 0001 0010
补数 1111 1111 1111 1111 1111 1111 1110 1101
加一 1111 1111 1111 1111 1111 1111 1110 1110
得-18
2
3
4
5
按位非:~,返回数值的补数
按位与:&,根据真值表,两个操作数为 1 时返回 1,有 0 时则为 0.
按位或:|,两个数均为 0 时返回 0,有 1 时则为 1.
按位异或:^,两个数相同时返回 0,相异时为 1.
左移:<<,所有位数向左移动,注意符号位不动。
有符号右移:>>,所有位数向右移动,不足补 0,符号位不动。
无符号右移:>>>,所有位数向右移动,不足补 0,符号位也跟着移动。
# 布尔操作符
逻辑非:!,先转为布尔值,再取反。
逻辑与:&&,真真得真,有假得假。
逻辑或:||,有真得真,假假得假。
注意,&&和||都是短路操作符。
# 乘性操作符
乘法操作符:数学运算中的乘法,*
表示。
除法操作符:数学运算中的乘法,/
表示。
取模操作符:%,取余数。指数操作符:**,代替 Math.pow()
console.log(Math.pow(3, 2)) // 9
console.log(3 ** 2) // 9
2
# 加性操作符
加法操作符:+,求 2 个数的和。注意如果有字符串,则不是加法运算,而是字符串拼接。
let num1 = 5,
num2 = 10
console.log('result is ' + num1 + num2) // result is 510
console.log('result is ' + (num1 + num2)) // result is 15
2
3
4
减法操作符:-,数学运算减,也会进行类型转换。
# 关系操作符
比较两个值的大小,包括<
、>
、<=
、>=
,注意如下规则:
- 如果操作数都是数值,则比较数值
- 如果都是字符串,则逐个比较字符串中对应字符的编码
- 如果有一个是数值,则将另一个数转换为数值,进行数值比较
- 如果有对象,则调用其 valueOf 方法,取结果进行比较,否则调用 toString()方法比较
- 如果是布尔值,将其转为数值,进行比较
此外,任何关系操作符涉及比较 NaN 时都返回 false
console.log(NaN < 3) // false
console.log(NaN >= 3) // false
2
# 相等操作符
- ==,比较前会执行转换
- ===,不会执行类型转换,推荐使用
console.log(10 == '10') // true
console.log(10 === '10') // false
2
# 条件操作符
即三元表达式,variable = boolean_expression ? true_value : false_value
# 赋值操作符
包括简单赋值(=)和复合赋值(如+=,-=,*=等)
let num = 10
num = num + 10
// 可以简写为
num += 10 // 只是写法简洁,并不会提升性能
2
3
4
# 逗号操作符
有 2 种常见场景:
1.声明变量
let num1 = 1,
num2 = 2
2
2.辅助赋值,取表达式最后的一个值
let num = (3, 2, 1) // num=1
for (let i = 0, j = 0; i < 4, j < 3; i++, j++) {
console.log(i) // 0,1,2
}
for (let i = 0, j = 0; i < 3, j < 4; i++, j++) {
console.log(i) // 0,1,2,3
}
2
3
4
5
6
7
8
# 语句
也称为流控制语句。
# if 语句
if (true) {
console.log(1)
}
if (false) {
// ...
} else if (true) {
// ...
} else {
}
2
3
4
5
6
7
8
9
# while 语句
while (true) {
// ...
}
2
3
# do-while 语句
do {
// ...
} while (true)
2
3
# for 语句
for (let i = 0; i < 5; i++) {
console.log(i)
}
2
3
# for-in 语句
用来枚举对象中的非符号属性,是无序遍历
let obj = { a: 1, b: 2 }
for (const key in obj) {
console.log(obj[key])
}
2
3
4
# for-of 语句
用于遍历可迭代对象的元素,比如数组
let nums = [1, 2, 3]
for (const item of nums) {
console.log(item)
}
2
3
4
# 标签和 break/continue 语句
标签用来给语句做标识,一般配合 break 和 continue 使用
break 用于立即退出当前循环体,执行循环体外的下一条语句。
continue 用于中断当前轮次循环,会再次从循环体顶部继续执行下一轮次。
for (let i = 0; i < 3; i++) {
if (i === 1) {
continue
}
console.log(i) // 0 2
}
console.log('----')
for (let i = 0; i < 3; i++) {
if (i === 1) {
break
}
console.log(i) // 0
}
console.log('----')
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) continue
console.log(j) // 0 2 0 2 0 2
}
}
console.log('----')
// 仅跳出当前循环体
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) break
console.log(j) // 0 0 0
}
}
console.log('----')
// 使用标签:中断外层循环体
outer: for (let i = 0; i < 3; i++) {
inner: for (let j = 0; j < 3; j++) {
if (j === 1) continue outer
console.log(j) // 0 0 0
}
}
console.log('----')
// 使用标签:跳出外层循环体
outer: for (let i = 0; i < 3; i++) {
inner: for (let j = 0; j < 3; j++) {
if (j === 1) break outer
console.log(j) // 0
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# switch 语句
let i = 5
switch (i) {
case 1:
console.log('small')
break
case 5: // 没有break则表示满足一个即可
case 6:
console.log('mid')
break
default:
console.log('big')
break
}
2
3
4
5
6
7
8
9
10
11
12
13
# 函数
1.函数声明会提前
2.基本写法如下
function a(arg1, arg2) {
console.log(arg1, arg2)
return 1
}
2
3
4
包括函数名(a),参数(arg1,arg2),函数体 console.log(...),返回值(如过没有,则默认返回 undefined)
3.return 可以出现在函数任何地方,一旦出现,函数会立即停止执行并退出。