ES6 学习笔记

ES6 学习笔记

1. 认识 ES6

1.1 ECMAScript 和 JavaScript 关系
  • ECMA“European Computer Manufacturers Association" 的缩写,中文称欧洲计算机制造联合会
    • 这个组织的目标是评估,开发和认可电信和计算机标准
  • ECMA标准JavaScript实现
    • ECMAScript 是跨多平台的许多厂商(包括浏览器厂商在内的各方组成)实施的不断发展的标准
    • ES6(ECMAScript 2015)花费六年的时间敲定,是一个很大的发行版

2. ES6 兼容性解决

  • 兼容表:http://kangax.github.io/compat-table/es6/
  • IE10+ChromeFireFox移动端Node.js 现在都支持
  • 兼容低版本浏览器
    • 在线转换(这种编译会加大页面渲染的时间)
    • 提前编译(强烈建议这种方式,不影响浏览器渲染时间)
      • 比较通用的工具方案有 babeljsxtraceures6-shim
// ES6
var fn = (
	v => console.log(v);
)

转为

// ES5
"use strict"

var fn = function fn(v){
	return console.log(v);
};
2.1 使用 Babel 工具搭建ES6项目环境
2.1.1 nodejs 安装
2.1.2 初始化工程项目
# 使用默认配置初始化项目
npm init -y
2.1.3 安装对应的依赖包
# 本地安装babel-cli及对应的转化规则
npm install --save-dev babel-cli babel-preset-es2015
2.1.4 创建项目目录
  • src 目录用于存放编写的 ES6 代码
  • dist 目录用于存放由ES6转化后的代码
2.1.5 配置babel
  • 新建.babelrc文件
// 配置用于转化当前代码的规则集
{
	"presets": ["es2015"]
}
2.2 Babel 兼容性列表
ES6 特性兼容性
箭头函数支持
类的声明和继承部分支持,IE8不支持
增强的对象字面量支持
字符串模版支持
解构支持,但注意使用方式
参数默认值,不定参数,拓展参数支持
let 与 const支持
for ofIE 不支持
iterator,generator不支持
模版 module,Proxies,Symbol不支持
Map,Set 和 WeakMap,WeakSet不支持
Promises,Math,Number,String,Object 的新 API不支持
export & import支持
生成器函数不支持
数组拷贝支持

3. let 和 const

3.1 var 命令
  • var 可以重复声明
  • var 无法限制修改
  • var 没有块级作用域,会进行变量提升
// 1. 重复声明
var a = 10;
var a = 20;
console.log(a); // 20

// 无法限制修改
var a = 10;
a = 30;

// 没有块级作用域
if(true){
	var a = 10;
	console.log(a); // 10
}
console.log(a); // 10
3.2 let 命令
  • let 不能重复声明
  • let 具有块级作用域,暂存性死区,不会变量提升
// 不能重复声明
let a = 10;
let a = 20;
console.log(a); // 报错

// 具有块级作用域
例子一:
if(true){
	let a = 10;
	console.log(a); // 10
}
console.log(a); // 报错

例子二:
for(var i=0; i<10; i++){
	console.log(i); // 1~9
}
console.log(i); // 10

for(let i=0; i<10; i++){
	console.log(i); // 1~9
}
console.log(i); // 报错
  • var与let经典案例
<input type="button" value="one">
<input type="button" value="two">
<input type="button" value="three">
<script>
	window.onload = function(){
		var btns = document.getElementsByTagName("input");
		
		// 当用 var 时,需要闭包方法
		for(var i=0; i<btns.length; i++){
			(function(i){
				btns[i].onclick = function(){
					console.log(i + 1);
				}
			})(i);
		}
		// 当用 let 时,计数器 i 只在 for 循环体内有效,在循环体外引用就会报错
		for(let i=0; i<btns.length; i++){
			btns[i].onclick = function(){
				console.log(i + 1);
			}
		}
	}
</script>
3.3 const 命令
  • const 命令声明一个只读的常量,如声明的是简单类型的数据,常量的值就不能改变不可重复声明,一旦声明,就必须立即初始化
  • const 具有块级作用域,暂存性死区,不会变量提升
// 只读,不能改变,不可重复声明
const PI = 3.1415;
console.log('PI=' + PI);

PI = 3; // 报错

