ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制造商协会,这个组织目标是评估、开发和认可电信和计算机标准。1994年该组织改名为 Ecma 国际。
ECMAScript 简称 ES,是脚本语言的规范,平时编写的 JavaScript,是 ECMAScript规范的一种实现,ES 新特性指的是 JavaScript 新特性
ECMAScript 是由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言,发展历史如下:
(ECMA-262 是 Ecma国际制定了许多标准中的一个)
ECMA版本 | 发布日期 | 新特性 |
---|---|---|
1 | 1997 年 | 制定了语言的基本语法 |
2 | 1998 年 | 较小改动 |
3 | 1999 年 | 引入正则、异常处理,格式化输出等,IE浏览器开始支持 |
4 | 2007年 | 未发布 |
5 | 2009年 | 引入严格模式,JSON、扩展对象、数组、原型、字符串、日期方法 |
6 | 2015年 | 模块化,面向对象语法、Promise、箭头函数、let、const、数组解构赋值等等 |
7 | 2016年 | 幂运算符、数组扩展、Async/await 关键字 |
8 | 2017年 | Async/await 、字符串扩展 |
9 | 2018年 | 对象解构赋值,正则扩展 |
10 | 2019年 | 扩展对象、数组方法 |
注:从 ES6 开始,每年发布一个版本,版本号比年份最后一位大1
TC39 (Technical Committee 39)是推进 ECMAScript 发展的委员会,其会员都是各种公司(主要是浏览器厂商,有苹果、谷歌、微软等)。
TC39 会定期召开会议,会议由会员公司的代表与特邀专家出席
为什么要学习 ES6
ES6与不同浏览器版本的兼容性查询
http://kangax.github.io/compat-table/es6/
let 是 ES6 新推出的数据类型,let 用于声明变量,若不赋值,则默认为 undefined 类型
{
let name = 'uni'
}
console.log(name) // 报错, 无法访问
这里包括但不限于 if、else、while、for 、funtion 所包含的 { }
let name = 'uni';
function fn(){
console.log(name);
}
fn();
const 用于声明常量
{
const NAME = 'uni'
}
console.log(NAME) // 报错
const users = []
users.puth('uni')
解构赋值:ES6 允许按照一定模式从数组和对象中提取值,对变量进行赋值
解构赋值一般分为数组和对象这两种
【例】对数组进行解构赋值
const words = ['a', 'b', 'c']
let [a, b, c] = words
// a = 'a'
// b = 'b'
// c = 'c'
【例】对对象进行解构赋值
const user={
id: 1,
name: 'uni',
say: function(){
console.log(`name: ${name}, id: ${id}`)
}
}
let {id, name, say} = user;
say()
使用
反引号,支持使用 ${} 取值,例:
let name = 'uni'
console.log(`name: ${name}`)
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法
let name = 'uni'
let say = function(){
console.log(`name: ${uni}`)
}
const uni = {name, say}
// 相当于: const uni = {name: name, say: say}
console.log(uni)
ES6 允许使用 =>
箭头 定义函数
【例】定义求和函数
let fn = (a, b) => { return a + b }
箭头函数的特性:
【例】直接调用两种定义方式的函数
function getName(){
console.log(this.name)
}
function getName2(){
console.log(this.name);
}
window.name = 'uni',
const school = {
name: 'UNI'
}
console.log(getName())
console.log(getName2())
运行结果
uni
uni
例,使用 call 来改变函数的this
getName.call(school)
getName2.call(school)
运行结果
UNI
UNI
【例】箭头函数的错误使用
let Person = (name, age) => {
this.name = name
this.age = age
}
let uni = new Person('uni', 22)
console.log(uni) // 报错
let fn = () => {
console.log(arguments);
}
简写前
let abs = (a) => {
if(a < 0)
return -a;
else
return a;
}
简写后
let abs = a => {
if(a < 0)
return -a;
else
return a;
}
// 简写前
let pow = (n) => {
return n * n
}
// 简写后
let pow2 = n => n * n
console.log(pow(1)
使用场景:
ES6 允许给函数参数赋值默认值,经常与解构赋值结合使用
【例】
function connect({
host="127.0.0.1",
username,
password,
port
})
connect({username: 'root', password: 'root', port: 3306})
ES6 引入 REST 参数,用于获取函数的实参,用于代替 arguments
// ES5 获取实参的方式
function data(){
console.log(arguments);
}
data(1,2,3)
// ES6 使用 rest 参数
function data(...args){
console.log(args)
}
data(1,2,3)
rest 参数必须要放到参数最后
ES6 的扩展运算符能将数组转换为逗号分隔的参数序列
const words = ['a', 'b', 'c']
function data(...args){
console.log(args)
}
data(...words)
ES6 引入了一种新的基本数据类型 (第八种)Symbol,表示独一无二的值,类似于字符串。
Symbol 数据的类型的特点有:
// 1. 创建 Symbol
let s1 = Symbol();
console.log(s1, typeof s1) // Symbol() "symbol"
let s2 = Symbol('uni')
let s3 = Symbol('uni')
console.log(s2 == s3) // false
Symbol.for(key) 方法会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
【例】
let s4 = Symbol.for('hello')
let s5 = Symbol.for('hello')
console.log(s4 === s5) // true
Symbol 可用于表示对象唯一的属性,例如:
let methods = {
say1: Symbol(),
say2: Symbol()
}
let game = {}
game[methods.say1] = function(){ console.log('say1'); }
game[methods.say2] = function(){ console.log('say2'); }
game[methods.say1]()
除了上述这种方法,先定义包含 Symbol() 属性的对象外,还有更简化的方法,直接在定义方法时创建 Symbol 对象,例如:
let game = {
[Symbol('say1')]: function(){ console.log('say1'); },
[Symbol('say2')]: function(){ console.log('say2'); }
}
// 这里的 Symbol('say1') 无法调用,通常是使用 Symbol 的内置属性来代替
除了定义自己使用的 Symbol 值意外, ES6 还提供了 11个内置的 Symbol 值,指向语言内部使用的方法,等用到的时候可以查看文档
参考资料:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol
在 Symbol 的这些内置属性中,比较关键的就是 iterator 迭代器
Symbol.iterator
为每一个对象定义了默认的迭代器。该迭代器可以被 for…of 循环使用。
迭代器 (Iterator)是一种接口,为各种不同的数据类型提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
【例】
const words = ['a', 'b', 'c']
for(let word of words){
console.log(word)
}
// a
// b
// c
通过输出一个空的数组,我们可以观察到其原型对象支持迭代遍历的实现对象
console.log(new Array())
可以看到在 Array 类型数据的 prototype 原型对象中,包含了 Symbol 类型的属性名,其值为一个 values() 函数
【例】遍历数组的迭代器
a = [1,2,3]
let iterator = a[Symbol.iterator]()
// 调用对象的 next
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
自定义迭代器分为两步,1)声明 Symbol.iterator 属性,值为 一个函数,2)该函数需返回 next()方法
【例】实现只遍历长度 >= 2 的字符串数据,其他则为 undefined
const users = {
data: ['aa', 'bb', 'cc', 'A', '1', 'DD'],
[Symbol.iterator](){
// 索引变量
let index = 0
let _this = this
return {
next: function(){
if(index >= _this.data.length) // 若索引不合理, 则迭代结束
return { value: undefined, done: true }
if(_this.data[index].length >= 2)
return { value: _this.data[index++], done: false }
else{
index++
return { value: undefined, done: false }
}
}
}
}
}
for (let u of users){
console.log(u)
}
生成器函数是 ES6 提供的一种异步编程的解决方案,语法行为与传统函数完全不同,例:
function * gen(){
console.log(1);
yield '1'; // 类似于断点
console.log(2);
yield '2';
console.log(3);
yield '3';
}
let iterator = gen();
iterator.next();
iterator.next();
iterator.next();
调用 生成器函数的 next()方法时,方法里传递的参数会作为函数执行 yield 方法后的返回值
function * gen(arg){
console.log(arg);
let aaa = yield 1
console.log(aaa)
let bbb = yield 2
console.log(bbb)
let ccc = yield 3
console.log(ccc)
}
let iterator = gen('hi')
console.log(iterator.next('aaa'))
console.log(iterator.next('bbb'))
console.log(iterator.next('ccc'))
了解回调地狱 callback hell
参考资料:http://callbackhell.com/
回调:使用 JavaScript 函数的约定的名称,通常在执行 I/O时实行,例如下载文件,读取文件,数据库连接等。
回调通常发生在 异步 async 操作中,异步是指多个操作同时进行,常见的有 setTimeout 延时函数 和 AJAX 发送请求等
回调地狱:回调函数中又嵌套了一个或多个回调,例:在3秒内分别输出3, 2,1
setTimeout(() => {
console.log(3)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(1)
}, 1000)
}, 1000)
}, 1000)
使用生成器函数可以解决回调地狱的问题:
function one(){
setTimeout(() => {
console.log(1)
}, 3000)
}
function two(){
setTimeout(() => {
console.log(2)
}, 2000)
}
function three(){
setTimeout(() => {
console.log(3)
}, 1000)
}
function * gen(){
yield three();
yield two();
yield one();
}
let iterator = gen()
iterator.next()
iterator.next()
iterator.next()
返回值具有关联的回调地狱如何解决呢?比如要先后获取用户数据、订单数据和商品数据
function getUsers(){
setTimeout(() => {
let data = '用户数据'
iterator.next(data)
}, 3000)
}
function getOrders(){
setTimeout(() => {
let data = '订单数据'
iterator.next(data)
}, 2000)
}
function getGoods(){
setTimeout(() => {
let data = '商品数据'
iterator.next(data)
}, 1000)
}
function * gen(){
let users = yield getUsers();
console.log(users)
let orders = yield getOrders();
console.log(orders)
let goods = yield getGoods();
console.log(goods)
}
let iterator = gen()
iterator.next()
Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
promise 译为 承诺,里面主要有 resolve 决定 和 reject 拒绝 这两个行为
// 定义 Promise
const p = new Promise(function(resolve, reject){
if(...)
reject(a)
else
resolve(b)
}
// 使用 Promise
p.then(
(a) => {},
(b) => {}
).catch(error => {}) // 捕获异常
需要node环境,使用node命令执行下方的 JS代码
原生的 AJAX 异步请求实现
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
// 该 API 接口可以随机返回一条诗句
const url = 'https://api.apiopen.top/api/sentences'
// 1. 创建对象
const xhr = new XMLHttpRequest();
// 2. 初始化
xhr.open("GET", url)
// 3. 发送
xhr.send()
// 4. 绑定事件,处理响应结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
// 判断状态响应码
if(xhr.status >= 200 && xhr.status < 300){ // 成功
console.log(xhr.responseText)
} else{ // 失败
console.error(xhr.status)
}
}
}
运行结果
{“code”:200,“message”:“成功!”,“result”:{“name”:“河海不择细流,故能就其深
;”,“from”:“李斯《谏逐客书》”}}
使用 Promise 封装原生的 AJAX 请求
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
// 定义 Promise
const p = new Promise((resolve, reject) => {
const url = 'https://api.apiopen.top/api/sentences'
// 1. 创建对象
const xhr = new XMLHttpRequest();
// 2. 初始化
xhr.open("GET", url)
// 3. 发送
xhr.send()
// 4. 绑定事件,处理响应结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
// 判断状态响应码
if(xhr.status >= 200 && xhr.status < 300){ // 成功
resolve(xhr.responseText)
} else{ // 失败
reject(xhr.status)
}
}
}
})
// 调用 Promise
p.then(
(result) => {
console.log('请求成功', result)
},
(reason) => {
console.log('请求失败', reason)
}
).catch((error) => {
console.error('程序出错', error)
})
ES6 提供了新的数据结构 Set(集合),类似于数组,其内部的值都是唯一的。
Set 集合实现了 iterator 接口,所以可使用 for…of 进行遍历
集合的属性和方法:
【例1】使用 Set 去重
let arr = [1,1,2,2,2,3,3]
let result = [...new Set(arr )];
console.log(result);
【例2】 使用 Set 求两个数组的交集
let arr1 = [1,1,2,2,2,3,3]
let arr2 = [3,3,2]
let result = [...new Set(arr1)].filter(
item => {
let a2 = new Set(arr2)
if(a2.has(item))
return true
else
return false
}
)
console.log(result)
简化写法
let result = [...new Set(arr1)].filter(item => new Set(arr2).has(item));
【例3】 使用 Set 求两个数组的并集
let arr1 = [1,1,2,2,2,3,3]
let arr2 = [3,3,2]
let result = [...new Set([...arr1, ...arr2])]
console.log(result)
【例4】使用 Set 求两个数组的差集
let arr1 = [1,1,2,2,2,3,3]
let arr2 = [3,3,2]
let diff = [...new Set(arr1)].filter(item => !(new Set(arr2).has(item)))
console.log(diff)
ES6 提供了 Map 数据结构,类似于 Object,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当做键。Map也实现了 iterator 接口,所以可以使用 for…of 进行遍历,Map 的常用方法有:
map = new Map()
map.set('name', 'uni')
map.set('age', 22)
for(let node of map){
console.log('key=',node[0], ',value=',node[1])
}
ES6 引入了 Class 类的概念,作为对象的模板,通过 class 关键字可以定义类。基本上,ES6 的 class 可以看做一个语法糖,它的绝大部分功能, ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
知识点:
【例】面向对象编程(ES5的写法)
function User(name, age){
this.name = name;
this.age = age
}
// 给类添加方法
User.prototype.sayHello = function(){
console.log(`My name is ${this.name}, age is ${this.age}`)
}
// 实例化对象
let Uni = new User('Uni', 22)
// 调用对象的方法
Uni.sayHello()
运行结果
My name is Uni, age is 22
ES6的写法
class User{
// 构造方法
constructor(name, age){
this.name = name;
this.age = age;
}
// ES6 Class类里的方法必须使用方法名() ,而不能使用 ES5的 方法名:function()
sayHello(){
console.log(`My name is ${this.name}, age is ${this.age}`)
}
}
let Uni = new User('Uni', 22)
Uni.sayHello()
ES6 的 Class 支持静态属性,使用 static 关键字定义
class User{
// 静态属性
static body = 'normal'
// 构造方法
constructor(){}
}
【例】面向对象编程实现手机类 (ES5写法)
// 定义父类手机
function Phone(brand, price){
this.brand = brand
this.price = price
}
// 定义方法
Phone.prototype.call = function(){
console.log('打电话')
}
// 定义子类
function SmartPhone(brand, price, color, size){
// 调用父类的构造方法并修改父类的 this 指向
Phone.call(this, brand, price)
this.color = color
this.size = size
}
//设置子级构造函数的原型
SmartPhone.prototype = new Phone
SmartPhone.prototype.constructor = SmartPhone
// 声明子类的方法
SmartPhone.prototype.phone = function(){
console.log('拍照')
}
const HuaWei = new SmartPhone('华为', 3099, '黑色', '5.5英寸')
console.log(HuaWei)
运行结果
SmartPhone { brand: ‘华为’, price: 3099, color: ‘黑色’, size: ‘5.5英寸’ }
使用 ES6 实现上述案例:
class Phone{
constructor(brand, price){
this.brand = brand
this.price = price
}
call(){
console.log('打电话')
}
}
class SmartPhone extends Phone{
constructor(brand, price, color, size){
super(brand, price) // 相当于 Phone.call(this, brand, price)
this.color = color
this.size = size
}
photo(){ console.log('拍照') }
}
const HuaWei = new SmartPhone('华为', 3099, '黑色', '5.5英寸')
console.log(HuaWei)
class A{
constructor(){}
hello(){ console.log('A: hello')}
}
class B extends A {
constructor(){super()}
hello() {
// 重写方法时可调用父类的方法
super.hello();
console.log('B: hello')}
}
b = new B()
b.hello()
【例】
class Phone{
// constructor 构造函数可以不写
get price(){ // 默认返回当前属性值
console.log('价格属性被读取')
return this.p
}
set price(val){
console.log('价格属性被修改')
this.p = val
}
}
let phone = new Phone()
console.log(phone.price) // undefined
phone.price = 100
console.log(phone.price)
运行结果
价格属性被读取
undefined
价格属性被修改
价格属性被读取
100
Number.EPSILON 可以表示最小精度,用于浮点数之间的运算,当两个数的差值小于这个精度时候,则视为相等,默认值为 2.220446049250313e-16
0b 可表示二进制,0o表示八进制,0x表示十六进制
console.log(0b1010)
console.log(0o12)
console.log(10)
console.log(0xA)
运行结果
10
10
10
10
**Number.isFinite **检测一个数值是否为有限数
console.log(Number.isFinite(100)) // true
console.log(Number.isFinite(100/0)) // false
console.log(Number.isFinite(Infinity)) // false
Number.isNaN 判断是否为 NaN类型
console.log(Number.isNaN(1)) // false
console.log(Number.isNaN('2')) // false
console.log(Number.isNaN(new Number('3a'))) // false
console.log(Number.isNaN(new Number('a'))) // false
console.log(Number.isNaN(NaN)) // true
console.log(Object.is(NaN, NaN)) // true
console.log(NaN === NaN) // false
const config1 = {
host: 'localhost',
port: 3306,
username: 'root',
password: '123456',
}
const config2 = {
host: '127.0.0.1'
}
console.log(Object.assign(config1, config2))
运行效果
{ host: ‘127.0.0.1’, port: 3306, username: ‘root’, password: ‘123456’ }
const user = { name: 'uni'}
const role = { name: '普通用户'}
Object.setPrototypeOf(user, role)
console.log('user:', user)
console.log('getPrototypeOf:', Object.getPrototypeOf(user))
运行结果
user: { name: ‘uni’ }
getPrototypeOf: { name: ‘普通用户’ }
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来
模块化的优势:
ES6 之前的模块化规范:
CommonJS => NodeJS、Browserify
AMD => requireJS
CMD => seaJS
ES6 模块化语法:
模块功能主要由两个命令构成:export 和 import
【例】
uni.js
export let name = 'uni'
export function sayHello(){
console.log(`hi, i am ${name}`)
}
index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
head>
<body>
<script type="module">
import * as uni from './static/uni.js'
console.log(uni)
script>
body>
html>
除了上述的 export 方法以外,还可以统一 export
let name = 'uni'
function sayHello(){
console.log(`hi, i am ${name}`)
}
export { name, sayHello }
ES6 支持 默认的 export,这样在 import 的时候,需通过 default 来调用里面的属性或方法
export default {
name: 'uni',
sayHello(){
console.log(`hi, i am ${name}`)
}
}
ES6 支持 解构赋值的方式导入其他的模块
// 导入统一的export
import {name, sayHello} from './static/uni.js'
// 导入默认的export
import {default as uni} from './static/xxx.js'
如果要 import 的 js 文件是使用 export default 方式导出的,那么则可以简写import
import xxx from 'xxx.js'
假设 app.js 是 web 程序的入口脚本文件,通常需要引入一些内容
uni.js
export let name = 'uni'
export function sayHello(){
console.log(`hi, i am ${name}`)
}
app.js
import * as uni from './static/uni.js'
console.log(uni)
index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
head>
<body>
<script src="./static/app.js" type="module">script>
body>
html>
Babel 官网:https://www.babeljs.cn/docs/
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
这里以了解为主,用到的时候再查阅相关资料