JavaScript ES(6-11)学习笔记

课程资源:链接:https://pan.baidu.com/s/1YChjK2-p9rkl0Veds6nMNw 提取码:897y

笔记PDF版本:链接:https://pan.baidu.com/s/1dzTkWnpGXeofYjvJ4sUUpg 提取码:a6rl

文章目录

1 课程介绍与环境搭建

1-1 课程介绍


ECMAScript6(2015)思维导图

ECMAScript711(20162020)思维导图

配套电子书地址:http://es.xiecheng.live/introduction/preface.html

1-2 Node安装与NPM源切换

1、Node环境安装:

下载地址:https://nodejs.org/zh-cn/ (下载长期支持版本LTS)

安装完成,在终端输入:node -v 查看版本号

2、NPM:Node Package Manager

查看版本:npm -

3、NRM(Npm Registry Manager )是npm的镜像源管理工具源切换,找到当前最快的安装源

安装:npm install -g nrm

NRM安装不成功

如果安装nrm出现下面信息,安装失败,解决办法参考:https://blog.csdn.net/blue_698/article/details/117874021

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gcXs54pK-1650374741706)(C:\Users\联想\AppData\Roaming\Typora\typora-user-images\image-20220418121123274.png)]

  1. 执行:npm config list -l,出现下面结果

  2. 执行npm cache clean --force清除缓存

  3. 执行nrm ls出现下面结果即解决

NRM常用命令

测试源的速度:nrm test npm

切换源:nrm use taobao

查看可用源:nrm ls

查看当前源:nrm current

增加定制源:nrm add imooc http://192.168.1.100:6666

删除源:nrm del imooc

1-3 构建开发环境

1、构建步骤

安装:npm install -g imooc-es-cli 或者 npm i -g imooc-es-cli

查看版本号:imooc-es-cli --version

进入项目建立文件夹:cd 文件夹位置

初始化项目:imooc-es-li init (注意:此处需要输入项目名称:imooc-es-demo,一定要是英文的)

]

进入项目:cd imooc-es-demo

安装当前项目所依赖的包:npm install

启动项目:npm run start

1

访问地址:http://localhost:8080/出现下面结果即成功

在这里插入图片描述

2、项目结构说明

在这里插入图片描述

用webstorm打开项目,查看项目结构

  1. build:webpack相关的配置文件

  2. node_modules:当前项目所依赖的包,即前面npm install所下载的包

  3. static:不会被webpack打包编译,而是直接复制到生产环境当中

  4. package-lock.json package.json:当前项目包所需要的一些配置文件

  5. src项目文件

