序言
开始第三章前,回顾一下第二章:介绍 script 元素将 JavaScript 引入 HTML 中,并在脚本禁用或不支持的情况下可以使用 noscript 元素。那么,第三章就开始需要认认真真了解 JavaScript 的语言基础了。其实做前端的这部分大家都会,不过有一些代码风格和没有注意到的点,查漏补缺,岂不是更好。好的,开始第三章。
MIND
语法
我之前是学 JAVA 的,JavaScript 的语法很它很像,很亲切。这里简单介绍下语法:
- 区分大小写
- 标识符:第一个字符必须 in
['字母','下划线(_)','美元符号($)'],推荐使用驼峰大小写形式,如colorList - 注释:采用 C 语言风格,“//” 单行、“/* */”多行
- 严格模式:使用
use strict设置严格模式 - 语句:推荐语句末尾添加分号
- 关键字和保留字:不可作为标识符或属性名,点击查看
变量声明
变量只不过是一个用于保存任意值的命令占位符
JavaScript 变量类型不像 Java 那种非常严谨,它是那种“鳝”变的,可以随时很轻松的改变变量类型。声明变量也相对简单方便很多,有 3 个关键字可以声明变量:var、let 和 const。
var
可以使用 var 随意的声明变量,是的,非常随意:
- 可以不初始化变量
- 会生成函数作用域:声明的变量会成为包含它的函数的局部变量
- 函数中声明忘记带 var,变量会成为全局变量,不推荐
- 可以重复声明变量
- 存在变量提升:所有声明提升到函数作用域顶部,声明前引用报 “undefined”
let
let 是 ES6 新增的,它和 var 很像,但是比 let 更适合用来声明变量,有以下特点
- 也可以不初始化变量
- 块作用域内不能重复声明变量
let 声明会构建块作用域,不允许出现冗余声明,这样会导致报错 - 不会引起变量提升:
let 声明前执行瞬间称为暂时性死区,在此阶段引用任何后面才声明的变量都会抛出引用错误 - 在全局作用域中声明变量不会成为全局变量
- 不存在变量提升
let 的作用域是块,不能重复声明,不存在同名变量需要在顶部作用域合并声明 - 适合 for 循环中的迭代变量,
let 声明迭代变量,会在后台为每个迭代循环声明一个新的迭代变量
const
const 和 let “同根生”,但是,是用来声明常量的,有以下特点:
- 与 var 和 let 不同,声明变量时必须初始化
- 声明仅限制所指向的变量的引用
- 如果引用的是非对象,不可修改
- 如果引用的是对象,可以修改对象内部属性,不能修改对象
- const 适合 for-in 和 for-of 的循环变量
声明风格
- 不使用 var,可以避免变量提升、重复声明等问题
- const 优先,let 次之,const 能够保持变量不变,提前发现不合法的赋值操作。在未来需要修改时,改为 let 声明即可。
数据类型
书中介绍的基础数据类型 6 种:Undefined、null、Boolean、Number、String 和 Symbol,其实还有 ES10 新增的 Bigint。还有复杂数据类型 Object。现在一一介绍
typeof
首先介绍检测工具,tyoeof 是检测数据类型的方法之一,返回规则如下:
- Undefined 和 Undeclared => "undefined"
- Boolean => "boolean"
- String => "string"
- Number => "number"
- Null 和 Object => "object"
- Function => "function"
- Symbol => "symbol"
- ES10 新增的 Bigint => "bigint"
Undefined
Undefined 类型就一个值,就是特殊值 undefined,目的是正式明确空指针(null)和未初始化变量的区别。
使用上文介绍的 var 或 let 声明但未初始化的时候,该变量就被赋予了 undefined 值。虽然被赋予这个值,但是不建议显式地给变量赋这个值
undefined 和 undeclared 的区别
- 由来:undefined 是声明还未赋值,而 undeclared 是未声明
- 引用:undeclared 变量引用会报错,而 undefined不会
另外,使用 typeof 检测,两者都是返回 "undefined",因此建议在声明变量时初始化,避免使用 typeof 混淆,从而知道那些变量是 undeclared。
Null
Null 类型同样是一个值,即特殊值 null。逻辑上讲,null 值表示一个空对象指针,这也就是给 typeof 传一个 null 会返回 “object” 的原因。
值得注意的是:
- 在定义将来要保存对象值的变量时,建议使用 null 来初始化
- undefined 值由 null 派生而来,
null == undefined
null 与 undefined 区别
- 意义:null 代表对象指针,undefined 代表声明未定义
- 标识符:undefined 不是保留字,可以用作变量名(不推荐),null 是保留字
- typeof:undefined 返回 "undefined",而 null 返回 "object"
Boolean
Boolean 使用频繁,就两个值: true 和 false。可通过 Boolean() 函数将其他类型转为 Boolean 类型。
Number
Number 类型使用 IEEE754 格式 表示整数和浮点数。
进制
Number 类型有用不同的数值字面量格式:
- 十进制:最基本的,如
10 - 八进制:前缀为
0或0o,如0o12 - 十六进制:前缀为
0X,如0XC
浮点数
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。
有以下特点:
- 浮点数存储空间是整数存储空间的两倍,所以 ECMAScript 会想方设法的将值转移为整数
- 数值采用 IEEE754 数值,存在舍入错误,导致
0.1 + 0.2 = 0.300 000 000 000 000 04
值的范围
- 最小数值:
Number.MIN_VALUE => 5e-324 - 最大数值:
Number.MAX_VALUE => 1.797 693 134 862 315 7e+308 - 正负无穷:超出范围采用
Infinity和-Infinity表示,用isFinite()函数检测
NaN
NaN 代表不是一个有效数值,用来表示本应返回数值的操作失败了。
它有以下特点:
- 涉及 NaN 的操作始终返回 NaN
- NaN 不等于任何数,包括自身
- 采用
isNaN()检测
数值转换
有三种方法:Number()、parseInt()、parseFloat() 三种,其中 Number() 转换最为复杂和特殊,优先使用后两者为好,所以先介绍后两者,Number() 在下文会介绍。
首先,parseInt() 有以下特点:
- 如果第一个字符是数值字符、加号或减号,则继续监测每个字符,直到字符串末尾或碰到非数值字符,如 "1234ABCD" 转为 “1234”
- 如果第一个字符不是上述情况,立即返回 NaN,包括空字符
- 可以传入第二个参数用于指定底数,值介于 2 到 36,不在区间返回 NaN
接着,parseFloat() 也很特殊:
- 始终忽略字符串开头的 0
- 只解析十进制,不能指定底数
- 其他进制返回 0
String
String 数据类型表示零或多个 16 位 Unicode 字符系列。
字符串有以下特点:
- 可以使用双引号(")、单引号(')、反引号(`)标示,但不可混用,如 'string"
- 转移序列表示一个字符,只能算一个字符
- 字符串通过 length 属性获取长度,但若字符串包含双字节字符,length 值可能不精确
- 字符串是不可变的,修改会销毁原始字符串,然后将新字符串保存到该变量
模板字符串
值得注意的是 ES6 新增的模板字符串,真的是非常好用:
- 定义 HTML 模板
let pageHtml = ` <div> <a href="#">google<a> </div>` - 字符串插值
let value = 1; console.log(`value is ${value}`) // value is 1
Symbol
Symbol 的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
这个数据类型时 ES6 新增的,先简单了解一下,应该需要新增一章讲解 Symbol。
定义
使用 Symbol() 进行初始化,可以传入字符串作为描述,该描述与 Symbol 定义 或 Symbol 本身无关。这个描述和 Symbol.for() 和 Symbol.keyFor() 结合使用。
let sym = Symbol(); // 初始化
let sym2 = Symbol(2); // 传入描述 2
全局 Symbol
Symbol.for() 会对每个字符串键执行幂等操作:先检查全局注册表中是否有描述的 Symbol,如果没有就生成全局 Symbol,否则返回 Symbol。
Symbol.keyFor() 则是用来查询是否是全局 Symbol,若是就返回 Symbol 描述,否则返回 undefined。
使用 Symbol 作为属性
这里是 Symbol 的关键:凡是可以使用字符串或数值作为属性的地方,都可以使用 Symbol
Bigint
用来表示大于 Number.MAX_VALUE,也就是大于 2^53 - 1 的整数。在整数后面添加 n 即可定义一个 Bigint。其中,后面介绍的无符号右移 Bigint 不支持,还有不能和非 Bigint 类型使用 + 运算符。
Object
对象其实就是一组数据和功能的集合。
JavaScript 里“万物基于对象”,原因是,Object 是所有对象的基类,通过 new 操作符来创建“子孙后代”。有以下任何对象通用的属性和方法:
- constructor:用于创建当前对象的函数
- hasOwnProperty(propertyName):用于判断当前对象实例上是否有给定属性,propertyName 必须为字符串
- isPrototypeof(object):用于判断当前对象是否是另一个对象的原型
- propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用 for-in 语句枚举,propertyName 必须为字符串
- toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
- toString():返回对象的字符串表示。
- valueOf():返回对象对应的字符串、数值或布尔值表示,通常与 toString() 返回值相同
类型转化
转 Boolean
使用 Boolean() 转换
| 数据类型 | true | false |
|---|---|---|
| String | 非空字符串 | 空字符串 |
| Number | 非零数值 | 0、NaN |
| Object | 所有对象 | 无 |
| Null | 无 | null |
| Undefined | 无 | undefined |
转 Number
使用 Number() 函数转换
- Boolean,true 转为 1, false 转为 0
- Null,返回 0
- Undefined,转为 NaN
- String:
- 数值字符转为数字
- 空字符串转为 0
- 含非数值字符转为 NaN
- Object:
- 先调用 valueOf() 转为基础类型,再转换
- 如果转换为 NaN,则使用 toString() 转为基础类型,再转换
- 还是不能转换,会报错
转 String
使用 String() 函数转换
- Number,转换为字符串形式
- Boolean,转换为 “true”,“false”
- Null,转为 “null”
- Undefined,转为 “undefined”
- Object:
- 如果没有修改对象的 toString() 方法,会返回内置属性 [[class]],如 “[Object Object]”
- 如有有修改,则实行修改的 toString() 方法并返回方法的返回值
操作符
操作符主要讲特殊规则
一元操作符
- 递增/递减:会返回递增或递减的值,但前缀相比后缀,变量的值会在语句求值前改变,而后缀在语句求值之后改变
- 一元加和减:非数值操作时,会使用 Number() 转型函数
位操作符
这部分很硬核,看看就好,后面再慢慢掌握
按位非(~)
作用是返回数值的一补数,最终效果是对数值取反并减一,相比正常操作来说,基于底层操作的按位非速度更快
let num = 6;
console.log(~num); // -7
按位与(&)
作用是将两个数的每一位对齐,然后基于真值表中的规则,对每一位执行响应的与操作。最终效果,两个位都是 1 时返回 1,在任何一位是 0 时返回 0.
let res = 25 & 3;
console.log(res) // 1
// 25 => 0000 0000 0000 0000 0000 0000 0001 1001
// 3 => 0000 0000 0000 0000 0000 0000 0000 0011
// AND=> 0000 0000 0000 0000 0000 0000 0000 0001
按位或(|)
至少一位是 1 时返回 1,两位是 0 时返回 0;
let res = 25 | 3;
console.log(res) // 27
// 25 => 0000 0000 0000 0000 0000 0000 0001 1001
// 3 => 0000 0000 0000 0000 0000 0000 0000 0011
// AND=> 0000 0000 0000 0000 0000 0000 0001 1011
按位异或(^)
只在一位上是 1 的时候返回 1
let res = 25 ^ 3;
console.log(res) // 26
// 25 => 0000 0000 0000 0000 0000 0000 0001 1001
// 3 => 0000 0000 0000 0000 0000 0000 0000 0011
// AND=> 0000 0000 0000 0000 0000 0000 0001 1010
左移(<<)
会按照指定的位数将数值的所有位向左移动
let num = 3; // 二进制 11
console.log(num << 5) // 96
// 1100000 => 96
有符号右移(>>)
左移的逆运算:将数值的 32 位向右移动,保留正负符号
let num = 96; // 二进制 1100000
console.log(num >> 5) // 3
// 11 => 96
无符号右移(>>>)
与有符号右移不同,无符号右移会给空位补 0,在处理负数时,与有符号右移结果不同
let num = -96; // 1111 1111 1111 1111 1111 1111 1101 0000
console.log(num >>> 5); // 134217725
// 0000 0111 1111 1111 1111 1111 1111 1101
布尔操作符
布尔操作符共“与或非”三种
逻辑非(!)
会将操作符根据 Boolean() 转换为布尔值,然后取反
逻辑与(&&)
逻辑与由两个和号(&&)表示,但返回的不一定是布尔值,有以下规则
- 两操作数都是布尔值时,全真为真,遇假为假
- 当第一个操作数为对象就返回第二个操作数
- 当第二个操作数为对象时,只有第一个操作数求值为 true,才会返回对象
- 如果有操作数为 null\NaN\undefined,则返回该操作数
逻辑或(||)
- 两操作数都是布尔值时,全假为假,遇真为真
- 第一个操作数求值为 false,返回第二个操作数
- 第一个操作数为对象,返回该对象
- 两个操作数为 null\NaN\undefined,返回这些值
乘性操作符
乘性操作符共乘法、除法、取模三种操作符,非数值操作数,通过 Number() 转换为数值再计算
乘法操作符(*)
| 被乘数 | 乘数 | 返回 |
|---|---|---|
| 数值 | 数值 | 积或正负 Infinity |
| NaN | 其他 | NaN |
| 其他 | NaN | NaN |
| NaN | NaN | NaN |
| Infinity | 0 | NaN |
| Infinity | 非 0 的有限数值 | 正负形式的 Infinity |
| Infinity | Infinity | Infinity |
除法操作符(/)
| 被除数 | 除数 | 返回 |
|---|---|---|
| 数值 | 数值 | 商或正负 Infinity |
| NaN | 其他 | NaN |
| 其他 | NaN | NaN |
| NaN | NaN | NaN |
| 0 | 0 | 0 |
| 非 0 的有限值 | 0 | 正负 Infinity |
| Infinity | 其他 | 正负 Infinity |
取模操作符(%)
| 被除数 | 除数 | 返回 |
|---|---|---|
| 数值 | 数值 | 余数 |
| Infinity | 有限值 | NaN |
| 有限值 | 0 | NaN |
| Infinity | Infinity | NaN |
| 有限值 | Infinity | 被除数 |
指数操作符
ES7 新增 ** 操作符替代 Math.pow()
console.log(Math.pow(3, 2) // 9
console.log(3 ** 2) // 9
加性操作符
加法操作符(+)
| 被加数 | 加数 | 返回 |
|---|---|---|
| 数值 | 数值 | 和 |
| NaN | 其他 | NaN |
| 其他 | NaN | NaN |
| NaN | NaN | NaN |
| Infinity | Infinity | Infinity |
| -Infinity | -Infinity | -Infinity |
| Infinity | -Infinity | NaN |
| +0 | +0 | +0 |
| -0 | +0 | +0 |
| -0 | -0 | -0 |
如果有操作数是非 Number 类型,存在规则:
- 两数为字符串,后者拼接到前者末尾
- 其一为字符串,将另一操作数转换为字符串,再拼接
- 其一为对象、数值或布尔值,调用该值的 toString() 方法获取字符串,再计算
减性操作符(-)
| 被减数 | 减数 | 返回 |
|---|---|---|
| 数值 | 数值 | 差 |
| NaN | 其他 | NaN |
| 其他 | NaN | NaN |
| NaN | NaN | NaN |
| Infinity | Infinity | NaN |
| -Infinity | -Infinity | NaN |
| Infinity | -Infinity | Infinity |
| -Infinity | Infinity | -Infinity |
| +0 | +0 | +0 |
| +0 | -0 | -0 |
| -0 | -0 | +0 |
如果有操作数是非 Number 类型,存在规则:
- 其一为字符串、布尔值、null、undefined,先在后台使用 Number() 转换为数值,再计算
- 其一为对象,先调用 valueOf 转数值,如果没有该方法就调用 toString() 方法,再根据规则计算
关系操作符(<、>、<=、>=)
- 操作数都是数值,进行比较
- 操作数都是字符串,逐个比较字符串中对应字符的编码
- 操作数有数值,则另一操作数执行 Number() 转化
- 操作数有对象,则调用对象的 valueOf() 取数值,没有 valueOf() 则调用 toString() 取字符串,再比较
- 操作数有布尔值,将布尔值转为数值,再比较
相等操作符
该操作符分为等于操作符和全等操作符
等于和不等于(==、!=)
- 操作符其一为布尔值,将其转为数值,再比较
- 操作符其一为字符串,另一操作符为数值,尝试将字符串转换为数值,再比较
- 操作符其一为对象,另一个操作符为非对象,调用对象的 valueOf(),如无调用 toString(),再比较
null == undefined- null 和 undefined 不能转换为其他值再进行比较
- 操作符其一为 NaN,则不相等
- 操作符皆对象,比较二者是否指向同一个对象
全等和不全等(===、!==)
全等操作符比较时不转换操作数,更为推荐使用
条件操作符
其实使用的是三元表达式,语法:
variable = boolean_expression ? true-value : false_value;
boolean_expression 为真时,把 true-value 赋值给 variable,反之 将 false-value 赋值给 false_value。
赋值操作符
num = num + 10 => num += 10,类似很多简写语法
语句
- if:会调用 Boolean() 转换函数,建议使用语句块
- do-while:循环体中代码至少执行一次,才进行退出条件判断
- while:先检测条件
- for:使用 let 声明迭代器变量,变量作用域可以限定在循环中
- for-in:用于枚举对象中的非符号属性,不能保证返回对象属性的顺序,通常用于遍历对象
- for-of:用于遍历可迭代对象的元素,根据迭代对象 next() 产生指的顺序迭代元素
- break:用于退出循环,强制循环下一句语句
- continue: 用于退出循环,再从顶部执行循环
- with:将代码作用域设置为特定的对象,不推荐使用
- switch:根据 case 执行语句,case 可以是常量、变量或表达式,建议每个条件后面添加 break 语句
函数
函数对于任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。
基本语法为:
function functionName (arg0, arg1, ..., argN) { // 声明
statements // 语句
}
functionName(); // 调用
函数使用 return 返回数值,需要注意:
- return 不是强制的
- 如果带 return 不建议使用条件返回(特定条件才返回)
- 函数执行到 return 语句会立即停止执行并退出
- return 不带值的时候,返回的是 undefined
还需要注意严格模式下的限制:
总结
第三章不比前两章,知识点多且密集,主要介绍了 JavaScript 的语法、变量声明、数据类型、操作符、语句还有函数。虽然大多数知识点大家都了解,但还是要注意数据类型转换和部分操作符等这些小知识点。下一章是介绍变量、作用域和内存。