1、语法
1.1区分大小写
ECMAScript 中一切都区分大小写
1.2 标识符
标识符:变量、函数、属性或者函数参数的名称。
- 第一个字符必须是一个字母、下划线(_)或美元符号($);
- 剩下的其他字符可以是字母、下划线、美元符号或数字。
按照惯例,使用驼峰大小写形式,eg. doSomethingImportant
1.3 注释
// 单行注释
/* 这是多行
注释 */
1.4 严格模式
对整个脚本启用严格模式:
- 在脚本开头加上:“use strict”;
单独指定一个函数在严格模式下执行,将预处理指令放在函数体的开头:
function doSomething() {
"use strict";
// 函数体
}
1.5 语句
- 以分号为结尾,省略分号意味着由解析器确定语句在哪里结尾,加分号有助于防止省略造成的问题
- 始终在控制语句中使用代码块,即使要执行的只有一条语句。代码块由一个左花括号({)标识开始,一个右花括号(})标识结束
2、关键字与保留字
ECMA-262 第 6 版规定的所有关键字如下:
以下是 ECMA-262 第 6 版为将来保留的所有词汇:
这些词汇不能用作标识符,但现在还可以用作对象的属性名。一般来说,最好还是不要使用关键字和保留字作为标识符和属性名,以确保兼容过去和未来的 ECMAScript 版本
3、变量
3.1 var 关键字
3.1.1 定义变量
var操作符+变量名(即标识符)
var message;
3.1.2 定义变量并初始化
var message = "hi";
不初始化的情况下,变量会保存一个特殊值 undefined
3.1.3 定义多个变量并初始化
使用逗号分隔
var message = "hi",
found = false,
age = 29;
3.1.4 var声明作用域
var 操作符定义的变量会成为包含它的函数的局部变量
function test() {
var message = "hi"; // 局部变量
}
test();
console.log(message); // 出错!
使用 var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁。
但是!
在函数内定义变量时省略 var 操作符,可以创建一个全局变量
function test() {
message = "hi"; // 全局变量
}
test();
console.log(message); // "hi"
虽然可以通过省略 var 操作符定义全局变量,但不推荐这么做。
3.1.5 var声明提升
使用var关键字声明的变量会自动提升到函数作用域顶部,也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用 var 声明同一个变量也没有问题。
3.2 let关键字
3.2.1 let声明作用域
let 声明的范围是块作用域,而 var 声明的范围是函数作用域。
if (true) {
var name = 'Matt';
console.log(name); // Matt
}
console.log(name); // Matt
age 变量之所以不能在 if 块外部被引用,是因为它的作用域仅限于该块内部
if (true) {
let age = 26;
console.log(age); // 26
}
console.log(age); // ReferenceError: age 没有定义
3.2.2 let声明冗余
let 不允许同一个块作用域中出现冗余声明
var name;
var name;
let age;
let age; // SyntaxError;标识符 age 已经声明过了
JavaScript 引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而这是因为同一个块中没有重复声明。
var name = 'Nicholas';
console.log(name); // 'Nicholas'
if (true) {
var name = 'Matt';
console.log(name); // 'Matt'
}
let age = 30;
console.log(age); // 30
if (true) {
let age = 26;
console.log(age); // 26
}
对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在
var name;
let name; // SyntaxError
let age;
var age; // SyntaxError
3.2.3 暂时性死区
let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升
// name 会被提升
console.log(name); // undefined
var name = 'Matt';
// age 不会被提升
console.log(age); // ReferenceError:age 没有定义
let age = 26;
在 let 声明之前的执行瞬间被称为 “暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。
3.2.4 全局声明
使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声明的变量则会)
var name = 'Matt';
console.log(window.name); // 'Matt'
let age = 26;
console.log(window.age); // undefined
不过,let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免 SyntaxError,必须确保页面不会重复声明同一个变量
3.2.5 条件声明
没这么看懂其实
对于 let 这个新的 ES6 声明关键字,不能依赖条件声明模式
3.2.6 for循环中的let声明
在 let 出现之前,for 循环定义的迭代变量会渗透到循环体外部,使用 let 之后,这个问题就消失了,因为迭代变量的作用域仅限于 for 循环块内部
在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改。之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是同一个最终值。
for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 你可能以为会输出 0、1、2、3、4
// 实际上会输出 5、5、5、5、5
而在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。这种每次迭代声明一个独立变量实例的行为适用于所有风格的 for 循环,包括 for-in 和 for-of 循环
for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 会输出 0、1、2、3、4
3.3 const关键字
const 的行为与 let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。
- 声明时必须同时初始化
- 不能修改其值
- 不允许重复声明
- 作用域是块
- 不能使用const来声明迭代对象,比如for循环中,因为迭代变量会自增
- 但可以用const声明一个不会被修改的for循环变量
const 声明的限制只适用于它指向的变量的引用。换句话说,如果 const 变量引用的是一个对象,那么修改这个对象内部的属性并不违反 const 的限制。
const person = {};
person.name = 'Matt'; // ok
3.4 声明风格及最佳实践
- 不使用var,限制自己只使用 let 和 const有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值
- const 优先,let 次之,使用 const 声明可以让浏览器运行时强制保持变量不变,应该优先使用 const 来声明变量,只在提前知道未来会有修改时,再使用 let
4、数据类型
6种简单数据类型
- Undefined
- Null
- Boolean
- Number
- String
- Symbol
1种复杂数据类型
- Object
4.1 typeof操作符
作用:确定任意变量的数据类型
console.log(typeof 95); // "number"
对一个值使用 typeof 操作符会返回下列字符串之一:
- "undefined"表示值未定义;
- "boolean"表示值为布尔值;
- "string"表示值为字符串;
- "number"表示值为数值;
- "object"表示值为对象(而不是函数)或 null;
- "function"表示值为函数;
- "symbol"表示值为符号。
调用typeof null 返回的是"object"。这是因为特殊值 null 被认为是一个对空对象的引用
4.2 Undefined类型
Undefined 类型只有一个值,就是特殊值 undefined。
当使用 var 或 let 声明了变量但没有初始化时,就相当于给变量赋予了 undefined 值。所以不需要显示以undefined来初始化,因为默认情况下,任何未经初始化的变量都会取得undefined值
let message;
console.log(message == undefined); // true
无论是声明还是未声明,typeof 返回的都是字符串"undefined"。逻辑上讲这是对的,因为虽然严格来讲这两个变量存在根本性差异,但它们都无法执行实际操作。但是直接log输出未声明变量会报错
let message; // 这个变量被声明了,只是值为 undefined
// 确保没有声明过这个变量
// let age
console.log(typeof message); // "undefined"
console.log(typeof age); // "undefined"
undefined 是一个假值。因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多
其他可能的值同样是假值。所以一定要明确自己想检测的就是 undefined 这个字面值,而不仅仅是
假值
4.3 Null类型
- Null 类型同样只有一个值,即特殊值 null。
- 逻辑上讲,null 值表示一个空对象指针,这也是给typeof 传一个 null 会返回"object"的原因
- 在定义将来要保存对象值的变量时,建议使用 null 来初始化,不要使用其他值。这样,只要检查这个变量的值是不是 null 就可以知道这个变量是否在后来被重新赋予了一个对象的引用。最好是显示初始化未保存的对象
- undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等
- null 是一个假值
console.log(null == undefined); // true
4.4 Boolean类型
- Boolean(布尔值)类型有两个字面值:true 和 false。这两个布尔值不同于数值,因此 true 不等于 1,false 不等于0
- 布尔值字面量 true 和 false 是区分大小写的
- 调用特定的 Boolean()转型函数,将其他类型的值转换为布尔值
不同类型与布尔值之间的转换规则
理解以上转换非常重要,因为像 if 等流控制语句会自动执行其他类型值到布尔值的转换,自动转换
4.5 Number类型
- Number 类型使用 IEEE 754 格式表示整数和浮点值(在某些语言中也叫双精度值)
- 十进制整数:最基本的数值字面量格式
- 八进制:第一个数字必须是零(0),然后是相应的八进制数字(数值 0~7),在严格模式下无效—0o
- 十六进制:值前缀 0x(区分大小写),然后是十六进制数字(0~9 以及 A~F)
4.5.1 浮点值
- 要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字
- 存储浮点值使用的内存空间是存储整数值的两倍
- 在小数点后面没有数字的情况下,数值就会变成整数
- 如果数值本身就是整数,只是小数点后面跟着 0(如 1.0),那它也会被转换为整数
- 对于非常大或非常小的数值,浮点值可以用科学记数法来表示
4.5.2 值的范围
- 最小值 Number.MIN_VALUE
- 最大值 Number.MAX_VALUE
如果某个计算得到的数值结果超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity(无穷)值。如果计算返回正 Infinity 或负 Infinity,则该值将不能再进一步用于任何计算。
- -Infinity(负无穷大)
- Infinity(正无穷大)
确定一个值是不是有限大,使用 isFinite()函数
在计算非常大或非常小的数值时,有必要监测一下计算结果是否超出范围
4.5.3 NaN
NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)
在 ECMAScript 中,0、+0 或 -0 相除会返回 NaN
- 任何涉及 NaN 的操作始终返回 NaN(如 NaN/10)
- NaN 不等于包括 NaN 在内的任何值
isNaN()函数,接收一个参数,判断这个参数是否“不是数值”,任何不能转换为数值的值都会导致这个函数返回
true
console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false,10 是数值
console.log(isNaN("10")); // false,可以转换为数值 10
console.log(isNaN("blue")); // true,不可以转换为数值
console.log(isNaN(true)); // false,可以转换为数值 1
4.5.4 数值转换
将非数值转换为数值:Number()、parseInt() 和 parseFloat()
**Number()**是转型函数,可用于任何数据类型
let num1 = Number("Hello world!"); // NaN
let num2 = Number(""); // 0
let num3 = Number("000011"); // 11
let num4 = Number(true); // 1
parseInt()函数主要用于将字符串转换为数值
字符串最前面的空格会被忽略,从第一个非空格字符开始转换。
如果第一个字符不是数值字符、加号或减号,parseInt()立即返回 NaN。
如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符
let num1 = parseInt("1234blue"); // 1234
let num2 = parseInt(""); // NaN
let num3 = parseInt("0xA"); // 10,解释为十六进制整数
let num4 = parseInt(22.5); // 22
let num5 = parseInt("70"); // 70,解释为十进制值
let num6 = parseInt("0xf"); // 15,解释为十六进制整数
parseInt()也接收第二个参数,用于指定底数(进制数),多数情况下解析的应该都是十进制数,此时第二个参数就要传入 10,建议始终传给它第二个参数。
let num1 = parseInt("10", 2); // 2,按二进制解析
let num2 = parseInt("10", 8); // 8,按八进制解析
let num3 = parseInt("10", 10); // 10,按十进制解析
let num4 = parseInt("10", 16); // 16,按十六进制解析
parseFloat()函数主要用于将字符串转换为数值
工作方式跟 parseInt()函数类似,都是从位置 0 开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。
它始终忽略字符串开头的零
parseFloat()只解析十进制值,因此不能指定底数
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"); // 31250000
4.6 String 类型
String(字符串)数据类型表示零或多个 16 位 Unicode 字符序列。
字符串可以使用双引号(")、单引号(’)或反引号(`)标示。
以某种引号作为字符串开头,必须仍然以该种引号作为字符串结尾。