在浏览器界面,F12进入编辑,查看source下的index.js,看到eval("var sum = function sum(x, y) {\n return x + y;\n};说明webpack打包编译成功

在这里插入图片描述

2 ECMAScript2015(ES6)之必备知识

2-1 新的声明方式let & const

  1. 不属于顶层对象window
  2. 不允许重复声明
  3. 不存在变量提升
  4. 暂时性死区
  5. 块级作用域

作用域

对象类型
global/window全局作用域
function函数作用域(局部作用域)
{}块状作用域
this动态作用域

可以直接使用webstorm自带的终端启动运行项目,启动后,在static文件夹下创建js文件,并在src中的index.html中引入js文件,<script src="static/2-1.js"></script>

写好代码之后,新建一个终端,输入npm run build使项目打包重新上线,项目结构中会出现dist文件夹。(注意:src下的文件会被webpack打包编译,而static会被直接复制过去

在这里插入图片描述

在这里插入图片描述

let

// delete 删除对象的属性,并不能删除变量
// var a = 5//变量
// b = 6//隐藏的对象
// //早期,js将顶层对象属性和全局变量挂钩,污染了全局变量
// console.log(window.a)
// console.log(window.b)
// delete a //删除失败
// delete b //删除成功
// console.log(a)
// console.log(b) //报错undefined

// 1.不属于顶层对象window
// let声明的变量不可以通过window.变量名 的方式访问,而var声明的全局变量是window的属性,是可以通过window.变量名的方式访问
// let a = 5
// console.log(a)
// console.log(window.a) //undefined

//2.不能重复声明
// var可以重复定义,使用let不可以
// let a = 5
// let a = 6 //报错:Uncaught SyntaxError: Identifier 'a' has already been declared

// 3.不能变量提升
// let声明的变量必须先定义,后使用,否则会报错
// console.log(a) //2-1-let.js:22 Uncaught ReferenceError: Cannot access 'a' before initialization
// let a = 5

//4.暂时性死区:防止在变量声明之前使用变量,使代码更加安全
// 只要块级作用域内存在let命令,它所声明的变量就绑定在了这个区域,不再受外部的影响
// 在代码块内,使用let命令声明变量之前,该变量都是不可用的
// var a = 5
// if (true) {
//     a = 6 //报错,在{}作用域内,没有声明就使用
//     let a
// }
// 较隐蔽的暂时性死区
// function foo(a = b, b = 2) {
//     console.log(a, b) //报错:b在没有声明的情况下就使用了
// }
// foo()

//5.块级作用域
// for (let i = 0; i < 3; i++) {
//     console.log('循环内:' + i)
// }
// console.log('循环内:' + i) //报错,let有块级作用域,此处i没有声明

// if (true) let a = 5 //Lexical declaration cannot appear in a single-statement context
// let有块级作用域,必须写在{}内

//6.面试题
// for (var i = 0; i < 3; i ++) {
//     setTimeout(function () {
//         console.log(i) //3个3
//     }, 100)
// }
// setTimeout异步执行,js有事件循环机制,会等到主线程执行完成,异步操作才开始执行
// for循环是同步操作,setTimeout会等for循环执行完毕才开始执行,此时i=3

// 解决办法:
// for (var i = 0; i < 3; i ++) {
//     //1)使用闭包
//     (function (j) {
//         setTimeout(function () {
//             console.log(j) //0 1 2
//         }, 100)
//     })(i)
// }
//闭包特点:有一个外部函数,一个内部函数,内部函数会调用外部函数传来的变量,使得外部函数的变量不被释放

// 2)使用let
for (let i = 0; i < 3; i ++) {
    setTimeout(function () {
        console.log(i) //0 1 2
    }, 100)
}

const

// es5定义一个常量
// Object.defineProperty(window, 'PI', {
//     value: 3.14,
//     writable: false // writable:当前变量是否可写
// })
// console.log(PI)
// PI = 5
// console.log(PI) //3.14  变量值不可修改

// es6
// 1.const修饰的变量不能被修改
// const a = 5
// a = 6 //报错

//2.const变量只能在定义的时候赋值
// const a
// a = 5 //报错

//3.const作用域:块级作用域
//4.const存在暂时性死区

// 5.重点
// 基本数据类型存储在 栈 内存中,引用数据类型存储在 堆 内存中(在栈内存中只保存引用地址,实际的值是在堆内存中)
// const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
const obj = {
    name: 'mys',
    age: 18,
    skill: {
        name: 'code',
        year: 4
    }
}
console.log(obj)
obj.school = 'imooc' // 成功 const变量指向地址的不变,内容如果是数组或对象可以改变,常量不能改变
console.log(obj)

Object.freeze(obj) // 冻结,freeze只能传递对象,不能传数组
obj.skill.year = 5 // 修改成功,因为freeze只是浅层的冻结
console.log(obj)
// 如果需要深层的冻结,需要:Object.freeze(obj.skill)

//如果变量在后续的业务逻辑中不会被改变,即可以用const,否则用let定义

在这里插入图片描述

2-2 解构赋值

解构赋值:允许按照一定模式,从数组和对象中提取值,对变量进行赋值

  1. 数组解构
  2. 对象解构
  3. 字符串解构

最常用:Object 和 Array

// 1.数组解构赋值
// let [a, b, [c, d]] = [1, 2, [3, 4]]
// console.log(a, b, c, d) // 1 2 3 4

// let [a, b, [c]] = [1, 2, [3, 4]]
// console.log(a, b, c) // 1 2 3

// let [a, b, c] = [1, 2, [3, 4]]
// console.log(a, b, c) // 1 2 [3, 4]

// let [a, b, c, d] = [1, 2, [3, 4]]
// console.log(a, b, c, d) // 1 2 [3, 4] undefined
// 数组是通过下标去对应的,是惰性的

//2.对象解构赋值 (常用)
// let user = {
//     name: 'mys',
//     age: 18
// }
// let {age: uage, name: uname} = user // uname uage使用别名
// console.log(uname, uage) // mys 18
//对象是通过key名称去对应的,不是顺序

// 3. 字符串解构赋值(类似于数组解构赋值)
// let str = 'imooc'
// let [a, b, c, d, e] = str
// console.log(a, b, c, d, e)

// 4.默认值
// 1)数组
// let [a, b, c = 8] = [4, 5]
// console.log(a, b, c) // 4 5 8
// 2)对象
// let {name, age = 18} = {
//     name: 'mys',
//     // age: 20
// }
// console.log(name, age)
// 3)函数
// function foo() {
//     console.log(123)
// }
// // let [a = foo()] = [1]
// // console.log(a) //1
// let [a = foo()] = []
// console.log(a) //123 undefined

// 5.应用
// 1)函数参数解构赋值
// function foo([a, b, c]) {
//     console.log(a, b, c) //1 2 3
// }
// let arr = [1, 2, 3]
// foo(arr)

// 2)函数参数是对象
// function foo({name, age, school = 'imooc'}) {
//     console.log(name, age, school) //mys 18 xxx
// }
// let obj = {
//     name: 'mys',
//     age: 18,
//     school: 'xxx'
// }
// foo(obj)

// 3)返回值解构
// function foo() {
//     let obj = {
//         name: 'mys',
//         age: 18,
//         school: 'xxx'
//     }
//     return obj
// }
//
// let {name, age} = foo()
// console.log(name, age)

// 4)提取json数据
let json = '{"a": "hello", "b": "world"}'
let {a, b} = JSON.parse(json)
console.log(a, b)

2-3 数组的各种遍历方式

在这里插入图片描述

let arr = [1, 2, 3]
// es5
// 1.for
// for (let i = 0; i < arr.length; i ++) {
//     console.log(i)
// }

// 2.forEach 
/*
    参数:(遍历的数组内容,对应的数组索引,数组本身)
    语法:[ ].forEach(function(value,index,array){

      });
    没有返回值  
*/  
// arr.forEach(function (elem, index, array) {
//     console.log(elem, index)
// })
// forEach不支持break continue

// 3.map
/*
	语法:arr.map(function(currentValue,index,arr),thisValue)
	参数:currentValue  必须 当前元素值
		 index         可选 当前元素的索引值
		 arr           可选 当前元素属于的数组对象
	返回:新数组	 
*/
// let res = arr.map(function (value) {
//     value += 1
//     return value
// })
// console.log('res:', res) // [2, 3, 4]
// console.log('array:', arr) // [1, 2, 3]
// map不会改变原有数组

//4.filter
/*
	语法:filter(function(item,index){
			return func(item);
		 }):
	返回:新数组
*/
// let res = arr.filter(function (value) {
//     return value === 2
// })
// console.log(arr) // [1, 2, 3]
// console.log(res) // 2

// 5.some
// 遍历数组中是否有符合条件的值,返回结果是Boolean类型的
// let res = arr.some(function (value) {
//     return value === 2
// })
// console.log(arr) // [1, 2, 3]
// console.log(res) // true

// 6.ever:每一个都符合,返回true
// let res = arr.every(function (value) {
//     return value === 2
// })
// console.log(arr) // [1, 2, 3]
// console.log(res) // false
// every 的代码块中不能使用 break、continue

// 7.reduce
/*
	reduce() 方法接收一个函数作为累加器
	语法:array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
	参数:total			上一次回调函数的返回值
		 currentValue	  当前元素值
		 currentIndex	  当前索引
		 arr			  调用 reduce 的数组
		 initialValue	  初始值
*/
// 1)求数组每项的和
// let sum = arr.reduce(function (prev, cur, index, arr) {
//     return prev + cur
// }, 0) //第二个参数:初始值
// console.log(sum) //6

// 2)求数组中最大值
// let max = arr.reduce(function (prev, cur) {
//     return Math.max(prev, cur)
// })
// console.log(max)

// 3)去重
// arr1 = [1, 2, 2, 3, 3]
// let res = arr1.reduce(function (prev, cur) {
//     // prev.indexOf(cur):上一个数组中是否包含当前值
//     // prev.push(cur):如果不包含就放入当前元素
//     prev.indexOf(cur) === -1 && prev.push(cur)
//     return prev
// }, []) // 初始值为一个新的数组
// console.log(res) //1 2 3

// 8.for in (不使用)
// 如果给原型定义了函数,遍历数组时函数也会输出
// Array.prototype.foo = function () {
//     console.log('foo')
// }
// for (let index in arr) {
//     console.log(index, arr[index])
// }

// es6
// 9.find:返回第一个通过测试的值
// arr1 = [1, 2, 2, 3, 3]
// let res = arr1.find(function (value) {
//     return value === 2
// })
// console.log('res', res) // 2
// console.log('arr', arr1) // [1, 2, 2, 3, 3]

// 10.findIndex:返回第一个通过测试的值的下标(从0开始)
// let res = arr1.findIndex(function (value) {
//     return value === 2
// })
// console.log('res', res)
// console.log('arr', arr)

// 11.for of(常用)
// 1)获取值 arr/arr.values()
// // for (let item of arr.values()) 效果一样
// for (let item of arr) {
//     console.log(item) // 1 2 3
// }

//2)获取当前的下标:arr.keys()
// for (let item of arr.keys()) {
//     console.log(item) // 0 1 2
// }

//3)获取下标和值:arr.entries()  [index, item]
for (let [index, item] of arr.entries()) {
    console.log(index, item)
}

2-4 数组的扩展

  1. 类数组/伪数组:具有length属性;按照索引方式存储数据;不具有数组的方法,比如push() pop()
  2. Array.from():类数组/伪数组 => 数组
  3. Array.of()
  4. CopyWithin()
  5. fill()
  6. includes()
// 1. DOM
// let divs = document.getElementsByTagName('div');
// console.log(divs) //HTMLCollection
//
// let divs2 = document.getElementsByClassName('xxx');
// console.log(divs2) //HTMLCollection

// let divs3 = document.querySelectorAll('.xxx');
// console.log(divs3) //NodeList
// console.log(divs3 instanceof Array) //false

// 2.类数组、伪数组 =>数组
// 1)slice es5 : Array.prototype.slice.call() 把调用方法的参数取出来
// let arr = Array.prototype.slice.call(divs3);
// console.log(arr) // []
// arr.push(123)
// console.log(arr) // [123]

// function foo() {
//     console.log(arguments instanceof Array) //false 说明arguments也不是一个真正的数组
// }
// foo(1, 'imooc', true)

//2)es6 Array.from()
/*
	语法:Array.from(arrayLike[, mapFn[, thisArg]])
	参数: arrayLike	必选,想要转换成数组的伪数组对象或可迭代对象
		 mapFn		 可选,如果指定了该参数,新数组中的每个元素会执行该回调函数
		 thisArg	 可选,执行回调函数 mapFn 时 this 对象
*/
// let arrayLike = {
//     0: 'es6',
//     1: 'es7',
//     2: 'es8',
//     length: 3
// }
// let arr = Array.from(arrayLike)
// arr.push('es9')
// console.log(arr) //['es6', 'es7', 'es8', 'es9']

// 3.构造数组
// 1)通过new Array()构造数组时,参数数量不同,表现得结果不同
// 如果只传一个参数,表示的是数组的长度;如果传多个参数,表述的是数组的内容
// let arr = new Array(1, 2)
// let arr2 = new Array(3)
// console.log(arr, arr2)

// 2)Array.of()构造数组,参数都是数组的值
// let arr = Array.of(3)
// console.log(arr)
// Array.of()可以将不同类型的数据拼装成一个数组
// let arr = Array.of(1, true, 'imooc', [1, 2, 3], {name: 'mys'})
// console.log(arr) //[1, true, 'imooc', Array(3), {…}]

// 4. copyWithin() 应用:替换元素
/*
	从数组的 指定位置 拷贝元素到数组的另一个指定位置
	语法:copyWithin(target, start = 0, end = this.length)
	参数:target	复制的位置
		 start	  复制的起始位置
		 end	  复制的结束位置(不包含)(默认为 *array*.length) 如果为负值,表示倒数
*/
// let arr = [1, 2, 3, 4, 5]
// console.log(arr.copyWithin(1, 3)) // [1, 4, 5, 4, 5]

// 5.fill 
/*
	用一个固定值填充到一个数组中
	语法:arr.fill(value, start, end)
	参数:value	用来填充数组元素的
		 start	  起始索引,默认值为0
		 end	  终止索引,默认值为 this.length
*/
// let arr = new Array(3).fill(7) //填充一个长度为3的全为7的数组
// console.log(arr) // [7, 7, 7]

// let arr = [1, 2, 3, 4, 5]
// arr.fill('imooc', 1, 3)
// console.log(arr) // [1, 'imooc', 'imooc', 4, 5]

//6.includes:检查数组中是否包含目标元素,包含返回true,否则返回false
let arr = [1, 2, 3, NaN]
//indexOf:检查数组中是否包含目标元素,包含返回1,否则返回-1;不能检查NaN
console.log(arr.indexOf(NaN)) //-1
console.log(arr.includes(NaN)) //true

2-5 函数的参数

  1. 参数的默认值
  2. 与解构赋值结合
  3. length属性
  4. 作用域
  5. 函数的name属性
//1.函数默认参数
// function foo(x, y = 'world') {
//     console.log(x, y)
// }
// foo('hello', 0)

// function foo(x = 5) {
//     //都报错,因为函数参数是默认声明的,相当于在函数内部加一条语句:let x = 5
//     // let x = 1
//     // let const = 1
// }
// 注意:
// 1.函数参数默认声明
// 2.参数不能重复
// 3.参数默认值一定要放在最后

// 2.与解构赋值结合
// function foo({x, y = 5}) {
//     console.log(x, y) // 1 5
// }
// foo({
//     x: 1
// })
// 解构赋值两边的结构需要匹配

// 应用:
// function ajax(url, {
//     body = '',
//     method =  'GET',
//     headers = {}
// } = {}) { //默认参数为{}空对象
//     console.log(method)
// }
// //解构赋值匹配
// ajax('http://www.imooc.com', {
//     method: 'POST'
// })

// 3. length属性
// function foo(x = 1, y) {
//     console.log(x, y)
// }
// console.log(foo.length) //foo.length:返回函数 没有指定默认值 的参数个数

// 4.作用域 *****
// 题目1:
// let x = 1
// function foo(x, y = x) {
//     console.log(y) //2
// }
// foo(2)
//当函数执行时,传入一个参数2,赋值给x,在函数参数作用域中,再将x的值赋给y,此时x的值为2,所以y=2

// 题目2:
// let x = 1
// function foo(y = x) {
//     let x = 2
//     console.log(y) //1
// }
// foo()
//函数执行时,参数y=x形成一个单独的作用域,当前作用域没有x,沿着作用域链网上找,此时找到的x是全局变量,所以y=x=1

// 题目3:
// function foo(y = x) {
//     let x = 2
//     console.log(y) //报错,x未定义
// }
// foo()

// 5.函数的name属性
// function foo() {}
// console.log(foo.name) //foo
// console.log((new Function).name) //anonymous

function foo(x, y) {
    //this指向bind参数对象
    console.log(this, x, y)  //{name: 'mys'} 1 2
}
foo.bind({name: 'mys'})(1, 2)
console.log(foo.bind({}).name) // bound foo(函数名)
console.log((function () {}).bind({}).name) // bound (没有函数名就不输出)
// bind()方法主要就是将函数绑定到某个对象
// bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值

2-6 扩展运算符与rest参数

  • ...
  1. 扩展运算符:把数组、类数组展开成用隔开的值
  2. rest参数:把用隔开的值组合成一个数组
// 1.扩展运算符
// function foo(a, b, c) {
//     console.log(a, b, c)
// }
// let arr = [1, 2, 3]
// foo(...arr)
// console.log(...arr) //1 2 3
// ...将数组或类数组展开成用,隔开的值

// 合并数组
// let arr1 = [1, 2, 3]
// let arr2 = [4, 5, 6]
// //es5
// // Array.prototype.push.apply(arr1, arr2)
// // console.log(arr1)
// //es6
// arr1.push(...arr2)
// console.log(arr1)

// let str = 'imooc'
// var arr = [...str]
// console.log(arr) // ['i', 'm', 'o', 'o', 'c']

// 2.rest参数
//求和
// function foo(x, y, z) {
//     // console.log(arguments) // arguments:不是真正的数组
//     let sum = 0
//     //es5
//     // Array.prototype.forEach.call(arguments, function (item) {
//     //     sum += item
//     // })
//     //es6
//     Array.from(arguments).forEach(function (item) {
//         sum += item
//     })
//     return sum
// }
// console.log(foo(1, 2)) // 3
// console.log(foo(1, 2, 3)) // 6

// 1)...args:将当前参数变成数组
// function foo(...args) {
//     // console.log(args) // [1, 2] 将以,隔开的数转换成数组
//     let sum = 0
//     args.forEach(function (item) {
//         sum += item
//     })
//     return sum
// }
// console.log(foo(1, 2))  // 3
// console.log(foo(1, 2, 3)) // 6

// 2)接收剩余参数
// function foo(x, ...args) {
//     console.log(x) // 1
//     console.log(args) // [2, 3]
// }
// foo(1, 2, 3)

// 3)数组解构
// let [x, ...y] = [1, 2, 3]
// console.log(x) // 1
// console.log(y) // [2, 3]

//总:
// 1)...放在=的左边或者形参上,就是rest参数 (合并成数组)
// 2)...放在=的右边或者实参上,就是扩展运算符 (展开单个值)

2-7 箭头函数

  1. this指向定义时所在的对象,而不是调用时所在的对象

  2. 不可以当作构造函数

  3. 不可以使用arguments对象

箭头函数:左边参数,右边函数体

箭头函数中本质上没有this,而是继承了其上层的this

// es5
// 函数声明:会有函数的预定义
// function sum(x, y) {
//     return x + y
// }
// console.log(sum(4, 5))

// 函数表达式
// let sum = function (x, y) {
//     return x +y
// }
// console.log(sum(4, 5))

// es6
// 箭头函数:左边参数,右边函数体
// let sum = (x, y) => {
//     return x + y
// }
// 简写:参数:只有一个参数,()可省略;函数体:只有一条语句,且有返回值:省略{}、return
// let sum = (x, y) => x + y
// console.log(sum(4, 5))

// let x = x => x
// //等价于下面:
// let x = function (x) {
//     return x
// }

let oBtn = document.getElementById('#btn');
// oBtn.addEventListener('click', function () {
//     setTimeout(function () {
//         console.log(this) // window 当前对象
//     }, 1000)
// })

// 三个方法可以改变this指向:call apply bind
// oBtn.addEventListener('click', function () {
//     console.log(this) // button
//     setTimeout(function () {
//         console.log(this) // button
//     }.bind(this), 1000) // bind的this指向第一个参数对象
// })

// 使用箭头函数
// 1. 在箭头函数中,this指向的是定义时的对象
// 箭头函数中本质上没有this,而是继承了其上层的this
// oBtn.addEventListener('click', function () {
//     setTimeout (() => {
//         console.log(this) // button 此处外层就是oBtn
//     })
// })

// 2. 箭头函数不可以当做构造函数
// 类
// function People(name, age) {
//     // this.name:属性名称 name:传递的参数
//     this.name = name
//     this.age = age
// }
// let People = (name, age) => {
//         this.name = name
//         this.age = age
//     }
// let p1 = new People('mys', 18)
// console.log(p1) // People {} 空的,在浏览器中运行会报错:People is not a constructor

//eval()函数:参数是字符串,作用是把这段字符串当作javascript语句

// 3. 箭头函数中不可以使用arguments对象
// let foo = () => {
//     console.log(arguments) // 浏览器中运行报错:arguments is not defined
// }
// foo(1, 2, 3)
//箭头函数可以使用...args去代替arguments参数来使用
let foo = (...args) => {
    console.log(args) // [1, 2, 3]
}
foo(1, 2, 3)

2-8 对象的扩展

  1. 属性简洁表示法
  2. 属性名表达式
  3. Object.is()
  4. 扩展运算符与Object.assign()
  5. in
  6. 对象的遍历方式
// let name = 'mys'
// let age = 18
// let s = 'school'
// let obj = {
//     // name: name,
//     // age: age
//     // 对象中key和value一样,可以简写
//     // 1.属性简写表达式
//     name,
//     age,
//     // 2.属性名表达式
//     [s]: 'imooc',
//     //对象里的方法不要用箭头函数,箭头函数的this是定义时的对象
//     // study: function () {
//     //     console.log(this.name + ' is learn')
//     // }
//     //3. 对象中方法的简写
//     study() {
//         console.log(this.name + ' is learn')
//     }
// }
// console.log(obj) // {name: 'mys', age: 18, school: 'imooc', study: ƒ}
// obj.study() // mys is learn

// 4.Object.is()判断两个值是否相等
// console.log(Object.is(2, '2')) // false
// console.log(Object.is(NaN, NaN)) // true
// console.log(Object.is(+0, -0)) // false

// let obj1 = { // new Object 在堆中开辟新空间
//     name: 'mys',
//     age: 18
// }
// let obj2 = {
//     name: 'mys',
//     age: 18
// }
// console.log(Object.is(obj1, obj2)) // false 对象比较的是地址
// let obj3 = obj1 //指向同一个地址
// console.log(Object.is(obj1, obj3)) // true

// 5.扩展运算符 & Object.assign()
// let x = {
//     a: 3,
//     b: 4
// }
// let y = {...x}
// console.log(y) // {a: 3, b: 4}
// let z = {}
// Object.assign(z, x) // 合并对象
// console.log(z) // {a: 3, b: 4}
// let x1 = {
//     a: 5,
//     b: 6
// }
// Object.assign(x, x1) // 用后面的去合并前面的,如果变量相同会覆盖
// console.log(x) //{a: 5, b: 6}

//6.in
// let x = {
//     a: 3,
//     b: 4
// }
// console.log('aa' in x) // false

// let arr = [1, 2, 3]
// console.log(3 in arr) // false
// console.log(2 in arr) // true  2是位置

// 7.对象的遍历
let obj = {
    name: 'mys',
    age: 18
}
// 1) for in
for (let key in obj) {
    console.log(key, obj[key]) // name mys  age 18
}
// 2) forEach Object.keys
Object.keys(obj).forEach(key => {
    console.log(key, obj[key]) // name mys  age 18
})
// 3) forEach Object.getOwnPropertyNames
Object.getOwnPropertyNames(obj).forEach(key => {
    console.log(key, obj[key])
})
//4) forEach Reflect.ownKeys
Reflect.ownKeys(obj).forEach(key => {
    console.log(key, obj[key])
})

2-9 深拷贝与浅拷贝

如何把一个对象复制给另一个对象

Object.assign()

//1. Object.assign()浅拷贝,不安全
// let target = {
//     a: {
//         b: {
//             c: 1
//         },
//         d: 2,
//         e: 5
//     }
// }
// let source = {
//     a: {
//         b: {
//             c: 1
//         },
//         d: 4
//     }
// }
// Object.assign(target, source)
// console.log(target)
//输出:
// a:
//     b: {c: 1}
// d: 4
// e不见了,Object.assign()拷贝有问题,对于基本数据类型的拷贝没问题,
// 但是对于引用类型的数据,拷贝的是地址,如果拷贝的内容结构复杂,有些属性可能会被丢掉

//2.深拷贝
//深拷贝:两变量的值变化互不影响
// let a = 5
// let b = a
// a = 6
// console.log(a, b) // 6 5
//浅拷贝:两个的值变化时会相互影响,此处两个对象指向的是同一个内存地址
// let obj1 = {
//     name: 'mys',
//     age: 18
// }
// let obj2 = obj1
// obj1.age = 20
// console.log(obj1) // {name: 'mys', age: 20}
// console.log(obj2) // {name: 'mys', age: 20}

// // json:'{"a": "hello", "b": "world"}' json的本质是字符串
// let obj = JSON.parse('{"a": "hello", "b": "world"}') // 将json格式的数据转换成对象
// console.log(obj) // {a: 'hello', b: 'world'}
// let str = JSON.stringify(obj) // 将对象转换成json格式
// console.log(str) // {"a":"hello","b":"world"}

//1) 利用JSON的方法实现深拷贝
// let obj1 = {
//     name: 'mys',
//     age: 18
// }
// let str = JSON.stringify(obj1)
// let obj2 = JSON.parse(str)
// obj1.age = 20 // obj1改变,obj2不会改变,obj1 obj2是两个独立的对象
// console.log(obj2) // {name: 'mys', age: 18}

//typeof只能判断基本类型数据,不能判断引用类型数据

//2) 可以通过toString()方法判断类型
// 检查类型
let checkType = data => {
    //slice(8, -1):直接获取的类型有[]{},需要去除
    //[object String]:从第8位开始截取,-1代表截取到倒数第一位(不包含),正好截取到需要的String
    return Object.prototype.toString.call(data).slice(8, -1)
}
//深拷贝
let deepClone = target => {
    let targetType = checkType(target) // 当前数据的类型
    let res // 返回值
    if (targetType === 'Object') {
        //当前数据是Object类型,返回空对象
        res = {}
    } else if (targetType === 'Array') {
        //当前数据是Array类型,返回空数组
        res = []
    } else {
        //当前数据既不是对象,也不是数组,就是基本数据类型
        return target
    }

    for(let i in target) {
        let value = target[i]
        let valueType = checkType(value)
        // 如果获取到的value也是对象或数组类型,需要递归进行拷贝
        if (valueType === 'Object' || valueType === 'Array') {
            res[i] = deepClone(value)
        } else {
            // 如果是基本数据类型,直接赋值即可
            res[i] = value
        }
    }
    return res
}

let obj1 = {
    name: 'mys',
    hobby: ['coding', 'eating']
}
let obj2 = deepClone(obj1)
obj2.hobby[0] = 'sleeping'
console.log(obj1) // hobby: ['coding', 'eating']
console.log(obj2) // hobby: ['sleeping', 'eating']

3 ECMAScript2015(ES6)之新特性

3-1 ES5中的类与继承

// 1.类
// 构造函数
function People(name, age) {
    // console.log(this) // this指向当前实例化的对象
    // 实例属性
    this.name = name
    this.age = age
    People.count++
}

// 静态属性
People.count = 0
//静态方法
People.getCount = function () {
    console.log(this) // this指向当前构造函数
    console.log('当前人数:' + People.count)
}

// 方法不定义在构造函数中,而是定义在类的原型中
// 实例方法
People.prototype.showName = function () {
    console.log('名字:' + this.name)
}
let p1 = new People('mys', 18)
console.log(p1)
p1.showName()
console.log(People.count)
console.log(People.getCount())

// 静态属性通过类来获取
// 静态属性定义在类中,实例属性定义在构造函数中


//静态方法
console.log(Math.max(4, 5))

console.log('------------------')

// 2.类的继承 (组合继承)
//父类
function Animal(name) {
    this.name = name
}
Animal.prototype.showName = function () {
    console.log('名字:' + this.name)
}

//子类
function Dog(name, color) {
    Animal.call(this, name) // 继承属性
    this.color = color
}
// 继承方法
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

let d1 = new Dog('wangcai', 'white')
console.log(d1)
d1.showName()

3-2 ES6中的类与继承

  1. class
  2. extends
  3. constructor
  4. static
  5. super
  6. get/set
// 类
class People {
    constructor(name, age) {
        this.name = name
        this.age = age
        this._sex = -1
    }
    // gex/set:设置属性,可以实现一些业务逻辑操作,可以对属性的读写进行一些拦截操作
    // 如果只是constructor就做不到
    // eg.0:female 1:male
    get sex() {
        if (this._sex === 1) {
            return 'male'
        } else if (this._sex === 0) {
            return 'female'
        } else {
            return 'error'
        }
    }
    set sex(val) {
        if (val === 0 || val === 1) {
            this._sex = val
        }
    }
    // 普通方法
    showName() {
        console.log(this.name)
    }
    // 静态方法
    static getCount() {
        return 5
    }
}
// 静态属性
People.count = 9

let p1 = new People('mys', 18)
console.log(p1)
p1.sex = 1
console.log(p1.sex)
console.log(People.getCount()) // 静态方法只能用类来调用
console.log(People.count)


// 继承
class Coder extends People {
    constructor(name, age, company) {
        super(name, age); // 调用父类构造函数
        this.company = company // 子类自身的属性
    }
    showCompany() {
        console.log('company:' + this.company)
    }
}
let c1 = new Coder('mys', 18, 'imooc')
console.log(c1)
c1.showName() // 子类调用父类的方法
c1.showCompany() // 子类自身的方法
c1.sex = 0 // 子类调用父类的set get设置的属性
console.log(c1.sex)
console.log(Coder.getCount()) // 子类也可以调用父类的静态方法

3-3 新的原始数据类型Symbol

  1. 一种新的原始数据结构
  2. 声明方式
  3. 应用场景
// Symbol 独一无二
// 1. 声明方式
// 1) 直接声明
// let s1 = Symbol()
// let s2 = Symbol()
// console.log(s1) // Symbol()
// console.log(s1 === s2) // false

// 2) 可以传递一个字符串作为参数,用来描述
// let s1 = Symbol('foo')
// console.log(s1) // Symbol(foo)

// 3) 如果传递一个对象,会调用对象的toString()方法,将其转成字符串,再生成Symbol
// const obj = {
//     name: 'mys',
//     toString() {
//         return this.name
//     }
// }
// let s = Symbol(obj)
// console.log(s) // Symbol(mys)

// 1.description
// let s = Symbol('foo')
// console.log(s.description) // foo

// 2.for
// 通过Symbol.for()声明的是全局的
// let s1 = Symbol.for('foo')
// let s2 = Symbol.for('foo')
// console.log(s1 === s2) // true
//
// function foo() {
//     return Symbol.for('foo')
// }
// const x = foo()
// const y = Symbol.for('foo')
// console.log(x === y) // true

// 3.kekFor
// 返回一个已经登记的Symbol的key
// const s1 = Symbol('foo')
// console.log(Symbol.keyFor(s1)) // undefined
// const s2 = Symbol.for('foo')
// console.log(Symbol.keyFor(s2)) // foo

// const grade = {
//     张三: {address: 'xxx', tel: '111'},
//     李四: {address: 'yyy', tel: '222'},
//     李四: {address: 'zzz', tel: '333'}
// }
// console.log(grade)
// // 张三: {address: 'xxx', tel: '111'}
// // 李四: {address: 'zzz', tel: '333'} 会覆盖上一个李四

// 对象的key也可以是一个变量
// const stu1 = '李四'
// const stu2 = '李四'
// const grade = {
//     [stu1]: {address: 'yyy', tel: '222'},
//     [stu2]: {address: 'zzz', tel: '333'}
// }
// console.log(grade) // 李四: {address: 'zzz', tel: '333'} 还是被覆盖

// const stu1 = Symbol('李四')
// const stu2 = Symbol('李四')
// const grade = {
//     [stu1]: {address: 'yyy', tel: '222'},
//     [stu2]: {address: 'zzz', tel: '333'}
// }
// console.log(grade)
// // Symbol(李四): {address: 'yyy', tel: '222'}
// // Symbol(李四): {address: 'zzz', tel: '333'}
// 利用Symbol声明不会被覆盖
// 利用Symbol声明变量,作为对象的key,保证key不重复

// const sym = Symbol('imooc')
// class User {
//     constructor(name) {
//         this.name = name
//         this[sym] = ' imooc.com'
//     }
//     getName() {
//         return this.name + this[sym]
//     }
// }
// const user = new User('mys')
// console.log(user.getName()) // mys imooc.com
//
// for (let key in user) {
//     console.log(key) // name 无法遍历Symbol声明的变量,即可以用Symbol声明来隐藏变量
// }

// for (let key in Object.keys(user)) {
//     console.log(key) // 0
// }

// for (let key of Object.getOwnPropertySymbols(user)) {
//     console.log(key) // Symbol(imooc) 只能获取Symbol
// }
//
// for (let key of Reflect.ownKeys(user)) {
//     console.log(key) // name  Symbol(imooc) 能够获取普通属性和Symbol属性
// }

// function getArea(shape) {
//     let area = 0
//      switch (shape) {
//          case 'Triangle':
//              area = 1
//              break
//          case 'Circle':
//              area = 2
//              break
//      }
//      return area
// }
// console.log(getArea('Triangle')) // Triangle耦合,魔术字符串

//应用场景:消除魔术字符串
const shapeType = {
    triangle: Symbol(),
    circle: Symbol
}
function getArea(shape) {
    let area = 0
    switch (shape) {
        case shapeType.triangle:
            area = 1
            break
        case shape.circle:
            area = 2
            break
    }
    return area
}
console.log(getArea(shapeType.triangle))

3-4 新的数据结构Set

  1. 一种新的数据结构
  2. 常用方法
  3. 遍历
  4. 应用场景
  5. WeakSet
// Set里面的值不重复
// 1. Set常用方法
// let s = new Set([1, 2, 2, 3])
// console.log(s) // {1, 2, 3}
// s.add('imooc').add('es') // 链式添加
// console.log(s) // {1, 2, 3, 'imooc', 'es'}
// s.delete(2) // 删除
// s.has('imooc') // 判断是否含有某个值 true
// s.size // 长度
// // s.clear() // 清空
//
// 2.遍历
// 1) forEach
// s.forEach(item => console.log(item))
//
// //Set中的key和value值相同, keys\values\entries结果相同
// 2) for of  s.keys()
// for (let item of s.keys()) {
//     console.log(item)
// }
// 3)for of  s.values()
// for (let item of s.values()) {
//     console.log(item)
// }
// 4) for of  s.entries()
// for (let item of s.entries()) {
//     console.log(item)
// }

// 3.应用场景
// 1)数组去重
// let arr = [1, 2, 3, 3, 2, 1]
// let s = new Set(arr)
// console.log(s) // {1, 2, 3}

// 2)合并去重
// let arr1 = [1, 2, 3, 4]
// let arr2 = [1, 2, 3, 5, 6]
// let s = new Set([...arr1, ...arr2])
// console.log(s)
// 0: 1
// 1: 2
// 2: 3
// 3: 4
// 4: 5
// 5: 6
// 3)Set转为数组Array
// console.log([...s]) // [1, 2, 3, 4, 5, 6]
// console.log(Array.from(s)) // [1, 2, 3, 4, 5, 6]

// 4)交集
// let s1 = new Set(arr1)
// let s2 = new Set(arr2)
// let res = new Set(arr1.filter(item => s2.has(item)))
// console.log(res) // {1, 2, 3}

// 5)差集
// let arr3 = new Set(arr1.filter(item => ! s2.has(item)))
// let arr4 = new Set(arr2.filter(item => ! s1.has(item)))
// console.log([...arr3, ...arr4]) // [4, 5, 6]

// 4.WeakSet
// 添加和删除必须是同一个对象
let ws = new WeakSet()
// 添加
ws.add({
    name: 'mys'
})
const obj = {
    age: 18
}
ws.add(obj)
// 删除  不成功,因为此处删除的是另一个对象,虽然与上面添加的对象长的一样,但这两个不是同一个对象
ws.delete({
    name: 'mys'
})
ws.delete(obj) // 删除成功
console.log(ws)

// WeakSet不能遍历

// 垃圾回收机制 GC
// WeakSet中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用
// 如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中
// 应用:创建临时对象;利用对象绑定相关信息

// WeakSet与Set区别
// 1. WeakSet只能存储对象,Set各数据类型都可以
// 2.WeakSet不可以循环遍历,Set可以
// 3.WeakSet是弱引用,不会被垃圾回收机制考虑

3-5 新的数据结构Map

  1. 一种新的数据结构
  2. 常用方法
  3. 遍历
  4. 应用场景
  5. WeakMap
// Map(key, value)
// 对于Array的key:字符串;Symbol
// 对于Map的key:各类型都可以作为key

// 1.常用方法
// let m = new Map()
// // 对象可以作为key
// let obj = {
//     name: 'imooc'
// }
// m.set(obj) // 设置
// console.log(m)
// console.log(m.get(obj)) // 获取
// // m.delete(obj) // 删除
// console.log(m.has(obj)) // true

// let map = new Map([
//     ['name', 'mys'],
//     ['age', 18]
// ])
// console.log(map) // {'name' => 'mys', 'age' => 18}
// console.log(map.size) // 2
// console.log(map.get('age')) // 18
// console.log(map.has('name')) // true
// map.set('name', 'zhangsan')
// console.log(map) // {'name' => 'zhangsan', 'age' => 18}
// map.delete('name')
// console.log(map) // {'age' => 18}

// 2. 遍历
let map = new Map([
    ['name', 'mys'],
    ['age', 18]
])
//1)
// map.forEach((value, key) => console.log(key, value)) // name mys   age 18
// 2)
// for (let [key, value] of map) {
//     console.log(key, value) // // name mys   age 18
// }
// 3)
// for (let key of map.keys()) {
//     console.log(key) // name age
// }
// 4)
// for (let value of map.values()) {
//     console.log(value) // mys 18
// }
// 5)
// for (let [key, value] of map.entries()) {
//     console.log(key, value) // // name mys   age 18
// }

// map object 应用场景一样
// map比object更加灵活,key也各种各样
// 频繁增删map性能更好

// 3. WeakMap
// let wm = new WeakMap()
// wm.set([1], 2)
// wm.set({
//     name: 'mys'
// }, 'es')
// console.log(wm)
// WeakMap不支持clear,也不支持遍历,没有size方法
// WeakMap也是弱引用,有助于防止内存泄漏,当没有对象引用时,自动消失

let wm = new WeakMap()
let elem = document.getElementsByTagName('h1')
wm.set(elem, 'info')
console.log(wm.get(elem))

3-6 字符串的扩展

在这里插入图片描述

模板字符串重要

// 1.字符的unicode表示
// es6 \uxxxx 码点 0000~ffff
// \u20BB7 -> \u20BB+7  ===> 超出码点范围用{}表示:\u{20BB7}

// 表示字符
// console.log('\z' === 'z') // true  \z是非制表符,反斜杠\什么也不表示
// 浏览器中运行:console.log('\172' === 'z')  //true

/**
 * 6中表示字符的方式
 * (0)z
 * (1)\z
 * (2)\172
 * (3)\x7A
 * (4)\u007A
 * (5)\u{7A}
 */

// 2. 字符串的遍历器接口
// for (let item of 'imooc') {
//     console.log(item)
// }

// 3.模板字符串 *****
// ``
// 1)多行字符串可以直接换行
// const str = `
// <ul>
//     <li></li>
//     <li></li>
//     <li></li>
// </ul>
// `
// console.log(str)
// // <ul>
// //     <li></li>
// //     <li></li>
// //     <li></li>
// // </ul>
//
// // 2)使用${表达式}直接运算
// const a = 2, b = 3
// const str1 = `a+b=${a + b}`
// console.log(str1) // a+b=5
//
// // 3)嵌套模板
// // 需求: icon icon-big
// const isLargeScreen = () => {
//     return true
// }
// // es5
// let class1 = 'icon'
// class1 += isLargeScreen() ? ' icon-big' : ' icon-small'
// console.log(class1)
// // es6
// const class2 = `icon icon-${isLargeScreen() ? 'big' : 'small'}`
// console.log(class2)
//
// // 3) 带标签的模板字符串
// const foo = (a, b, c, d) => {
//     console.log(a) // ['姓名:', ',年龄:', ',', '', raw: Array(4)]
//     console.log(b) // mys
//     console.log(c) // 18
//     console.log(d) // 123
// }
// const name = 'mys'
// const age = 18
// foo`姓名:${name},年龄:${age},${123}`

// 4.方法
// 1)静态方法 fromCodePoint
console.log(String.fromCharCode(0x20BB7)) // ஷ es5 不能识别码点超过范围:0000~ffff
console.log(String.fromCodePoint(0x20BB7)) // 𠮷 es6 范围外也能识别
// 2) indexOf includes startsWith endsWith repeat
const str = 'imooc'
console.log(str.indexOf('mo')) // 1
console.log(str.includes('mo')) // true
console.log(str.startsWith('im')) // true
console.log(str.endsWith('oc')) // true
const newStr = str.repeat(4)
console.log(newStr) // imoocimoocimoocimooc

3-7 正则表达式的扩展

  1. es5中修饰符:i:忽略大小写 m:多行匹配 g:全局匹配
  2. y修饰符
  3. u修饰符 eg:\uD842\uDFB7
// es5 i:忽略大小写 m:多行匹配 g:全局匹配
//es6
// y修饰符 粘连(sticky)修饰符
// const str = 'aaa_aa_a'
// const reg1 = /a+/g
// const reg2 = /a+/y
// // g 每次匹配剩余的
// console.log(reg1.exec(str)) // ['aaa', index: 0, input: 'aaa_aa_a', groups: undefined]
// console.log(reg1.exec(str)) // ['aa', index: 4, input: 'aaa_aa_a', groups: undefined]
// console.log(reg1.exec(str)) // ['a', index: 7, input: 'aaa_aa_a', groups: undefined]
// // y 每次匹配会在剩余的第一个进行匹配
// console.log(reg2.exec(str)) // ['aaa', index: 0, input: 'aaa_aa_a', groups: undefined]
// console.log(reg2.exec(str)) // null 剩余:_aa_a 第一个为_,匹配不上
// // 上次没匹配上,现在有从头开始匹配
// console.log(reg2.exec(str)) // ['aaa', index: 0, input: 'aaa_aa_a', groups: undefined]

// u修饰符 unicode  \u以unicode模式匹配
const str = '\uD842\uDFB7' // 表示一个字符
console.log(/^\uD842/.test(str)) // es5 true 错误的
console.log(/^\uD842/u.test(str)) // es6 false

// . 除换行意外的任意单个字符
console.log(/^.$/.test(str)) // false
console.log(/^.$/u.test(str)) // true

console.log(/\u{61}/.test('a')) // false
console.log(/\u{61}/u.test('a')) // true

console.log(/𠮷{2}/.test('𠮷𠮷')) // false
console.log(/𠮷{2}/u.test('𠮷𠮷')) // true

3-8 数值的扩展

在这里插入图片描述

// es5
// // 十进制 => 二进制
// const a = 5
// console.log(a.toString(2)) // 101
// // 二进制 => 十进制
// const b = 101
// console.log(parseInt(b, 2)) // 5

/**
 * Number方法
 * Number.parseInt()
 * Number.parseFloat()
 * Number.isFinite()
 * Number.isNaN()
 * Number.isInteger()
 * Number.MAX_SAFE_INTEGER
 * Number.isSafeInteger()
 */

// es6 0B二进制 0O八进制
// const a = 0B0101
// console.log(a) // 5
// const b = 0O77
// console.log(b) // 63

// Number.isFinite() 判断当前数是否是有限的(只判断Number类型,其他都为false)
// console.log(Number.isFinite(5)) // true
// console.log(Number.isFinite()) // false
// console.log(Number.isFinite('imooc')) // false
// console.log(Number.isFinite(true)) // false

// Number.isNaN()
// console.log(Number.isNaN(NaN)) // true
// console.log(Number.isNaN(15)) // false

// console.log(Number.isInteger(5.5)) // false

//精度缺失问题
// 数字在计算机中是以二进制存储的,IEEE 754 双精度标准存储
// console.log(0.1000000000000001) // 0.1000000000000001
// console.log(0.10000000000000001) // 0.1
// console.log(0.10000000000000001 === 0.1) // true 精度缺失

// 整数范围 (-2^53, 2^53)
// const max = Math.pow(2, 53)
// console.log(Number.MAX_SAFE_INTEGER === max - 1) // true
// console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER)) // true
// console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER +1)) // false

/**
 * Math新增方法
 * Math.trunc()
 * Math.sign()
 * Math.cbrt()
 *
 */
// console.log(Math.trunc(5.5)) // 5
// console.log(Math.trunc(- 5.5)) // -5
// console.log(Math.trunc(true)) // 1
// console.log(Math.trunc(false)) // 0
// console.log(Math.trunc(NaN)) // NaN
// console.log(Math.trunc(undefined)) // NaN
// console.log(Math.trunc()) // NaN
//
// console.log(Number.parseInt(5.5)) // 5
// console.log(Number.parseInt(- 5.5)) // -5
// console.log(Number.parseInt(true)) // NaN

// console.log(Math.sign(5)) // 1
// console.log(Math.sign(- 5)) // -1
// console.log(Math.sign(0)) // 0
// console.log(Math.sign(true)) // 1
// console.log(Math.sign(false)) // 0
// console.log(Math.sign(NaN)) // NaN

console.log(Math.cbrt(8)) // 2 立方根

3-9 Proxy

  1. 代理
  2. 常用拦截方法

在这里插入图片描述

// es5
// let obj = {}
// let newVal = ''
// Object.defineProperty(obj, 'name', {
//     get(){
//         return newVal
//     },
//     set(val){
//         console.log('set')
//         newVal = val
//     }
// })
// obj.name = 'es'
// console.log(obj.name)

// es6 proxy
// let obj = {}
// let p = new Proxy(obj, {})
// p.name = 'imooc'
// console.log(obj.name)
// for (let key in obj) {
//     console.log(key)
// }


// 1.get
// 需求:通过下标访问数组,如果能访问到直接返回值,访问不到返回error
// let arr = [7, 8, 9]
// // Proxy参数:1.包装的对象 2.代理的相关配置
// arr = new Proxy(arr, {
//     get(target, prop) {
//         console.log(target, prop)
//         return prop in target ? target[prop] : 'error'
//     }
// })
// console.log(arr[1]) // 8
// console.log(arr[10]) // error

// let dict = {
//     'hello': '你好',
//     'world': '世界'
// }
// dict = new Proxy(dict, {
//     get(target, prop) {
//         return prop in target ? target[prop] : prop
//     }
// })
// console.log(dict['hello']) // 你好
// console.log(dict['imooc']) // imooc

// 2. set
// let arr = []
// arr = new Proxy(arr, {
//     set(target, prop, val) {
//         if (typeof val === 'number') {
//             target[prop] = val
//             return true
//         } else{
//             return false
//         }
//     }
// })
// arr.push(5)
// arr.push(6)
// console.log(arr[0], arr[1], arr.length)

// 3. has
// let range = {
//     start: 1,
//     end: 5
// }
// range = new Proxy(range, {
//     has(target, prop) {
//         return prop >= target.start && prop <= target.end
//     }
// })
// console.log(2 in range) // true
// console.log(9 in range) // false

// 4.ownKeys
// let obj = {
//     name: 'imooc',
//     [Symbol('es')]: 'es' // []表示变量
// }
// console.log(Object.getOwnPropertyNames(obj)) // ['name']
// console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(es)]
// console.log(Object.keys(obj)) // // ['name']
// for (let key in obj) {
//     console.log(key) // name
// }

// 保护属性,拦截_开头的属性
// let userinfo = {
//     username: 'mys',
//     age: 18,
//     _password: 'xxx'
// }
// userinfo = new Proxy(userinfo, {
//     ownKeys(target) {
//         return Object.keys(target).filter(key => ! key.startsWith('_'))
//     }
// })
// for (let key in userinfo) {
//     console.log(key) // username  age
// }
// console.log(Object.keys(userinfo)) // ['username', 'age']

// get set deleteProperty ownKeys应用
// let user = {
//     name: 'mys',
//     age: 18,
//     _password: 'xxx' // 私有属性
// }
// user = new Proxy(user, {
//     get(target, prop) {
//         if (prop.startsWith('_')) {
//             throw new Error('不可访问')
//         } else {
//             return target[prop]
//         }
//     },
//     set(target, prop, val) {
//         if (prop.startsWith('_')) {
//             throw new Error('不可访问')
//         } else {
//             target[prop] = val
//             return true
//         }
//     },
//     deleteProperty(target, prop) {
//         if (prop.startsWith('_')) {
//             throw new Error('不可访问')
//         } else {
//             delete target[prop]
//             return true
//         }
//     },
//     ownKeys(target) {
//         return Object.keys(target).filter(key => !key.startsWith('_'))
//     }
// })
// try {
//     delete user._password
// } catch (e) {
//     console.log(e.message) // 不可访问
// }
// for (let key in user) {
//     console.log(key)
// }


// 5.apply
// let sum = (...args) => {
//     let num = 0
//     args.forEach(item => {
//         num += item
//     })
//     return num
// }
// sum = new Proxy(sum, {
//     // 函数调用时会被拦截,执行apply方法
//     apply(target, ctx, args) {
//         return target(...args) * 2
//     }
// })
// console.log(sum(1, 2)) // 6
// console.log(sum.call(null, 1, 2, 3)) // 12
// console.log(sum.apply(null, [1, 2, 3])) // 12

// 6. construct  new
let User = class {
    constructor(name) {
        this.name = name
    }
}
User = new Proxy(User, {
    construct(target, args, newTarget) {
        console.log('construct')
        return new target(...args)
    }
})
console.log(new User('imooc')) // construct  {name: 'imooc'}

3-10 反射Reflect

在这里插入图片描述

// Object => Reflect
// es5中,很多方法会设置到Object下面
// 1. 将Object属于语言内部的方法放到Reflect上
// let obj = {}
// let newVal = ''
// Reflect.defineProperty(obj, 'name', {
//     get() {
//         return newVal
//     },
//     set(val) {
//         console.log('set')
//         newVal = val
//     }
// })
// obj.name = 'es'
// console.log(obj.name)

// 2. 修改Object某些方法的返回结果,使其更加合理
// // es5
// try {
//     Object.defineProperty()
// } catch (e) {
//
// }
// // es6
// if (Reflect.defineProperty()) { // 返回boolean
//
// }

// 3. 让Object操作变成函数行为
// console.log('assign' in Object) // true 命令式操作
// console.log(Reflect.has(Object, 'assign')) // true 函数式操作

// 4.Reflect对象的方法和Proxy对象的方法一一对应
// let user = {
//     name: 'mys',
//     age: 18,
//     _password: 'xxx' // 私有属性
// }
// user = new Proxy(user, {
//     get(target, prop) {
//         if (prop.startsWith('_')) {
//             throw new Error('不可访问')
//         } else {
//             // return target[prop]
//             return Reflect.get(target, prop)
//         }
//     },
//     set(target, prop, val) {
//         if (prop.startsWith('_')) {
//             throw new Error('不可访问')
//         } else {
//             // target[prop] = val
//             Reflect.set(target, prop, val)
//             return true
//         }
//     },
//     deleteProperty(target, prop) {
//         if (prop.startsWith('_')) {
//             throw new Error('不可访问')
//         } else {
//             // delete target[prop]
//             Reflect.deleteProperty(target, prop)
//             return true
//         }
//     },
//     ownKeys(target) {
//         // return Object.keys(target).filter(key => !key.startsWith('_'))
//         return Reflect.ownKeys(target).filter(key => !key.startsWith('_'))
//     }
// })
// try {
//     // console.log(user._password)
//     // user._password = 'vv'
//     delete user._password
// } catch (e) {
//     console.log(e.message) // 不可访问
// }
// for (let key in user) {
//     console.log(key)
// }


let sum = (...args) => {
    let num = 0
    args.forEach(item => {
        num += item
    })
    return num
}
sum = new Proxy(sum, {
    // 函数调用时会被拦截,执行apply方法
    apply(target, ctx, args) {
        // return target(...args) * 2
        return Reflect.apply(target, target, [...args])  * 2
    }
})
console.log(sum(1, 2)) // 6
console.log(sum.call(null, 1, 2, 3)) // 12
console.log(sum.apply(null, [1, 2, 3])) // 12

4 ECMAScript2015(ES6)之异步编程与模块化

4-1 异步操作必备知识

  1. JS是单线程的
  2. 同步任务与异步任务
  3. Ajax原理
  4. Callback Hell

为什么 JS 是单线程的?作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM 。这决定了它只能是单线程,否则会带来很复杂的同步问题。

在这里插入图片描述

JS是单线程的,如果有多个线程会排队等待。首先执行的是同步任务的主线程,当主线程执行完毕才会执行异步线程;异步线程如setTimeout(() => {}, 1000),如果此时主线程没有执行完毕,异步线程先进入Event Table,等待延时时间,此处是1000ms,之后会进入Event Queue继续等待,直到主线程执行完毕才开始执行。

// const a = 2
// const b = 3
// console.log(a + b) // 同步
// setTimeout(() => {
//     console.log(a + b) // 异步
// }, 1000)

// 前后端数据分离  前端<->后端  ajax

// 输出顺序?
console.log(1)
setTimeout(() => {
    console.log(2)
}, 0) // 注意:虽然写的是0,但是在底层最小时间是4ms
console.log(3)
// 1 3 2

// 异步任务
setTimeout(() => {
    task() // 表示一个任务
}, 1000)
sleep() // 表示一个很复杂的同步任务

4-2 Ajax原理与Callback Hell

Ajax(Asynchronous Javascript And XML)异步 JavaScript 和 XML,是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

// 1. Ajax原理
function ajax(url, callback) {
    // 1)创建对象
    var xmlhttp // 此处使用var是为了兼容老版本浏览器
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    } else { // 兼容早期浏览器
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2)发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3)服务器响应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            // console.log(obj)
            callback(obj)
        }
    }
}

