JavaScript 语言基础
语法
-
区分大小写。无论是变量、函数名还是操作符,都区分大小写。
-
标识符(变量、函数、属性或函数参数的名称)
- 第一个字符必须是字母、下划线(_)、或美元符号($);
- 剩余字符可以是 字母、数字、下划线 和 美元符号($);
- 标识符使用驼峰大小写形式命名:firstCard、myCar等;
- 关键字、保留字、true\false、null不能作为标识符
-
注释:(采用c语言风格的注释)
// 单行注释:xxxxx /** * 多行注释:xxxxxxx */
-
严格模式
对整个脚本启用严格模式,在脚本开头加上 "use strict" 单独指定一个函数使用严格模式: function doSomething() { "use strict"; }
-
语句
ECMAScript 中的语句以分号结尾。可以省略分号,但不推荐。加上分号有助于防止省略分号带来的问题,如:
- 避免输入内容不完整;
- 便于开发者删除空行来压缩代码,避免语法错误;
- 有助于在某些情况下提升性能。解析器会尝试在合适的位置上加上分号以纠正语法错误。
控制流程语句,通常是由多条语句组成,也可能是单条语句,都推荐用代码块 花括号
{}
包裹,便于阅读和减少出错的可能性。
变量
ECMAScript 中的变量是松散类型,可以用于保存任何类型的数据,每个变量是用于保存任意值的命名占位符。有3个关键字可以声明变量:var、let、const。
var、let 声明的变量,如果没有初始化赋值,默认为
undefined
;const 声明变量的同时,必须初始化变量,且不可被改变。如果被定义的变量是一个引用类型的数据,则这个引用地址不可被改变,即对象不能被整体覆盖,但对象内部的属性可以被修改。
如果一个变量声明省略了声明关键字,会被var隐式声明为全局变量。(不推荐的做法!不方便变量的管理)。在严格模式下,给这样声明的变量赋值会抛出ReferenceError。
var
-
作用域:函数作用域
var 声明的变量,会成为包含这个变量的函数的局部变量。当这个变量不被下级或外部持有时(没有形成闭包),函数调用完毕,该变量就会被回收
-
声明提升
用var 声明的变量,会自动被提升到函数作用域的顶部,并会对多次重复声明的变量进行合并,值为
undefined
。所以在声明之前调用该变量,不会报错,但值为 undefined。 -
全局声明
var 声明全局变量时,该变量会成为
window
对象的属性,可以用in
操作符判断
let
-
作用域:块级作用域
块级作用域 相当于 函数作用的子集。let 声明的变量,作用域范围是被花括号
{ }
包裹的代码块内,代码块外无法访问到其内声明的变量。 -
同一作用域内,不能重复声明同名变量。但可以该代码块内的嵌套代码块内声明同名变量,因为javascript引擎会记录用于声明变量的标识符 及其 所在的块级作用域。
-
变量提升
不会在声明的作用域内被提升,有暂时性死区。javascript在解析代码时(执行上下文的创建阶段),会注意到出现在块后面的 let 声明的变量,该变量在声明之前都不能被 调用,否则报错,即为暂时性死区
-
全局声明
用 let 声明的全局变量,不会成为
window
对象的属性!相应变量的生命周期 与 页面的生命周期相同,所以需要保证同一个页面避免重复let声明同一个变量,否则报错。
for 循环中的变量声明
// var 声明
for(var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 5次都是5,因为setTimeout是异步的,会等for循环结束后再执行,所以i的值已经是5了。
}, 0)
}
// IIFE匿名自执行函数改造
for(var i = 0; i < 5; i++) {
(function(i){
setTimeout(() => {
console.log(i); // 0 1 2 3 4
}, 0)
})(i) // 将当此循环的变量作为参数,值传入。
}
// let 声明
for(let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) // 0 1 2 3 4
}, 0
}
使用let声明迭代变量时,javascript引擎会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量,是当前循环变量。
if(true) {
var age = 30;
let age = 26; // 报错 SyntaxError age已经被声明过,不会因为是var还是let声明的同名变量而产生影响
}
if(true) {
let age = 26;
if(age < 18 {
let age = 18; // 不会报错,两个age处在不同的代码块内
}
}
const
const 与 let 基本相同,唯一重要区别是const声明变量的同时,需要初始化赋值变量,且后续不可修改该变量。
- 作用域:块级作用域
不能用const
声明for循环的变量,但可以声明 for-of、for-in循环
for(const i = 0; i < 10; i++) {} // 报错!i会自增
for(const key in {a: 1, b: 2}) {
console.log(key); // 只是在每次迭代循环时,用const声明一个key变量
}
for(const value of [1, 2, 3, 4]) {
console.log(value); // 只是在每次迭代循环时,用const声明一个key变量
}
ECMAScript 6 增加了 let
const
提供了更精确的声明作用域和语义支持,推荐不再使用 var
。const
优先使用,let
次之。
数据类型
ECMAScript的数据类型分为 原始数据类型 和 引用数据类型。
原始数据类型有 6 种:
- Number
- String
- Boolean
- Null
- Undefined
- Symbol (es6新增)
Number
Number类型使用IEEE 754
格式表示整数和浮点数,不同的数值类型(整数/浮点数)也有相应的不同字面量格式:十进制、八进制、十六进制
IEEE 754 是一个由 IEEE(Institute of Electrical and Electronics Engineers)制定的浮点数运算标准,广泛用于计算机系统中表示实数(如 float、double)。
常见的浮点数格式
类型 总位数 符号位 指数位 尾数位 单精度(float) 32 位 1 8 23 双精度(double) 64 位 1 11 52
🧮 示例:32位浮点数(二进制)
一个单精度浮点数(float)可能表示如下:
0 10000001 01100000000000000000000
| |--------|---------------------|
| 指数 尾数(23 位)
符号位
表示的是一个正数(符号位 0),指数为 129(即实际指数为 129 - 127 = 2),尾数是 1.75(二进制),
单精度的固定偏移量是:2^7 - 1 = 127
双精度的固定偏移量是:2^10 - 1 = 1023
1. 浮点数
存储浮点数使用的内存空间是存储整数值的两倍,所以ECMAScript总是会想方设法将值转为整数。
对于非常大或者非常小的浮点值,可以使用科学计数法表示。浮点数的精度最高可达17位小数,因此使用中会存在误差,如:0.1 + 0.2 != 0.3
因此永远不要测试某个特定的浮点值。
2. 值的范围
由于内存的限制,ECMAScript能表示的值的范围有限。能表示的最小值保存在 Number.MIN_VALUE
, 在大多数浏览器中是 5e-324
;最大值保存在 Number.MAX_VALUE
中,在多数浏览器中是1.797 693 134 862 315 7e+308
;
如果计算的值超出了这个范围 Number.MIN_VALUE ~ Number.MAX_VALUE
, 就会自动转换为一个特殊的值 Infinity
或者 -Infinity
,都不能在进行任何运算。可以使用 isFinity()
方法判断。
使用
Number.NEGATIVE_INFINITY
可以获取infinity
Number.POSITIVE_INFINITY
获取-infinity
3. NaN
NaN
是一个特殊的数值,表示 不是数值 (not a number),用于表示本来要返回一个数值,但失败了。
NaN
的几个独特属性:
- NaN 与任何值都不等,包括 NaN 本身;
- NaN 与任何值进行操作,结果都是 NaN;
- 可使用
isNaN()
判断任意数据是否是NaN,会将接受到的值 先转为数值类型,再判断。如果接受的是一个对象类型的值,首先会调用对象的valueOf()
方法,然后再确定返回的值是否可转为数值,若不能,再调用toString()
方法,并测试其返回值。
4. 数值转换
有三个方法可以将非数值转换为数值:Number()
、parseInt()
、parsefloat()
。
方式一: Number()
Number()
方法是强制转换,可以用于任意数据类型。
遵循的规则:
- 布尔值,true转为1,false转为0;
- 数值,直接返回;
- null,返回 0;
- undefined,返回 NaN;
- 字符串,应用以下规则:
- 如果字符串包含数值,包括前面带
+
-
的情况,转为十进制数值; - 如果字符串包含有效浮点值格式,如
'1.01'
, 转为浮点数; - 如果字符串包含有效的十六进制格式,如
"0xf"
,会转为与该十六进制值对应的十进制整数值; - 空字符串,返回 0;
- 包含除以上情况之外的字符串,返回NaN。
- 如果字符串包含数值,包括前面带
- 对象。先调用
valueOf()
方法,按照上述规则进行转换,如果是NaN
, 再调用toString()
方法,按照字符串的转换规则进行转换。
方式二:parseInt()
主要用于将字符串转换为数值。
Number()
方法更专注于是否包含数值模式。字符串前面的空格会被忽略,从第一个非空格字符开始转换,如果第一个字符不是数值、加号或减号,直接返回 NaN;意味着空字符也会返回NaN
parseInt("") // NaN
这点于Number() 方法不同!!依次检测每个字符,直到字符串末尾,或碰到非数值字符,如
"123blue"
被转换为123
,"11.2"
被转换为11
, 小数点也不是有效的字符。
let num = parseInt("123blue"); // 123
let num = parseInt(""); // NaN
let num = parseInt("0xA"); // 10 解释为十六进制整数
let num = parseInt("22.4"); // 22
let num = parseInt("70"); // 70 解释为 十进制整数
// parseInt() 也接受第二个参数,用于指定进制数, 默认是十进制
let num = parseInt("0xAF", 16); // 175
let num = parseInt("AF", 16); // 175 如果提供了进制数,前面的0x可以省略
let num = parseInt("AF"); // NaN
let num = parseInt("10", 2); // 2 按二进制解析
let num = parseInt("10", 8); // 8 按八进制解析
方式三: parseFloat()
与
parseInt()
用法相似,都是从字符串的第0位开始,检测每个字符,直到字符串末尾,或者解析的一个无效的浮点数值字符为止,第一次出现的小数点有效,第二次出现的小数点无效。只能解析十进制,不能指定进制数。
let num = parseFloat("123blue"); // 123 按整数解析
let num = parseFloat("0xA"); // 0
let num = parseFloat("22.5"); // 22.5
let num = parseFloat("22.32.4"); // 22.32
let num = parseFloat("0908.4"); // 908.4
let num = parseFloat("3.123e7"); // 31230000
String
字符串可以用单引号''
、双引号""
、反引号标识;
字符串是不可变的,一旦创建了,他们的值就不能再被改变,如果要修改原来某个变量的字符串值,就必须先销毁原来的字符串,然后将新值的另一个字符串保存到该变量。
转换为字符串
转换为字符串的方法有两个:toString()
、String()
toString()
方法:
几乎所有值都有
toString()
方法,除了undefined
、null
, 该方法返回当前值的字符串等价值多数情况下,
toString()
不接受任何参数,如果是数值调用这个方法时,可以传入一个底数参数,标识进制,默认情况是十进制
let num = 10;
num.toString() // "10"
num.toString(2) // "1010"
num.toString(8) // "12"
num.toString(16) // "a"
因为 null
和 undefined
没有 toString()
方法,所以如果不确定传入的值是否是这两个值时,可以用第二种方式: String()
强制转换,遵循如下规则:
- 如果值有
toString()
方法,调用该方法转换; - 如果值是
null
, 返回"null"
- 如果值是
undefined
,返回"undefined"
模板字面量
es6新增的使用模板字面量定义字符串。保留了换行符,可以跨行定义字符串;
可以使用字符串插值,插入javascript语句和变量。字符串使用${}
插入js表达式;
使用模板字面量标签函数:
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。
标签函数 会接收被插值记号分隔后的模板和对每个表达式求值的结果。
let a = 1;
let b = 2;
let tag = `sum: ${ a } + ${ b } = ${ a + b }`
// 标签函数
function simpleTag(str, ...vals) {
console.log(str) // ['sum: ', ' + ', ' = ', '', raw: Array(4)] 被插值记号分割的字符串数组
console.log(vals) // [1, 2, 3] 表达式的计算结果
}
console.log(tag); // "sum: 1 + 2 = 3"
simpleTag`sum: ${ a } + ${ b } = ${ a + b }` // 调用模板字面量标签函数
Boolean
布尔值有两个字面量值:
true
false
, 并严格区分大小写。
布尔值类型转换:
notes: 除了一下 个值,其余值都是转为true
false
0、NaN
空字符串("")
null
undefined
Null
Null类型也只有一个字面量值:
null
, 表示一个空对象指针。使用typeof
判断null
的类型时,返回"object"
在定义一个将来要保存对象值的变量时,建议用 null
来初始化
Undefined
唯一的字面量值:
undefined
,由null
派生而来,ECMAScript定义他们表面上是相等的,即:undefined == null // true
定义一个变量时,没有初始化,默认的初始化值是 undefined
Object
Object 类型就是 一组数据和功能的集合 ,通过
new
关键字来实例化对象
Symbol
Symbol 类型是 es6新增的数据类型,符号类型。
符号是原始值,是唯一 且 不可变的。
用途:
确保对象属性使用唯一标识符,不会出现属性冲突的危险。同样,可以使用Symbol来创建唯一记号,用作非字符串形式的对象属性。
基本用法
使用
Symbol()
函数直接初始化,注意:不能使用new
!new 关键字是实例化对象的,Symbol是原始类型。只要创建 Symbol()实例并将其 用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。
let sym = Symbol();
let sym1 = Symbol();
console.log(sym == sym1); // false
console.log(sym); // Symbol()
// 可以传入一个字符串参数作为对符号的描述。但这个描述与符号的定义和标识完全无关,只是方便将来调试代码。
let sym2 = Symbol("foo");
let sym3 = Symbol("foo");
console.log(sym2 == sym3); // false
console.log(sym2); // Symbol("foo")
Symbol.for()
全局符号注册表。
Symbol.keyfor()
在全局符号注册表中查找相关描述的符号,返回描述字符串