基础语法
语法
区分大小写
变量名、关键字等一切都区分大小写
标识符
标识符,是函数、变量、属性、参数的名称,必须满足以下规则:
- 第一个字符必须是下划线(_)、字母或 $
- 剩下的字符可以是数字、下划线(_)、字母或 $
最佳实践:
myNewName;
推荐驼峰命名法,因为内置函数和对象的命名也是如此
严格模式
ES5 增加了严格模式,开启严格模式,需要在开头加一行:
"use strict";
这是一个预处理指令,任何支持 JS 的引擎都会切换到严格模式,严格模式会有更多的保留字
语句
最佳实践:
// 不省略大括号
if (test) {
//语句末尾加分号
console.log(test);
}
关键字与保留字
关键字,有特殊用途,比如 if、else、break
保留字,给未来保留,这些保留字可能在将来成为关键字
变量
JS 变量是弱类型(松散类型)的,变量可以保存任何数据类型,有以下三种变量
var
最佳实践:
// 不省略 var,哪怕省略也是可以的
var message = "hi";
// 定义多个变量,用逗号分隔
var message = "hi",
age = 29;
声明作用域:函数作用域
- 在函数内声明是局部变量,函数调用结束后被销毁
- 如果使用
message = "hi";
的定义方式,会声明一个全局变量,可以被外部访问
声明提升:
如下定义一个变量:
function foo() {
console.log(age);
var age = 29;
}
这样不会报错,结果是 undefined,因为会被等价为以下代码:
function foo() {
var age;
console.log(age);
age = 29;
}
这就是变量提升(hoist),并且这样也没有问题:
function foo() {
var age = 16;
var age = 32;
var age = 76;
console.log(age); // 76
}
输出 76
let
ES6 新增
let 与 var 的区别:
- let 作用域较小
- 没有变量提升
- let 在全局作用域声明的变量不会成为 window 对象的属性(var 全局变量会)
声明作用域:块作用域,例如,以下代码输出结果会报错: ReferenceError: age not defined
function test() {
if (true) {
let age = 26;
}
console.log(age);
}
没有变量提升:导致暂时性死区(temporal dead zone)
由于不会自动提升变量,let 声明之前,执行瞬间叫暂时性死区,此阶段任何对未声明变量引用都会有报错:ReferenceError
const
const 与 let 区别:
- 声明时必须初始化
- 修改变量时会报错(如果变量引用的是一个对象,那么修改对象内的属性是可以的)
最佳实践
- 不使用 var
- const 优先,let 次之(只有提前知道需要修改时才使用 let)
数据类型
一共有 6 种简单数据类型(原始类型):
- undefined
- null
- boolean
- string
- number
- symbol
以及 1 种复杂类型:
- object
typeof 操作符
由于 JS 的数据类型是松散的,所以需要一个方法来判断数据类型:
let message = 66;
//传入变量
console.log(typeof message); // number
//传入字符串
console.log(typeof message); // string
typeof 不是一个函数,它不需要传入参数(也可以传入),会返回以下 7 种类型:
- undefined
- boolean
- string
- number
- object
- function
- symbol
typeof null 返回结果是 object,因为 null 被认为是空对象的引用
严格来说,在 JS 中,function 是一种特殊 object,但是 function 有特殊的属性,所以需要进行区分
undefined
声明变量,但是没有进行初始化时,变量的值就是 undefined
- 设置这个数据类型,是为了区分未初始化变量和空对象指针(null)的区别
- 没有声明变量,或者声明了未初始化,结果都是 undefined,所以建议声明时就进行初始化,这样一旦出现 undefined ,你就知道是变量没有声明
- undefined 在逻辑判断中被当作假值
null
表示空对象指针
- 在定义一个用于保存对象的变量时,建议用 null 初始化,这样之后判断如果变量的值不为 null ,那么它的引用是一个对象
- undefined == null,这是 ECMAScript 定义的
- null 的逻辑值为假
boolean
有两个字面值:true 和 false
Boolean() 函数可以将其他类型的值转换为布尔值,满足以下规则:
- string,非空字符串为 true,空字符串 "" 为 false
- number,非零数(无穷)为 true,0、NaN 为 false
- object,对象为 true,null 为 false
- undefined,都为 false
number
表示整数或浮点数
进制数
JS 中可以有 +0 和 -0,但是这两种都等同于 0
十进制:
let int = 55;
八进制(以 0 开头):
let octal = 070; //八进制的52
let octal = 079; //无效的八进制,当作 79
十六进制(以 0x 开头):
let hex = 0xa; //十六进制 10
浮点值
let float = 1.1;
let float = 0.1; //不推荐
let int = 1.0; //为了节省空间,1.0 会被转换为整数 1
科学计数法
let float = 3.123e5; //等于 312300
let float = 3 - e4; //等于 0.0003
精度问题
let a = 0.1;
let b = 0.2;
console.log(a + b == 0.3); // 结果为 false
因为 JS 采用的 IEEE 754 数值规则:
单精度浮点数(32 位)包括:1 位符号位 8 位指数位 23 位符号位
如何存贮浮点数?
- 0.1 被转为二进制 0.000110011...(无限循环)
- 二进制转换为科学计数法:1.100110011... * 2^-4
- 因此符号位: 0(正数),指数位:0111 1111(127+(-4)= 123,再将 123 转换成二进制),数值位:1001 1001 1001 1001 1001 100(超过了 23 位,多余的省略)
在对 0.1 进行存贮的过程中,精度丢失,因此 0.1 会有细微的偏差,可以讨论以下它在丢失精度后的值是多少:
- 符号位:0,正数
- 指数位:0111 1111,转为十进制是 123,123 - 127 = -4
- 数值位:1001 1001 1001 1001 1001 100,结合指数位得到:1.1001 1001 1001 1001 1001 100 * 2^-4,即 0.0001 1001 1001 1001 1001 1001 100
- 转为十进制:0.0999999940395355
可见,虽然存储的是 0.1,但是一旦参与运算,0.1 实际上是 0.0999999940395355
无限值
JS 可表示的最小数保存在 Number.MIN_VALUE 中,最大值保存在 Number.MAX_VALUE 中,如果计算结果超过最大值,或者小于最小值,就会被自动转换为无穷值:Infinity 或 -Infinity,并且该值不能再参与计算
console.log(5 / 0); //Infinity
console.log(5 / -0); //-nfinity
isFinite() 函数,判断一个变量是否是无限值
NaN
Not a Number,表示不是数值
console.log(0 / 0); //NaN
console.log(-0 / +0); //NaN
- 任何有 NaN 的运算结果都为 NaN
- NaN 不等于任何值,也不等于它自身
isNaN() 函数,会先尝试将参数内容转化为 Number 类型(比如 数字字符串会被转换成数字,true 会被转换成 1),如果可以返回 true,否则返回 false
数值转换
Number(),可以转换任何类型,规则复杂
parseInt(),转换为整数,可以接受两个参数:转换对象,进制
parseFloat(),转换为浮点数,只能解析十进制数,如果参数为整数,则会返回整数
string
字符串字面量
\n 换行,\t 制表,\r 回车,' 单引号," 双引号,\\ 反斜杠
不可变
字符串是不可变的(immutable),要修改其值,必须销毁之前的字符串,再创建新的字符串
字符串转换
let age = 10;
let string = age.toString(); //"10"
let string = age.toString(2); //转换为二进制字符串 "1010"
由于 null 和 undefined 没有 toString() 方法,可以通过 String() 创建字符串:
String(10); //"10"
String(null); //"null"
String(undefined); //"undefined"
字符串模板
字面量
模板字面量会保存反引号之间的所有空格、换行
//以下两个字符串是等价的
let string = "first line\nsecond line";
//使用字符串模板
let template = `first line
second line`;
插值
可以在模板字面量中通过插值的方式,使用变量,变量会被 toString() 转换为字符串
- 插值中,可以使用变量对象的函数
- 插值中,可以对多个变量进行运算
let val = 5;
let template = `value is ${val}`;
标签函数
标签函数是自定义的函数:
function template(strings, ...expressions) {
console.log(strings);
for (const expression of expressions) {
console.log(expression);
}
return "result";
}
let a = 6;
let b = 9;
let result = template`${a} + $ {b} = ${a + b}`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15
console.log(result); // "result"
函数参数 strings 接受的是原始字符串数组,...expressions(数组)接受了其他插值的结果
可以自定义插值的行为
模板-原始字符串
可以获取模板字面量的原始内容:
String.raw`first line\nsecond line`;
//first line\nsecond line
symbol(TODO)
确保对象的属性使用唯一标识符,不会发生冲突
Object
对象类型 Object,一组功能和数据的集合
开发者可以通过 new 关键字创建 Object 对象,然后再给对象添加方法或属性
let o = new Object();
Object 作为所有对象的基类,其他对象都继承以下的属性或方法,但是 BOM 和 DOM 的对象可以不遵守 ECMAscript 规则,所以有些对象可能不会继承这些方法
属性和方法
- constructor:创建当前对象的构造函数,上面例子中就是 Object()
- hasOwnProperty(proptypeName):用于判断当前对象实例(不是原型)上是否存在名为 proptypeName(字符串或符号) 的属性
- isProptypeOf(object):判断当前对象是否为另一个对象的原型
- propertyIsEnumerable(proptypeName):判断给定属性是否可以使用 for-in 枚举,参数必须是字符串
- toLocaleString():返回对象的字符串表示,该字符串反应对象所在的本地化执行环境
- toString():返回对象的字符串表示
- valueOf():返回对象的字符串、数值或布尔值,通常与 toString() 返回值相同,用于操作符比较大小或运算时,会调用这个方法,用于自动转换
操作符
一元操作符
递增与递减
let age = 10;
++age;
操作符 ++ ,等同于 age = age + 1 ,++age 与 age++ 有以下区别:
- 前缀,先计算操作符结果,再进行其他计算
- 后缀,先进行其他计算,再进行操作符计算
对于 number 之外的数据类型,也可以使用,只不过需要先转换为 number(尽可能)再运算
一元加减
let num = 2;
num = +num; // 2
num = -num; // -2
负号取相反数
位操作符
ECMAscript 采用 IEEE 754 64 位格式存储数值,但位操作只采用 32 位,之后再把结果转换为 64 位。对于开发者,只有 32 位整数。
按位非
let num1 = 25;
let num2 = ~num1; // -26
num2 转换为十进制是 -26,按位非等效于:-num1 -1 ,不过位运算更高效
按位与
let result = 25 & 3; // 1
与运算:全是 1 结果才为 1
按位或
let result = 25 | 3; // 27
或运算:有一个 1 结果就是 1
按位异或
let result = 25 ^ 3; // 26
异或运算:一个是 1 一个是 0 结果才为 1
左移
let old = 2;
let val = 2 << 5; // 64
左移会保留正负号
数值 m 左移 n 位 = m * 2^n
右移
let old = 64;
let val = 64 >>> 5; // 2
正数 m 右移 n 位 = m / 2^n
对于正数,右移没有影响。对于负数,右移影响很大(负数符号位位 1 ,右移左边补 0 ,会变正数)
布尔操作符
逻辑非
!val
,将操作数 val 转换为布尔值后,取反
!!val
,效果等同于 Boolean(val) ,表示取布尔值
逻辑与
&&
短路特性:第一个操作数位 false ,则不会对第二个操作数求值
逻辑或
||
短路特性:第一个操作数位 true ,则不会对第二个操作数求值
短路特性的应用:
let ob = preob || backob;
preob 包含首选的值,backob 包含备用的值
乘性操作符
乘法
*
除法
/
取模
%
指数操作符
ES7 新增 **
3 ** 2; // 9
Math.pow(3, 2); // 9
加性操作符
加法操作符
+
注意:字符串之间 + 表示连接字符串,数值之间 + 表示加法运算
减法操作符
-
关系操作符
>
<
>=
<=
注意:
- 如果有一个操作数位 NaN,结果都是 false
- 字符串之间比较,如果都可以转换为数值,那么会从左到右依次比较两个操作数每一位的字符编码大小
- 字符串之间比较,如果有操作数不可以转换为数值,那么视作 NaN
相等操作符
等于和不等于
==
!=
比较时会通过 valueOf() 自动转换操作数,即 3 == "3" 结果为 true
全等和不全等
===
!==
比较时不转换操作数,即 3 === "3" 结果为 false
条件操作符(三元运算)
let max = num1 > num2 ? num1 : num2;
如果 num1 > num2 为 true ,那么取值为 num1 ,否则取值 num2
赋值运算符
=
与其他操作符的结合(简写,并不提升性能):a -= b,表示 a = a - b,其他数学运算符同理
逗号操作符
let num1 = 1,
num2 = 2,
num3 = 3;
逗号赋值(赋值最后一项 0 ):
let num = (5, 1, 2, 0); // 0
语句
if
ECMAscript 会调用 Boolean() 函数将 condition 转换成布尔值
最佳实践:
if (condition1) {
statement1
} else if (condition2){
statement2
} else {
statement3
}
不要省略大括号,这样可以避免错误
do-while
最佳实践:
do {
statement
} while (expression);
while
最佳实践:
while(expression) {
statement
}
for
最佳实践:
for(let i = 0; i < 10; i++) {
console.log(i);
}
使用 let 可以将作用域固定在循环中,因为循环外用不到这个变量了
无穷循环:
for(;;) {
console.log(1);
}
while 循环:
for(;i < 10;) {
console.log(1);
}
for-in
最佳实践:
for (const propName in window) {
console.log(propName);
}
如上,遍历了一个 window 对象的所有属性,遍历的顺序因浏览器而异
为了保证遍历的变量 propName 不被修改,推荐使用 const
如果遍历的变量是 null 或 undefined,不会执行循环
for-of
最佳实践:
for (const element of [2,4,6,8]) {
console.log(element);
}
for-of 会按照可迭代对象的 next() 方法产生值的顺序迭代元素
如果尝试迭代的元素不支持迭代,会抛出错误
标签、break 和 continue
- break:退出当前这一层的循环
- continue:跳过当前这一层的本次循环
label 和 break 的配合使用:
outermost:
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost;
}
}
}
如上,创建了一个名为 outermost 的 label,当最内层的循环执行到 break outermost; 时,会退出到标签 outermost 的位置(即直接退出到最外层的循环)
标签可以和 break 或 continue 配合,退出或者跳过到标签所指定的位置
with
示例如下:
let qs = location.search;
let hostName = location.hostname;
let url = location.href;
这段代码每一次赋值都有重复的 location 对象,可以使用 with 简化代码:
with(location) {
let qs = search;
let hostName = hostname;
let url = href;
}
严格模式不允许 with
switch
基本用法:
switch(i) {
case 25:
console.log(25);
break;
case 35:
console.log(25);
break;
default:
console.log("other");
}
一个结果有多个条件(最好写明注释是故意忽略 break):
switch(i) {
case 25:
/* skip */
case 35:
console.log(25);
break;
default:
console.log("other");
}
ECMAscript 中 switch 的特点:
- 可以接收任何数据类型
- 在 case 比较时会使用全等操作符(===),因此不会强制转换数据类型
函数
基本写法:
function functionName(arg0,arg1,...,argN) {
statements
}
只要遇到 return 语句,函数会立即停止执行
最佳实践:要么有返回值,要不没有返回值(默认返回 undefined),只在某个条件下返回值的函数,在调试时会很麻烦