// var url = 'http://jsonplaceholder.typicode.com/users'
// ajax(url, res => {
//     console.log(res)
// })

// 2.Callback Hell   回调地狱/回调深渊
// 请求1 -> 2 -> 3 顺序执行
// 1) 在static文件夹下创建三个json文件 a.json b.json c.json,内容如下:
// {               {               {
//     "a": "A"        "b": "B"        "c": "C"
// }               }               }
// 2) 调用ajax函数
ajax('static/a.json', res => {
    console.log(res)
    // b在a的回调函数中执行
    ajax('static/b.json', res => {
        console.log(res)
        ajax('static/c.json', res => {
            console.log(res)
        })
    })
})

4-3 异步编程解决方案Promise

Promise 就是为了解决“回调地狱”问题的,它可以将异步操作的处理变得很优雅。回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象promise可以支持多个并发的请求,获取并发请求中的数据这个promise可以解决异步的问题,本身不能说promise是异步的。

// 1.Promise精髓:状态管理
// resolve:异步操作执行成功时的回调函数,reject:失败
// let p = new Promise((resolve, reject) => {
//     setTimeout(() => {
//         console.log('imooc')
//         // // 使用场景
//         // if () {
//         //     resolve()
//         // } else {
//         //     reject()
//         // }
//
//         // resolve('成功')
//         reject('失败')
//     }, 1000)
// }).then(res => { // then的第一个参数必写,第二个参数可选
//     // 第一个参数:运行成功进入
//     console.log(res)
// }, res => {
//     // 第二个参数:运行失败进入
//     console.log(res)
// })

