[JS红宝书笔记]语言基础

1,121 阅读11分钟

序言

开始第三章前,回顾一下第二章:介绍 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
  • 八进制:前缀为 00o,如 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() 转换

数据类型truefalse
String非空字符串空字符串
Number非零数值0、NaN
Object所有对象
Nullnull
Undefinedundefined

转 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
其他NaNNaN
NaNNaNNaN
Infinity0NaN
Infinity非 0 的有限数值正负形式的 Infinity
InfinityInfinityInfinity

除法操作符(/)

被除数除数返回
数值数值商或正负 Infinity
NaN其他NaN
其他NaNNaN
NaNNaNNaN
000
非 0 的有限值0正负 Infinity
Infinity其他正负 Infinity

取模操作符(%)

被除数除数返回
数值数值余数
Infinity有限值NaN
有限值0NaN
InfinityInfinityNaN
有限值Infinity被除数

指数操作符

ES7 新增 ** 操作符替代 Math.pow()

console.log(Math.pow(3, 2) // 9
console.log(3 ** 2) // 9

加性操作符

加法操作符(+)

被加数加数返回
数值数值
NaN其他NaN
其他NaNNaN
NaNNaNNaN
InfinityInfinityInfinity
-Infinity-Infinity-Infinity
Infinity-InfinityNaN
+0+0+0
-0+0+0
-0-0-0
如果有操作数是非 Number 类型,存在规则:
- 两数为字符串,后者拼接到前者末尾
- 其一为字符串,将另一操作数转换为字符串,再拼接
- 其一为对象、数值或布尔值,调用该值的 toString() 方法获取字符串,再计算

减性操作符(-)

被减数减数返回
数值数值
NaN其他NaN
其他NaNNaN
NaNNaNNaN
InfinityInfinityNaN
-Infinity-InfinityNaN
Infinity-InfinityInfinity
-InfinityInfinity-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

还需要注意严格模式下的限制:

  • 函数不能以 evalarguments 作为名称
  • 函数参数不能叫 eval 或 arguments
  • 两个函数的参数不能叫同一个名称

总结

第三章不比前两章,知识点多且密集,主要介绍了 JavaScript 的语法、变量声明、数据类型、操作符、语句还有函数。虽然大多数知识点大家都了解,但还是要注意数据类型转换和部分操作符等这些小知识点。下一章是介绍变量、作用域和内存。