工作后,重新学习 ES6+
- 前言
- 一、ES5 & ES6 基础知识
- 1、变量声明方式
- 1-1 var 声明方式(variable 变量)
- 1-2 新的声明方式:let
- 1-3 新的声明方式:const
- 小结
- 2、解构赋值
- 小结
- 3、数组的遍历
- 3-1 ES5 中遍历数组的方式
- 3-2 ES6 中遍历数组的方式
- 小结
- 4、数组的扩展
- 4-1 类数组 / 伪数组
- 4-2 ES6 数组扩展方法
- 4-3 数组中查找元素 indexOf
- 小结
- 5、函数的参数
- 5-1 参数默认值
- 5-2 与解构赋值结合
- 5-3 函数的属性
- 5-4 参数作用域
- 小结
- 6、扩展运算符 与 rest 参数
- 小结
- 7、箭头函数
- 7-1 箭头函数的定义
- 7-2 箭头函数的特性
- 小结
- 8、对象的扩展
- 8-1 对象的常用方法与表示方式
- 8-2 对象的遍历方式
- 小结
- 9、深拷贝与浅拷贝
- 9-1 浅拷贝
- 9-2 深拷贝
- 小结
- 二、ES6 新特性
- 1、类与继承
- 1-1 ES5 中的类与继承(未完成)
- 1-2 ES6 中的类与继承
- 2、新的数据类型 Symbol
- 2-1 新的原始数据类型
- 2-2 Symbol 的声明
- 2-3 Symbol 的应用场景
- 3、新的数据结构 Set & Map
- 3-1 数据结构 Set
- 3-2 数据结构 Map
- 4、字符串的扩展
- 5、正则表达式
- 5-1 修饰符
- 6、数值的扩展
- 7、代理 Proxy
- 8、反射 Reflect
- 三、ES6 异步编程
- 1、Ajax 原理与 Callback Hell
- 2、Promise
- 3、Generator
- 4、迭代器 Iterator
- 5、模块化 Module
- 四、ES7 数组扩展
- 1、
- 五、ES8 异步编程之 async & await
- 六、ES9 异步迭代
- 七、ES10
- 八、ES11
- 总结
前言
一、ES5 & ES6 基础知识
1、变量声明方式
1-1 var 声明方式(variable 变量)
- 用 var 和不用 var 声明变量的区别:
当使用 var 关键字定义变量时,表示的是在当前作用域内声明变量。如果在方法里声明,是一个局部变量;如果在全局里声明,就是一个全局变量
不使用 var 关键字时,表示的是在给 window 这个全局对象的属性赋值。这种方式会污染全局变量
var a = 2
console.log(a) // 2
delete a
console.log(a) // 2b = 3
console.log(b) // 3
delete b
console.log(b) // 报错:'b' is not defined
- 当使用 var 声明的变量在全局作用域时,也可以通过 window 去调用,这是 JavaScript 受人诟病的问题之一,这样的方式会污染全局变量
1-2 新的声明方式:let
- 不属于顶层对象 window : 使用 let 关键字声明的变量即使定义在全局作用域,也不会被挂载在 window 对象上,解决了污染全局变量的问题
let a = 2
console.log(a) // 2
console.log(window.a) // undefined
- 不允许重复声明: 当一个变量被声明过一次后,再次声明就会报错
// var:后一个会把前一个的覆盖
var a = 2
var a = 3
console.log(a) // 3// let
let b = 5
let b = 6
console.log(b) // 报错:'b' already been declared
- 不存在变量提升: 在变量被声明之前,不允许被使用
// var:存在变量提升
console.log(a) // undefined
var a = 2
// 相当于 =>
var a
console.log(a) // undefined
a = 2 // let
console.log(b) // 报错:Cannot access 'b' before initialization
let b = 5
- 形成暂时性死区: 在 let 声明变量的作用域内,所有的变量必须先声明后使用
// var
if(true) {a = 3var a console.log(a) // 3
}// let
if(true) {b = 5let bconsole.log(b) // Cannot access 'a' before initialization
}
- 形成块级作用域: 使用 let 定义的变量仅在当前语句块内能使用
// var
for(var i = 0; i < 3; i++) {console.log("循环内:" + i) // 分别为:0 1 2
}
console.log("循环外:" + i) // 3// let
for(let i = 0; i < 3; i++) {console.log("循环内:" + i) // 分别为:0 1 2
}
console.log("循环外:" + i) // 'i' is not defined
1-3 新的声明方式:const
- ES5 中定义一个常量:
// 使用 Object 顶层对象的 defineProperty() 方法
// 第一个参数:表示在哪个属性上定义
// 第二个参数:自定义属性的名称
// 第三个参数:是一个对象,用于对该自定义属性的描述
Object.defineProperty(window, 'PI', {value: '3.14', // 属性的默认值writable: false // 表示该属性是否可改变,true 可变,false 不可变
})
- ES6 中定义一个常量:
// 使用 const 关键字,并且在声明并赋值
const a = 2// 错误
const b
b = 3 // 报错:Missing initializer in const declaration
const 定义的变量不属于顶层对象 window
const 定义的变量不允许被重复声明
const 定义的变量不存在变量提升
const 定义的变量会形成暂时性死区
const 定义的变量会形成块级作用域
- const 声明的常量仅限于基本数据类型,对于引用类型不起作用
原因是基本数据类型存储在栈内存(stack);引用类型的值存储在堆内存(heap),在栈中仅是存放堆中值的引用地址 - 如果想将引用类型(只对对象类型起作用)的值锁定不能再改变,可以使用 Object.freeze()
注意:freeze() 方法里只能传一个对象,不能传数组
注意:freeze() 方法只能进行浅层的冻结,对于对象里的对象不能冻结,如果需要对对象里的对象进行冻结,需要进行额外使用 freeze() 去冻结
// 未使用 freeze()
const obj = {name: 'zhangsan',age: 12
}
obj.phone = '123456'
console.log(obj) // {name: 'zhangsan', age: 12, phone: '123456'}// 使用 freeze()
const obj = {name: 'zhangsan',age: 12
}
Object.freeze(obj)
obj.phone = '123456'
console.log(obj) // {name: 'zhangsan', age: 12}// 使用 freeze() 也只能冻结当浅层
const obj = {name: 'zhangsan',age: 12,skill: {name: 'code',year: 3}
}
Object.freeze(obj)
// 如果需要冻结其它层,需要手动冻结
// Object.freeze(obj.skill)
obj.hobby.year= 12
console.log(obj) // {name: 'zhangsan', age: 12, skill: {name: 'code', year: 12}}// 使用 freeze() 也只能冻结当浅层,如果需要冻结其它层,需要手动冻结
const obj = {name: 'zhangsan',age: 12,skill: {name: 'code',year: 3}
}
Object.freeze(obj)
Object.freeze(obj.skill)
obj.hobby.year= 12
console.log(obj) // {name: 'zhangsan', age: 12, skill: {name: 'code', year: 3}}
小结
- delete 关键字用于删除 window 全局对象的属性,不能删除其它对象的属性
- let 与 const 和 var 的区别:
定义的变量不属于顶层对象 window
不允许被重复声明
不存在变量提升
形成暂时性死区
形成块级作用域 - ES5 定义一个常量,使用 Object.defineProperty()
- ES6 想要冻结对象,需要手动使用 Object.freeze() 方法手动冻结
2、解构赋值
- 按照一定模式,从数组和对象中提取值对变量进行赋值
- 数组解构
let [a, b] = [1,2]
console.log(a, b) // 1 2let [a, b, c] = [1, 2, [3, 4]]
console.log(a, b, c) // 1 2 [3, 4]let [a, b, [c]] = [1, 2, [3, 4]]
console.log(a, b, c) // 1 2 3
- 对象解构
let user = {name: 'lisi', age: 12}
let {name, age} = user
console.log(name, age) // lisi 12
// 交换位置不影响
let {age, name} = user
console.log(name, age) // lisi 12
// 取别名:如果取了别名,就不能使用原来的名称
let {age: a, name: n} = user
console.log(n, a) // lisi 12
console.log(name, age) // 'name' 'age' is not defined
- 字符串解构
// 字符串的解构和数组的解构类似
let str = 'helloworld'
let [a, b, c, d, e, f, g, h, i, j] = str
console.log(a, b, c, d, e, f, g, h, i, j) // h e l l o w o r l d
小结
- 对于对象类型,如果取了别名,就不能使用原来的名称
3、数组的遍历
3-1 ES5 中遍历数组的方式
- for 循环
for 循环会改变原数组
let arr = [1, 2, 3]for(let i = 0; i < arr.length; i++) {console.log(arr[i]) // 1 2 3arr[i] += 1
}
console.log(arr) // [2, 3, 4]
- forEach():没有返回值,只是针对每个元素调用函数
在 forEach() 循环中是不能使用 break 关键字
在 forEach() 循环中是不能使用 continue关键字
forEach() 循环只是针对数组简单遍历
forEach 循环会改变原数组
let arr = [1, 2, 3]
/*** item:当前遍历的数组对象* index:当前遍历对象的索引* array:当前正在遍历的数组本身**/
arr.forEach(function(item, index, array) {console.log(item, index)arr[i] += 1
})
console.log(arr)
- map():没有返回新的 Array,每个元素为调用函数的结果
map() 循环不会改变原数组,但会返回新的数组
let arr = [1, 2, 3]
/*** item:当前遍历的数组对象* index:当前遍历对象的索引* array:当前正在遍历的数组本身**/
let result = arr.map(function(item, index, array) {item += 1return item
})
console.log(arr, result) // [1, 2, 3] [2, 3, 4]
- filter():返回符合函数条件的元素数组
filter() 循环不会改变原数组,但会返回新的数组,新的数组是符合筛选条件的新数组
let arr = [1, 2, 3]
/*** item:当前遍历的数组对象* index:当前遍历对象的索引* array:当前正在遍历的数组本身**/
let result = arr.filter(function(item, index, array) {return item == 2
})
console.log(arr, result) // [1, 2, 3] [2]
- some():判断是否有元素符合筛选条件,返回 boolean
some() 循环不会改变原数组,但会返回一个 boolean 值
只有数组中有一个符合条件就会返回 true
let arr = [1, 2, 3]
/*** item:当前遍历的数组对象* index:当前遍历对象的索引* array:当前正在遍历的数组本身**/
let result = arr.some(function(item, index, array) {return item == 2
})
console.log(arr, result) // [1, 2, 3] true
- every():判断是否有元素符合筛选条件,返回 boolean
every() 循环不会改变原数组,但会返回一个 boolean 值
只有数组中所有元素都符合条件才会返回 true
let arr = [1, 2, 3]
/*** item:当前遍历的数组对象* index:当前遍历对象的索引* array:当前正在遍历的数组本身**/
let result = arr.every(function(item, index, array) {return item== 2
})
console.log(arr, result) // [1, 2, 3] false
- reduce():接收一个函数作为累加器
reduce() 循环不会改变原数组,但会返回一个累加的值
let arr = [1, 2, 3]
/*** prev:当前遍历数组上一次的元素* curr:当前遍历数组的元素* index:当前遍历对象的索引* array:当前正在遍历的数组本身* reduce 的第二个参数:初始值**/
let sum = arr.reduce(function(prev, curr, index, array) {return prev + curr
}, 0)
console.log(sum) // 6// 示例1:找出数组中的最大值
let arr2= [1,3,7,2,4,6]
let max = arr2.reduce(function(prev, curr) {return Math.max(prev, curr)
})
console.log(max) // 7// 示例2:数组去重
let arr3= [1,3,7,2,4,2,3]
let res = arr3.reduce(function(prev, curr) {prev.indexOf(curr) == -1 && prev.push(curr)return prev
}, [])
console.log(res) // [1, 3, 7, 2, 4]
- for … in …: 遍历数组存在一定的问题
let arr = [1, 2, 3, 5, 2]
for(let index in arr) {console.log(index) // 0 1 2 3 4
}
3-2 ES6 中遍历数组的方式
- find():返回第一个通过查找条件的元素
find() 不会改变原数组
let arr = [1,2,3,5,2]let result = arr.find(function(item) {return item == 2
})
console.log(result, arr) // 2 [1, 2, 3, 5, 2]
- findIndex():返回第一个通过查找条件的元素的索引
findIndex() 不会改变原数组
let arr = [1,2,3,5,2]let result = arr.findIndex(function(item) {return item == 2
})
console.log(result, arr) // 1 [1, 2, 3, 5, 2]
- for of:
item 是数组元素的对象
let arr = [1,2,3,4,5]for(let item of arr) {console.log(item) // 1 2 3 4 5
}
- values():
value 是对应数组元素键值对的值
let arr = [1,2,3,4,5]for(let value of arr.values()) {console.log(value) // 1 2 3 4 5
}
- keys():
key 是对应数组元素键值对的键
let arr = [1,2,3,4,5]for(let key of arr.keys()) {console.log(key) // 0 1 2 3 4
}
- entries():
of 前的是一个数组,数组第一项是键,第二项是值
let arr = [1,2,3,4,5]for(let [key,value] of arr.keys()) {console.log(key, value) // 0 1 1 2 2 3 3 4 4 5
}
小结
- break 关键字表示退出当前循环
- continue 关键字表示结束本次循环
- forEach 循环内不能使用 break 和 continue 关键字
- for 和 forEach 循环会改变原数组,map、filter、come、every、reduce等循环不会改变原数组
4、数组的扩展
4-1 类数组 / 伪数组
- 和数组类似,具备 length 属性,但是不具备数组方法的数组称为类数组或伪数组
- 伪数组的特点是:具有非负的整数索引和 length 属性
// DOM
let divDomList= document.getElementByTagName('div')
console.log(divDomList) // HTMLCollection
console.log(divDomList instanceof Array) // falselet boxDomList= document.getElementByClassName('.box')
console.log(boxDomList) // HTMLCollection
console.log(boxDomList instanceof Array) // falselet testDomList= document.querySelectorAll('.test')
console.log(testDomList) // NodeList
console.log(testDomList instanceof Array) // false
- ES5 将类数组转为真实的数组:Array.prototype.slice.call()
let testDomList= document.querySelectorAll('.test')
console.log(testDomList) // NodeList
let arr = Array.prototype.slice.call(testDomList)
console.log(arr) // [] __proto__: Array
arr.push(12)
console.log(arr) // [12]
- ES6 中的一个类数组 arguments
function func() {console.log(...arguments) // 1 'abc' true
}
func(1, 'abc', true)
4-2 ES6 数组扩展方法
- Array.from():ES6 中将类数组转为真正的数组的方法
// 定义一个类数组
let arrLike = {0: 'ES5',1: 'ES6',2: 'ES7',length: 3
}
// 将类数组转为真正的数组
let arr = Array.from(arrLike)
console.log(arr) // ['ES5', 'ES6', 'ES7']
console.log(arr instance Array) // true
- Array.of():ES6 创建数组
使用 new Array() 创建数组的缺点:
当传入多个参数时,会创建一个真正的数组
当只传一个 number 类型的参数时,表示创建一个参数长度的空数组
let arr1 = new Array(1, 2)
console.log(arr1) // [1, 2] 此时的 length 值为 2let arr2 = new Array(3)
console.log(arr2) // [empty x 3] 此时的 length 值为 3// 使用 Array.of()
let arr3 = Array.of(1, 2)
console.log(arr3) // [1, 2] length 值为 2let arr4 = Array.of(5)
console.log(arr4) // [5] length 值为 1let arr5 = Array.of(1, true, 'abc', [2,3,5], {name: 'zhangsan'})
console.log(arr5) // [1, true, 'abc', Array(3), {name: 'zhangsan'}] length 值为 5
- copyWithin():用于替换数组的元素
let arr = [1, 2, 3, 4, 5]
/*** 第一个参数(必需传):表示复制到指定索引位置* 第二个参数:元素复制的起始位置* 第三个参数:停止复制的索引位置 (默认为 array.length)。如果为负值,表示倒数**/
console.log(arr.copyWithin(1, 3)) // [1, 4, 5, 4, 5]
- fill():表示填充(或替换)数组元素
let arr = new Array(3)
console.log(arr) // [empty x 3]
/*** 第一个参数(必需传):表示填充的值* 第二个参数:可选。开始填充位置* 第三个参数:可选。停止填充位置 (默认为 array.length)**/
arr.fill(5)
console.log(arr) // [5, 5, 5]let arr2 = [1, 2, 3, 4, 5]
arr2.fill('test', 2, 4)
console.log(arr2) // [1, 2, 'test', 'test', 5]
- includes():判断数组是否包含某个指定值,返回一个 boolean 值
let arr = [1, 2, 'a', 4, 5, NaN]
/*** 第一个参数(必须传):查找的内容* 第二个参数:可选。表示从该索引位置处开始查找。如果为负值,表示从倒数的索引位置开始查找**/
console.log(arr.includes('a')) // true
console.log(arr.includes(NaN)) // true
4-3 数组中查找元素 indexOf
- indexOf() 方法用于搜索数组中的元素,并返回它所在的位置
let arr = [1, 2, 'a', 4, 5, NaN]
/*** 第一个参数(必须传):查找的内容* 第二个参数:可选。表示从该索引位置处开始查找。如果为负值,表示从倒数的索引位置开始查找**/
console.log(arr.indexOf('a')) // 2
console.log(arr.indexOf(NaN)) // -1
小结
- ES5 将类数组转为真实的数组:Array.prototype.slice.call()
- ES6 中将类数组转为真正的数组的方法:Array.from()
- ES6 创建数组的方法:Array.of()
- ES6 用于替换数组的元素:copyWithin()
- ES6 表示填充(或替换)数组元素:fill()
- ES6 判断数组是否包含某个指定值:includes(),返回一个 boolean 值
- ES5 判断数组是否包含某个指定值:indexOf(),匹配成功返回符合条件元素的索引,否则返回 -1
5、函数的参数
5-1 参数默认值
- 函数的行参默认是被声明的,因此在函数内部不能再声明与形参同名的参数
- 当有多个行参是,行参名称不能重名
- 有默认值的参数要放在行参的后面
- ES5 中为参数赋默认值
这种使用默认值的方式对一些特殊的数值会产生超出预期的效果,例如数字 0
function func(a, b) {b = b || '默认值'console.log(a, b)
}
func('hello') // hello 默认值// 传数字 0
// 预期结果应该是 hello 0
// 然而实际输出的是 hello 默认值
// 原因是 0 在 JS 中表示的是 false
func('hello', 0) // hello 默认值
- 在 ES6 中可以使用在行参赋值的方式赋默认值
function func(a, b = '默认值') {console.log(a, b)
}
func('hello') // hello 默认值
func('hello', 0) // hello 0
5-2 与解构赋值结合
function ajax(url, { data = [], method = 'get', headers = {} } = {}) {console.log(method)
}
ajax('http://www.baidu.com') // getajax('http://www.baidu.com', { method: 'POST'}) // POST
5-3 函数的属性
- 函数的 length 属性:可以获取到函数中没有指定默认值参数的个数
// 行参都不赋值
function func(x, y, z) {console.log(x, y, z)
}
console.log(func.length) // 3
// 赋一个默认值
function func1(x, y, z = 3) {console.log(x, y, z)
}
console.log(func1.length) // 2
// 全部赋一个默认值
function func1(x = 1, y = 2, z = 3) {console.log(x, y, z)
}
console.log(func1.length) // 0
- 函数的 name 属性
暂时不知道函数的 name 属性有什么作用
console.log((new Function).name) // anonymous 匿名console.log((function(){}).bind({}).name) // bound
// 改变 this 指向
function func(x, y) {console.log(this, x, y)
}
func.bind({name: '将函数的 this 指向这个对象'})('传入到函数的参数最好放到这个圆括号,当然也可以放到前面bind中', '传入到函数的参数2')
5-4 参数作用域
- 在调用函数时传参,在函数体内优先使用所传的参数
let x = 1
function func(x, y = x) {console.log(y)
}
func(2) // 2
- 若调用函数不传参数,就会沿着作用域链向外层作用域寻找参数并使用
let x = 1
function func(y = x) {console.log(y)
}
func() // 1let a = 1
function func(b = a) {let a = 2console.log(b)
}
func() // 1function func(n = m) {let m = 2console.log(n)
}
func() // m is not defined
小结
- 函数的行参默认是被声明的,因此在函数内部不能再声明与形参同名的参数。当有多个行参是,行参名称不能重名。有默认值的参数要放在行参的后面
- 参数作用域:在调用函数时传参,在函数体内优先使用所传的参数。若调用函数不传参数,就会沿着作用域链向外层作用域寻找参数并使用
6、扩展运算符 与 rest 参数
- 扩展运算符(…):把数组或者类数组展开成用逗号隔开的值
// 扩展运算符
function func(a, b, c) {console.log(a, b, c)
}let arr = [1, 2, 3]
console.log(...arr) // 1 2 3
func(...arr) // 1 2 3// 合并数组
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
// ES5 之前
Array.prototype.push.apply(arr1, arr2)
console.log(arr1) // [1, 2, 3, 4, 5, 6]
// or
let temp = arr1.concat(arr2)
console.log(temp) // [1, 2, 3, 4, 5, 6]
// ES6 使用扩展运算符
arr1.push(...arr2)
console.log(arr1) // [1, 2, 3, 4, 5, 6]// 字符串
let str = 'hello'
console.log([...str]) // ['h', 'e', 'l', 'l', 'o']
- rest 参数(…):把逗号隔开的值组合成一个数组
// 不定项参数求和
// arguments
function func() {let sum = 0Array.prototype.forEach.call(arguments, function(item) {sum += item})return sum
}
console.log(func(1, 2)) // 3
console.log(func(1, 2, 3)) // 6
// Array.from()
function func() {let sum = 0Array.from(arguments).forEach(function(item) {sum += item})return sum
}
console.log(func(1, 2)) // 3
console.log(func(1, 2, 3)) // 6
// 剩余参数 rest
function func(...args) {let sum = 0args.forEach(function(item) {sum += item})return sum
}
console.log(func(1, 2)) // 3
console.log(func(1, 2, 3)) // 6let [x, ...y] = [1, 2, 3, 4]
console.log(x) // 1
console.log(y) // [2, 3, 4]
小结
- 扩展运算符(…):把数组或者类数组展开成用逗号隔开的值
- rest 参数又称为剩余参数(…):把逗号隔开的值组合成一个数组
7、箭头函数
7-1 箭头函数的定义
- ES5 定义函数(函数的声明方式)
// 第一种方式:声明式
function func() {console.log('hello world')
}// 第二种方式:函数表达式形式
let func = function() {console.log('hello world')
}
- ES6 箭头函数定义
// 将函数表达式 ==> 箭头函数
let func = () => {console.log('hello world')
}
7-2 箭头函数的特性
- 单个参数可以省略圆括号
const func = x => {}
- 无参数或多个参数不能省略圆括号
const func = () => {}const func = (x, y) => {}
- 单行函数体可以 同时省略 {} 和 return
const func = x => {return x
}
// 简写为
const func = x => x
- 如果箭头函数返回单行对象,可以在 {} 外面加上 (),让浏览器不再认为那是函数体的花括号
const func = (a,b)=> {return {value:a + b;};
};
// 简写为
const func = (a,b) => ({value: a + b
});
- 箭头函数不可以当作构造函数
const People = function(name, age) {this.name = namethis.age = age
}
let people = new People('zhangsan', 12)
console.log(people)const People = (name, age) => {this.name = namethis.age = age
}
let people = new People('zhangsan', 12)
console.log(people) // People is not a constructor
- 箭头函数不可以使用 arguments 对象
let func = () => {console.log(arguments)
}
func(1, 2, 3) // Identifier 'func' has already been declared
- this 指向定义函数时所在的对象,而不是调用时所在的对象
小结
- this 指向定义函数时所在的对象,而不是调用时所在的对象
8、对象的扩展
8-1 对象的常用方法与表示方式
- 属性的简洁表示法:对于键与值完全相同的对象,可以简写为只写一个
let name = 'zhangsan'
let age = 12
let obj = {name: name,age: age
}// 可以简写为
let name = 'zhangsan'
let age = 12
let obj = {name,age
}
- 属性名表达式:可以用一个中括号 [] 包裹一个变量作为对象的键
let name = "zhangsan"
let a = "age"
let obj = {name,[a]: 12
}
console.log(obj) // { name: 'zhangsan', age: 12 }
- 对象中方法的简写
最好别使用箭头函数,箭头函数会导致 this 的指向发生变化
let obj = {name: 'zhangsan',age: 12,study: function() {console.log(this.name + '正在学习。。。')}
}
// 简写为
let obj = {name: 'zhangsan',age: 12,study() {console.log(this.name + '正在学习。。。')}
}
- Object.is():用于判断两个值是否严格相等
相当于 ===,但不完全相同
console.log(Object.is(2, '2')) // falseconsole.log(NaN == NaN) // false
console.log(Object.is(NaN, NaN)) // trueconsole.log(+0 === -0) // true
console.log(Object.is(+0, -0)) // falselet obj1 = {name: 'zhangsan',age: 12
}
let obj2 = {name: 'zhangsan',age: 12
}
console.log(obj1 == obj2) // false
console.log(Object.is(obj1, obj2)) // false
- 扩展运算符 与 Object.assign():用于将一个或多个源对象复制到目标对象
第一个参数是目标对象
第二个及后面的参数是源对象
扩展运算符的方式复制对象是浅拷贝
Object.assign()的方式复制对象是浅拷贝
let obj = {a: 2,b: 3,c: {d: 4,e: [1,2,3]}
}
// 扩展运算符
let obj2 = {...obj}
obj2.a = 10
console.log(obj2) // {a: 2, b: 3, c: { d: 4, e: [1,2,3] }}
console.log(obj == obj2) // false
console.log(Object.is(obj, obj2)) // false
// Object.assign()
let obj3 = {}
Object.assign(obj3, obj)
console.log(obj == obj3) // false
console.log(Object.is(obj, obj3)) // false
- in:可以用于判断对象是否包含某个属性
in 用于判断对象包含时,只能判断对象的第一层
in 用于判断数组包含时,表示的是该索引位置是否有值
// Object
let obj = {a: 2,b: 3,c: {d: 4,e: [1,2,3]}
}
console.log('c' in obj) // true
console.log('d' in obj) // false
// Array
let arr = [1, 2, 3, 'a', 'hello']
console.log('a' in arr) // false
console.log(3 in arr) // true
8-2 对象的遍历方式
let obj = {name: 'zhangsan',age: 12,addr: '南京'
}
- for in
for(let key in obj) {console.log(key, obj[key]) // name zhangsan age 12 addr 南京
}
- Object.keys()
Object.keys(obj).forEach(key => {console.log(key, obj[key]) // name zhangsan age 12 addr 南京
})
- Object.getOwnPropertyNames()
Object.getOwnPropertyNames(obj).forEach(key => {console.log(key, obj[key]) // name zhangsan age 12 addr 南京
})
- Reflect.ownKeys()
Reflect.ownKeys(obj).forEach(key => {console.log(key, obj[key]) // name zhangsan age 12 addr 南京
})
小结
- 如果对象的键值相同,可以简写为一个
- 可以用一个中括号 [] 包裹一个变量作为对象的键
- Object.is() 用于判断两个值是否严格相等
- 对象扩展运算符 和 Object.assign() 用于将一个或多个源对象复制到目标对象,是浅拷贝
- in 用于判断对象第一层是否包含某个元素;用于判断数组的某个索引是否有值
- 对象的遍历一般使用 for in
9、深拷贝与浅拷贝
9-1 浅拷贝
- 对象赋值
let target = {}
let source = {a: 2,b: {c: 3,d: 4}
}
target = source
source.b.c = 13
console.log(source)
console.log(target)
- Object.assign()
let target = {}
let source = {a: 2,b: {c: 3,d: 4}
}
Object.assign(target,source)
source.b.c = 13
console.log(source)
console.log(target)
- 扩展运算符
let source = {a: 2,b: {c: 3,d: 4}
}let target = {...source}
source.b.c = 13
console.log(source)
console.log(target)
9-2 深拷贝
- JSON.stringify() 和 JSON.parse()
不能拷贝对象中的方法
不能拷贝对象中的构造函数,例如:new Date()、new RegExp() 等
不能拷贝值为 undefined 的属性
let source = {a: 2,b: {c: 3,d: 4}
}
let target = JSON.parse(JSON.stringify(source))
source.b.c = 13
console.log(source)
console.log(target)
- 递归实现深拷贝
不能拷贝对象中的构造函数,例如:new Date()、new RegExp() 等
/*** 深拷贝**/
function deepClone(obj) {let result = nullif (typeof(obj) == 'object' && obj !== null){result = obj instanceof Array? [] : {}for(let key in obj){result [key] = deepClone(obj[key])}} else {result = obj}return result
}
- jQuery 的 extend 方法实现深拷贝
let source = {a: 2,b: {c: 3,d: 4}
}
let target = $.extend(true,{},source)
target.b.c = 13
console.log(source)
- 使用第三方库 lodash 的 deepClone()
let source = {a: 2,b: {c: 3,d: 4}
}
let target = _.deepClone(source)
target.b.c = 13
console.log(source)
小结
- 如果需要深拷贝且对象中有构造函数等时,最好使用 lodash 实现深拷贝
二、ES6 新特性
1、类与继承
1-1 ES5 中的类与继承(未完成)
类的属性与方法:
- 在 ES5 中并没有类的概念,只能通过函数去模拟,并且约定类名首字母大写
function Person(name, age) {this.name = namethis.age = age
}
let p1 = new Person("zhangsan", 12)
console.log(p1) // Person {name: 'zhangsan', age: 12}
- 在定义类的方法时,一般直接定义在函数内,而是定义在类的原型上
function Person(name, age) {this.name = namethis.age = age
}
// 实例方法
Person.prototype.showName = function() {console.log("my name is " + this.name)
}
let p1 = new Person("zhangsan", 12)
console.log(p1) // Person {name: 'zhangsan', age: 12}
p1.showName() // my name is zhangsan
类的静态属性与静态方法:
- 静态属性:直接挂在类上面的属性
function Person(name, age) {// this.name 这种是实例属性this.name = name// this.age 这种是实例属性this.age = age
}
// 这种是静态属性
Person.count = 0
- 静态方法:直接通过类打点创建的方法
function Person(name, age) {// this.name 这种是实例属性this.name = name// this.age 这种是实例属性this.age = age// 每被实例化一次,count 加 1Person.count++
}
// 这种是静态属性
Person.count = 0
Person.getCount = function() {console.log("被实例化" + Person.count + "次")
}let p1 = new Person("zhangsan", 12)
let p2 = new Person("李四", 16)
// 调用静态方法
Person.getCount() // 被实例化2次
- 静态属性和静态方法与实例化对象无关
类的继承:
- 构造函数继承:
构造函数继承只能继承父类的属性
- 原型链继承:
- 原型继承可以继承父类的方法,只不过需要将子类的原型构造函数重新指向子类
- 组合式继承:
- 既使用构造函数继承,又使用原型继承的方式称为组合式继承
- 寄生组合继承:
// 父类
function Animal(name) {this.name = name
}
Animal.prototype.run = function() {console.log("动物可以行走。。。")
}
// 子类
function Dog(name,age) {// 构造函数继承Animal.call(this,name) // 继承父类的属性this.age = age
}
Dog.prototype = new Animal() // 将子类的原型指向父类的实例
Dog.prototype.constructor = Dog // 将子类原型上的构造函数再指回子类
let d1 = new Dog("wangwang", 5)
d1.run()
1-2 ES6 中的类与继承
- 类的定义:
ES6 中提供 class 关键字用于定义类
class Person {constructor(name, age) {this.name = namethis.age = age}showName() {console.log("名字是:" + this.name)}
}
let p1 = new Person("zhangsan", 12)
console.log(p1)
- 类的继承:
ES6 提供 extends 关键字实现类的继承
子类继承父类的属性使用 super 关键字,且 super 关键字必须放在子类构造函数里的第一行
class Coder extends Person {constructor(name, age, company) {super(name, age)this.company = company}showCompany() {console.log("公司是:" + this.company)}
}let c1 = new Coder("lisi", 14, "xxx")
console.log(c1)
c1.showName()
c1.showCompany()
- 在 ES6 中还提供一种在类的顶层定义属性的方法
通过 get 和 set 进行属性的获取与赋值
所谓类的顶层指的是:在类的大括号内直接定义,而不在 constructor 里定义
使用 get 与 set 方法获取或设置属性时,需要使用一个变量来存储当前属性的值
class People {constructor(name, age) {this.name = namethis.age = agethis._sex = ""}get sex() {return this._sex}set sex(val) {this._sex = val}
}
- 静态属性与静态方法:ES6 定义静态属性和方法可以使用 static 关键字
class People {constructor(name, age) {this.name = namethis.age = agethis._sex = ""}get sex() {return this._sex}set sex(val) {this._sex = val}// 静态属性static count = 12// 静态方法static getNum() {console.log("123456")}
}
People.getNum()
console.log(People.count)
2、新的数据类型 Symbol
2-1 新的原始数据类型
- ES5 中的原始数据类型有:string、number、boolean、null、undefined 五个基本数据类型和引用类型 object
- ES6 中新增了新的原始数据类型 Symbol
- Symbol 有象征、符号的意思,在 JavaScript 中表示唯一
2-2 Symbol 的声明
- 基础的声明方式
let s1 = Symbol()
let s2 = Symbol()
console.log(s1) // Symbol()
console.log(s2) // Symbol()
console.log(s1 === s2) // false
- 带描述的声明方式
let s1 = Symbol('sym')
let s2 = Symbol('sym')
console.log(s1) // Symbol('sym')
console.log(s2) // Symbol('sym')
console.log(s1 === s2) // false
- 声明在全局的方式
- 使用 Symbol.for() 声明的变量无论在哪个作用域内声明,都相当于在全局作用域内声明
let s1 = Symbol.for('sym')
let s2 = Symbol.for('sym')
console.log(s1 === s2) // truelet s3 = Symbol.for('symbol')
function func() {return Symbol.for('symbol')
}
console.log(s3 === func()) // true
2-3 Symbol 的应用场景
- 解决同名不同信息的问题
const stu1 = 'zhangsan'
const stu2 = 'zhangsan'
const grade = {[stu1]: { address: 'xxx', tel: '111' },[stu2]: { address: 'yyy', tel: '222' }
}
console.log(grade) // { zhangsan: { address: 'yyy', tel: '222' }}// 使用 Symbol
const stu1 = Symbol('zhangsan')
const stu2 = Symbol('zhangsan')
const grade = {[stu1]: { address: 'xxx', tel: '111' },[stu2]: { address: 'yyy', tel: '222' }
}
console.log(grade) // { Symbol('zhangsan'): { address: 'xxx', tel: '111' }, Symbol('zhangsan'): { address: 'yyy', tel: '222' }}
console.log(greade[stu1]) // {address: 'xxx', tel: '111'}
- 使用 Symbol 消除魔术字符串
- 魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值
function getArea(shape) {let area = 0switch(shape) {case 'Triangle':area = 1breakcase 'Circle':area = 2break}return area
}
console.log(getArea('Circle')) // 2// 使用 Symbol
const shapeType = {triangle: Symbol(),circle: Symbol()
}
function getArea(shape) {let area = 0switch(shape) {case shapeType.triangle:area = 1breakcase shapeType.circle:area = 2break}return area
}
console.log(getArea(shapeType.circle)) // 2
3、新的数据结构 Set & Map
3-1 数据结构 Set
- 新建 Set 对象
let set = new Set()
- 增加元素
set.add('hello')
set.add(1)
set.add(2).add(5).add('world')
- 删除元素
set.delete(1)
set.delete('hello')
- 清空
set.clear()
- 获取 Set 元素个数
let len = set.size
- 判断是否拥有某个元素
set.has('world') // false
- 遍历
set.forEach(item => {console.log(item)
})for(let item of set) {console.log(item)
}
- Set 应用场景
- 去重
let arr = [1,2,1,3,5,3,6]
// 去重
let s = new Set(arr)
// 将 set 转为 array
let tmpArr = Array.from(s) // [...s]
- 交集:两个数组都有的
let arr1 = [1,2,3,4,5]
let arr2 = [2,5,6,7,8]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let result = new Set(arr1.filter(item => s2.has(item)))
console.log(Array.from(result)) // [2,5]
- 差集:两个数组不相同的部分组成的集合
let s3 = new Set(arr1.filter(item => !s2.has(item)))
let s4 = new Set(arr2.filter(item => !s1.has(item)))
console.log([...s3,...s4])
- WeakSet:这种只能添加对象类型的元素
let ws = new WeakSet()
let obj = {name: 'zhangsan'
}
ws.add(obj)
ws.add({age: 12
})
ws.delete({age:12
}) // 这种方式不能删除,因为这是引用类型
ws.delete(obj) // 这种方式可以删除
- Set 和 WeakSet 的区别
WeakSet 只能存放对象,Set不仅可以存放基础类型,也可以存放对象
WeakSet 不能遍历,Set 可以
WeakSet 是一种弱引用,并不会被垃圾回收机制引用,如果里面的对象消失,WeakSet 也会消失
3-2 数据结构 Map
- 创建 Map 对象
let m = new Map()
- 添加元素
m.set('key1','val1')
let obj = {name: 'zhangsan'
}
m.set('obj', obj)
- 获取值
m.get('key1')
m.get('obj')
- 判断某个键是否有值
m.has('key1') // true
- 删除
m.delete('key1')
- 清空
m.clear()
- 获取长度
let len = m.size
- 遍历
m.forEach((value,key) => {console.log(key,value)
})for(let [key,value] of m) {console.log(key,value)
}for(let key of m.keys()) {console.log(key)
}for(let value of m.values()) {console.log(value)
}for(let [key,value] of m.entries()) {console.log(key,value)
}
- Map 应用场景:和 object 类似
4、字符串的扩展
5、正则表达式
5-1 修饰符
- i 修饰符:忽略大小写
- m 修饰符:多行匹配
- g 修饰符:全局匹配
- y 修饰符:粘连修饰符,和 g相似,只不过是从剩余的第一个开始匹配
- u 修饰符: