Es6
ES6
本文主要介绍 Javascript Es6 语法特性,为初学 JS 时期整理的笔记,格式较为混乱,待二次整理。
ECMAScript 2015 ,是脚本语言的规范
谁在维护ECMA-262
TC39是推荐ECMAScript发展的委员会
阮一峰ES6电子书
https://es6.ruanyifeng.com/
let变量
let
1.let变量不能重复声明
2.块级作用域
3.不存在变量提升
4.不影响作用域链
其他
let声明的变量不会存储在window对象中
在函数内部、作用域、循环内部声明局部变量 局部作用域
用于循环内,不影响循环外的变量
在相同的块级作用域中,不能使用let关键词来重置let/var变量
在相同的块级作用域中,不能使用var关键词来重置let变量
但可重新赋值 num=1
let 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的
let变量没有提升,不可提前作用域声明
使用var声明的变量存储到了window对象中
可通过window.a引用
全局作用域,作为window对象的属性保存
let作用域案例
for(var i = 0; i<items.length; i++) {
// 此处为var的话,会污染全局作用域,点击事件修改语句会去全局找i
// 此时i叠加为了3,报错
items[i].onclick = function(){
// this.style.background = 'pink'
items[i].style.background = 'pink'
}
}
let的暂时性死区
存在全局变量
tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
const常量
const
1.一定要初始化,赋初始值
2.一般常量使用
大写(潜规则)
3.常量的值不能修改
4.块儿级作用域
5.对于数组和对象的元素修改,不算做对常量的修改,不会报错
使用const定义的对象或数组,其实是可变的
注意
let和const在不同作用域,不同块级作用域中是可以重新声明赋值的
var
var会提前声明,全局作用域
函数内使用 var 声明的变量只能在函数内访问,是【局部变量】,
如果不使用 var 则是全局变量
function fn(){ let a=10; var b=10; } // console.log(a);// 无法引用 局部变量
变量解构赋值
解构赋值
ES6允许按照一定模式从数组和对象中提取值,对变量进行赋值
这被称为解构赋值
数组的解构
const F4 = ['1','2','3','4']
let [w,s,a,d] = F4
console.log(w) // 1
对象的解构
const zhao = {
name: 'sun',
age: '2',
fn: function(){
...
}
}
let {name,age,fn} = zhao
fn()
...解构剩余参数
let arr = [1,2,3,4,5,6,7,8,9,0]
// ...c将剩下的元素都交给c
let [a, b ,...c] = arr // 三个变量
console.log(c); // 3,4,5,6,7,8,9,0
// 用于传递多个参数给函数
let arr2 = [123,456,789]
// 传为函数参数...arr2
console.log(sum(...arr2))
// 将arr解构进对象
...{arr}
默认值
解构赋值允许指定默认值
let [foo = true] = []
foo // true
字符串的解构赋值
字符串也可以解构赋值,因为此时,字符串被转换成了一个类似数组的对象
const [a, b, c, d, e] = 'hello'
a // "h"
b // "e"
解构重命名
解构赋值,并重命名解构变量
通过:把a解构变量重命名为b
const { a:b } = res
连续解构赋值
注:通过连续解构赋值,是没有获取到外侧属性的,为解构最内层的
let obj = {
a: {
b: { c: 1 }
}
}
const { a: { b: { c } } } = obj
// 重命名c为data
// const { a: { b: { c:data } } } = obj
console.log(c) // 1
用途
// 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x]
// 从函数返回多个值
function example() {
return [1, 2, 3]
}
let [a, b, c] = example()
模板字符串
ES6引入新的声明字符串的方式 ``
1.let = `` 声明
2.内容中可以出现换行符
3.可以使用
${}
拼接变量
对象的简写形式
ES6允许在大括号内,直接写入变量和函数,作为对象的属性和方法
let name = 'xx'
let change = () => {}
const school = {
name,
change,
imcc(){ // 函数简写
...
}
}
箭头函数
箭头函数
1.this是静态的,this始终指向函数声明时所在作用域下的this的值
2.不能作为构造函数实例化对象
3.不能使用arguments变量传递实数
let fn = (参数){
...
}
箭头函数的简写
省略小括号,当形参有且只有一个的时候
let add = n => {
return n + n
}
省略花括号,当代码体只有一条语句的时候,此时return必须省略
箭头后的表达式作为函数返回值
let pow = n => n*2
用途
箭头函数适合与this无关的回调,定时器,数组的方法回调
箭头函数
不适合
与this有关的回调 事件的回调,因为需要指向事件源
对象的方法,需要指向对象当前的属性,箭头函数指向外侧作用域了
{ name:'xx', getname:()=>{ this.name } }
当然在vue中用于获取外层this
函数参数的默认值
函数参数默认值
ES6允许给函数参数赋值初始值
形参初始值
:具有默认值的参数,一般位置都要靠后(潜规则)
比如初始值被实参覆盖后,剩下的又是undefined了
形参初始值
function num(a,b,c=10){
return a + b + c
}
num(1,2)
结合解构赋值
配合解构,提取出传入的需要的指定参数
还可再配合默认值
function connect({host,user,pwa = '666',port}){
console.log(host)
}
connect({
host:'zhiyu.ltd',
user:'zhiyu',
pwa:'111',
prot:8080
})
其他
NaN:不合法的数
数字
+/*
其他
rest参数
rest
ES6引入rest参数,用于获取函数的实参,用来替代
arguments
...args
rest
可以使用数组的一些方法,提高了参数处理的灵活程度
rest
参数必须要放到参数(多个参数)最后
// rest参数
function data(...args){
console.log(args)
}
data(1,2,3)
// arguments参数
function arg(){
console.log(arguments)
}
arg(1,2,3)
其他
arguments
返回的是类数组对象,rest
返回的是一个真实数组
arguments
是有length属性的
扩展运算符
扩展运算符
...
扩展运算符能将【数组】转换为逗号分隔的【参数序列】
const phone = ['mi','ios','huawwei']
function fn(){
console.log(arguments)
}
fn(...phone) // 就像被解构进去了
数组的合并
const kai = ['0']
const fa = ['1']
const kaifa = [...kai,...fa]
console.log(kaifa) // ['0','1']
数组的克隆
const on = ['0']
const no = [...on]
console.log(no) // ['0']
将伪数组转为真正的数组
const divs = document.querySelectorAll('button') // 伪数组,原型上还是对象
const divArr = [...divs]
console.log(divArr) // 数组
拓展-浅拷贝、深拷贝
数组的克隆如果有引用类型数据,也是浅拷贝
浅拷贝,只复制指针,不复制对象本身,共享内存地址,指向同一个堆
复制对象改变,原数据也会改变
深拷贝,直接来个新的对象,把堆内存中的数据复制一份,然后重新在堆中开辟一块自己的空间存储数据,
不影响原对象
arguments
也可以转为真正的数组,但有rest
参数,故没有必要
Symbol
Symbol
Es6引入了一种新的原始数据类型
Symbol
,表示独一无二的值
。它是javascript语言的第七种数据类型,是一种类似于字符串的数据类型
Symbol
特点1.
Symbol
的值是唯一的,可以用来解决属性名命名冲突的问题2.
Symbol
的值不能与其他数据进行运算,其他Symbol
也不行3.
Symbol
定义的对象属性不能使用for...in
循环遍历,但是可以使用Reflect.ownKeys
来获取对象的所有键名,或Object.getOwnPropertySymbols()
创建Symbol
Symbol
Symbol 值通过
Symbol
函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。Symbol.for()
它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果
有
,就返回这个 Symbol 值
,否则就新建
一个以该字符串为名称的 Symbol 值,并将其注册到全局,拥有全局登记特性
故这种方法创建通过描述字符串得出唯一Symbol值,会相等
sym.description
直接返回Symbol的描述
配合阮一峰大神的书学习
https://es6.ruanyifeng.com/#docs/symbol
// 创建Symbol,并添加x描述字符串
let s = Symbol('x')
console.log(s,typeof s) // Symbol('x') symbol
let s2 = Symbol('xxx')
let s3 = Symbol('xxx')
console.log(s2 === s3) // false
// 上面是函数,下面是对象,函数对象
// Symbol.for()创建
// 通过这种方式创建的可以通过描述字符串得出唯一Symbol值
let s4 = Symbol.for('xxx')
let s5 = Symbol.for('xxx')
console.log(s4,typeof s4)
console.log(s4 === s5) // true
Symbol作为属性名
Symbol
的值是唯一的,可以用来解决属性名命名冲突的问题以下摘自
阮一峰
ES6 由于每一个 Symbol 值都是
不相等
的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。注意,Symbol 值作为对象属性名时,不能用点运算符。
因为点运算符后面总是字符串,所以不会读取
mySymbol
作为标识名所指代的那个值,导致a
的属性名实际上是一个字符串,而不是一个 Symbol 值。 同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
let mySymbol = Symbol()
// 第一种写法
let a = {}
a[mySymbol] = 'Hello!'
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
}
// 注意,Symbol 值作为对象属性名时,不能用点运算符。
a.mySymbol = 'Hello!'
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
Symbol.for() 与 Symbol()
Symbol.for()
与Symbol()
这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境
中供搜索,后者不会。Symbol.for()
不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key
是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")
30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")
30 次,会返回 30 个不同的 Symbol 值。
用途
场景:有一个对象,有好多属性
简单,高效,安全地创建,不会冲突,破坏原有属性
let game = {}
// 简单,高效,安全地创建,不会冲突,破坏原有属性
let methods = {
// 独一无二
up: Symbol()
}
game[methods.up] = function(){
console.log(1)
}
console.log(game)
// 第二种添加方式
let yx = {
name: 'xxx',
// 表达式
[Symbol('say')]: function(){ // 这样好像没法调用,遍历?
console.log('yx')
}
}
Symbol的内置值
除了定义自己使用的
Symbol
值以外,ES6还提供了11个内置的Symbol
值,指向语言内部使用的方法拥有它的
自动执行场景,并自动触发
作为对象的属性设置,
来改变对象特点场景的表现
,扩展对象功能
Symbol.hasInstance
当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法
可以传入参数(param)
可改变特点场景的表现
class Person{
static[Symbol.hasInstance](param){
console.log(param)
console.log('用来检测啦')
return true // 改变结果
}
}
let o = {}
console.log(o instanceof Person)
Symbol.isConcatSpreadable
对象的Symbol.isConcatSpreadable属性等于的是一个布尔值,表示该对象用于Array.prototype.concat()合并数组时,是否可以展开
不展开,指整体合并,不拆散
['1','2',['合并的','合并的']]
const arr = ['1','2','3']
const arr2 = ['4','5']
arr2[Symbol.isConcatSpreadable] = false
console.log(arr.concat(arr2))
拓展-各种数据类型
USONB
u undefined
s string symbol
o object
n null number
b boolean
迭代器
迭代器
迭代器(iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要有部署iterator接口,就可以完成遍历操作
1.ES6创造了一种新的遍历命令for...of循环,iterator接口主要供for...of消费
2.原生具备
iterator
接口的数据(可用for...of遍历)Array Arguments Set Map String TypedArray NodeList
3.
迭代器的工作原理
1.创建一个指针对象,指向当前数据结构的起始位置
2.第一次调用
对象身上的next
方法,指针自动指向数据结构的第一个成员3.接下来
不断调用
next方法,指针一直往后移动,直到指向最后一个成员4.每调用
next
方法便返回
一个包含value
和done属性(表示循环是否遍历完成,完成true)
的对象Object { value: undefined, done: true }
注:需要自定义遍历数据时,需要联想到迭代器
示例
对象身上具有
Symbol.iterator
函数,即可完成遍历iterator 对象的一个属性
done 一个遍历的完成时
const arr = ['1','2','3','4']
// 具有Symbol.iterator函数的对象,可以完成遍历
let iterator = arr[Symbol.iterator]()
// 调用对象的next方法
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
//Object { value: undefined, done: true } // done:true 遍历完成
for...of无法遍历没有迭代器的普通对象,解决方法
const school = {
name: 'Student',
student:[
'a','b','c','d','e'
],
// 手动实现迭代器
[Symbol.iterator](){
let that = this // 改变this指向,也可以使用箭头函数
let i = 0
return {
next: function(){
if(i < that.student.length){
const result = {value:that.student[i],done:false}
i++ // 自增遍历下标
return result // 返回
}else{
return {value:undefined,done:true}
}
}
}
}
}
// for...of不能遍历普通对象,没有iterable接口,具有迭代器接口的对象才可以使用
for(let v of (school)){
// 也可使用kes和values方法,获取键名、键值
// for(let v of Object.keys(school.student)){
// for(let v of Object.values(school.student)){
console.log(v);
}
拓展方法
for...in 保存键名,for...of保存键值
for...in适合对象,for...of适合数组
Object.keys
返回一个数组,成员是参数对象自身的(不含继承)
所有可遍历属性
的键名
Object.values
返回一个数组,成员是参数对象自身的(不含继承)
所有可遍历属性
的键值
Object.entries
返回一个数组,成员是参数对对象自身的(不含继承)
所有可比遍历属性
的键值对数组
Object.fromEntries()
是
Object.entries()
的逆操作,用于将一个键值对数组
转为一个对象Object.fromEntries([ ['foo', 'bar'], ['baz', 42] ]) // { foo: "bar", baz: 42 }
Generator 生成器函数
生成器函数(Generator 函数)是ES6提供的一种异步编程解决方案,语法与传统函数完全不同
异步编程
新的解决方案之前是使用纯回调函数 node fs ajax mongodb
yield表达式
1.遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值(相当于函数的分割符,yield
分割产生多块代码分别执行)2.下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式(一次next()
只执行一块代码)3.如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值4.如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
5.
yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行7.Generator生成器函数可以不用
yield
表达式,这时就变成了一个单纯的暂缓执行函数
function * f() { console.log('执行了!') } var generator = f() // 如果是普通函数,此时已经执行log setTimeout(function () { generator.next() }, 2000)
上面代码中,函数
f
如果是普通函数,在为变量generator
赋值时就会执行。但是,函数f
是一个 Generator 函数,就变成只有调用next
方法时,函数f
才会执行
for...of循环
for...of
循环可以自动遍历 Generator 函数运行时生成的Iterator
对象,且此时不再需要调用next
方法,即可依此显示yield
表达式的值
示例
// 异步编程新的解决方案
function * gen(){
// yield 函数代码的分割符,分割yield之前代码,产生四块代码
// 一次next,只输出一块代码内容
// 通过多次next(),控制代码向下执行
console.log('第1块')
yield '1'
console.log('第2块')
yield '2'
console.log('第3块')
yield '3'
console.log('第4块')
}
let iterator = gen()
// log结果没有出现,但本身是一个迭代器对象,可以调用next方法
// console.log(iterator)
// console.log(iterator.next()) // 输出next()返回的对象{value done}
iterator.next() // 1
iterator.next() // 2
// 遍历该可迭代对象
for(let i of gen()){
console.log(i) // 1/2/3 当前yield后的表达式的迭代值及该块代码的log
// 如果加个定时器,只会多次yield,log并不会多次出现,因为不是迭代对象吧
}
生成器函数next()参数
next()方法可以传入实参
,实参作为yield语句返回结果,yield语句需使用变量接收返回结果第一次调用next(),传递的参数是无效的
function * gen(arg){ console.log(arg) let b = yield 1 // 接收返回结果 console.log(b) let c = yield 2 console.log(c) yield 3 } // 传递参数 let iterator = gen('a') console.log(iterator.next()) // 第一个next()无法传递参数 // next方法可以传入实参,实参作为yield语句返回结果,yield语句需使用变量接收返回结果 console.log(iterator.next('b')) console.log(iterator.next('c')) console.log(iterator.next())
异步实例1
异步操作,隔x秒打印x
定义函数,并将函数
放入yield语句
中
将下一步的next()交给该函数,完成异步调用自动执行
// 异步编程 文件操作 网络操作(ajax,request) 数据库操作
// 生成器函数方法
function one(){
setTimeout(()=>{
console.log('one')
// 将下一步的next()交给该函数,完成自动执行
iterator.next()
},1000)
}
function two(){
setTimeout(()=>{
console.log('two')
iterator.next()
},2000)
}
function three(){
setTimeout(()=>{
console.log('three')
},3000)
}
// 将函数放入yield语句中
function * gen(){
yield one()
yield two()
yield three()
}
let iterator = gen()
iterator.next() // next()执行
// 原先套娃做法,回调地狱,不断向前推进
// setTimeout(()=>{
// console.log(1)
// setTimeout(()=>{
// console.log(2)
// setTimeout(()=>{
// console.log(3)
// },3000)
// },2000)
// },1000)
异步实例2
next()方法可以传入实参,实参作为yield语句返回结果,yield语句需使用变量接收返回结果
// 模拟获取依次执行 用户数据->订单数据->商品数据
function user(){
setTimeout(() => {
let data = '用户数据'
iterator.next(data) // next()传参 往下执行
},1000)
}
function orders(){
setTimeout(() => {
let data = '订单数据'
iterator.next(data)
},1000)
}
function data(){
setTimeout(() => {
let data = '商品数据'
iterator.next(data)
},1000)
}
function * gen(){
let Guser = yield user()
console.log(Guser)
let Gorders = yield orders()
console.log(Gorders)
let Gdata = yield data()
console.log(Gdata)
}
let iterator = gen()
iterator.next()
Promise对象
Promise
是ES6引入的异步编程的新解决方案。语法上Promise
是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果1.Promise构造函数:Promise(excutor){}
2.Promise.prototype.then方法
3.Promise.prototype.catch方法
Promise对象及then方法
Promise
(承诺)的三种状态:pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)1.只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
2.一旦状态改变,就不会再变,任何时候都可以得到这个结果
3.
Promise
构造函数接收一个函数作为参数,该函数的两个参数分别是resolve
和reject
4.
resole
函数的作用是将Promise
对象的状态从'未完成'变为'成功'
reject
函数的作用是,将Promise
对象的状态从'未完成'变为'失败'5.
Promise
实例生成后,可以用then方法分别指定resolved
状态和rejected
状态的回调函数
then
方法1.
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态为resolved
时调用,第二个回调函数是Promise
对象的状态为rejected
时调用。这两个函数都是可选参数,不一定要提供。他们都接受Promise
对象传出的值作为参数2.状态为成功调用then方法的第一个函数,状态为失败则调用第二个函数,函数的形参接受
Promise
对象传出的值
// 实例化Promise对象
// Promise的三个状态:进行中,成功,失败
// 参数是个函数类型值,函数有两个形参,resolve,reject
const p = new Promise(function(resolve,reject){
setTimeout(function(){
// let data = '数据库调用成功'
// 调用resolve和reject来改变Promise的状态
// resolve(data)
let err = '数据库调用失败'
reject(err) // 传出err值
},1000)
})
// 成功则调用Promise对象的then方法
// then方法接收两个函数参数,函数参数成功失败的值分别为value和reason
// 状态为成功调用then方法的第一个函数,状态为失败则调用第二个函数
p.then(function(value){
console.log(value)
},function(reason){
console.error(reason);
})
Promise封装读取文件
// 1.引入fs模块
const fs = require('fs')
// 3.使用Promise封装
const p = new Promise((resolve,reject) => {
fs.readFile('./为学.md',(err,data) => {
if(err) reject(err)
resolve(data.toString())
})
})
p.then((value)=>{
console.log(value)
},(reason) => {
console.log("读取失败")
})
// 2.调用方法读取文件
/* fs.readFile('./为学.md',(err,data)=>{
// 失败,抛出错误
if(err) throw err
// 成功,输出内容
console.log(data.toString())
}) */
Promise封装AJAX
// 接口地址:https://api.apiopen.top/getJoke
const p = new Promise((resolve, reject) => {
// 1.创建对象
const xhr = new XMLHttpRequest()
// 2.初始化
xhr.open("GET", "https://api.apiopen.top/getJoke")
// 3.发送
xhr.send()
// 4.绑定事件,处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功调用resolve
resolve('成功' + JSON.parse(xhr.response))
} else {
// 失败调用reject
reject('error ' + xhr.status)
}
}
}
})
// 指定回调
p.then((value) => {
console.log(value)
},(reason) => {
console.log(reason)
})
Promise.prototype.then方法
then方法返回结果也是一个Promise对象
,该Promise对象的状态由回调函数的执行结果决定
1.如果回调函数中返回的结果是
非Promise类型的对象
,状态为成功,返回值为对象成功的值,不写return默认返回一个undefined,不是Promise对象,状态也为成功2.如果回调函数中的返回结果是
一个Promise对象
,那这个返回对象的状态就决定了then方法返回的Promise的状态,值也是返回对象结果Promise的值(就是全看儿子了)3.
then方法可以链式调用
,根据then返回的Promise对象决定是否继续,用来执行嵌套异步任务
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('用户数据')
},1000)
})
const result = p.then(value => {
console.log(value)
// 1.非Promise类型的属性
// return 1
// 2.是Promise对象
return new Promise((resolve, reject) => {
// 内部Promise决定外部
reject('404')
})
// // 3.抛出错误
throw new Error('500')
throw '500'
},reason => {
console.warn(reason)
})
// 查看result接受的then方法返回值
console.log(result)
// 可以链式调用,根据then返回的Promise对象决定是否继续
// 嵌套异步任务
p.then(value => {
return new Promise((resolve,reject)=>{
// 失败,返回结果决定下一个then走失败2
reject('400')
})
},reason => {
console.error('失败1')
}).then(value => {
console.log('成功')
},reason => {
console.error('失败2')
})
Promise封装读取多个文件
// 1.引入fs模块
const fs = require("fs")
// 3.使用Promise实现
const p = new Promise((resolve, reject) => {
fs.readFile("./为学.md", (err, data) => {
resolve(data)
})
})
p.then(
(value) => {
console.log(value)
return new Promise((resolve, reject) => {
fs.readFile("./测试2.md", (err, data) => {
// 合并两个md并向下传递
resolve([value,data])
})
})
},
(err) => reject(err)
).then((value) => {
return new Promise((resolve, reject) => {
fs.readFile("./测试3.md", (err, data) => {
// 压入第三个md
value.push(data)
resolve(value)
})
})
}).then((value) => {
// join拼接数组为字符串并换行,内部会转为字符串,不需要toString()
console.log(value.join('\n'))
})
// 2.调用方法读取多个文件,具有层级关系
// fs.readFile("./为学.md", (err, data1) => {
// fs.readFile("./测试2.md", (err, data2) => {
// fs.readFile("./测试3.md", (err, data3) => {
// let result = data1 + "\n" + data2 + "\n" + data3
// console.log(result)
// })
// })
// })
catch方法
catch方法
,相当于then方法中的失败状态回调,语法糖可以直接跟在then方法后
// catch方法,语法糖,相当于then方法中的失败状态回调
const p = new Promise((resolve, reject) => {
reject('error')
})
p.catch((reason) => {
console.warn(reason)
})
finally方法
finally方法
,用于指定不管Promise对象最后的状态如何,都会执行的操作。ES2018可以用来做收尾工作
promise.then(result => {···})
.catch(error => {···错误})
.finally(() => {···收尾})
try/catch/finaly
try/catch/finally
语句用于处理代码中可能出现的错误信息1.
try
语句允许我们定义在执行时进行错误测试的代码块2.
catch
语句允许我们定义当try
代码块发生错误时,所执行的代码块3.
finally
语句在try
和catch
只会无论有无异常都会执行4.在
try
中可使用throw
语句来抛出异常5.不能捕获异步抛出异常
function myFunction() {
var message, x;
message = document.getElementById("message");
message.innerHTML = "";
x = document.getElementById("demo").value;
try {
// 错误则抛出异常
if(x == "") throw "为空";
if(isNaN(x)) throw "不是一个数字";
if(x > 10) throw "太大";
if(x < 5) throw "太小";
}
catch(err) {
// err接受try的异常返回值
message.innerHTML = "输入的值 " + err;
}
finally {
// 清空input
document.getElementById("demo").value = "";
}
} // 来自菜鸟教程
Set
Set-集合
ES6提供了新的数据结构
Set
(集合)。它类似于数组,但成员的值都是唯一的
,没有重复的值(自动去重)。集合实现了iterator
接口,所以可以使用扩展运算符
和for...of
进行遍历。集合的属性和方法:1.
size
返回集合的元素个数2.
add
增加一个新元素,返回当前集合3.
delete
删除元素,返回boolean值4.
has
检测集合中是否包含某个元素,返回boolean值5.
clear
清空Set6.Set结构的实例拥有
四个遍历方法
,可以用于遍历成员
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每个成员7.可用于对数组的去重
let s = new Set()
// 成员值都是唯一的,会自动去重
let s2 = new Set(['1','1','2','3','4','5'])
console.log(s2,typeof s2) // 类型Object
// 元素个数
console.log(s2.size)
// 添加新的元素
s2.add('6')
// 删除元素
s2.delete('6')
// 检测元素,返回boolean值
console.log(s2.has('1')) // true
console.log(s2.has('100')) // false
// 清空
// s2.clear()
for(let v of s2){
// 可迭代
console.log(v)
}
// 可使用扩展运算符展开
let arr = [...s2]
用途
let arr = [1, 2, 3, 4, 5, 6]
// 1.数组去重
// 使用扩展运算符展开
// let result = [...new Set(arr)]
// 2.交集
let arr2 = [4, 5, 6, 5, 6]
let mix = [...new Set(arr)].filter(item => { // filter过滤
let s2 = new Set(arr2) // 去重arr2
if (s2.has(item)) { // 使用has判断包含
return true
} else return false
})
// 交集简化
// let mix = [...new Set(arr)].filter(
// // 省略花括号,此时return必须省略
// // 箭头后的表达式作为函数返回值
// item => new Set(arr2).has(item)
// )
console.log(mix)
// 3.并集
// 使用扩展运算符合并数组,再使用Set将数据展开到数组中,数据类型最终为数组
let union = [...new Set([...arr,...arr2])]
console.log(union)
// 4.差集,互相间没有的
// 差集为交集的逆运算,不在arr2的即为差集数 !s2.has(item)
let diff = [...new Set(arr)].filter( item => !new Set(arr2).has(item))
console.log(diff)
Map
Map
ES6提供了
Map
数据结构。它类似于对象,也是键值对的集合
。但是"键"的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了iterator
接口,所以可以使用扩展运算符
和for...of
进行遍历。Map
的属性和方法:1.
size
返回Map
的元素个数2.
set
增加一个新元素,返回当前Map
3.
get
返回键名对象的键值4.
delete
删除某个键,返回boolean值4.
has
检测Map
中是否包含某个元素,返回boolean值5.
clear
清空合集,返回undefined6.作为构造函数,
Map
也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组const map = new Map([ ['name', '张三'], ['title', 'Author'] ]);
示例
// 声明Map
// 接受一个数组作为参数,该数组的成员是一个个表示键值对的数组
let m = new Map([
['key','value'],
])
// 添加元素
m.set('name','school') // 键值对
m.set('change',function back(){
console.log('change')
})
let key = {
school: 'to'
}
// 各种类型的值都可以是键,对象key作为键
m.set(key,['1','2','3','4','5'])
console.log(m)
// 个数
console.log(m.size)
// 删除
m.delete('name')
// 获取,返回键名对象的键值
console.log(m.get(key))
// 清空
// m.clear()
// 遍历
for (let v of m) {
// 返回的是一个个键值对数组
console.log(v)
}
// 扩展
let arr = [...m]
Class类
class
ES6提供了更接近传统语言的写法,引入了
Class
(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。基本上,ES6的class
可以看作只是一个语法糖,它的绝大部分概念,ES5都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已
基本语法
声明一个class 类名{},内部包含
constructor()
构造方法,该构造方法名字不能修改,会自动执行,在class内部定义prototype
原型方法,必须使用对象简写形式
constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法,一个类必须有constructor()
方法与方法之间,不需要逗号分隔
ES6的类,完全可以看作构造函数的另一种写法
使用的时候,也是直接对类使用
new
命令,跟构造函数的用法完全一致
// class
class Phone{
// 构造方法 名字不能修改 自动执行
constructor(brand,price){
this.brand = brand
this.price = price
}
// 方法必须使用该语法(对象简写形式),不能使用ES5的对象完整形式
call(call){
console.log(`class ${call}`)
}
}
let onePlus = new Phone('一加',3999)
onePlus.call('一加') // __proto__
console.log(onePlus)
// ES5构造函数实例化对象
// function Phone(brand,price){
// this.brand = brand
// this.price = price
// }
// 添加方法
// Phone.prototype.call = function(){
// console.log('call')
// }
// 实例化对象
// let Huawei = new Phone('华为',5999)
// Huawei.call()
// console.log(Huawei)
class静态成员static
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上
static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就被称为静态方法
静态成员,属于函数对象不属于实例对象,属于类不属于实例
// class
// static标注的属性和方法,属于类,不属于实例
class Phone{
// 静态属性
static name = 'mi'
static change(){
console.log('change')
}
}
let mi = new Phone()
console.log(mi.name) // undifined
console.log(Phone.name) // mi
// ES5构造函数静态对象
function Phone(){}
Phone.name = 'mi' // 属于函数对象不属于实例对象,静态成员,属于类不属于实例
let nokia = new Phone()
// undifined,实例对象与函数对象的属性是不通的
console.log(nokia.name)
// 想通可以定义到原型prototype上去
Phone.prototype.name = 'mi'
console.log(nokia.name) // mi
// class Test {
// a = 1 // 内部的对象属性需new获取
// }
// new Test().a // 1
// 类属性,不用new即可获得
// Test.b = 12
对象extends继承父类
Class
实现:Class可以通过
extends
关键字实现继承,继承父类属性方法子类必须在
constructor
方法中调用super
方法,否则新建实例时会报错,子类会找不到thisclass 子类 extends 父类 { constructor(){ super(...) } }
super
关键字有两种使用方法,既可以当作函数使用(同上),也可以当作对象使用
作为
函数
使用,代表父类的构造函数,super代表父类的constructor方法作为
函数
时,super()
只能用在子类的构造函数之中,用在其他地方就会报错class B extends A { m() { // 需是constructor super(); // 报错 } }
作为
对象
使用,在普通方法中,指向父类的原型对象;在静态方法中,指向父类// 普通方法 class A { p() { return 2; } } class B extends A { constructor() { super(); console.log(super.p()); // 2 ,指向父类原型 // super.p()就相当于A.prototype.p() } } // 静态方法 super在静态方法之中指向父类,在普通方法之中指向父类的原型对象 class Parent { static myMethod(msg) { console.log('static', msg); } myMethod(msg) { console.log('instance', msg); } } class Child extends Parent { static myMethod(msg) { super.myMethod(msg); } myMethod(msg) { super.myMethod(msg); } } Child.myMethod(1); // static 1 var child = new Child(); child.myMethod(2); // instance 2
ES5实现:
于子级构造函数中,调用
phone
并通过call改变指向(同ES6异同)Phone.call(this,brand,proce)
设置子级函数构造函数的原型,使子级包含父级原型方法
SmartPhone.prototype = new Phone
校正,将子级函数的constructor从原型指回构造函数
SmartPhone.prototype.constructor = SmartPhone
声明子类的方法属性
SmartPhone.prototype.photo = xxx
ES5和class的异同
:于子函数调用父函数生成,call方法改变this指向
由于使用call方法,和class还是有点不一样的
比起ES5的通过修改原型链实现继承,class要清晰和方便很多
子函数原型上也有两个父函数缔造的属性,而class super是没有的
相当于把this指针交给父函数使用
摘自阮一峰:
ES5 的继承,实质是先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到
this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
Object.getPrototypeOf
方法:可以用来从子类上获取父类,可以用该方法判断,一个类是否继承了另一个类
Object.getPrototypeOf(ColorPoint) === Point // true
示例代码
// class
class Phone{
// 构造方法
constructor(brand,price){
this.brand = brand
this.price = price
}
// 父类的成员属性
call(){
console.log('call')
}
}
// class子类
// extends继承父类属性方法
class SmartPhone extends Phone{
// 构造方法
constructor(brand,price,color,size){
// super代表父类的constructor方法
super(brand,price)
this.color = color
this.size = size
}
photo(){
console.log('photo')
}
}
const apper = new SmartPhone('apper',9999,'black','6')
console.log(apper)
apper.photo()
// ES5构造函数继承
function Phone(brand,proce){
this.brand = brand
this.proce = proce
}
Phone.prototype.call = function(){
console.log('call')
}
// 子级构造函数
function SmartPhone(brand,proce,color,size){
// 调用Phone生成,call方法改变this指向
// 使用call方法,和class还是有点不一样的
// 比ES5的通过修改原型链实现继承,class要清晰和方便很多
// 子函数原型上也有两个父函数缔造属性,而class super是没有的
// 相当于把this指针交给父函数使用
Phone.call(this,brand,proce)
this.color = color
this.size = size
}
// 设置子级构造函数的原型,使子级包含父级原型方法
SmartPhone.prototype = new Phone
// 校正,constructor从原型指回构造函数
SmartPhone.prototype.constructor = SmartPhone
// 声明子类的方法
SmartPhone.prototype.photo = function(){
console.log('photo')
}
let chuizi = new SmartPhone('锤子',2499,'black','5.5')
console.log(chuizi) // 原型继承
console.log(chuizi.__proto__.photo()) // 没有返回值,photo + undefined
子类重写父类方法
在js class语法中,子类是不可以直接去调用父类方法的,通过重写
// 代码其他位置同上,就子类多了个call()重写
class SmartPhone extends Phone{
...代码
call(){
console.log('子类call')
}
}
...
apper.call() // 子类call
class中的get和set
get封装动态属性:读取price就会执行get,返回值就是这个属性的值 set控制判断值是否合法:修改就会执行,必须传
newVal
参数
// get 和 set
class Phone{
// 读取price就会执行get,返回值就是这个属性的值
get price(){
console.log('get读取price')
return 'price'
}
// 修改就会执行
set price(newVal){
console.log('set修改price' + '修改后:' + newVal)
}
}
// 实例化对象
let s = new Phone()
console.log(s.price) // 打印log/price
s.price = 'set' // log
ES6数值扩展
Number.EPSILON
Number.EPSILON
是Js表示的最小精度值接近于
2.220446049250313e-16
差值小于最小精度,则认为他们是相等的
用于
浮点数运算
,设置精度,因为浮点数计算是有误差的
console.log(.1 + .2 === .3) // false
function equal(a,b){
if(Math.abs(a-b) < Number.EPSILON){
return true
}else return false
}
console.log(equal(.1 + .2 , .3)) // true 误差小于最小精度,相等
进制数
let b = 0b1010 // 二进制 0b
let o = 0o777 // 八进制 0o
let d = 100 // 十进制
let x = 0xff // 十六进制 0x
Number.isFinite
检测一个数值是否为有限数
console.log(Number.isFinite(100)) // true
console.log(Number.isFinite(100/0)) // fasle
console.log(Number.isFinite(Infinity)) // false
Number.isNaN
检测一个数是否为NaN
ES5独立,ES6置于Number内(方便其他语言习惯)
console.log(Number.isNaN(123*'w')) // true
Number.parseInt Number.parseFloat
字符串转整数
console.log(Number.parseInt('321312js')) // 321312
console.log(Number.parseFloat('3.21312js')) // 3.21312
Number.isInteger
判断一个数是否为整数
console.log(Number.isInteger(2.3)) // fasle
Math.trunc
将数字的小数部分抹掉
console.log(Math.trunc(3.4343)) // 3
Math.sign
判断一个数到底为正数 负数 还是零 正数返回1 负数返回-1 零返回0
console.log(Math.sign(100)) // 1
console.log(Math.sign(-100)) // -1
console.log(Math.sign(0)) // 0
ES6对象方法扩展
Object.is
判断两个值是否完全相等
同值相等,和直接用全等号的区别:判断两个NaN时会相等,NaN和任何值都不相等
console.log(Object.is(120,120)) // true
console.log(Object.is(NaN,NaN)) // false
console.log(NaN === NaN) // false
Object.assign
对象的合并,不会影响原对象
参数:
第一个参数是目标对象,后面的参数都是源对象
后覆盖前,前作为模板,前具有后没有内容,保留
Object.assign()方法实行的是浅拷贝,而不是深拷贝
可用于配置合并
const config1 = {
host:'8080',
name:'1',
test:'1'
}
const config2 = {
host:'5000',
name:'2'
}
console.log(Object.assign(config1,config2))
Object.setPrototypeOf
设置对象原型
参数:对象,设置的原型
const school = {}
const cities = {'原型':['proto']}
Object.setPrototypeOf(school,cities) // 设school原型为cities
console.log(school)
Object.getPrototypeOf
读取对象原型
参数:对象,读取的原型
const school = {}
const cities = {'原型':['proto']}
Object.setPrototypeOf(school,cities)
console.log(school)
console.log(Object.getPrototypeOf(school)) // cities
拓展
Object.create
创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
const me = Object.create(person)
模块化
模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来
模块化的好处
1.防止命名冲突
2.代码复用
3.高维护性
模块化规范产品
ES6之前的模块化规范
CommonJS ==> NodeJS、Browserify
AMD ==> requireJS
CMD ==> seaJS
浏览器使用ES6模块化方式一
export
用于规定模块的对外接口
向外暴露
import
用于输入其他模块提供的功能
语法:
将module.js中暴露的数据存到
module
变量中
<script>
标签type="module"
于标签中引入模块
<script type="module"> // type="module" // 引入module.js模块内容 import * as module from "./module.js" console.log(module) </script>
模块化需在服务器环境运行
跨域的问题
浏览器使用ES6模块化方式二
引入模块的方法
1.制定入口文件
app.js
,于入口文件引入模块,最后于页面引入app.js
<script src="./app.js" type="module"></script>
2.html内
script type="module"
标签
ES6模块暴露数据语法
分别暴露
通过
export
暴露
export let school = 'school'
export function teacher() {
console.log('teacher')
}
统一暴露
通过
export{'暴露数据1','数据2'}
方式对象简化写法包含【要暴露的数据名】
let school = 'school'
function findJob(){
console.log('teacher')
}
// 统一暴露
export {school,findJob}
默认暴露
通过
export default{}
方式【内含数据】数据写为对象形式,为一个整体
export default {
school : 'school',
teacher : function(){
console.log('teacher');
}
}
区别
1.通过export方式导出,在import导入时可以选择{ }一个导入或多个导入,但不能自定义模块名
import { a,b } from ab // export {a,b}
2.export default在import导入不需要加{ },可以自定义模块名,但导入后多个模块为一个整体对象
import a from ab // export default {a,b} console.log(a) // { a,b }
ES6模块引入模块语法
1.通用导入方式
import * as m2 from "./m2.js"
2.解构赋值形式
从模块文件中取出对应模块
school,teach变量便可直接使用
import { school,teach } from "./module.js"
解构赋值形式-应对分别暴露
school变量重名,
as
决定别名
import { school as guigu, findJob } from "./m2.js"
解构赋值形式-应对统一暴露
应对默认暴露,
为default定义别名
,也可直接定义名称(简便形式),但不能直接使用default
import { default as m3 } from "./m3.js"
3.简便形式
简便形式,只能应对默认暴露
import mo from "./m3.js"
babel
是一个Js编译器,可以对ES6模块化代码转换,转化为ES5
安装工具
babel-cli
babel命令行工具包
babel-preset-env
将语法转换为ES5
browserify
browserify打包工具(项目中使用webpack)
安装
1.安装工具
babel-cli
babel命令行工具包
babel-preset-env
将语法转换为ES5
browserify
browserify打包工具(项目中使用webpack)
2.安装
初始化
npm init --yes
安装三
npm i babel-cli babel-preset-env browserify -D
安装 -D dev开发环境
局部安装(npx)
npx babel
全局安装
babel
执行转换为Es5
npx babel src/js -d dist/js --presets=babel-preset-env
第一个路径是代转换文件位置,第二个路径是生成文件位置
--presets=babel-preset-env是配置
3.打包
直接引入转换的js会报错,因为模块化语法是CommonJS的,需要打包
npx browserify dist/js/app.js -o dist/bundle.js
文件夹内需存在引入的依赖js,否则打包报错
import $ from 'jquery';
// 等同于
const $ = require("jquery");