第三章-语言基础
3.1 语法
3.1.1 区分大小写
ECMAScript中一切都区分大小写
3.1.2 标识符
- 第一个字母表示是一个字母、下划线(_)或美元符号($)
- 剩下的其它字母可以是字母、下划线、美元符号或数字
最佳实践:驼峰标识
3.1.3 注释
- 单行注释
// 这是注释
- 多行注释
/**
注释1
注释2
*/
3.1.4 严格模式
"use strict"
可以在脚本的开头声明严格模式,也可以在函数的开头声明严格模式;
3.1.5 语句
最佳实践:语句最后添加分号
- 加分号便于通过删除空行来压缩代码
- 提升性能:解析器会尝试在合适的位置补上分号以纠正语法错误
3.2 关键字和保留字
3.3 变量
3.3.1 var关键字
声明变量不初始化的情况下,变量为undefined
3.3.1.1 var声明作用域
使用var操作符定义的变量会变成包含它的函数的局部变量
function test() {
var message = 'hi'; // 局部变量
}
test();
console.log(message); // error:变量为定义
如果变量声明的时候省略了var,变量就会提升为全局变量,但是不推荐这么做!
function test() {
message = 'hi'; // 全局变量
}
test();
console.log(message); // 'hi'
3.3.1.2 var声明提升
关键字声明的变量会自动提升到函数作用域的顶部
function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined
上面代码相当于:
function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined
3.3.2 let声明
let和var的重要区别
-
let声明的范围是块作用域,而var声明的范围是函数作用域
if (true) { var name = 'zzz'; console.log(name); // zzz } console.log(name); // zzz var不具备块作用域,所以name相当于全局变量if (true) { let name = 'zzz'; console.log(name); // zzz } console.log(name); // ReferenceError: name没有定义 let是具有块级作用域的,只能在if块中访问name -
let声明的变量同一个作用域中不能多次声明
var name; var name; // 这是ok的 let age; let age; // syntaxError: age已经声明过 -
暂时性死区:let声明的变量不会在作用域中提升
console.log(name); // undefined var name = 'aaa'; console.log(age); // ReferenceError: age没有定义 let声明的变量不能在声明之前使用 let age = 18; -
全局声明:var声明在全局作用域中的变量会成为window对象的属性,而let不会
var name = 'zzz'; console.log(window.name); // zzz let age = 18; console.log(window.age); // undefined -
for循环中的let、var
for (var i = 0; i < 5; i++) { // do something } console.log(i); // 5 var没有块作用域,相当于声明了一个全局变量for (let i = 0; i < 5; i++) { // do something } console.log(i); // refrenceError: i没有定义 let具有块级作用域for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // 5 5 5 5 5 所有的i都是同一个变量 }, 0); }for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // 0 1 2 3 4 每个循环迭代会声明一个新的迭代变量,每个setTimeout引用的都是不同的变量实例 }, 0); } 类似于 { let i = 0; setTimeout(() => { console.log(i); }) } { let i = 1; setTimeout(() => { console.log(i); }) } ... { let i = 4; setTimeout(() => { console.log(i); }) }
3.3.3 const声明
- const声明的变量,声明时必须同时初始化,并且不能修改(这里不能修改指的是不能修改变量所在的内存地址,如果是一个对象,是可以修改对象内部的属性的)
- const声明的变量,同一作用域下不允许重复声明,并且const的作用域也是块
3.3.4 声明风格及最佳实践
声明变量使用优先级: const > let > var,尽量不使用var
3.4 数据类型
- 6种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol
- 1种复杂数据类型: Object
3.4.1 typeof操作符
typeof操作符会返回下列字符串之一(首字母要小写):
- undefined: 值未定义
- boolean: 布尔值
- string:字符串
- number:数值
- object: 对象或者null
- function: 函数
- symbol: 符号
3.4.2 Undefined类型
-
未声明量typeof会返回undefined,未定义的变量typeof也会返回undefined
let name; console.log(typeof name); // undefined 未定义 console.log(typeof age); // undefined 未声明
3.4.3 Null类型
- typeof null 返回Object
- null == undefined null !== undefined
3.4.4 Boolean类型
| 数据类型 | 转换为TRUE | 转换为FALSE |
|---|---|---|
| Boolean | True | False |
| String | 非空字符串 | "" |
| Number | 非0数值(包括无穷值) | 0,NaN |
| Object | 任意对象 | Null |
| undefined | 不存在 | undefined |
3.4.5 Number类型
-
八进制(严格模式下八进制无效,不推荐使用八进制)
let intNum1 = 070; // 56 第一个数字必须是0 let intNum2 = 079; // 79 如果字面量中包含的数字超出了8,则会忽略前导0,当做十进制 let intNum3 = 08; // 8 -
十六进制
前缀:0x
3.4.5.1 浮点值
-
小数点后面没有数字或者小数点后面全是0,会当做整数处理
let floatNum1 = 1.; // 1 let floatNum2 = 10.0; // 10 -
浮点数的精确度最高可达17位小数,会带来浮点数判断问题
0.1 + 0.2 == 0.3 // false
3.4.5.2 值的范围
-
最大值:Number.MIN_VALUE
-
最小值:Number.MIN_VALUE
-
无穷大: 正(Infinity) 负(-Infinity)
-
判断是否为有限大:
isFinite()函数let value = Number.MAX_VALUE + Number.MAX_VALUE; console.log(isFinite(value)); // false
3.4.5.3 NAN 非数值
-
0、+0、-0相除会返回NaN
console.log(0/0); // NaN console.log(-0/+0); // NaN console.log(5 / 0); // Infinity console.log(5 / -0); // -Infinity -
任何涉及NaN的操作始终返回NaN
-
NaN不等于任何值,包括自身
console.log(NaN == NaN); // false -
isNaN()函数是否为NaN,如果可以参数可以转换为数值,返回false,否则返回true
3.4.5.4 数值转换
非数值转换为数值的三种方法:Number()、parseInt()、parseFloat()
-
Number()函数
数据类型 转换规则 布尔值 true->1 false->0 数值 直接返回 null 0 undefined NaN 字符串 1. 如果字符串包含数值字符,包括带+号,-号的情况,则转换为一个十进制数 2. 如果字符串包含有效地浮点值格式,如“1.1”,则会转换为相应的浮点值,会忽略前导0 3. 如果字符串包含有效地十六进制格式,如“0xf”,则会转换十进制整数值 4. 如果是空字符串,则返回0 5. 如果字符串包含除上述情况之外的其他字符,则返回NaN 对象 1. 先调用对象的valueOf()方法,并且按照上述(上述五种)规则转换返回的值 2. 如果转换的结果是NaN,则调用toString()方法,再按照字符串的规则转换 -
parseInt()函数-将字符串转换为数值
- 字符串最前面的空格会被忽略,从第一个非空格字符开始转换
- 如果第一个字符不是数值字符、加号或者减号,立即返回NaN(空串会返回NaN)
- 如果第一个字符是数值字符、加号或者减号,则会依次检测每个字符,直到字符串末尾或者碰到非数值字符,比如“1234blue”会转换为1234, “22.5”会被转换为22
- 以0x开头的会以16进制解析为对应的十进制
- parseInt接收第二个参数,用于指定进制数
let num1 = parseInt("1234blue"); // 1234 let num2 = parseInt(""); // NaN let num3 = parseInt("0xA"); // 10,解释为十六进制整数 let num4 = parseInt("22.5"); // 22 let num5 = parseInt('0xAF', 16); // 175 let num6 = parseInt('AF', 16); // 175 let num7 = parseInt('AF'); // NaN 没有指定进制数 -
parseFloat()函数
- parseFloat工作方式与parseInt函数类似,都是从位置0开始检测每个字符,直到解析到字符串的末尾或者解析到一个无效的浮点数值字符为止
- 第一次出现的小数点是有效的,但第二次出现的小数点就无效了,剩余字符将会被忽略,例如
22.34.5将会被解析为22.34 - 会始终忽略字符串开头的0,并且只解析十进制值
- 如果字符串表示整数(没有小数点或者小数点后面只有一个0),则会返回整数
let num1 = parseFloat("1234blue"); // 1234 let num2 = parseFloat("0xA"); // 0,只解析十进制 let num3 = parseFloat("22.5"); // 22.5 let num4 = parseFloat("22.34.5"); // 22.34 let num5 = parseFloat("0908.5"); // 908.5 let num6 = parseFloat("3.125e7"); // 321250000
3.4.6 String类型
3.4.6.1 字符字面量
3.4.6.2 字符串的特点
- 不可变:要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量
3.4.6.3 转换为字符串
-
toString(),该方法可以接收一个底数参数,以什么底数来输出数值的字符串表示(2,8,10,16)
-
toString()方法对null和undefined无效,如果不确定值是不是null或者undefined,则可以使用String()转型函数
- 如果值有toString()方法,则调用该方法并返回结果
- 如果是null,则返回'null'
- 如果是undefined,则返回'undefined'
-
用加号操作符也可以转换字符串
a + ''
3.4.6.4 模板字面量
let value = `aaaaaa`;
- 反引号
- 模板字面量会保持反引号内部的空格,空格也算字符,所以字符串长度会跟预想的不一样
- 模板字面量中也有可能会包含换行符
3.4.6.5 字符串插值
let value = 5;
let exponent = 'second';
let res = `${value} to the ${exponent} power is ${value*value}`
3.4.6.6 模板字面量标签函数
3.4.6.7 原始字符串
3.4.7 Symbol(符号)类型
- 符号是基本类型
- 符号实例是唯一的,不可变的
- 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险
3.4.7.1 符号的基本用法
typeof 返回 symbol
let sym = Symbol();
console.log(typeof sym); // symbol
可以传入一个字符串参数,但是这个字符串参数与符号定义或标识完全无关
let sym1 = Symbol();
let sym2 = Symbol();
let sym3 = Symbol('foo');
let sym4 = Symbol('foo');
console.log(sym1 == sym2); // false
console.log(sym3 == sym4); // false
不能new Symbol(),可以使用Object(mySymbol)将其包装为对象
3.4.7.2 使用全局符号注册表Symbol.for()
-
当不同部分需要共享和重用符号实例,可以用一个字符串作为键,在全局符号注册表中创建并重用符号
-
Symbol.for():当传入某个字符串调用时,会检查全局运行时注册表,如果发现不存在对应的符号,则新建并添加至注册表;如果发现与该字符串对应的符号,则重用let sym1 = Symbol.for('foo'); let sym2 = Symbol.for('foo'); console.log(sym1 == sym2); // true -
📢:在全局注册表中定义的符号与使用
Symbol()定义的符号并不等同let sym1 = Symbol('foo'); let sym2 = Symbol.for('foo'); console.log(sym1 == sym2); // false -
Symbol.for()的参数必须是字符串,不是字符串会转换为字符串let sym1 = Symbol.for(); console.log(sym1); // undefined -
可以使用
Symbol.keyFor()来查询全局注册表,该方法接收一个符号,返回全局符号中对应的字符串键;let sym1 = Symbol.for('foo'); console.log(Symbol.keyFor(sym1)); // foo // 如果查询的不是全局符号而是普通符号,则返回undefined let sym2 = Symbol('bar'); console.log(Symbol.keyFor(sym2)); // undefined // 如果传入Symbol.keyFor()的参数不是符号,则抛出TypeError错误 Symbol.keyFor(123); // TypeError: 123 is not a symbol
3.4.7.3 使用符号作为属性
-
凡是可以使用字符串或数值作为属性的地方,都可以使用符号
-
包括了对象字面量属性、
Object.defineProperty()、Object.defineProperties()定义的属性// 对象字面量 let s1 = Symbol('foo'); let o = { [s1]: 'foo val' }; console.log(o); // { Symbol(foo): foo val }// Object.defineProperty() let s2 = Symbol('bar'); Object.defineProperty(o, s2, { value: 'bar val' }); console.log(o); // { Symbol(foo): foo val, Symbol(bar): bar val }// Object.defineProperties() let s3 = Symbol('baz'); let s4 = Symbol('qux'); Object.defineProperties(o, { [s3]: { value: 'baz val' }, [s4]: { value: 'qux val' } }); console.log(o); // { Symbol(foo): foo val, Symbol(bar): bar val, Symbol(baz): baz val, Symbol(qux): qux val } -
Object.getOwnPropertyNames()返回对象实例的常规属性数组let s1 = Symbol('foo'); let s2 = Symbol('bar'); let o = { [s1]: 'foo val', [s2]: 'bar val', baz: 'baz val', qux: 'qux val' }; console.log(Object.getOwnPropertyNames(o)); // ['baz', 'qux'] -
Object.getOwnPropertySymbols()返回对象实例的符号属性数组console.log(Object.getOwnPropertySymbols(o)); // [Symbol(foo), Symbol(bar)] -
Object.getOwnPropertyDescriptors()同时返回包含常规和符合属性描述符的对象console.log(Object.getOwnPropertyDescriptors(o)); // {baz: {…}, qux: {…}, Symbol(foo): {…}, Symbol(bar): {…}} -
Reflect.ownKeys()会返回两种类型的键
console.log(Reflect.ownKeys(o)); // ['baz', 'qux', Symbol(foo), Symbol(bar)]
3.4.7.4 常用内置符号
TODO
3.4.8 Object类型
ECMAScript中的Object是派生其他对象的基类。Object类型的所有属性和方法在派生的对象上同样存在,每个Object实例都有如下属性和方法:
constructor: 用于创建当前对象的函数hasOwnProperty(propertyName): 用于判断当前对象实例(不是原型)上是否存在给定的属性,要检查的属性名必须是字符串或符号isPrototypeOf(Object): 用于判断当前对象是否为另一个对象的原型propertyIsEnumerable(propertyName): 用于判断给定的属性是否可以使用for-in语句枚举toLocaleString(): 返回对象的字符串表示,该字符串反映对象所在的本地化执行环境toString(): 返回对象的字符串表示valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同
3.5 操作符
3.5.1 一元操作符
3.5.1.1 递增/递减操作符
遵循如下规则
- 对于字符串,如果是有效地数值形式,则转换为数值再应用改变。变量类型从字符串变成数值
- 对于字符串,如果不是有效地数值形式,则将变量的值设置为NaN。变量类型从字符串变成数值
- 对于布尔值,true转化为1,false转化为0,再进行操作。变量类型从布尔值变成数值
- 对于浮点值,加1或减1
- 如果是对象,则调用其
valudOf()方法取得可以操作的值,再应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成数值
3.5.1.2 一元加和减
3.5.2 位操作符
ECMAScript中的所有数值都是以64位格式存储,但是位操作并不直接应用到64位表示,而是先把值转换为32位整数,再进行位操作,之后再把结果转换为64位。因此就只需要考虑32位整数即可
-
有符号整数使用32位的前31位表示整数值,第32位表示数值的符号,0表示正,1表示负
-
符号位决定了数值的其余部分的格式,正值以真正的二进制格式存储,负值以补码的二进制格式存储
- 确定绝对值的二进制表示
- 找到数值的反码,就是0变成1,1变成0
- 给结果加1
-
如果将位操作符应用到非数值,那么应该首先使用
Number()将其转换为数值
3.5.2.1 按位非 ~
按位非的最终效果是对数值取反并减1,但是执行速度会比减1快的多
let num1 = 25;
let num2 = ~num1;
console.log(num2); // -26
3.5.2.2 按位与 &
let res = 25 & 3;
console.log(res); // 1
3.5.2.3 按位或 |
let res = 25 | 3;
console.log(res); // 27
3.5.2.4 按位异或 ^
let res = 25 ^ 3;
console.log(res); // 26
3.5.2.5 左移 <<
let value1 = 2;
let value2 = value1 << 5; // 64
左移会保留所操作数值的符号,比如-2左移5位,将得到-64
3.5.2.6 有符号右移 >>
let value1 = 64;
let value2 = 64 >> 5; // 2
右移之后空位会出现在左侧,且在符号位之后,会用符号位的值来填充这些空位
3.5.2.7 无符号右移 >>>
会将数值的所有32位都向右移
- 对于正数,有符号右移和无符号右移的结果是一样的
- 对于负数,无符号右移会给空位补0,差异会非常大
3.5.3 布尔操作符
3.5.3.1 逻辑非
- 如果操作数是对象,则返回false
- 如果操作数是空字符串,则返回true
- 如果操作数是非空字符串,则返回false
- 如果操作数是数值0,则返回true
- 如果操作数是非0数值(包括Infinity),则返回false
- 如果操作数是null、undefined或NaN,则返回true
- 两个叹号(!!),相当于调用了转型函数
Boolean(),无论操作数是什么类型,第一个叹号总会返回布尔值,第二个叹号对该布尔值取反,从而给出变量真正对应的布尔值
3.5.3.2 逻辑与
如果有操作数不是布尔值,则逻辑与并不一定返回布尔值,遵循如下规则
- 如果第一个操作数是对象,则返回第二个操作数
- 如果第二个操作数是对象,则只有第一个操作数求值为true才返回该对象
- 如果两个操作数都是对象,则返回第二个操作数
- 如果有一个操作数是null,NaN或者undefined,则返回null,NaN或undefined
- 逻辑与操作具有短路操作
3.5.3.3 逻辑或
如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值,遵循如下规则
- 如果第一个操作数是对象,则返回第一个操作数
- 如果第一个操作数求值为false,则返回第二个操作数
- 如果两个操作数都是对象,则返回第一个操作数
- 如果两个操作数都是null或NaN或undefined,则返回null或NaN或undefined
- 逻辑或也具有短路操作
3.5.4 乘性操作符
3.5.4.1 乘法操作符
-
如果有任一操作数是NaN,则返回NaN
-
Infinity * 0 = NaN Infinity * Infinity = Infinity -
如果
Infinity乘以非0的有限数值,则根据第二个操作符的符号返回Infinity或-Infinity -
如果有不是数值的操作数,则先调用
Number()将其转换为数值,再进行操作
3.5.4.2 除法操作符
-
如果有任一操作数是NaN,则返回NaN
-
Infinity / Infinity = NaN 0 / 0 = NaN -
如果是非0的有限值除以0,则根据第一个操作符的符号返回
Infinity或-Infinity -
如果是
Infinity除以任何数值,则根据第二个操作数的符号返回Infinity或-Infinity -
如果有不是数值的操作数,则先调用
Number()函数将其转换为数值,再进行操作
3.5.4.3 取模操作符
- 如果操作数是数值,则执行常规除法运算,返回余数
- 如果被除数是无限值,除数是有限值,则返回NaN
- 如果被除数是有限值,除数是0,则返回NaN
- 如果是
Infinity除以Infinity,则返回NaN - 如果被除数是有限值,除数是无限值,则返回被除数
- 如果被除数是0,除数不是0,则返回0
- 如果有不是数值的操作数,则先调用
Number()函数将其转换为数值,再进行操作
3.5.5 指数操作符
console.log(Math.pow(3, 2)); // 9
console.log(3 ** 2); // 9
console.log(Math.pow(16, 0.5)); // 4
console.log(16 ** 0.5); // 4
3.5.6 加性操作符
3.5.6.1 加法操作符
-
如果有任一操作符是NaN,则返回NaN
-
Infinity + Infinity = Infinity -Infinity + (-Infinity) = -Infinity Infinity + (-Infinity) = NaN -
+0 + (+0) = +0; -0 + (+0) = +0; -0 + (-0) = -0; -
如果两个操作符都是字符串,则将第二个字符串拼接到第一个字符串后面
-
如果只有一个操作数是字符串,则将第一个操作数转换为字符串,再将两个字符串拼接在一起
-
'5' + 5// '55'
3.5.6.2 减法操作符
-
如果有任一操作数是NaN,则返回NaN
-
Infinity - Infinity = NaN -Infinity - (-Infinity) = NaN Infinity - (-Infinity) = Infinity -Infinity - Infinity = -Infinity -
+0 - (+0) = +0; +0 - (-0) = -0; -0 - (-0) = +0; -
如果任一操作数是字符串、布尔值、null、undefined,则先调用
Number()将其转换为数值,再根据前面的规则执行运算;如果转换结果是NaN,则减法计算的结果是NaN -
如果任一操作数是对象,则调用其
valueOf()方法取得表示它的数值。如果该值是NaN,则减法计算的结果是NaN;如果对象没有valueOf()方法,则调用其toString()方法,然后再将得到的字符串转换为数值 -
let res1 = 5 - '2'; // 3 先进行转换,所以结果是3
3.5.7 关系操作符
比较规则:
-
如果操作数是数值,则进行数值比较
-
如果操作数都是字符串,则逐个比较字符串中对应字符的编码
let res = 'Bab' < 'al'; // true let res = '23' < '3'; // true -
如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较
let res = '23' < 3; // false let res = 'a' < 3; // 'a'会转换为NaN,所以结果是false -
如果有任一操作数是对象,则调用其
valueOf()方法,取得结果后再根据前面的规则执行比较。如果没有valueOf()操作符,则调用toString()方法,取得结果后再根据前面的规则进行比较 -
如果有任一操作符是布尔值,则将其转换为数值再执行比较
-
任何关系操作符在涉及比较NaN时都返回false
let res = NaN < 3; // false let res = NaN >= 3; // false
3.5.8 相等操作符
3.5.8.1 等于和不等于
等于(==)或不等于(!==)在判断时会进行强制类型转换,且操作符遵循如下规则:
- 如果任一操作数是布尔值,则将其转换为数值再比较是否相等,false转换为0,true转换为1
- 如果一个操作数字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等
- 如果一个操作数是对象,另一个操作数不是,则调用对象的
valueOf()方法取得其原始值,再根据前面的规则进行比较 - null和undefined相等
- null和undefined不能转换为其他类型的值在进行比较
- NaN和任何值都不相等,包括其自身
- 如果两个操作符都是对象,则比较它们是不是同一个对象,是同一个对象返回true,否则False
| 表达式 | 结果 |
|---|---|
| null == undefined | true |
| false == 0 | true |
| true == 1 | true |
| true == 2 | false |
| undefined == 0 | false |
| null == 0 | false |
3.5.8.2 全等和不全等
全等(===)和不全等(!==)操作符不会进行强制类型转换
null !== undefined- 推荐使用全等和不全等操作符
3.5.9 条件操作符
3.5.10 赋值操作符
3.5.11 逗号操作符
3.6 语句
3.6.1 if语句
3.6.2 do-while语句
循环体内在退出之前至少要执行一次
let i = 0;
do {
i += 2;
} while (i < 10);
3.6.3 while语句
3.6.4 for语句
3.6.5 for-in语句
for-in语句用于枚举对象中的非符号键属性
// 推荐使用const
for (const propName in window) {
document.write(propName);
}
ECMAScript中对象的属性是无序的,因此for-in语句不能保证返回对象属性的属性,也就是说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异
3.6.6 for-of语句
for-of语句y用于遍历可迭代对象的元素
// 推荐使用const
for (const el of [2, 4, 6, 8]) {
console.log(el);
}
如果尝试迭代的变量不支持迭代,则for-of语法会抛出错误
3.6.7 标签语句
可以将标签与continue与break结合起来使用
3.6.8 break和continue语句
3.6.9 with语句
不推荐使用with语句
3.6.10 switch语句
switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型
3.7 函数
如果没有返回值,函数会返回undefined
函数的具体内容会在后续内容解析
3.8 小结
无