// 输出顺序?
// let p = new Promise((resolve, reject) => {
//     console.log(1)
//     resolve()
// })
// console.log(2)
// // 1 2
// // Promise会立即执行
// p.then(res => {
//     console.log(3)
// })
// // 1 2 3
// // Promise的状态是不可逆的

// 2.Promise的状态
// let p1 = new Promise((resolve, reject) => {
//     resolve(1)
// })
// let p2 = new Promise((resolve, reject) => {
//     setTimeout(() => {
//         resolve(2)
//     }, 1000)
// })
// let p3 = new Promise((resolve, reject) => {
//     setTimeout(() => {
//         reject(3)
//     }, 1000)
// })
// console.log(p1) // fulfilled
// console.log(p2) // pending
// console.log(p3) // pending
// setTimeout(() => {
//     console.log(p2) // fulfilled
// }, 2000)
// setTimeout(() => {
//     console.log(p3) // rejected
// }, 2000)
//
// p1.then(res => {
//     console.log(res) // 1
// })
// p2.then(res => {
//     console.log(res) // 2
// })
// p3.catch(err => { // catch和then的第二个参数作用相同,表示失败
//     console.log(err) // 3
// })

// 3. Promise状态不可逆
// let p = new Promise((resolve, reject) => {
//     resolve(1)
//     reject(2)
// })
// p.then(res => {
//     console.log(res)
// }, err => {
//     console.log(err)
// })
// // 只输出1,因为Promise调用resolve(1)后,状态由pending变为fulfilled后,
// // 即使再调用reject(2)状态也不会再改变了

