ECMAScript简介
JavaScript是大家所了解的语言名称, 但它的正式名称叫做ECMAScript。
1996年11月, JavaScript的创造者网景公司将JavaScript提交给国际化组织 ECMA(欧洲计算机制造联合会), 希望这种语言能够成为国际标准。
随后 ECMA 发布了规定浏览器脚本语言的标准, 即ECMAScript。 它的历史版本:
version | 时间 | 描述 |
---|---|---|
1.0 | 1997 | 诞生,制定了语言的基本语法 |
2.0 | 1998 | 较小改动,同步独立的ISO国际标准 |
3.0 | 1999 | 引入正则,异常处理,格式化输出等。在业界得到了广泛的支持, 奠定了JS的基本语法 |
4.0 | 2000 | 过于激进,未发布 |
5.0 | 2009 | 引入严格模式,Json,扩展对象,数组,原型,字符串,日期方法 |
6.0 | 2015 | 模块,面向对象语法,Promise,箭头函数,let,const,数组解构赋值等 |
7.0 | 2016 | 幂运算符,数组扩展,Async/await关键字 |
8.0 | 2017 | Async/await,字符串扩展 |
9.0 | 2018 | 对象解构赋值,正则扩展 |
10 | 2019 | 扩展对象,数组方法 |
ES6
目前市面上推荐使用的是ECMAScript 6.0(ES6), 主要原因在于:
- 兼容性 目前并非所有的浏览器相关都支持最新语言的特性,而ES6已经被广泛支持。并且通过转译工具可将ES6转换为较低版本的JavaScript,以确保在各种环境中的运行
- 新特性 ES6引入了箭头函数,解构赋值,类等,使得代码更加简洁、易读和维护
- 模块化支持 可以提升代码的复用性和可维护性,使得项目结构更清晰
- 异步编程 引入
Promise
和async
特性, 支持异步编程
ES6的推广主要还是为了解决ES5的不足,目前浏览器的JavaScript虽是ES5版本,但大多数的高版本浏览器支持ES6的使用。
它又被称为ES2015, 因为它是2015年发布的,根据年份来命名的。
ES6所支持的新特性简要说明下:
- 增加
let
和const
, 可用于块作用域声明变量,避免了var
变量提升和作用域不明的问题 - 新增的原始数据类型
Symbol
, 可用于定义对象的唯一属性 - 增加了解构赋值的语法, 使得代码更加简洁和易于理解
- 拓展了
Number
,支持二/八进制表示,增加了isFinite
,整合了parseInt
方法等 - 拓展了
String
,新增了子字符串的识别, 模版字符串的拼接等 - 拓展了
Array
, 增加了对象转换为数组,以及查找的方法相关 - 拓展了函数的默认参数设置, 箭头函数等
- 新增
Map
容器和Set
对象的使用 - 新增
Proxy
和Reflect
- 引入class类的支持, 支持extends继承, super访问父类防范, 静态方法等
- 引入模块, 主要分为
export导出
和import导入
- 引入
Generator 、
Promise和
async等异步编程
let、const和Symbol
ES6之前只有全局变量 和函数内局部变量 。let
和const
为新增关键字:
let
必须在声明之后才能使用, 不可重复声明, 块作用域, 变量不能提升const
声明之后必须初始化,否则会报错。 且声明后不允许改变,属于只读变量。
typescript
// ---------- let -------------
{
let value = 0;
console.log(value); // 0
}
// Error: value is not defined
console.log(value);
// ---------- let -------------
const value: number = 10;
// Error: Assignment to constant variable
value = 100;
经常会拿var
与let
比较:
typescript
/*
var是ES5及之前的JavaScript的关键字,特点:
* 函数作用域,声明的变量在整个函数体内, 而不仅是在块作用域内
* 变量会提升,声明的变量会提升到函数的顶部, 即可在声明之前使用
* 允许重复声明
*/
function example() {
var x = 10;
if (true) {
var x = 20; // 在同一函数作用域内重复声明变量x
console.log(x); // 输出:20
}
console.log(x); // 输出:20
}
example();
/*
let是ES6及之后的JavaScript和TypeScript引入的关键子,特点:
* 块作用域, 比如if语句,循环语句,并不是整个函数体内
* 不存在变量提升,必须声明之后才能使用
* 不允许重复声明,否则会报错
*/
function example2() {
let y = 10;
if (true) {
let y = 20; // 在块级作用域内声明变量y
console.log(y); // 输出:20
}
console.log(y); // 输出:10
}
example2();
const
需要注意,所谓的不允许改变真正指的是:变量或引用所指向的内存地址不可改变。
在基础数据类型使用中,值就保存在变量指向的地址。 而对于类似于Array数组,Object对象等这些复杂类型, 对象内的子元素是可改变的
typescript
// ------------ 数组 ------------
const numData = [1, 2];
// 允许插入数值
numData.push(3);
console.log(numData);
// 不允许改变对象,Assignment to constant variable
numData = [4, 5];
// ------------ object ------------
const objectData = {
value: 1,
}
// 允许修改对象内部元素
objectData.value = 2;
// 不允许改变对象,Assignment to constant variable
objectData = {value: 2};
Symbol
除了number string boolean, object, null, undefined
外, Symbol为新增的原始数据类型。
它被用于表示独一无二的值,可被用于定义对象的唯一属性名,构成:
typescript
// lib.es2015.symbol.d.ts
interface SymbolConstructor {
readonly prototype: Symbol;
// 创建symbol对象
(description?: string | number): symbol;
// 全局搜索指定key字符串的symbol对象
for(key: string): symbol;
// 获取symbol对象的key, 如果没有则undefined
keyFor(sym: symbol): string | undefined;
}
declare var Symbol: SymbolConstructor;
基本使用:
typescript
let sym = Symbol("a_1");
console.log(typeof(sym), sym); // "symbol", Symbol(a_1)
主要用法:
- 定义常量,保证不同常量的唯一性
- 作为对象的属性名, 使用
[]
来设定,不要使用.
简单的实例:
typescript
let sy = Symbol("key1");
let obj = {
[sy]: "kk",
value: 10,
};
// 虽然作为对象的属性名,但其实是不可读的, 结果是:{"value": 10}
console.log(obj);
解构赋值
它是对赋值运算符的扩展, 可用于 **数组 **或 对象 进行模式匹配, 然后对其中的变量进行赋值。
typescript
// 基本使用
let [a, b, c] = [1,2,3];
console.log(a, b, c); // 1, 2, 3
let {a, b, c} = {a:1,b:2,c:3};
console.log(a, b, c); // 1, 2, 3
// 嵌套使用
let [a, [b, c]] = [1, [2, 3]];
console.log(a, b, c); // 1, 2, 3
let [A, [[B], C]] = [1, [[2], 3]];
console.log(A, B, C); // 1, 2, 3
// 忽略
let [a1, , c1] = [1, 2, 3];
console.log(a1, c1); // 1, 3
let [, b2, , d2] = [1,2, 3,4];
console.log(b2, d2); // 2, 4
let [a3, [, b3], [,,c3]] = [1, [2,3], [4,5,6]];
console.log(a3, b3, c3); // 1, 3, 6
支持不完全解构,可以做个简单了解,但不推荐使用,会有报错
typescript
// Error: Tuple type '[number]' of length '1' has no element at index '1'
let [a1, b1] = [1];
console.log(a1, b1); // 1, undefined
let [a2 = 1, b2] = [];
console.log(a2, b2); // 1, undefined
支持剩余参数 ,通过...参数
来设定,它会用于收集剩余的元素来创建一个新的数组。
typescript
// 数组
let [a, ...b] = [1, 2, 3];
console.log(typeof(a), typeof(b)); // "number", "object"
console.log(a, b); // 1, [2, 3]
// 对象
let {age, ...params} = {
name: "ES",
age: 2023,
value: 1,
}
// 参数age被赋值,其余的都给了params
// 2023, {"name": "ES", "value": 1}
console.log(age, params);
// 函数参数,比如求和
function AddNum(...numbers: any) {
let totalValue = 0;
for (let num of numbers) {
totalValue += parseInt(num);
}
return totalValue;
}
console.log(AddNum(1, 2, 3)); // 6
console.log(AddNum(1, "2", "3")); // 6
数字的拓展
- 二进制和八进制,分别增加了前缀
0b/0B
和0o/0O
typescript
console.log(0b1010 === 10); // true
console.log(0o12 === 10); // true
Number.EPSILON
的使用, 浮点数的存储和精度问题导致计算机无法精确计算,所以会设置一个很小的误差范围来比对
typescript
// 直接比较, 是不相等的
console.log(0.1 + 0.2 === 0.3); // false
// 通过Number.EPSILON比对
function isEqual(a:number, b:number) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(isEqual(0.1+0.2, 0.3)); // true
console.log(isEqual(0.1+0.2, 0.300001)); // false
- 增加了最大/最小安全整数, 范围在(2^-53, 2^53)之间
typescript
// 最小整数范围下限
const MIN_SAFE = Number.MIN_SAFE_INTEGER;
console.log(MIN_SAFE - 1 === MIN_SAFE - 2); // true
console.log(MIN_SAFE !== MIN_SAFE + 1); // true
// 最大整数范围上限
const MAX_SAFE = Number.MAX_SAFE_INTEGER;
console.log(MAX_SAFE + 1 === MAX_SAFE + 2); // true
console.log(MAX_SAFE !== MIN_SAFE - 1); // true
Number.isFinite()
检查数值是否为有限数字,即不为Infinity
typescript
// 基础数字,true
console.log(Number.isFinite(1));
// 最小浮点数误差,true
console.log(Number.isFinite(Number.EPSILON));
// 最小值,true
console.log(Number.isFinite(Number.MIN_VALUE));
// 最大安全整数上限,true
console.log(Number.isFinite(Number.MAX_SAFE_INTEGER));
// 正无穷大,false
console.log(Number.isFinite(Number.POSITIVE_INFINITY));
// NaN, false
console.log(Number.isFinite(Number.NaN));
// 注意不存在隐式类型转换,都是false
console.log(Number.isFinite("10"));
console.log(Number.isFinite(undefined));
console.log(Number.isFinite([]));
console.log(Number.isFinite({}));
parseInt()
从全局整合到Number
中, 它主要用于将字符串转换为指定进制的数字
typescript
/*
@funcName: parseInt(string: string, radix?: number | undefined): number
@param: string 转换的字符串
@param: radix: 可选参数进制,范围在[2, 36], 默认十进制
*/
console.log(Number.parseInt("2")); // 2
console.log(Number.parseInt("12,56")); // 12
console.log(Number.parseInt("0011",2)); // 3
// 注意:虽然整合了,但本质还是一个同一个函数
console.log(parseInt === Number.parseInt); // true
// 注意:无法解析为数字的,会返回NaN
console.log(Number.parseInt("10" + 1)); // 101
console.log(Number.parseInt("abc" + 1)); // NaN
// 拓展:可使用 isInteger 检测是否为number, 参数为unknown类型
console.log(Number.isInteger(1)); // true
// 如下一律为false
console.log(Number.isInteger("1"));
console.log(Number.isInteger(undefined));
console.log(Number.isInteger(NaN));
console.log(Number.isInteger(false));
console.log(Number.isInteger(null));
console.log(Number.isInteger(Infinity));
console.log(Number.isInteger(Number.NEGATIVE_INFINITY));
parseFloat()
从全局整合到Number
中, 它用于将字符串转换为数字
typescript
/**
@funcName: parseFloat(string: string): number
@param: string 要转换的字符串
*/
// 注意参数不能为非string, 否则报错
// console.log(Number.parseFloat([]));
// console.log(Number.parseFloat(undefined));
console.log(Number.parseFloat('123.45')); // 123.45
console.log(Number.parseFloat('123.45abc'));// 123.45
console.log(Number.parseFloat('123abc456'));// 123
// 解析不出来就是NaN
console.log(Number.parseFloat('abc')); // NaN
- Math对象增加了一些方法的支持
方法名 | 描述 |
---|---|
clz32 | 返回数字的32 位无符号整数形式的前导0的个数 |
imul | 返回两个数的32位整数乘积 |
sign | 返回一个数的符号,是正数(1)、负数(-1)还是0 |
log10 | 返回一个数的以10为底的对数 |
log2 | 返回一个数的以2为底的对数 |
log1p | 返回一个数加1的自然对数 |
expm1 | 返回一个数的自然指数减1 |
cosh | 返回一个数的双曲余弦值 |
sinh | 返回一个数的双曲正弦值 |
tanh | 返回一个数的双曲正切值 |
acosh | 返回一个数的反双曲余弦值 |
asinh | 返回一个数的反双曲正弦值 |
atanh | 返回一个数的反双曲正切值 |
hypot | 返回所有参数的平方和的平方根 |
trunc | 返回一个数的整数部分 |
fround | 返回一个数的最接近的单精度浮点数表示 |
cbrt | 返回一个数的立方根// |
typescript
// imul 计算 a * b
console.log(Math.imul(2, 3)); // 6
// sign 判定数字符号,为是正数(1)、负数(-1)还是零
console.log(Math.sign(0)); // 0
console.log(Math.sign(Number.MAX_VALUE)); // 1
console.log(Math.sign(Number.NEGATIVE_INFINITY));// -1
// hypot 计算 所有参数的平方和的平方根
console.log(Math.hypot(3)); // 3
console.log(Math.hypot(3, 4)); // 5
// trunc 返回数字的整数部分,没有四舍五入
console.log(Math.trunc(0.56)); // 0
console.log(Math.trunc(-1.56)); // -1
// cbrt 返回数字的立方根
console.log(Math.cbrt(8)); // 2
// log10 返回以10为底的对数
console.log(Math.log10(100)); // 2
其他内容可参考: TypeScript 之 Number
字符串的拓展
增加的新特性主要有:
- 模版字符串的支持, 支持使用反引号和
${}
对字符串进行组合
typescript
// 使用反引号支持多行
let content = `Hello:
You have a lot of names, ES6 and ES2015
`
console.log(content);
// 组合,且支持表达式的计算
const strName = 'Alice';
const ageNum = 25;
const msg = `My name ${strName} and I am ${ageNum+1} years old.`;
// "My name Alice and I am 26 years old."
console.log(msg);
// 表达式
const x = 10;
const y = 5;
const result = `${x} + ${y} = ${x + y}`;
// "10 + 5 = 15"
console.log(result);
// 调用函数
function f(){
return "have fun!";
}
let content = `Game start,${f()}`;
// "Game start,have fun!"
console.log(content);
- 增加了字符串检索子字符串的接口:
typescript
const str = 'Hello, world!';
// startsWith 检测字符串是否在原字符串串头部
console.log(str.startsWith('Hello')); // true
console.log(str.startsWith('ello')); // false
// endsWith 检测字符串是否在原字符串的尾部
console.log(str.endsWith('world!')); // true
console.log(str.endsWith('rld')); // false
// includes 检测是否包含字符串
console.log(str.includes('llo')); // true
console.log(str.includes('o, w')); // true
- 增加了重复字符串和填充字符串的接口:
typescript
let str = 'a';
/**
reqeat 返回指定次数的新字符串,注意:
* 如果参数为大于0的浮点数,则向下取整
* 如果为0到-1之间的数字,默认为0
*/
console.log(str.repeat(3)); // "aaa"
console.log(str.repeat(3.5)); // "aaa"
console.log(str.repeat(-0.1)); // ""
/*
@padStart(maxLength: number, fillString?: string): string
@func: 从头部补充字符串
@param: 设定的长度,如果小于当前字符串长度,则返回当前字符串
@param: 填充的字符串
*/
str = str.repeat(3);
// 长度小于当前字符串长度,不变
console.log(str.padStart(1, 'x')); // "aaa"
console.log(str.padStart(5, 'x')); // "xxaaa"
// padEnd 从末尾补充字符串
console.log(str.padEnd(6, 'y')); // "aaayyy"
其他内容可参考: TypeScript 之 String
数组的拓展
- 扩展元算符的拓展
...
, 支持将独立的元素合并为一个新的数组等
typescript
// 方式1
let [a, ...b] = [1, 2, 3];
console.log(typeof(a), typeof(b)); // "number", "object"
console.log(a, b); // 1, [2, 3]
// 方式2
const arr1 = [1, 2];
const arr2 = [4, 5];
const mergedArray = [...arr1, ...arr2];
console.log(mergedArray); // [1, 2, 4, 5]
// 方式3
function AddNum(...numbers: any) {
let totalValue = 0;
for (let num of numbers) {
totalValue += parseInt(num);
}
return totalValue;
}
console.log(AddNum(1, 2, 3)); // 6
Array.of()
将参数转换为数组
typescript
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
console.log(Array.of(1, '2', true)); // [1, "2", true]
Array.from()
将对象或可迭代器对象转换为数组
typescript
// 基本使用
const str = 'hello';
const arr = Array.from(str);
console.log(arr); // ["h", "e", "l", "l", "o"]
// 支持对每个元素处理
let array = Array.from([1, 2, 3], (n) => n * 2)
console.log(array); // [2, 4, 6]
find()
和findIndex()
查找满足条件的元素相关
typescript
const numbers = [1, 2, 3];
// find 查找并返回满足条件的第一个元素
const evenNumber = numbers.find(num => num % 2 === 0);
console.log(evenNumber); // 2
// findIndex 查找并返回满足条件的第一个元素索引
const index = numbers.findIndex(num => num % 2 === 0);
console.log(index); // 1
includes()
检测数组中是否包含指定元素
typescript
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits.includes('banana')); // true
console.log(fruits.includes('grape')); // false
fill()
填充数据
typescript
/**
@func: fill用指定的数值填充数组,会修改原有数组,并返回新的数组
@param: 填充的数值
@param: 开始索引, 如果忽略表示开始
@param: 结束索引, 如果忽略表示末尾
*/
let arr1 = Array.of(1, 2, 3);
console.log(arr1.fill(0)); // [0, 0, 0]
let arr2 = Array.of(1, 2, 3);
console.log(arr2.fill(0,1,2)); // [1, 0, 3]
flat()
将嵌套数组展平为一个新的数组
typescript
const arr = [1, 2, [3], [4]];
const flattenedArray = arr.flat();
console.log(flattenedArray); // [1, 2, 3, 4]
其他内容可参考: TypeScript 之 Array