// 声明后立即初始化
const a = 10;
const foo; // 报错
3.4 const 命令本质
  • const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动
    • 简单类型的数据(数值,字符串,布尔值
      • 值就保存在变量指向的那个内存地址,因此等同于常量
    • 复合类型的数据(主要是对象数组
      • 变量指向的内存地址,保存的只是一个指针const 只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了
const foo = {};
foo.prop = 123; // 为 foo 添加一个属性,可以成功
console.log(foo.prop); // 123

foo = {}; // 将 foo 指向另一个对象,就会报错

分析:上面的代码中,常量 foo 存储的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把 foo 指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性

4. 数据类型 Symbol

4.1 Symbol 类型介绍
  • ES6 之前的JavaScript数据类型
    • Number(数字)
    • String(字符串)
    • Boolean(布尔值)
    • Object(对象)
    • Null(空对象指针)
    • Undefined(声明的变量未被初始化时)
  • 引入的背景
    • 对象的属性名容易产生命名冲突,为保证健名的唯一性
    • ES6引入Symbol这种新的原始数据类型,确保创建的每个变量都是独一无二的
  • 特点
    • Symbol类型的数据是类似字符串的数据类型,由于Symbol函数返回的值是原始类型的数据,不是对象,故Symbol函数前不能使用new命令,否则会报错
    • 可选参数,由于控制台输出不同的Symbol变量时都是Symbol(),故为了区分,可在创建Symbol变量时传入参数进行区分
// 这里的a1,a2的作用可以说是为了备注,以至于我们输出Symbol变量时能够区分不同的变量
let a1 = Symbol('a1')
let a2 = Symbol('a2')

let b1 = Symbol.for('kk')
let b2 = Symbol.for('kk')
console.log(b1 === b2)

5. 解构赋值

5.1 介绍
  • 解构赋值可以理解为赋值操作的语法糖,它是针对数组或者对象进行模式匹配,然后对其中的变量进行赋值
  • 代码书写上言简意赅,语义明确,也方便了对象数据的读取操作
5.2 实质
  • ES6中只要某种数据有Iterator接口(也就是可以循环迭代),都可以进行数组的解构赋值
5.3 使用场景
5.3.1 数组解构
{
	let a,b,c
	[a,b,c] = [1,2]
	console.log(a,b,c) // 1,2,undefined
}

{
	let a,b,c
	[a,b,c = 6] = [1,2]
	console.log(a,b,c) // 1,2,6
}

{
	let a,other
	[a,...other] = [1,2,3]
	console.log(a,other) // 1,[2,3]
}

{
	let a,b
	[a,,b] = [1,2,3]
	console.log(a,b) // 1,3
}
5.3.2 对象解构
{
	let a,b
	({a,b} = {a:2, b:3})
	console.log(a,b) // 2,3
}

{
	let num, total
	({a: num,b: total} = {a:2, b:3})
	console.log(num,total) // 2,3
}

{
	function fn() {
		return {
			name: 'Nick',
			nameList: [{
				name: 'kk'
			}]
		}
	}
	
	let b = fn();
	let {name: person, nameList:[{name: otherPerson}]} = b;
	console.log(person, otherPerson) // Nick kk
}

6. 字符串和模版字符串

6.1 ES5 处理 Unicode 的缺陷
6.2 加强对 Unicode 的支持
  • 在ES5中JavaScript允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的Unicode码点
  • 这种表示法只限于码点在\u0000-\uFFFF之间的字符
  • 超出这个范围的字符,必须用两个双字节的形式表示,但是ES5却无法正确的识别这个有两个字节组成的字符
  • ES6中,JavaScript增加了对超出\u0000-\uFFFF Unicode范围的字符支持
  • ES6的方案:将超过两个字节的组成的字符的码点放在一对花括号李就可以正确的识别
{
	const str1 = 'a'
	const str2 = '\u20bb7'
	console.log(str2) // 乱码
}

{
	const str3 = '\u{20bb7}'
	console.log(str3)
}
6.3 字符串的遍历
{
	const str3 = '\u{20bb7}'
	for (let i = 0; i < str3.length; i++) {
		console.log(str3[i])
	}
	for (let word of str3) {
		console.log(word)
	}
}
6.4 扩展的 API
方法描述
includes(string, position)判断字符串中是否包含指定字符串,返回值是布尔值
startsWith(string, position)判断字符串的开头是否包含指定字符串,返回值是布尔值
endsWith(string, position)判断字符串的尾部是否包含指定字符串,返回值是布尔值
repeat(n)repeat()方法返回一个新字符串,表示将原字符串重复n次
字符串补全第一个参数是补全后的字符串长度,第二个参数是用于补全的字符串
padStart(length, str)用于头部补全
padEnd(length, str)用于尾部补全
{
	let str = '123Nick321'
	console.log('includes',str.includes('Nick')) // true
	console.log('startsWith',startsWith('Nick', 3)) // true
	console.log('endsWith',endsWith('Nick', 7)) // true
}

{
	let str = '123Nick321'
	str = str.repeat(3)
	console.log('repeat',str) // 123Nick321123Nick321123Nick321
}

{
	let str = 'Apple'
	str = padStart(8, 'asdsd')
	console.log('padStart',str) // asdApple
}
6.5 模版字符串
  • 传统的 JavaScript 语言,输出模版:
$('#result').append(
	'There are <br>' + basket.count + '</b>' + 
	'items in your basket,' +
	'<em>' + basket.onSale + 
	'</em> are on sale'
);
  • ES6 模版字符串
$('#result').append(`
	There are <b>${basket.count}</b> items
	in your basket,<em>${basket.onSale}</em>
	are on sale!
	`
);
  • 模版字符串(template string)是增强版的字符串,用反引号标识
  • 他可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`

console.log(`string text line 1
string text line2`);
// 字符串中嵌入变量
var name = "Bob", time = "today";
console.log(`Hello ${name}, how are you ${time}?`);
  • 使用模版字符串的注意事项
    • 在模版字符串中如需使用反引号,反引号前要用反斜杠转义
    • 使用模版字符串表示多行字符串时,所有的空格和缩进都会保留在输出之中
    • 模版字符串中引入变量,要用${变量名}这样的形式引入才可以
    • 大括号中的值不是字符串时,将按照一般的规则转为字符串
      • 比如,大括号中是一个对象,将默认调用对象的toString方法
    • 模版字符串中的${…}大括号内部可以放入任意的JavaScript表达式,可以进行运算、可以引用对象属性、可以调用函数、可以嵌套,甚至还能调用自己本身

7. 数组的扩展方法及扩展运算符的使用

7.1 扩展运算符的使用
7.1.1 复制数组
const list = [1,2,3,4,5]
let list2 = [...list]
console.log(list2)
7.1.2 分割数组
const list = [1,'a','b','c']
let [,...list2] = list
console.log(list2)
7.1.3 将数组转化成参数传递给函数
function add(x,y) {
	return x + y
}

let addList = [1,2]
console.log(add(...addList))
7.2 新增的常用方法
7.2.1 fill 填充数据
const list = [1,2,3,4,5]
let list2 = [...list].fill(3)
let list3 = [...list].fill(3,1,4)
console.log(list2,list3)
7.2.2 find & findIndex
const list = [{title: 'es6'},{title: 'web', id: 2},{title: 'vue'},{title: 'web',id: 3}]
let result = list.find(function(item) {
	return item.title === 'webpack'
})
let resultIndex = list.findIndex(function(item) {
	return item.title === 'webpack'
})
console.log(result,resultIndex)
7.2.3 includes & indexOf
const list = [1,2,3,4,5,6]
let result = list.includes(2)
console.log('includes',result)
7.2.4 flat
const list = [1,2,3,['2nd',4,5,6]]
let flatList = [].concat(...list)
console.log(flatList)

// 默认只展开第一层数组
let flatList2 = list.flat(2)
console.log('flat',flatList2)
7.2.5 map 数据映射
// map 数据映射
const json = [{title: 'es6', status: 1},{title: 'react', status: 0},{title: 'webpack', status: 1},{title: 'vue', status: 1}}
let video = json.map(function(item) {
	// return {
	//	 name: item.title,
	// 	 statusTxt: item.status ? '已上线' : '未上线'
	// }
	// 只更改一两个字段
	let obj = {}
	Object.assign(obj, item)
	obj.status = item.status ? '已上线' : '未上线'
	return obj
})
console.log('json',json)
console.log('video',video)
7.2.6. reduce 汇总
// reduce 对数组中的每个元素进行一次回调,升序执行然后将回调值汇总一个返回值
// cb(acc, currentValue, currentIndex, Array), initalValue
const letterList = 'abcadefrd'
const result = letterList.split('').reduce(function(acc, cur) {
	acc[cur] ? acc[cur]++ : acc[cur] = 1
	return acc
}, {})
console.log(result)

// 展开多层数组
const list = [1, ['2nd', 2, 3, ['3rd', 4, 5]],['2nd', 6, 7]]
const deepFlat = function(list) {
	return list.reduce(function(acc, cur) {
		return acc.concat(Array.isArray(cur) ? deepFlat(cur) : cur)
	}, [])
}
let flatList = deepFlat(list)
console.log('reduce-flat', flatList)
7.2.7. filter 过滤器
// filter 过滤器
// 判读真假
let arr = [2,4,6,8,10];

let narr = arr.filter(item => item%3 == 0);
console.log(narr); // [6]
7.2.8. forEach 循环(迭代)
// forEach 遍历
let arr = [2,4,6,8,10];

arr.forEach((value,index)=>{
	console.log(index,value);
});
7.2.9. some()
  • 一个为 true 就会返回 true
let arr = [2,4,6,8,10];
let narr = arr.some(item => item>10);
console.log(narr); // false
7.2.10. every()
  • 必须所有都返回 true 才会返回 true
let arr = [2,4,6,8,10];
let narr = arr.every(item => item>2);
console.log(narr); // false
7.2.11. Array.from
  • 类数组对象,有length,可遍历
const str = 'hello'
const strList = Array.from(str)
console.log(strList)

8. 对象的新特性及的新增方法

8.1 扩展运算符的使用
  • 注意
    • 简单类型的时候,使用扩展运算符是没问题的
    • 但是如果扩展运算符展开对象以后,还是一个对象的话,我们复制的只是一个指针
// 复制对象
const obj = { name: 'Nick', video: 'es6' }
const initObj = { color: 'red' }
let videoObj = { ...obj }
console.log(videoObj)

// 设置对象默认值
let obj2 = { ...obj, name: 'Jack' }
console.log(obj2)

// 合并对象
let obj3 = { ...obj, ...initObj }
console.log(obj3)
8.2 属性初始化的简写
let name = 'zs'
let age = 18
let es5Obj = {
	name: name,
	age: age,
	sayHello: function() {
		console.log('this is es5Obj')
	}
}

let es6Obj = {
	name,
	age,
	sayHello() {
		console.log('this is es6Obj')
	}
} 
8.3 可计算的属性名
let key = 'name'
let es5Obj = {}
es5Obj[key] = 'zs'
let es6Obj = {
	[key]: 'xh'
}
console.log(es5Obj,es6Obj)
8.5 新增方法
8.5.1 Object.is
// Object.is 判断值是否相等
// Object.is() 和 ===
let result = Object.is(NaN, NaN)
console.log(result, NaN === NaN)
8.5.2 Object.assign
// Object.assign 赋值
const person = {name:'zs', age: 19, info: { height: 190 }}
let person2 = {}
Object.assign(person2, person)
person.info.height = 180
console.log(person2)
8.5.3 Object.keys
const json = { name: 'Nick', video: 'es6', date: 2021 }
let obj = {}
for (const key of Object.keys(json)) {
	obj[key] = json[key]
}
console.log(obj) // name: Nick  video: es6  date: 2021
8.5.4 Object.values
const json = { name: 'Nick', video: 'es6', date: 2021 }
let obj = {}
for (const key of Object.values(json)) {
    obj[key] = json[key]
}
console.log(obj) // Nick es6 2021
8.5.5 Object.entries
const json = { name: 'Nick', video: 'es6', date: 2021 }
let obj = {}
for (const key of Object.entries(json)) {
    obj[key] = json[key]
}
console.log(obj) // name,Nick  video,es6  date,2021
8.6 Map与WeakMap结构的特点
8.6.1 背景
  • JavaScript中的对象,实质就是键值对的集合(Hash结构),但是在对象里却只能用字符串作为键名
  • 在一些特殊的场景里就满足不了我们的需求了,正因如此,Map这一数据提出了,它是JavaScript中的一种更完善Hash结构
8.6.2 Map对象
  • 用于保存键值对,任何值(对象或者原始值)都可以作为一个键或一个值
  • 使用介绍
// 通过构造函数创建一个Map
var m = new Map()
8.6.3 内置API
属性/方法作用例子
size返回键值对的数量m.size
clear()清除所有键值对m.clear()
has(key)判断键值对中是否有指定的键名,返回值是布尔值m.has(key)
get(key)获取指定键名的键值对,如不存在则返回undefinedm.get(key)
set(key, value)添加键值对,如键名已存在,则更新键值对m.set(key, value)
delete(key)删除指定键名的键值对m.delete(key)
// 添加
let map = new Map()
map.set([1,2,3], 'number')
console.log(map)

let map2 = new Map([['name','Nick'],['sex','male']])
console.log(map2)
console.log(map2.size)

map2.set('name','Jack').set('hobbies',['swimmin','running'])
console.log('set',map2)

// 获取
console.log('get',map2.get('hobbies'))
console.log('get2',map2.get('age'))

// 判断
console.log('has',map2.has('age'))

// 删除
map2.delete('hobbies')
console.log(map2)

// 清空
map2.clear()

// 遍历
// keys() values() entries() 遍历器生成函数 foreach
const map = new Map([
	['name', 'zs'],
	['age', 20]
])
// for of 循环 默认便利entries
for(let key of map){
	console.log(key)
}
8.6.4 WeakMap
// 1. 只接受对象作为一个键名,不接受其他类型的数据作为键名
// 2. 因为键名所指的对象不触发垃圾回收机制
// 3. 没有clear,没有size,无法遍历

let weakmap = new WeakMap({
	[{ name: 'hah'}, 'jack']
]}
console.log(weakmap)

const ulObj = document.getElementById('test')

let obj = {name: 'jack'}
let array = [obj, 'person']
array[0] = null
8.7 Set与WeakSet结构的特点
8.7.1 介绍
  • Set是ES6给开发者提供的一种类似数组的数据结构,可以理解为值的集合
  • 它和数组的最大的区别就在于
    • 它的值不会有重复项
  • 特点
    • 成员值唯一
8.7.2 基本使用
// 创建
let set = new Set()
let set2 = new Set([1,2,3])

// 添加元素
set.add(1)
8.7.3 属性及方法
属性/方法作用例子
size返回成员个数s.size
clear清除所有成员s.clear
has(value)判断键值对中是否有指定的值,返回值是布尔值s.has(value)
delete(value)删除指定值s.delete(value)
let set = new Set(['1',2,3,4,5])
// 添加元素
set.add(1)
console.log(set)
// 内部是使用Object.is(), 同值相等
console.log('size',set.size)

// 判断属性has, 删除属性delete, 清空clear
let set = new Set()
const item = {fruit: 'apple'}
// set.add({fruit: 'apple'})
set.add(item)
console.log('has',set.has({fruit: 'apple'})

// 遍历
// keys() values() entries() set里面key和value的值相等  for of 默认遍历 values
const set = new Set([1,2,3,4,5])
for (const keys of set) {
	console.log(keys)
}
for (const value of set) {
	console.log(value)
}
for (const value of set.entries()) {
	console.log(value)
}

// 数组去重
const array = [1,2,3,4,5,3,2,1,7,4]
let unique = new Set(array)
let uniqueArray = Array.from(unique)
console.log(uniqueArray)
8.7.4 WeakSet 和 Set 的区别
  • 元素只能是对象,对象也是弱引用
  • 无法遍历,没有size,也没有clear
let obj = {}
let weakSet = new WeakSet()
weakset.add(obj)
console.log(weakset)
8.8 Map、Set、Array及Object的区别
let array = []
let obj = {}
let map = new Map()
let set = new Set()
const gooditem = { fruit: 'apple' }

// 增加
array.push(gooditem)
obj['fruit'] = 'apple'
map.set('fruit', 'apple')
set.add(gooditem)
console.log('add',array,obj,map,set)

// 查询
const resultArray = array.includes(gooditem)
const resultObj = 'fruit' in obj
const resultMap = map.has('fruit')
const resultSet = set.has(gooditem)
console.log('search', resultArray, resultObj, resultMap, resultSet)

// 修改
array.forEach(function (item) {
	item.fruit = item.fruit ? 'orange' : ''
})
obj['fruit'] = 'orange'
map.set('fruit','orange')
set.forEach(function(item) {
	item.fruit = item.fruit ? 'orange' : ''
})
console.log('update', array, obj, map, set)

// 删除
const index = array.findIndex(funciton(item) {
	return item.fruit
})
array.splice(index, 1)
delete obj.fruit
map.delete('fruit')
set.delete(gooditem)
console.log('delete',array,obj,map,set)

// 类型转换 map 和对象间的转换
let obj = {
	name: 'Nick',
	hobbies: 'swimming'
}
console.log(Object.entries(obj))
let map = new Map(Object.entries(obj))
console.log('map',map)

let obj2 = Object.fromEntries(map)
console.log('obj',obj2)

// 数组和set
let array = [1,2,3,4,5]
let set = new Set(array)
console.log('set',set)
let array2 = Array.from(set)
console.log('array', array2)
8.9 代理Proxy和反射Reflect
8.9.1 Proxy
  • 概述
    • 正如Proxy的英译“代理”所示,Proxy是ES6为了操作对象引入的API
    • 它不直接作用在对象上,而是作为一种媒介,如果需要操作对象的话,需要经过这个媒介的同意
  • 使用方式
let p = new Proxy(target, handler)
let accout = {
	id: 001,
	name: 'admin',
	_private: 'test'.
	phone: '12345678901',
	create_item: '2019'
}

let accoutProxy = new Proxy(account, {
	// 拦截读取和设置的操作
	get: function(target, key) {
		switch(key){
			case 'phone':
				return target[key].substring(0,3) + '****' + target[key].substring(7)
			case 'create_item':
				return target[key].replace('2019', 2020)
			default: 
				break:
		}
	},
	// 拦截设置
	set: function(target, key, value) {
		if(key === 'id') {
			return target[key]
		} else {
			return target[key] = value
		}
	},
	// 拦截 key in obj
	has: function(target, key) {
		if(key in target) {
			console.log(`${key}:`, target[key])
			return true
		} else {
			console.log('并无此属性')
			return false
		}
	}
	// 拦截删除delete
	deleteProperty: function() {
		if(key.indexOf('_') === 0) {
			console.warn('私有属性不能被删除')
			return false
		} else {
			delete target[key]
			return true
		}
	}
	// 拦截Object.keys()
	ownKeys(target) {
		return Object.keys(target).filter(function(item) {
			return item !== 'id' && item.indexOf('_') !== 0
		})
	}
	
})

console.log('拦截读取', accountProxy.phone, accountProxy.create_item)
accountProxy.id = 1234
accountProxy.name = 'guast'
console.log('拦截设置', accountProxy.id, accountProxy.name)
console.log('拦截in', 'sex' in accountProxy)
console.log('删除', delete accountProxy['_private'])
console.log('拦截Object.keys()', Object.keys(accountProxy))
8.9.2 Reflect
  • 概述
    • 与Proxy相同,ES6引入Reflect也是用来操作对象的,它将对象里一些明显属于语言内部的方法移植到Reflect对象上,它对某些方法的饭后结果进行了修改,使其更合理,并且使用函数的方式实现了Object的命令式操作
  • 使用方法
let obj = {
	name: 'Nick',
	age: '21',
	sex: 'male',
	hobbies: 'swimming'
}
console.log(Reflect.get(obj, 'name'))
Reflect.set(obj, 'name', 'Jack')
console.log(obj.name)
'name' in obj
Reflect.has(obj, 'name')
8.9.3 双向数据绑定
<h1>使用Proxy和Reflect实现双向数据绑定</h1>
<input type="text" id="input">
<h2>您输入的是:<i id="txt"></i></h2>
// 获取dom元素
const inputObj = document.getElementById('input')
const txtObj = document.getElementById('txt')

// 初始化代理对象
const obj = {}

// 代理选项
const handler = {
    get: function(target, key) {
      return Reflect.get(target, key)
    },

    set: function(target, key, value) {
      if (key === 'text') {
        inputObj.value = inputObj.value === value ? inputObj.value : value
        txtObj.innerHTML = value
      }
      return Reflect.set(target, key, value)
    }
}

let objProxy = new Proxy(obj, handler)

// 给input添加键盘键入事件
inputObj.addEventListener('keyup',function(e) {
  objProxy.text = e.target.value
})

9. 函数的扩展、类、模块化

9.1 函数的扩展
9.1.1. 默认参数
// 默认参数
function es5Print(x,y) {
  y = y || 'world'
  console.log('es5', x+y)
}
es5Print('hello','')

function es6Print(x, y='world') {
  console.log('es6',x+y);
}
es6Print('hello','')
9.1.2. 参数扩展
// rest
function add(...rest) {
  let sum = 0
  console.log(rest);
  for(let value of rest) {
    sum += value
  }
  console.log(sum);
}
add(1,2,3,4,5)
9.1.3 扩展运算符
console.log(...[1,2,3,4,5])
9.1.4 尾调用
function step2(x) {
	console.log('尾调用',x);
}

function step1(x) {
 	return step2(x)
}
9.2 深入浅出箭头函数
  • 特点
    • 更短的函数
    • 不绑定this
  • 注意事项
    • 什么情况下用箭头函数
    • 箭头函数没有argument
## 允许使用箭头(=>)定义
var f = function(v){
	return v;
}
// 等同于
var f = v => v;
==============================================

## 不需要参数或需要多个参数,就用圆括号代替
var f = function(){
	return 5
};
// 等同于
var f = () => 5;

var sum = function(num1,num2){
	return num1 + num2;
}
// 等同于
var sum = (num1,num2) => num1+num2;
==============================================

## 代码块部分多于一条语句,就用大括号括起来,并且用 return 返回
var sum = (num1,num2) => {return num1+num2;}
==============================================

## 箭头函数返回对象时,必须在对象外面加上括号
var getTempItem = id => ({ id: id, name: 'Temp' });
console.log(getTempItem(2));
==============================================

## 箭头函数能够简化回调函数
[1,2,3].map(function(x){
	return x * x;
)}
// 等同于
[1,2,3].map(x => x * x);
============================================
var result = values.sort(function(a,b){
	return a - b;
});
// 等同于
var result = values.sort((a,b) => a - b);
9.3 this 指向问题
9.3.1 普通函数中的 this
  • this 总是代表它的直接调用者jsthis执行上下文)
    • 例如 obj.func,那么 func 中的 this 就是 obj
  • 非严格模式下未使用 use strict
    • 没找到直接调用者,则 this 指的是 window
  • 在严格模式下
    • 没有直接调用者,函数中的 thisundefined
  • 使用 callapplybind(ES5 新增)绑定的,this 指的是绑定的对象
9.3.2 箭头函数中的 this
  • 箭头函数没有自己的 this,它的 this 是继承而来的
  • 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象,定义它的时候,可能环境是 window
  • 箭头函数可以方便地让我们在 setTimeoutsetInterval 中方便的使用 this
  • 箭头函数中,this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this ,导致内部的 this 就是外层代码块的 this
function Person (){
	this.name = 'little bear123',
	this.age = 1234
	setInterval(() => {
		console.log(this);
		console.log('我叫' + this.name + '我今年' + this.age + '岁')
	},10000)
}
let p = new Person()

// 结果
Person{name:"little bear123",age:1234}
我叫little bear123我今年1234// 解析
这段函数中,this 指向的是 `Person` 对象,可以看到控制台打印结果,此时的箭头函数所处的宿主对象是 `Person`,所以 this 指向的是 `person`
9.4 类的概念
9.4.1 简介
  • 传统的JavaScript中只有对象,没有类的概念
  • 它是基于原型的面向对象语言
  • 原型对象特点就是将自身的属性共享给新对象
9.4.2 new 操作符做了什么
  • 返回(产生)了一个新的对象
  • 将这个空对象的_proto_指向了构造函数内部的prototype
  • 将构造函数的this绑定到这个对象上(即new创建的对象,其函数体内的this指向的是这个对象)
  • 返回到这个新对象上
const obj = {}
obj._proto_ = 构造函数.prototype
构造函数.call(obj)
// ES5的时候是通过构造函数来实现类的功能
function Person(name, age){
	this.name = name
	this.age = age
}
Person.prototype.sayHello = function() {
	console.log(this.name, this.age)
}
const p = new Person('zs',12)
console.log(p)
9.4.3 ES6 class的概念
  • 定义类
// ES6改造ES5实现类的方法
class Person {
	constructor(name, age) {
		this.name = name
		this.age = age
	}
	sayHello() {
		console.log(this.name, this.age)
	}
}
const p = new Person('ls',12)
console.log(p)
  • 类的继承
class Parent {
	constructor(name = 'Nick') {
		this.name = name
	}
}

class Child extends Parent {
	constructor(name = 'Jack') {
		super(name) // 复写属性,放在构造函数最前面
		this.name = name
	}
}

console.log('继承', new Child())
  • 子类向父类传递参数
class Person {
	constructor(name = 'Nick') {
		this.name = name
	}
	get fullName() {
		return this.name
	}
	set fullName() {
		this.name = value
	}
}
const p = new Person()
console.log('get',p.fullName)
p.fullName = 'Jack'
console.log('set',p.fullName)
  • 静态方法
class Person {
	constructor(name = 'Nick') {
		this.name = name
	}
	static sayHello(obj) {
		console.log(111,obj.name)
	}
}
const p = new Person('xx')
Person.sayHello(p)
  • 静态属性
class Person {
	static prop = 'test'
	constructor(name = 'Nick') {
		this.name = name
	}
	static sayHello(obj) {
		console.log(111,obj.name)
	}
}
Person.prop = 'test'
console.log(Person.prop)
9.5 模块化开发
9.5.1 背景
  • ES6之前是没有类的概念的,也就是没有模块这个说法
  • 理想情况下,开发者应该只注重核心业务的开发,对于其他有一定通用性的业务可以直接引入别人的代码,也就是模块
  • 多人开发,本应如此
9.5.2 提出的方案
  • Common JS
    • Commonjs是作为Node中模块化规范以及原生模块面世的
  • AMD和Requirejs
    • AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"
    • 它采用异步方式加载模块,模块的加载不影响它后面语句的运行
    • 所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载完成之后(前置依赖),这个回调函数才会运行
  • import 和 export
## a.js
export let a = 3;

export function sayHello() {
	console.log('hello')
}

export class test {
	say() {
		console.log('test')
	}
}

## b.js
// import {a,sayHello,test} from 'a.js'
import * as test from 'a.js'

// console.log(a,sayHello,test)
console.log(test)
## a.js
export let a = 3;

function sayHello() {
	console.log('hello')
}

export default {
	a,
	sayHello
}

## b.js
import test from 'a.js'

console.log(test.a)

10. 深入解析JavaScript中的异步编程

10.1 异步编程及JavaScript的异步实现
10.1.1 什么是同步
  • 当一个“调用”发出时,在没有得到结果之前,这个“调用”就会阻塞后面代码的执行,得到结果的时候才会返回
  • 换句话说,“调用者”要主动等待代码的执行结果,得到返回结果后,程序才会继续执行
10.1.2 什么是异步
  • “调用”发出的时候,就直接返回了,对应的结果会通过状态、通知来告诉“调用者”或通过回调函数处理这个调用
  • 异步调用发出后,不会阻塞后面的代码
10.1.3 为什么引入异步
  • JavaScript是单线程
  • 同步代码会阻塞后面的代码
  • 异步不会阻塞程序的运行
10.1.4 JavaScript异步实现
  • 回调函数
  • setTimeout 和 setInterval
  • Promise
  • Generator
  • async
10.2 Promise 应用
10.2.1 什么是Promise
  • ES6中一个非常重要和好用的特性就是Promise
  • Promise作用
    • Promise 是异步编程的一种解决方案
    • Promise保存着某个未来才会结束的事件
      • 通常是一个 异步操作--接口--Ajax--setTimeout--img 的结果
    • ES6 规定,Promise对象是一个构造函数,用来生成Promise实例
  • 什么时候处理异步事件
    • 常见的就是网络请求
      • 封装一个网络请求的函数,不能立即拿到结果返回
      • 所以往往会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去
      • 如果只是简单的网络请求,该方案可行
      • 但是当网络请求非常复杂时,就会出现回调地狱
10.2.2 网络请求的回调地狱
  • 场景
    • 需要通过一个url1从服务器加载一个数据data1data1中包含了下一个请求的url2
    • 需要通过data1取出url2,从服务器加载数据data2data2中包含了下一个请求的url3
    • 需要通过data2取出url3,从服务器加载数据data3data3中包含了下一个请求的url4
    • 发送网络请求url4,获取最终的数据data4
$.ajax('url1',function(data){
	$.ajax(data1['url2'],function(data2){
		$.ajax(data2['url3'],function(data3){
			$.ajax(data3['url4'],function(data4){
				console.log(data4);
			})
		})
	})
})
  • 问题
    • 代码难看而且不容易维护
    • 需要更加优雅的方式来进行这种异步操作
  • 使用Promise
    • Promise可以非常优雅的来解决这个问题
const promise = new Promise((resolve,reject)=>{
	// ...some code
	
	if(/*异步操作成功*/){
		resolve(value);
	}else{
		reject(error);
	}
});
  • Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject,它们是两个函数,由JavaScript引擎提供,不用自己部署
  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去
  • Promise实例生成后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数
promise.then(function(value){
	// success
},function(error){
	// false
});
  • then 方法可以接受两个回调函数作为参数。
    • 第一个回调函数是 Promise 对象的状态变为 resolved 时调用
    • 第二个回调函数是 Promise 对象的状态变为 rejected 时调用。
    • 其中,第二个函数是可选的,不一定要提供。这两个函数都接受 Promise 对象传出的值作为参数
10.2.3 Promise 的使用
  • all()
let one = new function(){
	setTimeout(()=>{
		resolve('aa'):
	},1000);
})

let two = new function(){
	setTimeout(()=>{
		resolve('bb'):
	},1000);
})

let three = new function(){
	setTimeout(()=>{
		resolve('cc'):
	},1000);
})

// 所有异步操作执行后再调用
// 可以并行执行多个异步操作
// 并且在一个回调中处理所有数据返回结果
Promise.all([one,two,three]).then(arr=>{
	let[one,two,three] = arr;
	console.log(one);
	console.log(two);
	console.log(three);	
  • race()
let one = new function(){
	setTimeout(()=>{
		resolve('aa'):
	},1100);
})

let two = new function(){
	setTimeout(()=>{
		resolve('bb'):
	},1050);
})

let three = new function(){
	setTimeout(()=>{
		resolve('cc'):
	},1300);
})

// 多台服务器的时候,请求资源同列,分布式
// 同时连接多个服务器地址,做负载均衡
// 先请求到哪个服务器数据,就用哪个服务器数据
Promise.race([one,two,three]).then(arr=>{
	console.log(arr); // bb
}
  • catch()
function foo(){
	return new Promise((resolve,reject)=>{
		setTimeout(()=>{
			resolve();
		},1000)
	});
}

foo().then(()=>{
	console.log('aa');
	return foo();
}).then(()=>{
	console.log('bb');
	console,log(data);
	return foo();
}).catch(rea=>{
 	console.log('error');
 	console.log(rea);
});
  • async/await
async function demo(){
	console.log('this is a async demo');
}

// 返回Promise对象
console.log(demo());
function demo(){
	return new Promise((resolve,reject) =>{
		setTimeout(()=>{
			resolve(2*num);
		},2000)
	})
}

async function test(){
	await demo(20);
}

test();
10.2.4 定时器的异步事件
  • 这里,我们用一个定时器模拟异步事件
    • 假设下面的data是从网络上1秒后请求的数据
    • console.log就是我们的处理方式
  • 这是我们过去的处理方式,我们将它换成Promise代码
setTimeout(function () {
	let data = 'hello world'
	console.log(content);
},1000)
new Promise((resolve,reject)=>{
	setTimeout(function(){
		resolve('hello world')
		reject('Error Data')
	},1000)
}).then(data => {
	console.log(data);
}).catch(error => {
	console.log(error);
})
10.2.5 定时器异步事件解析
  • 我们先来认认真真的读一读这个程序到底做了什么?
    • new Promise很明显是创建一个Promise对象
    • 小括号中((resolve, reject) => {})也很明显就是一个函数,而且我们这里用的是箭头函数
    • 我们先知道一个事实:在创建Promise时,传入的这个箭头函数固定的(一般我们都会这样写)
    • resolvereject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个
    • 成功还是失败?
      • 如果是成功的,那么通常我们会调用resolve(messsage),这个时候,我们后续的then会被回调
      • 如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调
10.2.6 Promise三种状态
  • 首先, 当我们开发中有异步操作时, 就可以给异步操作包装一个Promise
    异步操作之后会有三种状态
  • 我们一起来看一下这三种状态:
    • pending等待状态,比如正在进行网络请求,或者定时器没有到时间。
    • fulfill满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
    • reject拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
      在这里插入图片描述
new Promise((resolve,reject) => {
	setTimeout(function () {
		// resolve('hello world')
		reject('error data')
	},1000)
}).then(data => {
	console.log(data);
},error => {
	console.log(error);
})
10.2.7 Promise链式调用
  • 我们在看Promise的流程图时,发现无论是then还是catch都可以返回一个Promise对象
  • 所以,我们的代码其实是可以进行链式调用的:
  • 这里我们直接通过Promise包装了一下新的数据,将Promise对象返回了
    • Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
    • Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
// 链式调用的代码
new Promise((resolve,reject) => {
	setTimeout(function () {
		resolve('hello world')
	},1000)
}).then(data => {
	console.log(data); // => hello world
	return Promise.resolve(data + '111')
}).then(data => {
	console.log(data); // => hello world111
	return Promise.resolve(data + '222')
}).then(data => {
	console.log(data); // => hello world111222
	return Promise.reject(data + 'error')
}).then(data => {
	console.log(data); // 这里没有输出,这部分代码不会执行
	return Promise.resolve(data + '333')
}).catch(data => {
	console.log(data); // => hello world111222error
	return Promise.resolve(data + '444')
}).then(data => {
	console.log(data); // => hello world111222error444
  • 链式调用简写
    • 简化版代码
      • 如果我们希望数据直接包装成Promise.resolve,那么在then中可以直接返回数据
      • 注意下面的代码中,我讲return Promise.resovle(data)改成了return data
      • 结果依然是一样的
// 链式调用的简便写法
new Promise((resolve,reject) => {
	setTimeout(function(){
		resolve('hello world')
	},1000)
}).then(data => {
	console.log(data); // => hello world
	return data + '111'
}).then(data => {
	console.log(data); // => hello world111
	return data + '222'
}).then(data => {
	console.log(data); // => hello world111222
	return Promise.reject(data + 'error')
}).then(data => {
	console.log(data); // 这里没有输出,这部分代码不会执行
	return data + '333'
}).catch(data => {
	console.log(data); // => hello world111222error
	return data + '444'
}).then(data => {
	console.log(data); // => hello world111222error444
10.3 数据结构中的遍历接口 Iterator 和 for of 循环
10.3.1 什么是 Iterator
  • Iterator(遍历器)是一种接口,目的是为了给不同的数据结构提供统一的循环方式,任何数据结构如果部署了 iterator 接口,就能够实现遍历的操作
// 什么时Iterator
// 取出数据集合里的数据,通过遍历,Iterator提供了一个接口,通过for of调用这个接口,输出数据集合中的数据
const arr = [1,2,3]
const fn = arr[Symbol.iterator]()
console.log(fn.next())
console.log(fn.next())
console.log(fn.next())
console.log(fn.next())
10.3.2 Iterator 的作用
  • 为不同的数据结构,提供一个统一的、简便的访问接口
  • 将数据成员按照一定的顺序输出
  • 提供给ES6中的for of 的这个循环语句进行使用
10.3.3 什么结构具备原生的 iterator 接口
  • Array
  • String
  • Set
  • Map
  • 函数的 argument 对象
// 应用常见
const obj = {
	color: 'red',
	price: 18,
	size: 'small',
	[Symbol.iterator]() {
		let index = 0
		const values = Object.values(this)
		return {
			next() {
				if(index < values.length) {
					return {
						value: values[index ++],
						done: false
					}
				} else {
					return {
						done: true
					}
				}
			}
		}
	}
}

for (const value of obj) {
	console.log(value)
}
10.4 generator 应用
10.4.1 generator 和 yield 的使用
  • Generator 主要用于异步编程,就是封装一个异步任务,或者说是异步任务的容器
  • 特点是可以交出函数执行全(展停执行)
  • 在声明函数的 function 关键字与函数名之间有一*(用于区别普通函数)
  • yieldGenerator 函数体内使用,可以定义不同的内部状态(可以设置不同时候不一样的值)
  • yield 命令是异步不同阶段的分界线,有时也会把 yield 当成是 return(当然有本质区别)
  • 使用启动 next() 方法,分阶段执行 Generator 函数
function *demo(){
	console.log('1');
	yield
	console.log('2');
	yield
	console.log('3');
}

let genob = demo();
genobj.next();
genobj.next();
genobj.next();
let obj = {
	a: 1,
	b: 2,
	c: 3
}

obj[Symbol.iterator] = function() {
	for (const key of Object.keys(obj)) {
		yield obj[key]
	}
}

for(const value of obj) {
	console.log(value)
}
// 任何时候都只有一定数量种状态
const state = function() {
	whilw(1) {
		yield 'success'
		yield 'fail'
		yield 'pending'
	}
}
const stateData = state()
console.log(stateData.next())
console.log(stateData.next())
console.log(stateData.next())
console.log(stateData.next())
// 长轮询
function fn1() {
	return new Promise(resolve => {
		setTimeout{ () => {
			console.log('查询中')
			resolve({code: 0})
		},1000)
	})
}

const getStatus = function() {
	yield fn1()
}

function autoGetStatus() {
	const gen = getStatus()
	const status = gen.next()
	status.value.then(res => {
		if(res.code === 0) {
			console.log('用户付款成功')
		} else {
			console.log('暂未付款')
			setTimeout(()=> autoGetStatus(), 500)
		}
	}
}

autoGetStatus()	
const ajax = function() {
	console.log('start')
	yield function(cb) {
		setTimeout(() => {
			console.log('异步任务结束')
			cb && cb()
		},1000)
	}
	console.log('end')
}

const runAjax = ajax()
const first = runAjax.next()
first.value(() => runAjax.next())

11. ES7-ES9 新特性

11.1 ES7 新功能
  • Array.prototype.includes 检查数组中是否存在值;(区别ES6字符串的includes方法)
  • Exponentiation Operator 求幂运算(a**b等价于Math.pow(a,b))
11.2 ES8 部分功能
  • Object.values/entries/getOwnProertyDescriptors
  • String.prototype.padStart/padEnd
  • 函数参数列表和调用中的尾逗号(trailing commas)
  • Async Functions 异步函数(async/await
11.3 ES9 新增部分功能
  • 异步迭代
  • Promise.finally()
  • Rest/Spread 属性
  • 正则表达式命名捕获组
  • 正则表达式反向断言
  • 非转义序列的模版字符串
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值