// 4. Promise调用Ajax异步操作
function ajax(url, successCallback, failCallback) {
    // 1)创建对象
    var xmlhttp // 此处使用var是为了兼容老版本浏览器
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    } else { // 兼容早期浏览器
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2)发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3)服务器响应
    xmlhttp.onreadystatechange = function () {
        // 4:响应完成 200:成功
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            // console.log(obj)
            // 如果没有传递obj参数,直接 successCallback
            successCallback && successCallback(obj)
        } else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
            failCallback && failCallback(obj)
        }
    }
}

// 层层嵌套 => 复杂
// new Promise((resolve, reject) => {
//     ajax('static/a.json', res => {
//         console.log(res)
//         resolve()
//     })
// }).then(res => {
//     console.log('a成功')
//     // 此处需要return,表示把此处先创建的Promise返回出去,
//     // 使得下面输出b成功的then()是此处新建的Promise,而不是一个空的Promise
//     return new Promise((resolve, reject) => {
//         ajax('static/b.json', res => {
//             console.log(res)
//             resolve()
//         })
//     })
// }).then(res => {
//     console.log('b成功')
//     return new Promise((resolve, reject) => {
//         ajax('static/c.json', res => {
//             console.log(res)
//             resolve()
//         })
//     })
// }).then(res => {
//     console.log('c成功')
// })

