ECMAScript概念
ECMAScript 也是一门脚本语言 缩写 ES
通常看作为JavaScript 的标准化规范
实际上JavaScript 是ECMAScript 的扩展语言
因为 ECMASscript 只提供了最基本的语法
在浏览器中 JavaScript = ECMAScript + Web APIs (BOM , DOM 等)
在NodeJs中 JavaScript = ECMAScript + Node APIs(fs, net, etc. 等)
所以可以说 JavaScript 语言本身指的就是 ECMAScript
从2015年开始 ECMAScript 保持每年一个版本迭代
ECMAScript2015 又被称为 ES6
1.相比上一个版本 ES5.1变化比较大 相差5年
2.命名规则发生了变化
*有些开发人员把ES2015以后的所有新特性统称为ES6
ES2015 在 ES5.1基础之上的变化
1.解决原有语法上的一些问题或者不足 例如 let const 所提供的块级作用域 等
2.对原有语法进行增强 例如 解构 展开 参数默认值 模板字符串 等
3.全新的对象,全新的方法,全新的功能 例如 Promise 等
4.全新的数据类型和数据结构
(一)let 块级作用域 与 const
作用域 – 某个成员能够起作用的范围 es2015 之前 ES中只有两种作用域 分别是 全局作用域 和 函数作用域。ES2015中新增 块级作用域 {}
let 声明的变量只能在块级作用域 {} 内部 访问到 外部无法访问
1. 块级作用域例子:模拟注册onclick事件 然后获取注册时的值
var elements = [{}, {}, {}]
for(var i = 0; i < elements.length; i++){
elements[i].onclick = function(){
console.log(i)
}
}
elements[1].onclick() // 3 <-- 预期是 1 此时 i 位于全局作用域中 无论怎么获取 都是3
在 es2015之前 使用闭包解决这个问题, 因为没有块级作用域 只有全局和函数作用域, 所以创建函数限制访问范围
var elements = [{}, {}, {}]
for(var i = 0; i < elements.length; i++){
(function(i){
elements[i].onclick = function(){
console.log(i)
}
})(i)
}
elements[1].onclick() // 1
在 es2015之后,使用let 块级作用域解决这个问题
var elements = [{}, {}, {}]
for(let i = 0; i < elements.length; i++){
elements[i].onclick = function(){
console.log(i)
}
}
elements[1].onclick() // 1
2. for循环内部块级作用域嵌套
for(let i = 0; i < 3; i++){
let i = 'hello world'
console.log(i) // hello world <--- 这里的 i 为什么没有赋值的冲突
}
因为for循环的 i 与内部的i不在一个块级作用域。拆分这个for循环得到以下代码
let i = 0
if(i < 3){
let i = 'hello world'
console.log(i)
}
i++
第一个声明的 i 的作用域在外部,第二个声明的 i 的作用域在 if 内部。所以不会冲突
3. const (恒量/常量):在let的基础之上多了 [只读]
const msg = 'hello world'
msg = '你好' // TypeError: Assignment to constant variable.
// const 声明的常量 不可以修改(不能修改内存地址,不能修改值类型)
const msg1 // SyntaxError: Missing initializer in const declaration
msg1 = 'hello world'
// const 声明时 必须初始化
const obj = {}
obj.name = '张三'
// const声明的对象可以修改属性
ES2015之后的最佳实践: 不用var,主要用const,配合使用let
(二)数组的解构
const users = ['张三','李四','王五']
const [user1, user2, user3] = users
console.log(user1 + user2 + user3) // 张三李四王五
// 当只需要指定下标的元素时,空出其他声明
const [, , user4] = users
console.log(user4) //王五
// 使用...解构剩余的所有元素,变成数组
const [user5, ...userArray] = users
console.log(userArray) //[ '李四', '王五' ]
//解构时声明的变量超出数组长度为undefined
const [user6, user7, user8, user9] = users
console.log(user9) //undefined
//直接给声明的变量赋值,当没有解构到数据时,输出赋值的数据,反之输出解构的数据
const [user10, user11, user12 = '田七', user13 = 'default value'] = users
console.log(user12) //王五
console.log(user13) //default value
//例子,分割字符串取出下标为1的数据
const path = '/window/学习资料/index.js'
const[, name] = path.split('/')
console.log(name) //window
(三)对象的解构
const user = { name:'张三', age:18 }
const { name } = user
console.log(name) //张三
//当解构时声明的变量与其他变量名冲突时 使用 :重命名变量
const age = 20
const { age: age1} = user
console.log(age1) // 18
//例子 解构常用函数 console.log
const { log } = console
log('message') //message
(四)模板字符串 `` 和 新的字符串扩展方法
const str = `hello world`
//使用插值表达式 ${} 插入js代码
const name = `张三`
const msg = `${name}:今天是${new Date().getDate()}号`
console.log(msg) //张三:今天是8号
带标签的模板字符串 :在模板字符串之前加函数
console.log`hello world` // [ 'hello world' ] 返回值是数组
函数的参数为字符串中根据差值表达式分割后的字符串数组,和所有插值表达式
const name = '张三'
const gender = false
function strTagFunc(strings, name, gender){
const sex = gender ? 'body' : 'girl'
return strings[0] + name + strings[1] + sex + strings[2]
}
const msg = strTagFunc`hi,${name} is a ${gender}.`
console.log(msg) // hi,张三 is a girl.
.startsWith() <— 是否以某某字符串开头
const msg = 'hello new world'
console.log(msg.startsWith('hello')) // true
.endsWith() <— 是否以某某字符串结尾
const msg = 'hello new world'
console.log(msg.endsWith('world')) // true
.includes()<— 是否包含某某字符串
const msg = 'hello new world'
console.log(msg.includes('new')) // true
(五)参数默认值、剩余参数 和 展开参数
只有传递值为undefined时,参数默认值才会生效,并且带有默认值的形参只能写在最后
function say (msg = 'HELLO'){
console.log(msg)
}
say()//HELLO
say('hello world')//hello world
使用 … 接收当前形参位置以后的剩余参数,变成数组,只能写在所有形参最后,只可以使用一次
function foo (value,...values) {
console.log(value) //1
console.log(values) //[2,3,4,5]
}
foo(1,2,3,4,5)
使用…展开数组到方法参数中
const array = ['张三','李四','王五']
console.log(array[0], array[1], array[2]) //张三 李四 王五
console.log.apply(console,array) //张三 李四 王五
console.log(...array) // 张三 李四 王五
(六)箭头函数与this
基本语法 (参数) => {函数体}
当箭头函数没有参数或者有多个参数,要用 () 括起来。当有一个参数时可以省略()
当函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
箭头函数极大简化了回调函数的书写
const array = [1, 2, 3, 4, 5, 6, 7]
let newArr = array.filter(function(value){
return value > 3
})
console.log(newArr) // [ 4, 5, 6, 7 ]
let newArr1 = array.filter(v => v > 3)
console.log(newArr1) //[ 4, 5, 6, 7 ]
箭头函数不会改变this的指向
普通函数的this 指向的是 调用者。箭头函数的this 是作用域的this
const person = {
name : '张三',
sayHello : function(){
console.log(`my name is ${this.name}`) //my name is 张三
},
sayHello1 : () =>{
console.log(`my name is ${this.name}`) //my name is undefined
},
sayHelloAsync : function(){
setTimeout(function() {
console.log(`my name is ${this.name}`) //my name is undefined
}, 0);
},
sayHelloAsync1 :function(){
setTimeout(() => {
console.log(`my name is ${this.name}`) //my name is 张三
}, 0);
}
}
person.sayHello()
person.sayHello1()
person.sayHelloAsync()
person.sayHelloAsync1()
(七)对象字面量的增强
声明字面量时,如果变量名相同可以省略
声明方法时候可以省略function
动态字面量使用 [ ] 直接声明,不用使用对象的选择器声明
const age = 18
const obj = {
name :'张三'
,age
,sayHello : function(){
console.log(`my name is ${this.name}`)
}
,sayhello1 (){
console.log(`my name is ${this.name}`)
}
,['a'+ age] : 'a18'
}
obj.sayHello() //my name is 张三
obj.sayhello1() //my name is 张三
console.log(obj.a18) //a18
obj['b' + age] = 'b18'
console.log(obj.b18) //b18
(七)Object 对象新的扩展方法
Object.assign 将多个源对象中的属性复制到一个目标对象,同名属性会覆盖
const person = {
name : '张三'
,age : 18
}
const person2 = {
name : '李四'
,gender : 'girl'
}
const person3 = Object.assign(person2, person)
console.log(person3) //{ name: '张三', gender: 'girl', age: 18 }
console.log(person3 === person2) //true
//-------------------------------------------------------------
function say(person){
const person4 = Object.assign({}, person)
person4.name = '王五'
console.log(person4.name) //王五
}
say(person)
console.log(person.name) //张三
Object.is 新的比较方法
console.log(NaN === NaN) //false
console.log(Object.is(NaN, NaN)) //true
console.log(+0 === -0) //true
console.log(Object.is(+0, -0)) //false
(七)Proxy 代理对象 与 静态类 Reflect
监视某个对象的读写,比如使用Object.defineProperty,vue3.0之前就是使用这个方法实现数据响应,从而完成双向数据绑定
ES2015 新的 Proxy,为对象设置访问代理器,监视对象的读写
proxy 创建一个代理对象,第一个参数为需要代理的对象,第二个参数也是一个对象,提供了 get 和set 方法监视当代理的对象访问和赋值时的处理方法
get(代理的对象,访问的属性) 返回值为访问属性的返回值
set(代理的对象,赋值的属性,赋值的数据)
const person = {
name : '张三'
,age : 18
}
const personProxy = new Proxy(person,{
get(target,property){
console.log(target) //{ name: '张三', age: 18 }
console.log(property) //name
return '王五'
}
,set(target,property,value){
console.log(target) //{ name: '张三', age: 18 }
console.log(property) //name
console.log(value) //李四
}
})
console.log(personProxy.name) //王五
personProxy.name = '李四'
使用Proxy代理之后的对象,对源对象的访问和赋值进行校验
const person = {
name : '张三'
,age : 18
}
const personProxy = new Proxy(person,{
get(target,property){
if(!(property in target)) throw new Error(`not ${property}`)
return target[property]
}
,set(target,property,value){
if(property === 'age' && !Number.isInteger(value)) throw new TypeError(`${value} is not a int`)
target[property] = value
}
})
console.log(personProxy.gender) //Error: not gender
personProxy.age = '李四' //TypeError: 李四 is not a int
Object.defineProperty 只能监视对象的属性的读取和写入,Proxy可以监视对象的更多操作
handler方法 | 触发方式 |
---|---|
get | 读取某个属性 |
set | 写入某个属性 |
has | in 操作符 |
deleteProperty | delete 操作符 |
getPrototypeOf | Objcet.getPrototypeOf() |
setPrototypeOf | Objcet.setPrototypeOf() |
isExtensible | Objcet.isExtensible() |
preventExtensions | Objcet.preventExtensions() |
getOwnPropertyDescriptor | Objcet.getOwnPropertyDescriptor() |
defineProperty | Objcet.defineProperty() |
ownKeys | Objcet.getOwnPropertyNames(),Objcet.getOwnPropertySymbols() |
apply | 调用某个函数 |
construct | 用 new 调用一个函数 |
Proxy 更好的支持数组对象的监视
比如,使用Objcet.definepropoerty 监视数组对象时,vue3.0之前使用的是重写数组的操作方法,自定义数组操作方法覆盖数组原型上的方法(push等),以达到监视数组对象的功能。
而Proxy的监视对于监视对象没有修改
const list = []
const listProxy = new Proxy(list,{
set(target,property,value){
console.log('set操作', property, value) // set操作 0 hello world
target[property] = value
return true
}
})
listProxy.push('hello world')
Reflect 提供了默认且统一操作对象的API
const person = {
name : '张三'
,age : 18
}
const personProxy = new Proxy(person,{
get(target,property){
if(!(property in target)) throw new Error(`not ${property}`)
//return target[property]
return Reflect.get(target,property)
}
,set(target,property,value){
if(property === 'age' && !Number.isInteger(value)) throw new TypeError(`${value} is not a int`)
//target[property] = value
return Reflect.set(target,property,value)
}
})
console.log(person.name) //张三
person.age = 20
console.log(person.age) //20
//-----------------------------------------------------------------------
console.log('name' in person) //true
console.log(delete person['name']) //true
console.log(Object.keys(person)) //[ 'age' ]
console.log(Reflect.has(person,'age')) //true
console.log(Reflect.deleteProperty(person,'age')) //true
console.log(Reflect.ownKeys(person)) //[ ]
(八)Promise 一种全新的异步编程解决方案
参考:js之从Promise的基本使用模拟Promise源码
(九)class 类
使用class关键字定义类
在es2015之前,是通过定义函数,以及函数的原型对象,来实现类
现在通过关键字class可以直接定义类
function person (name){
this.name = name
}
person.prototype.sayHello = function(){
console.log(`my name is ${this.name}`)
}
class person1{
constructor(name){
this._name = name
}
sayHello(){
console.log(`my name is ${this._name}`)
}
}
new person('张三').sayHello()
new person1('李四').sayHello()
静态方法 static 关键词
定义静态方法快速创建对象,封装new关键字。
需要注意的是,由于静态方法不是通过实例创建,所以this指向的是当前的类型
class Person1{
static of(name){
return new person1(name)
}
constructor(name){
this._name = name
}
sayHello(){
console.log(`my name is ${this._name}`)
}
}
Person1.of('张三').sayHello()
类的继承 extends 关键词
使用 extends 实现类的继承,在子类中使用 super 访问父类的属性和方法
class Person1{
static of(name){
return new person1(name)
}
constructor(name){
this._name = name
}
sayHello(){
console.log(`my name is ${this._name}`)
}
}
class Student extends Person1 {
constructor(name, number){
super(name)
this._number = number
}
show(){
super.sayHello()
console.log(`my number is ${this._number}`)
}
}
new Student('张三',10086).show() //my name is 张三 my number is 10086
(十) 数据结构 Set ,Map , Symbol
Set
使用set创建的数据结构(集合) ,内部存储的数据不允许重复,如果重复添加会被忽略
遍历集合对象可以使用 .forEach() 或者 for of
使用 size 属性 获取 集合长度 ,这与数组的length一样
使用 has 方法 判断集合中是否存在某个值
使用 delete 方法 删除某个指定的值 成功 返回true
使用 clear 方法 清除所有数据
使用Set 对数组去重:
const list = [1, 2, 3, 2]
const r = new Set(list)
const r1 = Array.from(r)
console.log(r1) //[ 1, 2, 3 ]
const r2 = [...r]
console.log(r2) //[ 1, 2, 3 ]
Map
Map数据解构与对象相似,本质都是键值对集合。
但是,普通对象中键值对的键只能是字符串类型,就算使用其他类型,也会把toString的结果当作键,无法使用复杂数据类型当作键,比如:
const obj ={}
obj[false] = 'value'
obj[123] = 'value'
obj[{a : 1}] = 'value'
console.log(Reflect.ownKeys(obj)) //[ '123', 'false', '[object Object]' ]
Map数据结构才能算是严格意义上的键值对集合,用来映射两个任意数据类型之间的对应关系
set 赋值,get 取值, has 判断某个键是否存在,delete 删除某个键,clear 清空, 使用 .forEach() 遍历
const p = { name : '张三'}
const m = new Map()
m.set(p, 18)
console.log(m) //Map(1) { { name: '张三' } => 18 }
m.forEach((value,key) => console.log(value,key)) //18 { name: '张三' }
Symbol
最主要的作用就是为对象添加独一无二的属性名,也就是键值对中的键,并且常规方法无法获取这个键
const p = {
[Symbol()] : '张三'
,age : 18
}
for(let key in p){
console.log(key) //age <--- 没有 [Symbol()]
}
console.log(Object.keys(p)) //[ 'age' ] <--- 没有 [Symbol()]
console.log(JSON.stringify(p)) // {"age":18} <--- 没有 [Symbol()]
console.log(Object.getOwnPropertySymbols(p)) // [ Symbol() ]
console.log(Reflect.ownKeys(p)) //[ 'age', Symbol() ]
使用 Symbol 的特点 模拟实现对象的私有成员
const name = Symbol()
const p = {
[name] : '张三'
,show(){
console.log(this[name])
}
}
p.show() //张三
console.log(p.name) //undefined
console.log(p[Symbol()])//undefined
通过 symbol 创建的值一定是唯一的,不管传入的描述文本是否相同
console.log(
Symbol('symbol') === Symbol('symbol')
) // false
使用 静态方法 for 获取相同的symbol,描述文本也相同的值。
for方法内部维护了一个全局的表,为字符串和 symbol 值维护了对应关系。
注意,如果描述文本不是字符串,也会 toString 之后的结果当作描述文本
console.log(
Symbol.for('symbol') === Symbol.for('symbol')
)//true
console.log(
Symbol.for(true) === Symbol.for('true')
)//true
通过实现 symbol 的 toStringTag 接口,修改对象 tostring 的结果
console.log({}.toString()) //[object Object]
console.log({
[Symbol.toStringTag] : '空对象'
}.toString()) //[object 空对象]
(十一) for…of循环 、可迭代接口
使用 for…of 遍历 set 和 map 数据结构
const s = new Set(['1','2','3'])
for(const i of s){
console.log(i) // 1 2 3
}
const m = new Map()
m.set('张三', 18)
m.set('李四', 20)
for(const [key,value] of m){
console.log(key,value) //张三 18 李四 20
}
for…of 是一种统一的数据遍历方式,可以遍历所有数据结构
Iterable 可迭代接口
es2015中提供了 Iterable 接口,实现 Iterable 接口,就是使用 for…of 遍历的前提
打开浏览器的开发人员工具,输入 console.log([]),console.log(new Set()),console.log(new Map())。
观察每一个输出结果,原型中都有一个 Symbol.iterator 的方法。
定义一个数组,然后调用 Symbol.iterator ,再调用返回对象的 next 方法,就是数组中的每个值
总结,为了实现循环,数组原型维护了 Iterator 方法 ,这个方法返回了 带有 next() 方法的对象,不断调用 next() 方法,就可以实现对数组的遍历
使用js代码调用数组的迭代器(Iterator):
const array = [1, 2, 3, 4]
const iterator = array[Symbol.iterator]()
console.log(iterator.next()) //{ value: 1, done: false }
console.log(iterator.next()) //{ value: 2, done: false }
console.log(iterator.next()) //{ value: 3, done: false }
console.log(iterator.next()) //{ value: 4, done: false }
console.log(iterator.next()) //{ value: undefined, done: true }
对象实现 Iterable 接口
理论知识:
const p = { // <-- 这个对象实现了 Iterable 接口
[Symbol.iterator] : function(){
return { // <-- 这个对象实现了 Iterator 接口
next : function(){
return {// <-- 这个对象实现了 IterationResult 接口
value : ''
,done : false
}
}
}
}
}
具体实现:
const person = {
name : ['张三']
,age : [18]
,gender : ['girl']
,each(callBack){
const all = [...this.name, ...this.age, ...this.gender]
for(const item of all){
callBack(item)
}
}
,[Symbol.iterator] : function(){
const all = [...this.name, ...this.age, ...this.gender]
let tag = 0
return {
next : function(){
return {
value : all[tag]
,done : tag++ > all.length -1
}
}
}
}
}
person.each(value =>{
console.log(value) // 张三 18 girl
})
for(const item of person){
console.log(item) // 张三 18 girl
}
(十二) 生成器 Generator
解决异步编程回调函数过深的问题
例子,使用 Generator 实现 Iterator 方法
const person = {
name : ['张三']
,age : [18]
,gender : ['girl']
,[Symbol.iterator] : function * (){
const all = [...this.name, ...this.age, ...this.gender]
for(const item of all){
yield item
}
}
}
for(const item of person){
console.log(item) // 张三 18 girl
}
(十三) Modules 模块化标准
敬请期待