工作后,重新学习 ES6+

工作后,重新学习 ES6+

前言

一、ES5 & ES6 基础知识

1、变量声明方式

1-1 var 声明方式(variable 变量)

  • 用 var 和不用 var 声明变量的区别:
      当使用 var 关键字定义变量时,表示的是在当前作用域内声明变量。如果在方法里声明,是一个局部变量;如果在全局里声明,就是一个全局变量
      不使用 var 关键字时,表示的是在给 window 这个全局对象的属性赋值。这种方式会污染全局变量
var a = 2
console.log(a)  // 2 
delete a
console.log(a)  // 2

b = 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 = 3
	var a 
	console.log(a)  // 3
}

// let 
if(true) {
	b = 5
	let b
	console.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 2

let [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 3
	arr[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 += 1
	return 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)  // false

let boxDomList= document.getElementByClassName('.box')
console.log(boxDomList)  // HTMLCollection
console.log(boxDomList instanceof Array)  // false

let 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 值为 2

let 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 值为 2

let arr4 = Array.of(5)
console.log(arr4)  // [5]  length 值为 1

let 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')  // get

ajax('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()  // 1

let a = 1
function func(b = a) {
	let a = 2
	console.log(b)
}
func()  // 1

function func(n = m) {
	let m = 2
	console.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 = 0
	Array.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 = 0
	Array.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 = 0
	args.forEach(function(item) {
		sum += item
	})
	return sum
}
console.log(func(1, 2))  // 3
console.log(func(1, 2, 3))  // 6

let [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 = name
	this.age = age
}
let people = new People('zhangsan', 12)
console.log(people)

const People = (name, age) => {
	this.name = name
	this.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'))  // false

console.log(NaN == NaN)  // false
console.log(Object.is(NaN, NaN))  // true

console.log(+0 === -0)  // true
console.log(Object.is(+0, -0))  // false

let 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 = null
    if (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 = name
	this.age = age
}
let p1 = new Person("zhangsan", 12)
console.log(p1)  // Person {name: 'zhangsan', age: 12}
  • 在定义类的方法时,一般直接定义在函数内,而是定义在类的原型上
function Person(name, age) {
	this.name = name
	this.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 加 1
	Person.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 = name
		this.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 = name
		this.age = age
		this._sex = ""
	}
	get sex() {
		return this._sex
	}
	set sex(val) {
		this._sex = val
	}
}
  • 静态属性与静态方法:ES6 定义静态属性和方法可以使用 static 关键字
class People {
	constructor(name, age) {
		this.name = name
		this.age = age
		this._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) // true

let 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 = 0
	switch(shape) {
		case 'Triangle':
			area = 1
			break
		case 'Circle':
			area = 2
			break
	}
	return area
}
console.log(getArea('Circle')) // 2

// 使用 Symbol
const shapeType = {
	triangle: Symbol(),
	circle: Symbol()
}
function getArea(shape) {
	let area = 0
	switch(shape) {
		case shapeType.triangle:
			area = 1
			break
		case shapeType.circle:
			area = 2
			break
	}
	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 修饰符

6、数值的扩展

6-1 进制转换 ES5

  • 十进制 => 其它进制
const a = 12 // 十进制数
const radix = 2  // 将要转为的进制
console.log(a.toString(radix)) // 1100
  • 其它进制 => 十进制
const b = 1010
const currRadix = 2  // 当前数据的进制
console.log(parseInt(b, currRadix))  // 10

6-2 Number 扩展

  • Number.isFinite():判断是否是有限的【Infinity: 无限】
console.log(Number.isFinite(5))  // true
console.log(Number.isFinite(0))  // true
console.log(Number.isFinite(Infinity))  // false
console.log(Number.isFinite('str'))  // false
console.log(Number.isFinite(true))  // false
  • Number.isNaN():判断是否是 NaN
console.log(Number.isNaN(NaN))  // true
console.log(Number.isNaN(12))  // false
  • Number.isInteger():判断是否是整数
console.log(Number.isInteger(12))  // true
console.log(Number.isInteger(12.5))  // false

6-3 0.1 + 0.2 === 0.3 ???

  • 0.1 + 0.2 === 0.3 false

6-4 Math 扩展

  • Math.trunc():去除小数部分
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(undefined))  // NaN
console.log(Math.trunc(NaN))  // NaN
  • Math.sign():判断一个数是正数、负数、零
console.log(Math.sign(5.5))  // 1
console.log(Math.sign(-5.5))  // -1
console.log(Math.sign(0))  // 0
console.log(Math.sign(NaN))  // NaN
console.log(Math.sign(true))  // 1
console.log(Math.sign(false))  // 0
  • Math.cbrt():计算一个数的立方根
console.log(Math.cbrt(8))  // 2
console.log(Math.cbrt(-8))  // -2
console.log(Math.cbrt(0))  // 0
console.log(Math.cbrt(NaN))  // NaN
console.log(Math.cbrt(true))  // 1
console.log(Math.cbrt(false))  // 0
console.log(Math.cbrt(undefined))  // NaN
console.log(Math.cbrt('str'))  // NaN

7、代理 Proxy

7-1 代理

  • ES5
Object.defineProperty(要拦截的对象, 要拦截的属性, {
	writable: false,  // 属性是否可以被修改,默认 false
	value: '张三',  // 设置属性值
	configurable: false,  // 属性是否可以被删除,默认 false
	enumerable: false,  // 属性是否可以被枚举,默认 false
	set(val) {},  // 写入值时内部调用的函数
	get()  // 读取时内部调用的函数
})

let obj = {}
let newVal = ''
Object.defineProperty(obj, 'name', {
	get() {
		return newVal
	},
	set(val) {
		newVal = val
	}
})
  • ES6
// 通过对对象的拦截,产生一个新的对象
// target 是需要被代理的对象,它可以是任何类型的对象,比如数组、函数等等,但不能是基础数据类型
// handler 是一个对象,主要定义在代理对象后的拦截或者自定义的行为
let target = {}
target = new Proxy(target, handler)

let obj = {}
let p = new Proxy(obj, {})
p.name = '张三'
console.log(obj.name)  // 张三

7-2 Proxy 常用拦截方法

  • handler.get(target, property, receiver)
  • target:被代理的目标对象
  • property:想要获取的属性名
  • receiver:Proxy 或者继承 Proxy 的对象
let arr = [1, 2]
arr = new Proxy(arr, {
    get(target, property, receiver) {
        console.log(target, property, receiver, target === receiver)  // [1,2] 0 [1,2] false
        return property in target ? target[property] : 'err'
    }
})
console.log(arr[0])  // 1
  • handler.set(target, property, newValue, receiver)
  • target:被代理的目标对象
  • property:将要设置的属性名
  • newValue:将要设置的值
  • receiver:Proxy 的对象本身
let dict = {
    'hello': '你好',
    'world': '世界'
}
dict = new Proxy(dict, {
    get(target, property, receiver) {
        return property in target ? target[property] : property
    },
    set(target, property, newValue, receiver) {
        console.log(target, property, newValue, receiver)  // { hello: '你好', world: '世界' } name 张三 { hello: '你好', world: '世界' }
        target[property] = newValue
    }
})
console.log(dict['hello1'])  // hello1
dict["name"] = '张三'
console.log(dict['name'])  // 张三
  • handler.has(target, property) : boolean
  • target:被代理的目标对象
  • property:将要设置的属性名
let range = {
    start: 1,
    end: 5
}
range = new Proxy(range, {
    has(target, property) {
        return property >= target.start && property <= target.end
    }
})
console.log(2 in range) // true
console.log(7 in range) // false
  • handler.ownKeys(target)
  • target:被代理的目标对象

以下操作会触发拦截:
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()

let obj = {
    name: '张三',
    age: 12,
    _password: '123'
}
obj = new Proxy(obj, {
    ownKeys(target) {
        return Object.keys(target).filter(item => !item.startsWith('_'))
    }
});
console.log(Object.keys(obj)) // ['name', 'age']
  • handler.deleteProperty(target, property)
  • target:被代理的目标对象
  • property:将要设置的属性名
let obj = {
    name: '张三',
    age: 12,
    _password: '123'
}
obj = new Proxy(obj, {
    deleteProperty(target, property) {
        if (property.startsWith('_')) {
            throw new Error('err delete')
        } else {
            delete target[property]
        }
    }
});
try {
    delete obj.age
    console.log(obj)  // { name: '张三', _password: '123' }

    delete obj._password
} catch (e) {
    console.log(e.message)  // err delete
}
  • handler.apply(target, context, args)
  • target:被代理的目标对象
  • context:调用时的上下文对象,也就是 this 指向
  • args:函数调用的参数数组
function sum(..._args) {
    let num = 0
    _args.forEach(item => {
        num += item
    })
    return num
}

sum = new Proxy(sum, {
    apply(target, context, args) {
        return target(...args) * 2
    }
})
console.log(sum(1, 2)) // 6
console.log(sum.call(null, 1, 2)) // 6
console.log(sum.apply(null, [1, 2])) // 6
  • handler.construct(target, args, newTarget)
  • args:使用 new 操作符是传入的参数列表
  • newTarget:被调用的构造函数
class User {
    constructor(name) {
        this.name = name
    }
}
User = new Proxy(User, {
    construct(target, args, newTarget) {
        return new target(...args)
    }
})
console.log(new User('hello'))
  • handler.defineProperty()
  • handler.getOwnPropertyDescriptor()
  • handler.getPrototypeOf()
  • handler.isExtensible()
  • handler.preventExtensions()
  • handler.setPrototypeOf()

8、反射 Reflect

  • Reflect 的目的

将 Object 属于语言内部的方法放到 Reflect 上
修改某些 Object 方法的返回结果,让其变得更合理
让 Object 操作变成函数行为
Reflect 对象的方法与 Proxy 对象的方法一一对应

let obj = {
    name: '张三',
    age: 12,
    _password: '123'
}
obj = new Proxy(obj, {
    get(target, property, receiver) {
        if (property.startsWith('_')) {
            throw new Error('err get')
        } else {
            return Reflect.get(target, property, receiver)
        }
    },
    set(target, property, newValue, receiver) {
        if (property.startsWith('_')) {
            throw new Error('err set')
        } else {
            // target[property] = newValue
            Reflect.set(target, property, newValue, receiver)
        }
    },
    deleteProperty(target, property) {
        if (property.startsWith('_')) {
            throw new Error('err delete')
        } else {
            // delete target[property]
            Reflect.deleteProperty(target, property)
        }
    }
});

console.log(obj.age) // 12
try {
    console.log(obj._password)
} catch (e) {
    console.log(e.message)  // err get
}

obj.age = 27
console.log(obj.age)  // 27
try {
    obj._password = '123'
} catch (e) {
    console.log(e.message)  // err set
}

delete obj.age
console.log(obj)  // { name: '张三', _password: '123' }
try {
    delete obj._password
} catch (e) {
    console.log(e.message)  // err delete
}

三、ES6 异步编程

1、Ajax 原理与 Callback Hell

2、Promise

3、Generator

4、迭代器 Iterator

5、模块化 Module

四、ES7 数组扩展

1、

五、ES8 异步编程之 async & await

六、ES9 异步迭代

七、ES10

八、ES11

总结

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值