// 优化
function getPromise(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
            resolve(res)
        }, err => {
            reject(err)
        })
    })
}

// 1) 三种都成功
// getPromise('static/a.json')
//     .then(res => {
//         console.log(res) // {a: 'A'}
//         return getPromise('static/b.json')
//     }).then(res => {
//     console.log(res) // {b: 'B'}
//     return getPromise('static/c.json')
// }).then(res => {
//     console.log(res) // {c: 'C'}
// })

// 2)a失败
// getPromise('static/aa.json')
//     .then(res => {
//         console.log(res) // Not Found
//         return getPromise('static/b.json')
//     }, err => {
//         console.log(err) // undefined
//         // 注意:a即使是失败了,也要return getPromise,因为这里创建了b的Promise,
//         // 否则后边b将输出undefined,因为如果没有return,b的Promise就是空
//         return getPromise('static/b.json')
//     }).then(res => {
//     console.log(res) // {b: 'B'}
//     return getPromise('static/c.json')
// }).then(res => {
//     console.log(res) // {c: 'C'}
// })

// 3) 统一管理失败状态
getPromise('static/a.json')
    .then(res => {
        console.log(res) // {a: 'A'}
        return getPromise('static/bb.json')
    }).then(res => {
    console.log(res) // Not Found
    return getPromise('static/c.json')
}).then(res => {
    console.log(res) // 无
}).catch(err => { // 统一管理失败的状态,如果a成功,b不成功
    // a:正常, b:Not Found, c:无
    console.log(err) // undefined
})

4-4 Promise的静态方法

  1. Promise.resolve()
  2. Promise.reject()
  3. Promise.all()
  4. Promise.race()
// 1. Promise.resolve()
// let p1 = Promise.resolve('success')
// p1.then(res => {
//     console.log(res) // success
// })

// 2. Promise.reject()
// let p2 = Promise.reject('fail')
// p2.catch(err => {
//     console.log(err) // fail
// })

//  应用场景:利用Promise的静态方法,包装需要返回的字符串
// function foo(flag) {
//     if (flag) {
//         return new Promise(resolve => {
//             resolve('success')
//         })
//     } else {
//         // return 'fail'
//         return Promise.reject('fail') // 利用Promise的静态方法,包装需要返回的字符串
//     }
// }
// foo(false).then(res => {
//     console.log(res)
// }, err => {
//     console.log(err)
// })

// 3. Promise.all():全部成功才成功,有一个失败则都失败
// 需求:三个异步操作都执行完成之后再做其他事
// let p1 = new Promise((resolve, reject) => {
//     setTimeout(() => {
//         console.log(1) // 1
//         resolve('1成功')
//     }, 1000)
// })
// let p2 = new Promise((resolve, reject) => {
//     setTimeout(() => {
//         console.log(2) // 2
//         resolve('2成功')
//     }, 2000)
// })
// let p3 = new Promise((resolve, reject) => {
//     setTimeout(() => {
//         console.log(3) // 3
//         resolve('3成功')
//     }, 3000)
// })
// Promise.all([p1, p2, p3]).then(res => {
//     console.log(res) // ['1成功', '2成功', '3成功']
// })
// // 输出顺序: 1 2 3 ['1成功', '2成功', '3成功']
// // 实现了需求,当三个异步操作执行完成,再执行其他的操作(then中)

// let p4 = new Promise((resolve, reject) => {
//     setTimeout(() => {
//         console.log(4) // 4
//         reject('4失败')
//     }, 1000)
// })
// Promise.all([p1, p2, p3, p4]).then(res => {
//     console.log(res)
// }, err => {
//     console.log(err) // 4失败
// })

// 4.Promise.race() 只要有一个完成,则全部都完成
// Promise.race([p1, p2, p3]).then(res => {
//     console.log(res) // 1成功
// }, err => {
//     console.log(err)
// })

// 应用场:1:上传图片
// const imgArr = ['1.jpg', '2.jpg', '3.jpg']
// let promiseArr = []
// imgArr.forEach(item => {
//     promiseArr.push(new Promise((resolve, reject) => {
//         // 图片上传操作
//         // resolve()
//         reject()
//     }))
// })
// Promise.all(promiseArr).then(res => {
//     // 插入数据库操作
//     console.log('图片全部上传完成')
// }, err => {
//     console.log('图片上传失败')
// })

// 应用场景2:图片加载
function getImg() {
    return new Promise((resolve, reject) => {
        let img = new Image()
        img.onload = function () {
            resolve(img)
        }
        img.src = 'https://www.imooc.com/static/img/index/logo2020.png'
        // img.src = 'http://www.xxx.com'
    })
}
function timeout() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('图片请求超时')
        }, 2000)
    })
}
// race:因为图片要么加载成功,要么加载失败
Promise.race([getImg(), timeout()]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

4-5 异步编程解决方案Generator

// generator 生成器函数
// function* foo() {
//     for (let i = 0; i < 3; i ++) {
//         yield i
//     }
// }
//
// console.log(foo()) // Generator{_invoke: ƒ}
// let f = foo()
// console.log(f.next()) // {value: 0, done: false}  value:当前的值,done:执行的情况
// console.log(f.next()) // // {value: 1, done: false}
// console.log(f.next()) // // {value: 2, done: false}
// console.log(f.next()) // // {value: undefined, done: true} done:true说明函数执行完成
// 生成器函数不会立即执行,而是返回生成器的迭代器对象。每次执行直到yield后面的语句为止
// 注意:
// 1.不能作为构造函数使用
// 2.关键字yield只能在生成器函数中使用,不能在其他地方使用

// function* gen(args) {
//     args.forEach(item => {
//         // yield item + 1 // 报错
//     })
// }

// next generator是如何执行的?
// function* gen(x) {
//     let y = 2 * (yield(x + 1))
//     let z = yield(y / 3)
//     return x + y + z
// }
// next的参数,上一个yield的返回值

// 1)next不传递参数的情况
// let g = gen(5)
// console.log(g.next()) // 6
// // 此处没有返回值,则yield(x + 1)为undefined,所以y=2*undefined => NaN
// console.log(g.next())  // NaN
// console.log(g.next()) // NaN
// 2)给next传递参数
// let g = gen(5)
// console.log(g.next()) // 6
// console.log(g.next(12))  // 此时y=24,返回8
// console.log(g.next(13)) // 此时z=13, x=5,y=24,返回42

// 应用:每次输出7的倍数
// function* count(x = 1) {
//     while (true) {
//         if (x % 7 === 0) {
//             yield  x // 利用yield使得当前循环不是死循环
//         }
//         x ++
//     }
// }
// let n = count()
// console.log(n.next().value) // 7
// console.log(n.next().value) // 14
// console.log(n.next().value) // 21
// console.log(n.next().value) // 28
// console.log(n.next().value) // 35

// 应用:generator管理异步状态
// 案例:利用Ajax请求a/b/c.json
function ajax(url, callback) {
    // 1)创建对象
    var xmlhttp // 此处使用var是为了兼容老版本浏览器
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    } else { // 兼容早期浏览器
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2)发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3)服务器响应
    xmlhttp.onreadystatechange = function () {
        // 4:响应完成 200:成功
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            // console.log(obj)
            callback(obj)
        }
    }
}

function request(url) {
    ajax(url, res => {
        getData.next(res)
    })
}

function* gen() {
    let res1 = yield request('static/a.json')
    console.log(res1)
    let res2 = yield request('static/b.json')
    console.log(res2)
    let res3 = yield request('static/c.json')
    console.log(res3)
}

let getData = gen()
getData.next()

4-6 迭代器Iterator

  1. 是一种接口机制,为各种不同的数据结构统一访问的机制
  2. 主要供for of消费
  3. 对于不支持遍历的数据结构=>可bianli
// function makeIterator(arr) {
//     let nextIndex = 0
//     return {
//         next() {
//             return nextIndex < arr.length ? {
//                 value: arr[nextIndex ++],
//                 done: false
//             } : {
//                 value: undefined,
//                 done: true
//             }
//         }
//     }
// }
// let it = makeIterator(['a', 'b', 'c'])
// console.log(it.next()) // {value: 'a', done: false}
// console.log(it.next()) // {value: 'b', done: false}
// console.log(it.next()) // {value: 'c', done: false}
// console.log(it.next()) // {value: undefined, done: true}

// 可迭代: 当前对象中有Symbol.iterator
// 1)Array
// let arr = ['a', 'b', 'c']
// let it = arr[Symbol.iterator]()
// console.log(it.next())
// 2)Map
// let map = new Map()
// map.set('name', 'es')
// map.set('age', '6')
// // console.log(map)
// let it = map[Symbol.iterator]()
// console.log(it.next()) // {value: Array(2), done: false}
// console.log(it.next()) // {value: Array(2), done: false}
// console.log(it.next()) // {value: undefined, done: true}

// 原生具备Iterator
// Array Map Set String TypedArray 函数的arguments对象  NodeList对象

// 需求:将course变成可迭代的
let courses = {
    allCourse: {
        frontend: ['ES', '小程序', 'Vue', 'React'],
        backend: ['Java', 'Python', 'SpringBoot'],
        webapp: ['Android', 'IOS']
    }
}
// 可迭代协议:当前对象中是否含有Symbol.iterator属性
// 迭代器协议:return { next() { return {value, done}}}
// courses[Symbol.iterator] = function () {
//     let allCourse = this.allCourse
//     let keys = Reflect.ownKeys(allCourse) // 获取allCourse中的key 返回:['frontend', 'backend', 'webapp']
//     let values = [] // 保存通过key获取到的值
//     // 返回的结构固定
//     return {
//         next() {
//             if (!values.length) { // 每次只取一个值,当values为空的时候进入开始取值
//                 if (keys.length) { // 没取完
//                     values = allCourse[keys[0]]
//                     keys.shift() // 取完一个之后删除 shift()删除数组的第一个元素,并返回删除的结果
//                 }
//             }
//             return {
//                 done: !values.length, // 当取完是values长度为0 !0就是true
//                 value: values.shift()
//                 // 注意:此处需要先求done再求values,因为如果先求values会进行删除操作,此时获取到的done信息就不准确l
//             }
//         }
//     }
// }
//
// for (let c of courses) {
//     console.log(c)
// }

