语法
- 一切都区分大小写
- 标识符:变量 函数 属性 函数参数的名称
第一个字符必须是字母 下划线_ 美元符号$
剩下的字符可以是字母 下划线_ 美元符号$ 数字
- 适用驼峰大小写形式 即第一个单词的首字母小写 后面每个单词的首字母大写 如
myCar
- 关键字 保留字 true false null不能作为标识符
严格模式:ECMAScript 5新增 是一种不同的JS解析和执行模型 ECMAScript 3中的一些不规范写法在严格模式下会被处理 对于不安全的活动将抛出错误
(1)在脚本开头加上"use strict"
整个脚本启用严格模式
(2)单独指定一个函数在严格模式下执行 将"use strict"
放在函数体开头
function f(){
"use strict"
// 函数体
}
关键字与保留字
- 不能用作标识符或属性名
break do in typeof switch case default if else instanceof var try throw catch import export new void class extends return while const finally super with continue for yield debugger function this delete
- 为将来保留的关键字 不能用作标识符 可以用作属性名 但建议不用做标识符或属性名
始终保留:enum
严格模式下保留:implements package public interface protected static let private
模块代码中保留:await
变量
- 变量可以保存任何类型的数据 每个变量不过是一个用于保存任意值的命名占位符
var let const
可以声明变量let const
只能在ECMAScript 6及更晚的版本中使用- 在严格模式下 不能定义名为eval和arguments的变量
var
- 声明变量
var message;// 此时message=undefined
var message = "hi";// 这样声明变量并不会把变量标志为String 这只是一个简单的赋值而已
// 随后可以改变保存的值 也可以改变值得类型 但不建议改变值的类型
message = 100;
var a,b,c;
var x = 0, y = 1, z = 2;
- var能够声明的作用域只有两种:全局作用域和函数局部作用域
(1)使用var在函数内部定义一个变量 该变量在函数执行完毕后会被销毁
function f(){
var message = "hi";// 局部变量
}
f();// 调用f()会创建message变量并给他赋值 f()函数执行完毕 变量message随即被销毁
console.log(message);// 报错
(2)在函数内定义变量时 省略var操作符 创建的是全局变量 不建议这么做 在严格模式下 如果像这样给未声明的变量赋值会报错ReferenceError
function f(){
message = "hi";// 全局变量
}
f();// 只要调用f()就会创建message变量并给他赋值 并且可以在外部访问到
console.log(message);// "hi"
- var声明的变量会自动提升到(全局/函数局部)作用域顶部 变量可以先使用再声明
- 在使用var声明变量时 由于声明会被提升 JS引擎会自动将多余的声明在作用域顶部合并为一个声明
<script>
// 始终铭记两点:1. let有全局 块级作用域(包括函数作用域) 2. var有全局作用域 函数作用域
// 所以以下两个变量都是全局的
var name = 'cara';
let age = 26;
</script>
<script>
var name = 'yq';
// let age = 25;// 报错 前面已经声明
console.log(age)// 26
console.log(name)// yq
</script>
也就是说 在不同的script
标签内部 不是函数作用域内部 用var声明的同名变量 都会进行一次变量提升 提升到第一个script
标签顶部 后续所有的初始化 相当于一次次的赋值
- 可以使用var重复声明同一个变量
- var在全局作用域中声明的变量会成为window对象的属性
let
- let声明的范围是块作用域{}
if (true) {
var name = 'cara';// var声明的是函数作用域或全局作用域
console.log(name);// cara
}
console.log(name);// cara
if (true) {
let age = 25;
console.log(age);// 26
}
console.log(age);// ReferenceError age没有定义
- let不允许同一个块作用域中出现冗余声明
- 对声明冗余报错不会因混用let var而受影响 这两个关键字声明的并不是不同类型的变量 他们只是指出变量在相关作用域如何存在 也就是说 同一个变量在其作用域只能有一个 无论他是let声明的还是var声明的
var name;
let name;// SynntaxError
let age;
var age;// SynntaxError
- let声明的变量不会在作用域中被提升
- 在let声明之前的执行瞬间被称为“暂时性死区” 在此阶段引用任何后面才声明的变量都会抛出ReferenceError 即从函数开头到let或const定义的标识符之前被称为“暂时性死区” 在这里面使用后面通过let或const声明的标识符会报错
- let在全局作用域中声明的变量不会成为window对象的属性 不过let声明仍然是在全局作用域中发生的 相应变量会在页面的生命周期内存续
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0)
// 5 5 5 5 5
// 除了将var改为let可以解决这个问题之外 使用闭包也可以解决这个问题
}
因为在退出循环时 迭代变量保存的是导致循环退出的数值:5 在之后执行超时逻辑时 所有的i都是同一个变量 因而输出的都是同一个最终值 而使用let声明迭代变量时 JS引擎在后台会为每一个迭代循环声明一个新的迭代变量 每个setTimeout引用的都是不同的变量实例 所以输出的是循环执行过程中每个迭代变量的值 这种每次迭代声明一个独立变量实例的行为适用于所有for循环 包括for-in for-of
for (let i = 0; i < 5; i++) {
// 使用let声明迭代器变量 可以将变量的作用域限定在循环中
setTimeout(() => console.log(i), 0)
// 0 1 2 3 4
}
以上两段代码:在for三个语句中 第一个语句i=0 在循环代码块开始前执行 每次循环都会执行 由于var存在变量提升 所以var i = 0实际上只执行了一次 而let不存在变量提升 所以每次循环都会执行一次let i 声明一个新的变量(初始化的值不一样) for的每次循环都是不同的块级作用域 let声明的变量是块级作用域 所以不存在重复声明的问题
但注意 立即使用是不会出现上述情况的
for (var i = 0; i < 5; i++) {
console.log(i)
// 0 1 2 3 4
}
const
- const与let相同 唯一的区别是const声明变量时必须同时初始化变量 且尝试修改const声明的变量会报错
- const声明的限制只适用于它指向的变量的引用 换句话说 如果const变量引用的是一个对象 那么修改这个对象内部的属性并不违反const的限制
const person = {};
person.name = 'cara';// 正确
- 如果想让整个对象都不能修改 可以用
Object.freeze()
const o = Object.freeze({});
o.name = 'cara';
console.log(o.name);//undefined
- 在for循环里(包括for-in for-of) 可以使用const声明一个不会被修改的变量 每次迭代只是创建一个新变量
let i = 0;
for (const j = 7; i < 5; i++) {
console.log(j);// 7 7 7 7 7
}
for (const x in {a:1,b:2}) {
console.log(x);// a b
}
for (const v of [1,2,3,4,5]) {
console.log(v);// 1 2 3 4 5
}
数据类型
- 简单数据类型/原始数据类型:Undefined Null Number String Symbol Boolean
- 复杂数据类型:Object
- typeof的返回值:
(1)undefined表示值未定义
(2)Boolean表示值为布尔值
(3)string表示值为字符串
(4)number表示值为数值
(5)object表示值为对象或null
(6)function表示值为函数
(7)symbol表示值为符号 console.log(typeof null);// object
因为JS在底层存储变量时 会在变量的机器码的低1-3位存储其数据类型信息 000表示对象 001表示整数 010表示浮点数 100表示字符串 110表示布尔值 而null所有机器码均是0 低三位也是0 所以typeof null
返回object
Undefined
- Undefined类型只有一个值undefined 当使用var或let声明了变量但没有初始化时 相当于给变量赋值undefined
- 对未声明的变量只能执行typeof操作 其实也可以执行delete操作 但没什么意义 并且在严格模式下会报错
- 对声明了但未初始化的变量执行typeof时返回undefined 对未声明的变量执行typeof时还是返回undefined
- 建议在声明变量时要初始化 这样 当typeof返回undefined时 你就知道这个变量未声明而不是声明了未初始化
- undefined为false
Null
- Null类型只有一个值null null值表示一个空对象指针
- 在定义将来要保存对象值的变量时 建议使用null来初始化
- null为false
console.log(undefined == null)// true
console.log(undefined === null)// false
Boolean
- Boolean类型有两个值:true false 这两个值不等同于数值 即true不等于1 false不等于0
- 使用
Boolean()
可以将非Boolean类型的值转换为Boolean类型 - 像if等语句会自动将其他类型的值转为Boolean类型的值
Number
- 八进制:0开头 后面跟着0-7范围的数 如果超过这个范围 会忽略前缀0 当成十进制处理 如079就是79 079是56
严格模式下 前缀0会被视为语法错误 所以在严格模式下 用前缀0o表示八进制
- 十六进制:0x开头 后面跟着0-9及a-f 不区分大小写
- 使用八进制 十六进制的数 在计算前都会转为十进制 然后在计算 最后计算结果也用十进制表示
- 正零(+0)和负零(-0)在所有情况下都被认为是等同的
- 因为存储浮点数使用的内存空间是存储整数的两倍 所以ECMASxript总是想方设法的把值转化为整数 如小数点后面没有数字 浮点数会被转换为整数 如1.0会被当成1处理
- 科学计数法格式:数值(整数/浮点数)后跟一个E/e 再加上一个要乘的10的多少次幂
let float = 3.125e7// 31250000
let xiaoshu = 3E-3// 0.003
- 浮点数不精确 永远不要测试某个特定的浮点数 如
console.log(0.9 - 0.8 == 0.1);// false
出现这种情况是因为使用了IEEE 754数值 - ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中 在大部分浏览器中 这个值为5e-324 可以表示的最大数值保存在Number.MAX_VALUE中 在大部分浏览器中 这个值为1.7976931348623157e+308
- 如果某个计算得到的数值结果超出了JS可表示的范围 那么这个数值会被自动转换为无穷值 任何无法表示的负数以负无穷大-Infinity表示 任何无法表示的正数以无穷大Infinity表示
Number.NEGATIVE_INFINITY
和Number.POSITIVE_INFINITY
可以获取正、负Infinity - 如果计算返回Infinity或-Infinity 则该值不能再进一步用于任何计算 因为Infinity没有可用于计算的数值表示形式 可以使用
isFinite()
判断一个值是否介于JS能表示的最小值和最大值之间 - 如果分子非0 分母是0/+0/-0 会返回Infinity或-Infinity
- Infinity除以任何数 会根据第二个数返回Infinity或-Infinity
- NaN:不是数值 用于表示本来要返回数值的操作失败了 比如:
(1)只要分子为0都返回NaN 0 +0 -0相除也会返回NaN
(2)任何涉及NaN的操作都返回NaN 如NaN/10返回NaN
(3)NaN不等于包括NaN在内的任何值
console.log(NaN == NaN)// false
console.log(NaN === NaN)// false
console.log(Infinity * 0);// NaN
console.log(Infinity / Infinity)// NaN
console.log(Infinity + -Infinity)// NaN
console.log(Infinity - Infinity)// NaN
console.log(-Infinity - -Infinity)// NaN
console.log(Infinity - -Infinity)// Infinity
console.log(-Infinity - Infinity)// -Infinity
isNaN()
:接受一个任意数据类型的参数 然后判断这个参数是否“不是数值” 该函数会尝试把参数转换为数值 任何不能转换为数值则返回trueNumber() parseInt() parseFloat()
可以将非数值转换为数值Number()
是转型函数 可用于任何数据类型 后两个函数主要用于将字符串转换为数值
(1)Number()
转换规则:
数值:直接返回
null: 转为0
undefined:转为NaN
Boolean:true和false被转为1和0
String:如果是纯数字形式(包括以+ -号开头的情况),则转为对应的数字,空字符转为0,其余一律转为NaN 注意这个纯数字console.log(Number("123ab4"))// NaN
对象:调用valueOf()方法 并按照上述规则转换返回的值 如果转换结果是NaN 则调用toString()方法 按照转换字符串的规则转换
(2)parseInt()
:字符串转整数 更专注于字符串中是否包含数值。字符串开头的空格会被忽略 从第一个非空格字符开始转换 如果第一个非空格字符不是数值字符、+、- 函数立即返回NaN 空字符串会返回NaN 如果第一个非空格字符是数值字符、+、-(parseInt()
能识别十进制 十六进制 八进制等不同的整数格式) 则继续依次检测每个字符 直到字符串末尾或碰到非数值字符
console.log(parseInt('123blue4'))// 123 第一个非空格字符满足条件 继续依次检测每个字符 直到碰到非数值字符
console.log(parseInt('1.3'))//1 小数点不是有效整数字符
parseInt()
接受第二个参数 用于指定进制数 虽然这个参数可选 但是建议传
(3)parseFloat()
:字符串转浮点数 工作方式同parseInt()
只不过他是解析到字符串末尾或一个无效的浮点数值字符为止 第一次出现的小数点是有效的 第二次出现的小数点就无效了 此时字符串的剩余字符会被忽略
parseFloat()
只接受一个参数 直接洗十进制数 不能指定进制数
String
- 可以使用单引号’’ 双引号"" 反引号``标识
- 字符字面量
- 转义序列表示一个字符
- 字符串是不可变的 一旦创建 他们的值就不能变了 要修改某个变量中的字符串值 必须先销毁原始的字符串 然后将包含新值的另一个字符串保存到该变量
- 转换为字符串
(1)toString()
:返回当前值的字符串等价物。可用于对象 数值 布尔值 字符串(返回自身得一个副本)。null undefined没有该方法。多数情况下toString()
不接受任何参数 当数值调用该方法时 可以接受一个底数(进制)参数 即以什么进制来输出数值得字符串表示 默认为十进制字符串表示
let num = 10;
console.log(num.toString())// '10'
console.log(num.toString(2))// '1010'
console.log(num.toString(8))// '12'
(2)String()
:
如果值有toString()
方法 不传参数调用toString()
并返回结果
如果值是null 返回’null’
如果值是undefined 返回’undefined’
(3)+‘字符串’
- 模板字面量保留换行字符 空格 可以跨行定义字符串
- 从技术上讲 模板字符串不是字符串 而是一种特殊的JS句法表达式 只不过求值后得到的是字符串 模板字符串在定义时立即求值并转换为字符串实例 任何插入的变量都会从他们最接近的作用域取值
- 在模板字符串中插入变量值:通过在
${}
中使用JS表达式实现(可使用任何JS表达式 包括函数和方法)。所有插入的值都会使用toString()强制转换为字符串类型 若${}
中又是模板字符串 则无需转换
let value = 5;
console.log(`值是:${value}`);// 值是:5
- 模板字面量标签函数(?)
- 可以使用
String.raw
标签函数来获取原始的模板字符串里的内容 而不是进行转义后的字符
console.log(`first line\nsecond line`);
// first line
// second line
console.log(String.raw`first line\nsecond line`);// first line\nsecond line
// 对实际的空格符是无效的
console.log(`first line second line`);// first line second line
console.log(String.raw`first line second line`);// first line second line
- 字符串数组.raw(?)
Symbol
- Symbol是原始值 且Symbol实例是唯一、不可变的 Symbol的用途是确保对象属性使用唯一标识符 不会发生属性冲突的危险
- 使用:使用
Symbol()
初始化 调用Symbol
函数时 也可以传入一个字符串参数作为对Symbol的描述
let sym = Symbol();
let sym1 = Symbol('另一个Symbol变量');
console.log(typeof sym);// symbol
- Symbol()函数不能与new关键字一起作为构造函数使用 Boolean String Number可以作为构造函数 初始化包含原始子的包装对象
let boolean = new Boolean();
console.log(typeof boolean);// object
let string = new String();
console.log(typeof string);// object
let number = new Number();
console.log(typeof number);// object
// 如果你确实想使用Symbol包装对象 可以用Object()函数
let symbol = Symbol();
let o = Object(symbol);
console.log(typeof o);
- 如果在代码的不同位置需要共享和重用Symbol实例 可以使用
Symbol.for()
方法 该方法接受一个字符串作为参数 会以该字符串作为键 在全局符号注册表中创建并重用Symbol 该字符串其实是Symbol实例的描述
Symbol.for()
对每个字符串键都执行幂等操作(任意多次执行与执行一次的结果一样) 第一次使用某个字符串调用时 他会检查全局运行时注册表 发现不存在对应的Symbol 会生成一个新Symbol实例并添加到注册表中 后续使用相同字符串的调用同样会检查注册表 发现存在与该字符串对应的Symbol 就会返回该Symbol实例
let globalSymbol = Symbol.for('foo');// 创建新符号
let other = Symbol.for('foo');// 重用已有符号
console.log(globalSymbol === other);// true
console.log(globalSymbol);// Symbol(foo) foo是Symbol实例globalSymbol的描述
即使采用相同的Symbol描述 在全局注册表中定义的Symbol跟使用Symbol()
定义的Symbol并不相同 因为Symbol.for()
会将Symbol实例登记在全局符号注册表中供搜索 搜索到了直接使用 搜索不到会创建 Symbol()
函数每次调用都会返回一个新的值 如果你调用Symbol.for(‘cara')
10次 每次都会返回同一个Symbol实例 但调用Symbol(’Cara‘)
10次 会返回10个不同的Symbol实例
- 可以使用
Symbol.keyFor()
来查询全局注册表 该方法接受一个Symbol实例作为参数 返回该Symbol实例在表中对应的字符串键 找不到则返回undefined 如果传的参数不是Symbol实例 会报错
let globalSymbol = Symbol.for('foo');
console.log(Symbol.keyFor(globalSymbol));// foo
let s = Symbol('bar');
console.log(Symbol.keyFor(s));// undefined
- 剩余内容见书上
Object
- 每个Object实例都有如下属性和方法:
(1)constructor:用于创建当前对象的函数
(2)hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)是否有给定属性 propertyName是字符串或符号
(3)isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型a.isPrototypeOf(b)
a是当前对象
(4)propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in
枚举 propertyName是字符串
(5)toLocaleString():返回对象的字符串表示 该字符串反映对象所在的本地化执行环境
(6)toString():返回对象的字符串表示
(7)valueOf():返回对象对应的字符串 数值 布尔值表示 通常与toString()返回值相同 - DOM BOM是由宿主环境定义和提供的宿主对象 他们可能会也可能不会继承Object
操作符
- 对象使用操作符时 对象会先调用valueOf()或toString()来取得可以计算的值
- ++ --可以作用于任何值 对象 布尔值 浮点数 字符串 整数
(1)字符串:如果是有效的数值形式 则转换为数值再计算 否则变为NaN
(2)布尔值:false转为0 true转为1再计算
(3)浮点数:+/-1
(4)对象:调用其valueOf()方法取得可以操作的值 若值是NaN 则调用其toString()方法取得可以操作的值 针对这个值使用上述方法进行转换再计算 num
与+num
值一样-num
是num
的相反数- +/-应用于非数值 会执行与使用Number()一样的类型转换 所以可以使用+/-进行类型转换
- ECMAScript中的所有数值都以IEEE 754 64位格式存储 但位操作并不直接应用到64位标识 而是先把值转换为32位整数 再进行位操作 之后再把结果转换为64位 对开发者而言 就好像只有32位整数一样 因为64位整数存储格式是不可见的
在对ECMAScript中的数值应用位操作符时 后台会发生转换:64位数值会转换为32位数值 然后执行位操作 最后再把结果从32位转换为64位存储起来
- NaN和Infinity在位操作中都被当成0处理
- 非数值应用位操作符 首先会自动使用Number()将非数值转换为数值 然后再运用位操作符
- 默认情况下 ECMAScript中的所有整数都表示有符号数 无符号整数比有符号整数标识范围大
- 0表示正 1表示负
- 按位与
&
按位或|
按位异或^
按位非~
(对数值取反并-1) - 左移
<<
会保留符号 - 有符号右移>>会将数值的所有32位都向右移 且保留符号 用符号位填充右移的空位
- 无符号右移>>> 不保留符号 将所有32位都向右移 空位用0填充
- 逻辑非
!
可用于任何值 永远返回布尔值 他先将操作数转换为布尔值 再对其取反 !!
相当于调用Boolean()console.log(!!'');// false
- 逻辑与
&&
逻辑或||
:第一个操作数决定了结果 用于不会对第二个操作数求值 - 逻辑非
||
可以避免给变量赋值位null或undefinedlet object = preferredObject || backupObject;
如果preferredObject不为null或undefined 则将preferredObject的值赋给object 否则将备用值backupObject付给object backupObject不能位null或undefined - 非数值类型使用*、/、% 会使用Number()将非数值转换为数值再计算
- 取模
%
规则:
(1)无限值 % 有限值 = NaN
(2)有限值 % 无限值 = 被除数(有限值)
(3)有限值 % 0 = NaN
(4)0 % 非0 = 0
(5)Infinity % Infinity = NaN Math.pow(3,2)
等同于3 ** 2
+0 + -0 = +0
- 对于
+
如果有一个操作数是对象/布尔值 则调用他们的toString()方法以获取字符串 然后再应用关于字符串的规则(拼接) 对于undefined和null 会调用String()方法 分别获取"undefined"和"null" - 对于
-
如果有一个操作数是字符串、布尔值、null、undefined 则先在后台使用Number()将其转换为数值 再计算 如果转换结果是NaN 则计算结果是NaN
console.log(+0 - +0);// +0
console.log(+0 - -0);// -0
console.log(-0 - -0);// +0
- 关系操作符
> < >= <=
如果有一个操作数是数值、布尔值 则将操作数们转换为数值 进行数值比较 如果都是字符串 则逐个比较字符串中对应字符的编码(ASCII码) ==
和!=
会进行类型转换(强制/隐式类型转换)
(1)true转1 false转0
(2)字符串转数字
(3)如果有一个操作数是对象 调用对象的valueOf()方法取得其原始值 再根据前面的规则进行比较
(4)如果两个操作数都是对象 则比较他们是不是同一个对象 如果两个操作数都指向同一个对象 则返回true
(5)null和undefined不能转换为其他类型的值===
和!==
不转换 硬比较
语句
- 在
if
语句里面 ECMAScript会自动调用Boolean()函数将这个表达式的值转换为布尔值 do-while
应用场景:循环体内代码在退出前至少执行一次- for循环里面三条语句都不是必须的 但是都不写会造成死循环
for( ; ; ){}
for-in
枚举对象的非Symbol键属性 所有可枚举的属性都会被返回一次 但返回的顺序可能会因浏览器不同而不同 推荐使用const声明变量- 如果
for-in
循环要迭代的变量是null或undefined 则不执行循环体 for-of
用于遍历可迭代对象的元素 会按照可迭代对象的next()方法产生值得顺序迭代元素- 标签语句用于给语句加标签
标签: 语句
let num = 0;
outermost: for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i === 5 && j === 5) {
break outermost;// 要退出到的标签
}
num++;
}
}
console.log(num);// 55 当i === 5 && j === 5时 两个for循环都会退出 此时num=55
let num = 0;
outermost: for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i === 5 && j === 5) {
continue outermost;// 跳到outermost继续执行 所以剩下的j=6 7 8 9都不会执行
}
num++;
}
}
console.log(num);// 95 少执行5次
- with语句:将代码作用域设置位特定的对象
with(表达式) 语句
- with语句应用场景:对某个对象反复操作时 可以将代码作用域设置为该对象
let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;
// 上面的代码都用到location对象 如果使用with语句 就可以少写一些代码
with (location) {
let qs = search.substring(1);
let hostName = hostname;
let url = href;
}
// 在这个语句内部 每个变量首先会被认为时一个局部变量 如果没有找到该局部变量 则会搜素location对象
// 看他是否有一个同名的属性 如果有 则该变量会被求值为location对象的属性
- 严格模式下不允许使用with语句 with语句影响性能且难以调试其中的代码 不建议使用
- switch语句可以用于所有数据类型 包括字符串、对象 case条件的值不需要是常量 也可以是变量或表达式
let num = 25;
switch (true) {
case num < 10:
console.log("小于10");
break;
case num >= 10 && num <= 10:
console.log("介于0到10之间");
break;
case num > 10 && num <= 20:
console.log("介于10到20之间");
break;
default: console.log("大于20");// 输出这个
}
- switch语句在比较每个条件的值时会使用
===
因此不会强制转换数据类型
函数
- 只要碰到return 函数就会立即停止执行并退出 因此 return语句后面的代码不会被执行
- return语句可以不带返回值 此时函数会停止执行并返回undefined
- 函数要么返回值 要么不返回值 不能在某个条件下返回值 在某个条件下不返回值
- 严格模式下对函数的一些限制:
(1)函数不能以eval或arguments作为名称、参数
(2)两个命名参数不能拥有同一个名称