ECMAScript
- 通常将 ECMAScript 看作是 JavaScript 的标准规范
- 实际上 JavaScript 是 ECMAScript 的扩展语言
- ECMAScript 只是提供了最基本的语法
- JavaScript 在语言基础上进行了扩展
JavaScript 语言本身指的就是 ECMAScript
2015 年开始 ES 保持每年一个版本的迭代
ES2015 开始按照年份开始命名
很多人习惯将 ES2015 称之为 ES6
ECMAScript 2015
最新 ECMAScript 标准的代表版本
用 ES6 来泛指所有的新标准
注意分辨用 ES6 是特指还是泛指
参考网址:www.ecma-international.org/ecma-262/6.…
重点了解在 ES5.1 基础之上的变化
- 解决原有语法上的一些问题或者缺陷
- 对原有语法进行增强
- 全新的对象、全新的方法、全新的功能
- 全新的数据类型和数据结构
准备工作
任何一个支持 ES2015 的环境都可以
在 VScode 软件中使用 Chrome 调试
安装插件:
- Debugger for Chrome
- Live Server
- Browser Preview
调试步骤:
- 安装上面的插件
- 点击菜单栏的运行——启动调试——选择环境:Chrome——修改调试的 url 地址:8080改为5500(要与临时服务器的端口号保持一致)——保存
- 配置文件设置好后就可以调试了
- 在使用时,再次点击菜单栏的运行——启动调试——就可以选择文件实时查看变化过程
- 同时在vscode里也可以在"查看"打开调试控制台,也可以实时记录当前代码的变化过程
let 与 块级作用域
作用域 - 某个成员能够起作用的范围
在 ES2015 之前,ES 只有前两种作用域
- 全局作用域
- 函数作用域
- 块级作用域
块,就是 {} 包裹起来的一个范围
以前的块是没有作用域的
if (true) {
var foo = 1;
}
console.log(foo); // 1
if (true) {
let foo = 1;
}
console.log(foo); // 报错
// 可以通过新的关键字 let 定义块内部的变量
// let 定义的变量在块级作用域内部能够被访问
// 非常适合设置在 for 循环中的循环变量
// 使用 var 时,内层循环变量与外层循环变量处于同一个作用域之内,相同的变量名会发生覆盖
// 第一次进入内部循环后,等内部循环完 i 的值就变为3了,再进行下一次外部循环时 i = 3 已经进不去内部循环了
for (var i = 0; i < 3; i++) {
for (var i = 0; i < 3; i++) {
console.log(i); // 0 1 2
}
}
// 通过 let 定义变量,只在自己的循环中生效
// 实际工作中尽量不要在循环内外用同样的变量名进行定义
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i); // 0 1 2 0 1 2 0 1 2
}
}
// 通过循环批量添加事件
// 通过 let 定义变量,只能在块级内部被调用
// 会封闭自己的作用域,i 就是局部变量,局部变量在调用时会去自己原来的作用域访问原来的值
var eles = [{}, {}, {}];
for (var i = 0; i < eles.length; i++) {
eles[i].onclick = function () {
console.log(i);
};
}
eles[0].onclick(); // 0
eles[1].onclick(); // 1
eles[2].onclick(); // 2
// 若for循环使用 var 定义变量时,不管点击哪个,都会输出3。由于受到闭包的影响, 定义的事件函数本身相当于定义在全局作用域里,记录的 i 变量也是全局变量,在调用时会去找到全局变量里当前最新的值,跳出循环后 i 变成 3,所以在调用时结果都是3
// 循环 实际上有两层作用域
// 第一层是 for 循环作用域,也就是整体作用域,还有一层是每一次大括号内部的块级作用域,块级作用域里每次定义一个 i ,每次 i 都是单独作用域里面的变量,在进行调用时结果是10次 foo
for (let i = 0; i < 10; i++) {
let i = "foo";
console.log(i); // 10次foo
}
// 把 for 循环进行拆分书写
let i = 0;
if (i < 10) {
let i = "foo"; // 块级作用域里的变量
console.log(i); // 在块级作用域里调用,自己内部定义了 i ,直接调用
}
i++; // 执行的是外部的 for 循环的作用域,而不是内部的块级作用域
// let 除了产生作用域的限制外,还和 var 有另外一个区别
// let 不会进行变量提升,必须先声明,再使用
console.log(a); // undefined
var a = 1;
console.log(a); // 报错,变量引用错误,不能在初始化之前接收变量 a
let a = 2;
const
- const 只读的常量/恒量
- 在let基础上多了【只读】特性,变量声明过后不能再被修改
- 变量在声明值时,必须设置一个 初始值
- const声明成员的不能被修改,已经分配了一块内存地址,如果给该成员赋值,会改变该成员的内存指向;但可以修改恒量成员中的属性值
- 最佳实践: 不用var, 主用const, 配合let
const obj = {}
obj.name = 'test' OK //这里修改的是成员属性的值,没有重新分配一块内存空间
obj = {} // error 赋值被改变obj的指向
ES2015 数组的解构
数组的解构 : 以前只能通过索引来获取数组中的值,数据的解构可以快速的获取数组中的值
现在定义一个数组:const arr = [100, 200,300]
- 传统方法
const foo = arr[0]
const bar = arr[1]
const baz = arr[3]
console.log(foo, bar, baz)
- [] 提取出来的数据所存放的变量名
const [, , baz] = arr
console.log(baz) // 300
- 获取其他某个位置其后的成员
// 获取其他某个位置的成员,但需要保留解构所在的逗号,
// 保证解构数组的格式与原来数组格式是一致的;
// ...: ...rest 只能放在解构位置最后一个成员上去使用
const [foo, ...rest] = arr
console.log(rest) // 200, 300
- 解构成员的个数小于数组长度,按照从前到后的顺序去提取,多出来的成员不会被提取
const [foo, bar , baz, more] = arr
console.log(foo) //100
- 解构成员的个数大于数组长度,提取的数据是 undefined
const [foo, bar , baz, more] = arr
console.log(more) // undefined
- 给提取的成员设置默认值,在解构位置的变量名赋值,如果解构到对应的值,那么就会得到默认值
const [foo, bar , baz = 123, more = 'defalut value'] = arr
console.log(baz, more) // 123, defalut value'
- 场景一:拆分字符串
// 传统方式
const path = '/foo/bar/baz'
const tmp = path.split('/')
const rootdir = tmp[1]
console.log(tmp,rootdir) // foo
//解构方式
const [, rootdir] = path.split('/')
console.log(rootdir) // foo
对象的解构:
对象解构:需要根据 属性名 去匹配提取,而不是根据位置;因为数组中元素是有下标的,有顺序规则的;而对象里面的成员 没有一个固定的次序,所以不能按照位置去提取
// 对象的解构
const obj = {name: 'zce', age: 18}
// 使用 {} 存储从对象提取的变量名,变量名去匹配对象的属性值
const { name } = obj
console.log( name )
// 解构的变量名同时去匹配被解构对象当中的属性名,就会产生冲突,使用重命名 objName
const name = 'tom'
const { name: objName } = obj
模版字符串字面量
- 传统定义字符串需要用单引号或者双引号表示, 不支持换行符
const str = 'hello es2015'
console.log(str)
- 模板定义字符串使用
const str = `hello es2015`
// 支持插值表达式${name}
const name = 'tom'
const msg = `hello, ${name} --- ${1+2}`
console.log(msg)
- 带标签函数的模板字符串:分割成数组
const name = 'tom'
const gender = true
function myTagFunc(strings ,naem , gender) {
console.log(strings, name , gender)
return strings[0] + name + strings[1] + gender + strings[2]
}
// 打印出就是模版字符串内容中分割过后的结果,因为模版字符串当中可能由嵌入的表达式,
//所以这个数组是按照表达式分割过后的静态内容, 模版中可以接受所有的值
const result = myTagFunc`hey,${name} is a ${gender}`
console.log(result)
//场景: 1.文本多语言话;2.检查模版字符串当中存在不安全的字符串;3.小型模版引擎
字符串的扩展方法
判断指定字符串当中是否包含所需的内容
- .includes()
- .startsWith() :
- .endsWith()
const message = 'Error: foo is not edfined'
console.log(
//是否以Error开头
message.startsWith('Error') , // true:
//是否以.结尾
message.endsWith('.'),
// 是否包含 is
message.includes('is')
)
Proxy:为对象设置代理器,监听某个对象属性的读写get(),set()等等
想像成门卫,也就说进去拿东西还是放东西,都需要门卫去拿;
- Proxy 和Object.defineProtperty的区别
- defineProperty只能监视属性的读写,Proxy能够监视到更多对象操作:delete属性等等
- Proxy 更好的支持数组对象的监视(重写数组的操作方法),通过自定义方法覆盖原型方法
- Proxy是以非侵入的方式监管了对象的读写(也就是说: 一个已经定好的对象,不需要对对象本身做操作,就可以监视到该对象属性)
Reflect
它是一个 静态类 ,不能实例化new Reflect,只能够调用静态类的方法Reflect.get();Reflect呢不封装了一系列对对象的底层操作;Reflect成员方法就是 Proxy处理对象的默认实现。统一提供一套用于操作对象的API;
静态方法
- 通过函数名本身去调用;
- 静态方法挂在类型上面的,静态方法内部的this不会指向某一个实例对象,而是指向当前类型
类的继承
- 可以抽象出来相似的地方;
- 子类型可以继承父类型所有的成员属性
- 用supper()访问父对象的方法
Set数据结构:
- 可以理解成集合,根传统的数组类似
- Set的内部成员是不能重复的,每个值都是唯一的;
- 通过Set的size属性可以获取数组长度
- has判断集合当中是否存在某个值
- delete删除集合中某个制定的值
- clear 清除集合的全部集合
- 使用Array.from转换为数组
- 常见的应用场景: 1.为数组去重;
const s = new Set()
// 因为返回集合可以链式调用
s.add(1).add(2).add(3)
s.size
s.has(100)
s.delete(3)
s.clear()
// 去重
const arr = [1,2,3,4,1,3]
方法一:
const result = [...new Ser(arr)]
方法二:
const result = Array.from(new Set(arr))
console.log(result)
Map 数据结构
- 与对象非常类似;本质上都是键值对集合,但是对象的键只能是 字符串类型;
- Map才能算是键值对集合,用来映射两个 任意类型数据 之间的关系( 任意数据类型作为键)
// 对象方式
const obj = {}
// 如果给我们对象添加的对象是布尔、数值、对象类型最终都会转换为 字符串类型
obj[true] = 'value' // ['ture']
obj[123] = 'value'
obj[{a:1}] = 'value'
//利用对象存储学生的成绩
//Map 方式
const m = new Map()
cosnt tom = {name: 'tom'}
m.set(tom, 90)
console.log(m)
console.log(.get(tom)) // 获取某个值
Symbol
- 为解决定义键名的重复问题
- 为对象添加一个独一无二的属性名
- 可以作为对象的私有属性:获取这种私有属性Object.getOwnPropertySmbol()
cosnt s = Symbol()
console.log(s)
cosnt obj = {}
obj[Symbol()] = '123'
for...of循环
- for, for...in, forEach 有局限
- for 适用于遍历普通的数组
- for...in 适用于遍历键值对
- forEach 对象的遍历方法,无法终止遍历
- for...of 作为遍历所有数据结构的统一方式
- 可以使用break方法,终止遍历;以前终止遍历,只能使用数组的some(),every().
- Map结构,遍历出是数组形式,可以直接用key,value拿到元素值
- 无法遍历普通对象,只能遍历具有数组之类的接口;因为具有Iterable接口(相同规格标准)
- 实现Iterable接口就是forfor...of的前提
// 基本用法
const arr = [100, 200, 300, 400]
for(const item of arr) {
// 不同于传统的方式,for...of拿到的是每个项的元素,而不是对应的下标
console.log(item)
}
// Set
const s = new Set(['foo', 'far'])
for(const item of s) {
console.log(item)
}
// Map
const m = new Map()
m.set('foo':'123')
m.set('far':'345')
for(const [key,value] of m) {
console.log(key, value)
}
// 普通对象: 无法遍历普通对象
const obj = {foo: 123, bar:123}
for(const item of obj) {
cosole.log(item)
}
可迭代接口
- 实现Iterable接口就是for...of的前提;
- Map,Array,Set都有一个Symbol.iterator 接口(),然后返回一个next()方法
const set = new Set['foo', 'bar', 'faz']
const iterator = set[Symbol.iterator]()
cosole.log(iterator.next()); // foo
cosole.log(iterator.next()); // bar
实现可迭代接口()
// 自定义 实现可迭代接口 iterabel,约定内部必须要有一个iterator方法
const obj = {
store: ['foo','bar','baz'], // 可迭代的数组
//实现迭代接口iterator,约定内部必须要有一个用于迭代的next()方法
[Symbol.iterator]: function() {
let index = 0
const self = this
return {
next: function() {
// 迭代结果接口IterationResult
const result = {
value: self.store[index], // 迭代到的数据
done: true >= self.store.length // 迭代有没有结束
}
index++
return result
}
}
}
}
迭代器模式
- 场景:你我协同开发一个任务清单应用
// 迭代器模式
// 场景:你我协同开发一个任务清单应用
// 我的代码======================
const todos = { // 对象结构
life: ['吃饭','睡觉','打游戏'],
learn: ['','',''],
work: ['喝茶'],
// 对外提供一个统一的接口,对调用着而言,不用关心对象内部的结构,不用担心内部改变之后的影响
each: function(callback) { // 只适用于当前的应用结构
const all = [].concat(this.life,this.learn, this.work)
for(const item of all) {
callback(item)
}
},
[Symbol.iterator]: function() { // 适用于任何的数据结构
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
const self = this
return {
next: function() {
const result = {
value: all[index],
done: index >= all.length
}
index++;
return result
}
}
}
}
// 你的代码=====================
for(const item of todos.life) {
console.log(item)
}
for(const item of todos.learn) {
console.log(item)
}
for(const item of todos.work) {
console.log(item)
}
//优化
todos.each(function(item) {
console.log(item)
})
for(const item of todos) {
console.log(item)
}
生成器
- 在复杂的异步代码中减少回调函数嵌套的问题,提供更好的异步编程解决方案
- 生成器的组成:在普通函数名前添加一个 *
- 生成器对象也迭代了Iterabel接口
- 生成器配合yield关键词
- 生成器函数会自动帮我们返回一个自动生成器对象,调用对象next()方法。才会让这个函数体方法开始执行,执行过程中遇到了yield关键词,函数执行就会被暂停下来,而且yield后面的值将会作为next结果返回,如果我们继续调用这个函数,将会从yield暂停的地方开始执行,直到这个函数结束
// 基本语法
function * foo() {
console.log('zec')
return 100
}
const result = foo()
console.log(result.next()) // zec { value: 100, done: true }
// yiled
function * foo() {
console.log('zec')
yield 100
console.log('2222')
yield 200
}
const generator = foo()
//- 生成器函数会自动帮我们返回一个自动生成器对象,调用对象next()方法。才会让这个函数体方法开始执行,执行过程中遇到了yield关键词,函数执行就会被暂停下来,
//而且yield后面的值将会作为next结果返回,如果我们继续调用这个函数,将会从yield暂停的地方开始执行,直到这个函数结束
console.log(generator.next())
console.log(generator.next())
//zec { value: 100, done: false }
//2222 { value: 200, done: false }
生成器应用
// 案例: 发号器:自增ID,在原有的ID上加1
function * createIDMaker () {
let id = 1
while (true) {
yield id++
}
}
const idMaket = createIDMaker()
console.log(idMaket.next().value)
console.log(idMaket.next().value)
console.log(idMaket.next().value)
console.log(idMaket.next().value)
// 案例二:Iterator 方法
const todos = { // 对象结构
life: ['吃饭','睡觉','打游戏'],
learn: ['java','html','javascript'],
work: ['喝茶'],
// 对外提供一个统一的接口,对调用着而言,不用关心对象内部的结构,不用担心内部改变之后的影响
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for(const item of all) {
yield item
}
}
}
for (const iterator of todos) {
console.log(iterator)
}
ECMAScript 2016
- Array.includes()
- Math.pow()
ECMAScript 2017
- Object.values(); //对象当中所有值的数组
- Object.entries();// 以数组的形式返回对象当中的所有键值对,然后可以for...of
- Object.getOwnPropertySmbol(); 主要配合get,set去使用
- String.prototype.padStart(); 另一个字符串填充当前字符串,从当前字符串的左侧开始填充。
- String.prototype.padEnd();
- 在函数参数中添加尾逗号;