// generator实现
courses[Symbol.iterator] = function* () {
    let allCourse = this.allCourse
    let keys = Reflect.ownKeys(allCourse)
    let values = []
    while (1) {
        if (!values.length) {
            if (keys.length) {
                values = allCourse[keys[0]]
                keys.shift()
                yield values.shift()
            } else {
                return false
            }
        }else{
            yield values.shift()
        }
    }
}
for (let c of courses) {
    console.log(c)
}

4-7 模块化Module

  1. 问题:
    • 全局变量污染:变量都放在window对象上
    • 变量重名:重名后面的变量会覆盖前面的
    • 文件依赖顺序:多个文件之间相互依赖,需要保证一定的加载顺序
  2. 模块化:将每个js文件看作是一个模块,每个模块通过固定的方式引入,并且通过固定的方式向外暴露指定的内容。 按照js模块化的设想,一个个模块按照其依赖关系组合,最终插入到主程序中。
  3. 模块化规范
    • CommonJS:Node.js 可以在服务端实现模块同步加载, 但是局限于服务端,客户端如果同步加载依赖的话时间消耗非常大
    • AMD:require.js异步模块定义, 允许指定回调函数。采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载完成之后(前置依赖),这个回调函数才会运行。
    • CMD:sea.js阿里推出的,CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置、提前执行 CMD推崇依赖就近、延迟执行
    • ES6 使用 import 关键字引入模块,通过 exprot 关键字导出模块。但是由于ES6目前无法在浏览器中执行,所以,只能通过babel将不被支持的import编译为当前受到广泛支持的 require
  4. export / import / as / export default

module.js export

// 1. 使用
// export const a = 5
// export const b = 'imooc'
// export const sum = (x, y) => x + y
// const obj = {
//     name: 'es'
// }
// export {obj}

// 统一导出
// const a = 5
// const b = 'imooc'
// const sum = (x, y) => x + y
// const obj = {
//     name: 'es'
// }
//
// class People {
//     constructor(name) {
//         this.name = name
//     }
//     showName() {
//         console.log(this.name)
//     }
// }
//
// export {
//     a,
//     b,
//     sum,
//     obj,
//     People
// }

// 2. default 注意:每个模块中只能有一个default
// const a = 5 // 先定义在导出
// export default a

// 3. 结合
// const add = (x, y) => x + y
// export default add
// export const str = 'imooc'

// 4.将所有内容放入default对象
const a = 5
const b = 'imooc'
const sum = (x, y) => x + y
const obj = {
    name: 'es'
}
class People {
    constructor(name) {
        this.name = name
    }

    showName() {
        console.log(this.name)
    }
}
export default {
    a,
    b,
    sum,
    obj,
    People
}

import

// 1.用法
// 导入,注意导入模块和导出模块名称相同,多个可用,隔开
// import {
//     a as aa, // as起别名
//     b,
//     sum,
//     obj,
//     People
// } from './module'
// console.log(aa)
// console.log(b)
// console.log(sum(1, 2))
// console.log(obj)
// let p1 = new People('mys')
// p1.showName()

// 2.default
// import aa from './module'
// console.log(aa)

// 3.结合使用
// import add, {str} from './module'
// console.log(add(1, 2), str) // 3 imooc

// 4.将所有内容放入default对象
// 1)
// import mod from './module'
// console.log(mod.a) // 5
// console.log(mod.b) // imooc
//2)
import * as mod from './module'
console.log(mod)
console.log(mod.default.a) // 5
console.log(mod.default.b) // imooc

5 ECMAScript2016(ES7)之数组&数值拓展

5-1 数组扩展:Array.prototype.includes()

  1. Array.prototype.includes(searchElement, fromIndex) 实例方法
  2. includes & indexOf
// includes:检查数组里面是否包含某个值
// const arr = ['es5', 'es6', 'es7']
// /**
//  * 参数:
//  * 第一个参数:要搜索的值
//  * 第二个参数:搜索开始的索引 负数:从倒数第index开始往后找
//  */
// console.log(arr.includes('es7'))  // true
// console.log(arr.includes('es8')) // false
// console.log(arr.includes('es6', 2)) // false
// console.log(arr.includes('es6', - 2)) // true
//
// console.log(arr.indexOf('es7')) // 2
// console.log(arr.indexOf('es7') > -1) // true

// 1. indexOf和includes只能判断基本类型的值,不能判断引用类型的值
// const arr = ['es5', ['es6', 'es7'], 'es8']
// console.log(arr.includes(['es6', 'es7'])) // false
// console.log(arr.indexOf(['es6', 'es7'])) // -1

const arr = ['es5', 'es6', NaN, 'es7', 2]
// 2. indexOf不能检查NaN
console.log(arr.includes(NaN)) // true
console.log(arr.indexOf(NaN)) // -1
// 3. includes和indexOf内部判断是严格相等,即 ===
console.log(arr.includes('2')) // false
console.log(arr.indexOf('2')) // -1
// 4.使用
// NaN => includes
// 只关心值是都存在,不关心其存在的位置 => includes
// 判断值出现的索引下标 => indexOf

5-2 数值扩展:幂运算符

  1. 幂运算符:**
  2. 等同于Math.pow()
// 2^10 -> 1024
// 指数运算
function pow(x, y) {
    let res = 1
    for (let i = 0; i < y; i ++) {
        res *= x
    }
    return res
}
console.log(pow(2, 10)) // 1024
console.log(Math.pow(2, 10))

// es7
console.log(2 ** 10) // 1024

6 ECMAScript2017(ES8)之异步编程&对象拓展

6-1 异步编程解决方案Async Await

  1. es6 Promise / Generator
  2. es7 async await
// function foo() {
//     return 'imooc'
// }
// console.log(foo()) // imooc

// 1. async返回的是Promise对象
// async function foo() {
//     return 'imooc'
// }
// console.log(foo()) // Promise{<fulfilled>: 'imooc'}

// 2. async/wait
// 需求:异步操作1执行完成之后再输出2
// function timeout() {
//     return new Promise(resolve => {
//         setTimeout(() => {
//             // console.log(1)
//             resolve(1)
//         }, 2000)
//     })
// }
// async function foo() {
//     const res = await timeout() // resolve的参数就是timeout()的返回值,await需要等其语句执行完毕才执行后面的内容
//     console.log(res)
//     console.log(2)
// }
// foo() // 1 2

// function timeout() {
//     return new Promise((resolve, reject) => {
//         // resolve('success')
//         reject('fail')
//     }, 2000)
// }
// async function foo() {
//     return await timeout()
// }
// foo().then(res => {
//     console.log(res)
// }).catch(err => {
//     console.log(err)
// })

// 伪代码 应用
// async function request() {
//     const data = await axios.get('http://www.xxx.com')
//     console.log(
//         data
//     )
// }

//async wait实现ajax请求依次访问a.json b.json c.json
import ajax from './ajax'
function request(url) {
    return new Promise(resolve => {
        ajax(url, res => {
            resolve(res)
        })
    })
}
async function getData() {
    const res1 = await request('static/a.json')
    console.log(res1) // {a: 'A' }
    const res2 = await request('static/b.json')
    console.log(res2) // {b: 'B'}
    const res3 = await request('static/c.json')
    console.log(res3) // {c: 'C'}
}
getData()
// 异步操作:
// 回调 Promise Generator async(最常用)

6-2 对象扩展:Object.values(),Object.entries()

  1. Object.values()
  2. Object.entries()
const obj = {
    name: 'mys',
    age:'18'
}
// 以前得到key
// console.log(Object.keys(obj)) // ['name', 'age']
// const res = Object.keys(obj).map(key => obj[key])
// console.log(res) // ['mys', '18']

console.log(Object.values(obj)) // ['mys', '18']
console.log(Object.entries(obj))
// 输出:
// [Array(2), Array(2)]
// 0: (2) ['name', 'mys']
// 1: (2) ['age', '18']
for (let [key, value] of Object.entries(obj)) {
    console.log(`${key}:${value}`) // name mys    age 18
} 

6-3 对象属性描述:Object.getOwnPropertyDescriptors()

  1. Object.getOwnPropertyDescriptors()
    • value writable configurable enumerable
// const obj = {
//     name: 'mys',
//     age: 18
// }
// const desc = Object.getOwnPropertyDescriptors(obj)
// console.log(desc)
// 输出:
// age: {value: 18, writable: true, enumerable: true, configurable: true}
// name: {value: 'mys', writable: true, enumerable: true, configurable: true}
/**
 * value:当前对象属性的默认值
 * writable:是否可修改
 * enumerable:当前属性是否可以通过for in遍历
 * configurable:当前对象属性能否用delete进行删除
 */

const obj = {}
Reflect.defineProperty(obj, 'name', {
    value: 'mys',
    writable: true,
    enumerable: true,
    configurable: true

})
console.log(obj)
delete obj.name

6-4 字符串扩展:String.prototype.padStart(),String.prototype.padEnd()

  1. String.prototype.padStart()
  2. String.prototype.padEnd()
const str = 'imooc'
// padStart从开始填充:第一个参数:填充之后目标参数的长度;第二个参数:用什么填充(可选,默认空格填充)
console.log(str.padStart(8, 'x')) // xxximooc
// padEnd从结尾填充
console.log(str.padEnd(8, 'x')) // imoocxxx

// 应用场景1:输出当前日期 yyyy-mm-dd
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = now.getDate().toString().padStart(2, '0')
console.log(`${year}-${month}-${day}`) // 2022-04-21

// 应用场景2:手机号加密,只显示后四位
const tel = '13012345678'
const newTel = tel.slice(-4).padStart(11, '*')
console.log(newTel) // *******5678

// 应用场景3:时间戳  伪代码
// console.log(new Date().getTime()) // 13位 ms
// timestamp.padEnd(12, '0')

6-5 尾逗号Trailing commas

function foo(
    a,
    b,
    c, // ES8 允许函数的最后一个参数有尾逗号,方便修改参数
) {
    console.log(a, b, c)
}
foo(1, 2, 3)

7 ECMAScript2018(ES9)之异步迭代&对象操作升级

7-1 异步迭代:for await of

  1. for await of
  2. Symbol.asyncIterator
// 迭代需要遵循:可迭代协议;迭代器协议

// 以前的同步迭代
// const arr = ['es6', 'es7', 'es8']
// arr[Symbol.iterator] = function () {
//     let nextIndex = 0
//     return {
//         next() {
//             return nextIndex < arr.length ? {
//                 value: arr[nextIndex ++],
//                 done: false
//             } : {
//                 value: undefined,
//                 done: true
//             }
//         }
//     }
// }
// for (let item of arr) {
//     console.log(item)
// }

// 异步迭代
function getPromise(time) {
    return new Promise(((resolve, reject) => {
        setTimeout(() => {
            resolve({
                value: time,
                done: false
            })
        }, time)
    }))
}

const arr = [getPromise(1000), getPromise(2000), getPromise(3000)]
arr[Symbol.asyncIterator] = function () {
    let nextIndex = 0
    return {
        next() {
            return nextIndex < arr.length ? arr[nextIndex++] :
                Promise.resolve({
                    value: undefined,
                    done: true
                })
        }
    }
}
async function test() {
    for await (let item of arr) {
        console.log(item)
    }
}
test() // 1000  2000  3000

7-2 正则表达式扩展:dotAll,具名组匹配,后行断言

// dot 点 匹配任意单个字符,不能匹配多字节的Unicode和行终结符,eg:\n \r
// const reg = /./
// console.log(reg.test('5')) // true
// console.log(reg.test('\n')) // false
// console.log(reg.test('\u{2028}')) // false

// 1. dotAll
// const reg = /./s
// console.log(reg.test('5')) // true
// console.log(reg.test('\n')) // true
// console.log(reg.test('\u{2028}')) // true

// 修饰符:g全局  i:不区分大小写  m:跨行匹配  y:粘性  u:unicode s:让.能够匹配任意单个字符

// 2. 具名组匹配
// const date = /(\d{4})-(\d{2})-(\d{2})/.exec('2022-04-21')
// console.log(date[1]) // 2022
// console.log(date[2]) // 04
// console.log(date[3]) // 21
// console.log(date) // ['2022-04-21', '2022', '04', '21', index: 0, input: '2022-04-21', groups: undefined]
//
// const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
// console.log(reg.exec('2022-02-01')) // ['2022-02-01', '2022', '02', '01', index: 0, input: '2022-02-01', groups: {…}]
// //  groups: {year: '2022', month: '02', day: '01'}
// const groups = reg.exec('2022-02-01').groups
// // const year = groups.year
// // const month = groups.month
// // const day = groups.day
// const {year, month, day} = groups
// console.log(year, month, day) // 2022 02 01

// 3.后行断言 先行断言
// 先行断言:匹配后面跟着script的ecma
const str = 'ecmascript'
console.log(str.match(/ecma(?=script)/)) // ['ecma', index: 0, input: 'ecmascript', groups: undefined]
// 后行断言:匹配前面是ecma的script
console.log(str.match(/(?<=ecma)script/)) // ['script', index: 4, input: 'ecmascript', groups: undefined]

7-3 对象扩展:Rest & Spread

// const arr1 = [1, 2, 3]
// const arr2 = [4, 5, 6]
// const arr3 = [...arr1, ...arr2]
// console.log(arr3) // [1, 2, 3, 4, 5, 6]

// const obj1 = {
//     name: 'mys',
//     age: 18
// }
// const obj2 = {
//     school: 'imooc'
// }
// // 克隆对象
// const obj3 = {
//     ...obj1
// }
// obj1.age = 20
// console.log(obj3) // {name: 'mys', age: 18}  ...克隆对象是一个新的对象
//
// // 合并对象
// const obj4 = {...obj1, ...obj2}
// console.log(obj4) // {name: 'mys', age: 20, school: 'imooc'} 后面的会覆盖前面的

// 剩余属性
const obj1 = {
    name: 'mys',
    age: 18,
    school: 'imooc',
    course: 'es'
}
const {name, age, ...rest} = obj1 // ...rest表示剩余元素,要放在最后
console.log(name) // mys
console.log(age) // 18
console.log(rest) // {school: 'imooc', course: 'es'}

7-4 Promise扩展:Promise.prototype.finally()

new Promise(((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 1000)
})).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
}).finally(() => {
    console.log('finally') // 成功失败都很进入
})

// 应用场景1:用户加载数据时的等待框消失操作可以写在finally中,因为不管加载成功还是失败,都会去除等待框
// 应用场景2:关闭数据库的连接

7-5 字符串扩展:放松模板字符串文字限制

放松了模板字符串的转义序列的语法限制

// 带标签的模板字符串
// const foo = (a, b, c, d) => {
//     console.log(a) // ['这是', ',年龄是', '岁', raw: Array(3)]
//     console.log(b) // mys
//     console.log(c) // 18
//     console.log(d) // undefined
// }
// const name = 'mys'
// const age = 18
// foo`这是${name},年龄是${age}岁`

const foo = arg => {
    console.log(arg)
}
foo`\u{61} and \u{62}` // ['a and b', raw: Array(1)]
foo`\u{61} and \uunicode` // [undefined, raw: Array(1)]
// let str = `\u{61} and \uunicode` //报错 只能在标签中使用

8 ECMAScript2019(ES10)之效率再提升

8-1 对象扩展:Object.fromEntries()

// const obj = {
//     name: 'mys',
//     age: 18
// }
// const entries = Object.entries(obj) // 将对象转换成数组形式
// console.log(entries) // ['name', 'mys']  ['age', 18]
//
// // es10
// const fromEntries = Object.fromEntries(entries)
// console.log(fromEntries) // {name: 'mys', age: 18} 返回成对象的形式
// // Object.entries和Object.fromEntries作用相反

// 应用场景1:将map结构转成对象结构
// const map = new Map()
// map.set('name', 'mys')
// map.set('age', 18)
// console.log(map) // {'name' => 'mys', 'age' => 18}
// const fromEntries = Object.fromEntries(map)
// console.log(fromEntries) // {name: 'mys', age: 18}

// 应用场景2:数组方法多,可以先转成数组,调用一些方法实现了功能之后,再转回原来的类型
const course = {
    math: 90,
    english: 85,
    chinese: 80
}
// Object.entries(course):转成数组,调用数组的相关方法
const res = Object.entries(course).filter(([key, val]) => val > 80);
console.log(res) // ['math', 90]  ['english', 85]
// 数组转对象
console.log(Object.fromEntries(res)) // {math: 90, english: 85}

8-2 字符串扩展:String.prototype.trimStart() ,String.prototype.trimEnd()

const str = '   imooc   '
// 正则
// 去掉前面的空格
console.log(str.replace(/^\s+/g, '')) // 'imooc   '
// 去掉后面的空格
console.log(str.replace(/\s+$/g, '')) // '   imooc'

// es10
// 去掉前面的空格
console.log(str.trimStart())
console.log(str.trimLeft())
// 去掉后面的空格
console.log(str.trimEnd())
console.log(str.trimRight())
// 去掉前后所有空格
console.log(str.trim())

8-3 数组扩展:Array.prototype.flat(),Array.prototype.flatMap()

// const arr = [1, 2, 3, [4, 5, 6, [7, 8, 9, [10, 11, 12]]]]
// // 扁平化输出 arr.flat() 默认参数为1,数字表示深度
// console.log(arr.flat()) // [1, 2, 3, 4, 5, 6, Array(4)]
// console.log(arr.flat(3)) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
// // Infinity直接输出一维数组(但是不推荐)
// console.log(arr.flat(Infinity)) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

arr = [1, 2, 3, 4, 5]
// const res = arr.map(x => x + 1) // [2, 3, 4, 5, 6]
// const res = arr.map(x => [x + 1]).flat() // [2, 3, 4, 5, 6]
// flat和map的结合
const res = arr.flatMap(x => [x + 1]) // [2, 3, 4, 5, 6]
console.log(res)

8-4 修订Function.prototype.toString()

返回源代码中的实际文本片段

// toString()
function foo() {
    console.log('imooc')
}

console.log(foo.toString())
// 输出
// function foo() {
//     console.log('imooc');
// }

8-5 可选的Catch Binding

省略catch绑定的参数和括号

const validJSON = json => {
    try {
        JSON.parse(json)
        return true
    } catch{ // 参数e可以不写
        return false
    }
}
const json = '{"name": "mys", "age":"18"}'
console.log(validJSON(json)) // true

8-6 JSON扩展:JSON superset,JSON.stringify()增强能力

// JSON 超集
// \u2029段分隔符 \u2028行分隔符 不会报错,以前会报错
// eval('var str = "imooc";\u2029 function foo(){return str}')
// console.log(foo())

// 0xD800 ~ 0xDfff
console.log(JSON.stringify('\uD83D\uDE0E')) // emoji
console.log(JSON.stringify('\uD83D')) // "\ud83d" 不是一个字符原样输出

8-7 Symbol扩展:Symbol.prototype.description

// Symbol
const s = Symbol('imooc')
console.log(s) // Symbol(imooc)
s.description = 'es'
console.log(s.description) // imooc 只读

const s2 = Symbol()
console.log(s2.description) // undefined

9 ECMAScript2020(ES11)之新特性初探

9-1 全局模式捕获:String.prototype.matchAll()

// 需求:获取div中的内容
const str = `
    <html>
        <body>
            <div>第一个div</div>
            <p>这是p</p>
            <div>第二个div</div>
            <span>这是span</span>
        </body>
    </html>
`
// 1. exec g
// function selectDiv(regExp, str) {
//     let matches = []
//     while (true) {
//         // console.log(regExp.lastIndex)
//         const match = regExp.exec(str);
//         if (match === null) {
//             break
//         }
//         matches.push(match[1]) // 可以选择子结果
//     }
//     return matches
// }
// const regExp = /<div>(.*)<\/div>/g
// const res = selectDiv(regExp, str);
// console.log(res) // ['第一个div', '第二个div']

// 2. match 匹配所有包含的结果 (不满足需求)
// console.log(str.match(regExp)) // ['<div>第一个div</div>', '<div>第二个div</div>']

// 3. replace
// function selectDiv(regExp, str) {
//     let matches = []
//     str.replace(regExp, (all, first) => {
//         matches.push(first)
//     })
//     return matches
// }
// const regExp = /<div>(.*)<\/div>/g
// const res = selectDiv(regExp, str)
// console.log(res) // ['第一个div', '第二个div']

// 4.matchAll
function selectDiv(regExp, str) {
    let matches = []
    for (let match of str.matchAll(regExp)) {
        matches.push(match[1])
    }
    return matches
}
const regExp = /<div>(.*)<\/div>/g
const res = selectDiv(regExp, str)
console.log(res) // ['第一个div', '第二个div']

9-2 动态导入:Dynamic import()

动态导入

按需加载

// 动态导入的按需加载
const oBtn = document.getElementById('#btn')
oBtn.addEventListener('click', () => {
    import('./ajax').then(mod => {
        // console.log(mod) // Module{__esModule: true, Symbol(Symbol.toStringTag): 'Module', default: ƒ}
        mod.default('static/a.json', res => {
            console.log(res) // {a: 'A'}
        })
    })
})

9-3 新的原始数据类型:BigInt

const max = 2 ** 53
console.log(max) // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991

const bigInt = 9007199254740993n
console.log(bigInt) // 9007199254740993n
console.log(typeof bigInt) // bigInt

console.log(1n == 1) // true
console.log(1n === 1) // false

// 扩大了数的范围
const bigInt2 = BigInt(9007199254740993n)
console.log(bigInt2) // 9007199254740993n
const num = bigInt + bigInt2
console.log(num.toString()) // 18014398509481986

9-4 Promise扩展:Promise.allSettled()

// 场景:现在页面上有三个请求,分别请求不同的数据,如果一个接口服务异常,整个都是失败的,都无法渲染出数据
// Promise.all() 一个失败,全部失败
// Promise.all([
//     Promise.resolve({
//         code: 200,
//         data: [1, 2, 3]
//     }),
//     Promise.reject({
//         code: 500,
//         data: []
//     }),
//     Promise.resolve({
//         code: 200,
//         data: [7, 8, 9]
//     }),
// ]).then(res => {
//     console.log(res)
//     console.log('成功')
// }).catch(err => {
//     console.log(err)
//     console.log('失败')
// })

// Promise.allSettled()
// 在并发任务中,无论一个任务正常或者异常,都会返回对应的的状态
Promise.allSettled([
    Promise.resolve({
        code: 200,
        data: [1, 2, 3]
    }),
    Promise.reject({
        code: 500,
        data: []
    }),
    Promise.resolve({
        code: 200,
        data: [7, 8, 9]
    }),
]).then(res => {
    // console.log(res)
    // console.log('成功')
    const data = res.filter(item => item.status === 'fulfilled')
    console.log(data)
}).catch(err => {
    console.log(err)
    console.log('失败')
})
// 输出
// 0: {status: 'fulfilled', value: {…}}
// 1: {status: 'fulfilled', value: {…}}
// 即使有一个失败,也能得到其他成功的状态

9-5 全局对象:globalThis

提供了一个标准的方式获取不同环境下的全局对象

// node:global
// web: window self

// self.setTimeout
self.setTimeout(() => {
    console.log('2022')
}, 1000)

const getGlobal = () => {
    if (typeof self !== 'undefined') {
        return self
    }
    if (typeof window !== 'undefined') {
        return window
    }
    if (typeof global !== 'undefined') {
        return global
    }
    throw new Error('无法找到全局对象')
}
const global = getGlobal()
console.log(global) // window

// 与上面函数的作用相同
console.log(globalThis) // window

9-6 可选链:Optional chaining

const user = {
    address: {
        street: 'xxx街道',
        getNum() {
            return '80号'
        }
    }
}
// 以前的判断:先是否存在,再获取需要的值
const street = user && user.address && user.address.street
console.log(street)
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(num)

// 可选链  ?.不能分开
const street2 = user?.address?.street
console.log(street2)
const num2 = user?.address?.getNum?.()
console.log(num2)

9-7 空值合并运算符:Nullish coalescing Operator

// 需求:null undefined时取默认值
// 以前:
// const b = ''
// const a = b || 5
// console.log(a) // 5  b不是null
// // 或undefined也是默认值5

// es2020
const b = ''
const a = b ?? 5
console.log(a) // ''
// 只有传递null和undefined时才是默认值5

10 ES新特性在Vue实战中的应用

11 Webpack构建环境

未完…

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值