const let var
var 存在声明提升,值为undefined;可以重复声明,声明后被覆盖
let 不存在声明提升,所以会存在暂时性死区;在同一个作用域内不能重复声明,声明的变量在所在的代码块内有效
const 定义常量,所以一旦声明就需要立即初始化,声明的变量在所在的代码块内有效,在同一个作用域内不能重复声明
js数据类型
基本数据类型
- Number
- String
- Boolean
- Undefined
- null
- symbol
引用类型(object)
复杂数据类型统称为Object es6新添加有 Set Map
基本数据类型存储在栈中;引用类型的对象存储在堆中
当变量赋值,解析器首先要确认引用类型还是基本类型
- 声明变量时不同的内存地址分配:
- 简单类型的值存放在栈中,在栈中存放的是对应的值
- 引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
- 不同的类型数据导致赋值变量时的不同:
- 简单类型赋值,是生成相同的值,两个对象对应不同的地址
- 复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象
!!! es6新增基本数据类型Symbol
因为对象属性的数据类型都是字符串,会导致属性名重复;symbol就是解决对象属性名重复,导致属性值被覆盖的问题
唯一性
1
2
3console.log(Symbol() === Symbol() )//false
//类似于NaN
console.log(NaN() === NaN() )//false不具备迭代器接口( !Symbo.iterator ) 不能用for in 或 for of 循环
1
2
3
4
5
6
7var person = {
name: "张三",
age: 12,
[Symbol('level')]: 'A'
}
//需要用Reflect.ownKeys() 才能获取到所有的key
Reflect.ownKeys(person)Symbol.for() 与 Symbol.keyFor()
1
2
3let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
s1 == s2 //true 创建Symbol.for('foo')会全局寻找是否之前创建过Symbol.for('foo'),如果有则直接使用以创建的没有则创建
数组常用方法
- 增
- push() unshift() splice() concat()
1 | //push() 接收任意数量参数,并添加到末尾,返回数组最新长度 |
- 删
- pop() shift() splice() slice()
1 | //pop() 用于删除数组的最后一项,返回被删除项 |
- 改
- splice() 删一个加一个,相当于改
1 | // 下标 |
- 查
- indexOf() includes() find()
1 | // indexOf() 返回查找元素位置,找到返回index,找不到返回 -1 |
- 排序
- reverse() 反转 sort() 传入一个比较函数
1 | function compare(value1, value2) { |
转换方法
- join() 方法接收一个参数,即字符串分隔符,返回包含所有项的字符串
1
2let array = [12,23,34]
let arr = array.join("+") // "12+23+34"迭代方法
- some()[ES6] every()[ES6] forEach() filter() map()
1 | //some() 对数组每一项都运行传入的测试函数,如果至少有1个元素返回 true ,则这个方法返回 true |
ES6新增数组方法
- Array构造函数新增方法:扩展运算符 Array.from() Array.of()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//扩展运算符
console.log(...[1, 2, 3]) // 1 2 3
console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5
[...'hello'] // [ "h", "e", "l", "l", "o" ]
[...document.querySelectorAll('div')] // [<div>, <div>, <div>] 伪数组转成真数组
/**
伪数组:伪数组没有Array.prototype,它只是一个对象;伪数组的索引,就是那些键值对的key,没有真正的顺序可言;长度是手动设置的
数组:数组有Array.prototype,他是对象的同时,也是数组;数组的索引和长度是内置属性
*/
//Array.from() 将两类对象转为真正的数组:类似数组的对象和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
//还可以接受第二个参数,用来对每个元素进行处理
Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9]
//Array.of()
/**
没有参数的时候,返回一个空数组;
将一组值,转换为数组*/
Array.of() // []
Array.of(3) // [3]
Array.of(3, 11, 8) // [3,11,8]- 实例对象新增方法:copyWithin() find()、findIndex() fill() entries(),keys(),values() includes() flat(),flatMap()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//fill() 使用给定值,填充一个数组
new Array(3).fill(7)// [7, 7, 7]
//还可以带上参数,填充到指定位置
['a', 'b', 'c'].fill(7, 1, 2)// ['a', 7, 'c']
//entries(),keys(),values()
/**
entries()是对键值对的遍历
keys()是对键名的遍历
values()是对键值的遍历*/
for (let [index, item] of ['a', 'b'].entries()) {
console.log(index, item);
}
// 0 "a"
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let item of ['a', 'b'].values()) {
console.log(item);
}
// 'a'
// 'b'
//flat() 数组扁平化处理
[1, 2, [3, [4, 5]]].flat(2)// [1, 2, 3, 4, 5]
// flatMap()相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2]) //[2, 4, 3, 6, 4, 8]
数组incloudes方法
可以简便实现同一个属性的多种判断
1 | //正常判断用法 |
字符串常用方法
- 增 concat
1 | //concat 用于将一个或多个字符串拼接成新字符串,不会改变原字符串 |
- 删 slice() substr() substring()
1 | let stringValue = "hello word" |
改
- trim() trimLeft() trimRight()
1
2
3
4
5
6
7
8
9
10let stringValue = " hello world "
//trim() 删除前后打头的所有空格符,返回新字符串
let trimStringValue = stringValue.trim()
console.log(trimStringValue) // "hello word"
//trimLeft() 删除前面打头所有空格,返回新字符串
let trimLeftStringValue = stringValue.trimLeft()
console.log(trimLeftStringValue) //“hello word ”
//trimRight() 删除后面打头所有空格,返回新字符串
let trimRightStringValue = stringValue.trimRight()
console.log(trimRightStringValue) //" hello word"- repeat()
1
2
3//repeat() 接收一个整数参数,表示要将字符串复制多少,然后返回拼接所有副本后的结果
let strintValue = "nana "
let copyResult = stringValue.repeat(2) //"nana nana "- padStart() podEnd()
1
2
3
4
5
6
7let stringValue = "foo"
//padStart() 复制字符串,如果小于指定长度,则在前面填充字符,直到长度满足,默认填充空格
console.log(stringValue.padStart(6)) // " foo"
console.log(stringValue.padStart(6,"a")) // "aaafoo"
//padEnd() 复制字符串,如果小于指定长度,则在后面填充字符,直到长度满足,默认填充空格
console.log(stringValue.padEnd(6)) // "foo "
console.log(stringValue.padEnd(6,"a")) // "fooaaa"- toLowerCase() toUpperCase()
1
2
3
4
5let stringValue = "hello word"
// toLowerCase() 将字符串转成小写
console.log(stringValue.toUpperCase()) //""hello word""
// toUpperCase() 将字符串转成大写
console.log(stringValue.toLowerCase()) //"HELLO WORD"查
- chatAt()
1
2
3//chatAt() 返回给定索引位置的字符
let stringValue = "hello word"
console.log(stringValue.charAt(2)) //"l"- indexOf()
1
2
3//indexOf() 从字符串开头去搜索传入的字符串,并返回索引位置(没有找到,则返回-1)
let stringValue = "hello word"
console.log(stringValue.indexOf("o")) //4- startsWith() includes()
1
2
3
4
5
6
7
8//从字符串中搜索传入字符串,并返回一个表示是否包含的返回布尔值
let stringValue = "hello word"
//startWith()
console.log(stringValue.startsWith(llo)) //true
console.log(stringValue.startsWith(oll)) //false
//includes()
console.log(stringValue.includes(oll)) //false
console.log(stringValue.includes(llo)) //true转换成数组
- splic() 跟 数组方法 join() 相反
1
2let stringValue = "12+13+14"
let arr = str.split("+") //[12, 23, 34]模板匹配
- match() search() replace()
1
2
3
4
5
6
7
8
9
10
11
12
13let text = "cat, bat, sat, fat"
//match() 接收一个参数,可以是正则表达式,也可以是RexExp对象,返回数组
let pattern = /.at/;
let matches = text.match(pattern)
console.log(matches[0]) // "cat" 只要遇到匹配的后续不会再进行匹配
//search() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,找到则返回匹配索引,否则返回 -1
let pos = text.search(/at/)
console.log(pos) //1
//replace() 接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数),返回替换后的字符串,只会更换第一个匹配的字符串
let result = text.replace("at", "omd")
console.log(result) //"comd, bat, sat, fat"字符串反转(应用)
- 利用扩展运算符变成数组(或字符串的split() 方法),对数组进行反转,在合并成字符串
1 | let str = "123456" |
利用循环
1
2
3
4
5var res = [];
for(i = str.length; i>=0; i--){
res.push(i+1)
};
let unstr = res.join("") //"654321";
对象
对象便利 (对象不具备迭代器数据结构,不能使用for of 遍历)
- 对象 for in 遍历
1 | let obj = { |
- 对象keys遍历
1 | let obj = { |
- 对象value遍历
1 | let obj = { |
- 对象getOwnPropertyNames 遍历
1 | let obj = { |
- 使用Reflect.ownKeys(obj) 遍历
1 | let obj = { |
- 属性简写
1 | const onj = { |
- 属性名表达式
1 | //sysbol ES6 允许字面量定义对象时,将表达式放在括号内 |
- super关键字
1 | //this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象 |
- 扩展运算符的应用
1 | let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; |
- ES6对象新增的方法
1 | Object.is() //严格判断两个值是否相等,与 === 行为基本一致。 |
map数据类型使用实例
1 | var obj1 = { |
对象解构赋值
1 | let person = { |
函数
ES6新增方法
- 默认参数
1 | function foo({x, y = 5} = {}) { |
- 函数的length属性
1 | // 1.length将返回没有指定默认值的参数个数 |
- name属性
1 | //返回该函数的函数名 |
ES6新增
Set
Set是es6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合
- 增 添加某个值,返回
Set
结构本身
1 | const s = new Set(); |
- 删 删除某个值,返回一个布尔值,表示删除是否成功
1 | const s = new Set(); |
- 查 判断值是否存在
1 | const s = new Set(); |
- clear() 清除所有成员,没有返回值
1 | const s = new Set(); |
- 遍历
1 | let set = new Set(['red', 'green', 'blue']); |
Map
Map
类型是键值对的有序列表,而键和值都可以是任意类型
- 增 设置键名
key
对应的键值为value
,然后返回整个 Map 结构;可采用链式写法
1 | const m = new Map() |
- 删 delete
方法删除某个键,返回
true。如果删除失败,返回
false
1 | const m = new Map(); |
- 改
key
已经有值,则键值会被更新,否则就新生成该键
1 | const m = new Map() |
- 查
has
方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
1 | const m = new Map(); |
- get get
方法读取
key对应的键值,如果找不到
key,返回
undefined
1 | const m = new Map(); |
- size属性
1 | const map = new Map(); |
- clear
clear
方法清除所有成员,没有返回值
1 | let map = new Map(); |
- 遍历
1 | const map = new Map([['F', 'no'],['T', 'yes']]); |
ES6 之前, 如果对象属性是对象,则后面会覆盖前面的属性
1 | var obj1 = { |
WeakSet 和 WeakMap
WeakSet
WeakSet
可以接受一个具有Iterable
接口的对象作为参数没有遍历操作的
API
没有
size
属性WeakSet
只能成员只能是引用类型,而不能是其他类型的值;WeakSet
里面的引用只要在外部消失,它在WeakSet
里面的引用就会自动消失
1 | let ws=new WeakSet(); |
WeakMap
WeakMap
结构与Map
结构类似,也是用于生成键值对的集合;在API
中WeakMap
与Map
有两个区别:- 没有遍历操作的
API
- 没有遍历操作的
1
2
3
4
5
6
7
8
9
10
11
12// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2
// WeakMap 也可以接受一个数组,
// 作为构造函数的参数
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为键名;WeakMap
的键名所指向的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失
1
2
3
4
5
6
7const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key
箭头函数
箭头函数this指向
箭头函数不能当做构造函数
箭头函数不可以当做迭代器
this指向(谁调用我我指向谁)
1 | function get(content){ |
1 | var person = { |
1 | var name = 222; |
改变this指向
箭头函数没有自己的this,this是外层代码块的this,this是在定义函数时绑定的。不能够使用做构造函数
1 | var name = 11 |
手写call、apply
作用:改变this指向
场景: js的继承(原型链继承;构造函数继承(使用call实现) )
call
可以用作
- 继承
- 判断复杂数据类型
伪数组转换成数组
手写call方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32var person = {
getName: function(){
//以函数的形式调用对象方法则指向window(浏览器环境)|global(nodejs环境),以方法的形式调用时,this指向调用方法的对象
return this.name
}
}
var person1 = {
name: ”张三“
}
//因为call方法是对象直接调用,有点类似是类的静态变量,每个对象方法都能调用,实现方法是直接挂在到Function构造函数的原型上。
Function.prototype.myCall = function(context) {
//这里面的this是调用该myCall方法的对象方法function
//所以传入的参数第一个参数必须是function类型,不是function返回错误
if(typeof this !== 'function'){
throw new Error('error')
}
//如果参数为空则直接this指向window
context = context || window
//获取除第一个参数的其余参数
var args = [...arguments].slice(1)
//这里直接使用this,则直接指向getName,以函数的方式调用,所以getName的this指向window,获取到的name是全局变量。但是我们可以用context传入的参数来改变getName的this
//首先是确定context传入的参数对象有getName方法,直接赋予getName方法
context.fn = this
//最后返回传入对象参数context.fn,也就是context.getName
let result = contest.fn(...args)//对数组args解构
delete context.fn
return result
}
//实际实现方法就是将传入的对象赋予该对象调用的函数,然后再用传入进去的对象调用该函数,从而达到改变this指向
person.getName.myCall(person1, 1, 2)apply
手写apply(其实与call方法一致,只是传入参数方式不同)
1 | var person = { |
js事件循环机制;宏任务与微任务 事件队列
js语言特点
- 单线程
- 解释性语言(解释一行执行一行)
event-loop - 事件循环机制
事件循环机制 是基于事件驱动的,主要涉及执行栈、事件队列、宏任务和微任务。
- 执行栈(Call Stack): 用于存储执行上下文(函数调用)的栈结构。当函数被调用时,会被推入执行栈,执行完毕后会被弹出。如果执行栈为空,则表示当前代码执行完毕。
- 事件队列(Event Queue): 用于存储待执行的任务,分为宏任务队列和微任务队列。宏任务和微任务会被按顺序放入对应的队列中等待执行。
- 事件队列 - 宏任务(Macrotask): 包括由浏览器或Node.js提供的异步API,如setTimeout、setInterval、I/O操作等。宏任务会被放入宏任务队列中等待执行。
- 事件队列 - 微任务(Microtask): 包括由JavaScript引擎自身提供的异步API,如Promise、process.nextTick等。微任务会被放入微任务队列中等待执行。
工作流程
- 当代码开始执行时,首先会执行同步任务,将同步任务按顺序推入执行栈。
- 遇到异步任务时,会将其放入对应的事件队列中。
- 当执行栈为空时,事件循环会从微任务队列中取出所有微任务依次执行,直到微任务队列为空。
- 如果在执行微任务的过程中,又产生了新的微任务,会继续执行微任务队列中的任务,直到微任务队列为空。
- 当微任务队列为空时,事件循环会从宏任务队列中取出一个宏任务执行。
- 执行完当前宏任务后,再次检查微任务队列,重复上述步骤。
- 这个过程会一直循环执行,直到执行栈和事件队列中都没有任务。
微任务的执行优先级高于宏任务,也就是说,当宏任务队列和微任务队列中都有任务时,JS引擎会先执行微任务队列中的所有任务,再去执行宏任务队列中的任务。
回调地狱优化
- 利用es11 的可选链式操作符
1 | var Animal = ({type, name, gender})=>{ |
- 利用提前退出和提前返回
1 | var Animal = ({type, name, gender})=>{ |
*
展开运算符
[…arr]
支持展开运算符是要有Symbol.iterator
arr[Symbol.iterator]
面试题:
1 | //我们能否以某种方式为下面的语句使用展开运算而不导致类型错误 ? |
- 对象转数组案例
- 使用 Object.keys() 方法获取对象的所有键,然后使用 map() 方法将每个键对应的值存入新数组中。
1 | const obj = { a: 1, b: 2, c: 3 }; |
- 使用 Object.values() 方法获取对象的所有值,直接将值存入新数组中。
1 | const obj = { a: 1, b: 2, c: 3 }; |
- 使用 Object.entries() 方法获取对象的键值对数组,然后对每个键值对进行处理。
1 | const obj = { a: 1, b: 2, c: 3 }; |
1 | #### 高效运算符 '?.' 和 '??' 和 '??=' 的区别 |
其他
- 继承:通过原型链,我们可以实现对象之间的继承关系,子对象可以继承父对象的属性和方法。可以尝试创建一个新的构造函数,让它继承自
Person
构造函数,并添加新的属性或方法。 - 原型修改:可以尝试修改
Person
构造函数的原型对象上的方法,然后看看实例对象是否能够访问到修改后的方法。 - 原型链终止:在原型链的顶端是
Object.prototype
,它的原型是null
。可以思考一下为什么原型链的顶端是Object.prototype
,而不是其他对象。
- 继承:通过原型链,我们可以实现对象之间的继承关系,子对象可以继承父对象的属性和方法。可以尝试创建一个新的构造函数,让它继承自
ES6 - ES11
ES6
怎么理解ES6 中的Promise;使用场景
- 介绍
1 | /** |
- 状态
1 | promise 有3种状态 |
- 特点
1 | 1.对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态 |
- 流程
- 用法
1 | const promise = new Promise(function(resolve, reject) {}); |
实例方法
Promise
构建出来的实例存在以下方法:- then()
1
2
3
4
5
6
7
8/**
then()是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数
then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因 */
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});- catch()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数 */
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
//Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
/**
一般来说,使用catch方法代替then()第二个参数
Promise对象抛出的错误不会传递到外层代码,即不会有任何反应 */- finally()
1
2
3
4
5//finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
构造函数方法
- all()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//Promise.all()方法用于将多个 Promise实例,包装成一个新的 Promise实例
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
//如果p2没有自己的catch方法,就会调用Promise.all()的catch方法- race()
1
2
3
4
5
6
7
8
9
10
11//将多个 Promise 实例,包装成一个新的 Promise 实例;只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);- allSettled()
1
2//Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
//只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束- resolve()
1
2
3
4//将现有对象转为 Promise对象
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))- reject()
- try()
使用场景
- 将图片的加载写成一个
Promise
,一旦加载完成,Promise
的状态就发生变化
1
2
3
4
5
6
7
8const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};- 通过链式操作,将多个渲染数据分别给个
then
,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/ 各司其职
getInfo().then(res=>{
let { bannerList } = res
//渲染轮播图
console.log(bannerList)
return res
}).then(res=>{
let { storeList } = res
//渲染店铺列表
console.log(storeList)
return res
}).then(res=>{
let { categoryList } = res
console.log(categoryList)
//渲染分类列表
return res
})- 通过
all()
实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading
即可
1
2
3
4
5
6
7
8
9
10
11
12function initLoad(){
loading.show() //加载loading
Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{
console.log(res)
loading.hide() //关闭loading
}).catch(err=>{
console.log(err)
loading.hide()//关闭loading
})
}
//数据初始化
initLoad()- 通过
race
可以设置图片请求超时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
//img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的
img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});- 将图片的加载写成一个
如何理解ES6中的Porxy; 使用场景
- 介绍
1 | Proxy 亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等) |
用法
Proxy
为 构造函数,用来生成Proxy
实例
1
2
3
4
5
6var proxy = new Proxy(target, handler)
/**
参数
target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))
handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
*/- 参数 handler 解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15关于handler拦截属性,有如下:
get(target,propKey,receiver):拦截对象属性的读取
set(target,propKey,value,receiver):拦截对象属性的设置
has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
ownKeys(target):拦截Object.keys(proxy)、for...in等循环,返回一个数组
getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作- Reflect
1
2
3
4
5
6若需要在Proxy内部调用对象的默认行为,建议使用Reflect,其是ES6中操作对象而提供的新 API
基本特点:
只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在
修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false)
让Object操作都变成函数行为一些参数用法
- get() get接受三个参数,依次为目标对象、属性名和 proxy 实例本身,最后一个参数可选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function(target, propKey) {
return Reflect.get(target,propKey)
}
});
proxy.name // "张三"
//get能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引
function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
//如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错
const target = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
},
});
const handler = {
get(target, propKey) {
return 'abc';
}
};
const proxy = new Proxy(target, handler);
proxy.foo
// TypeError: Invariant check failed- set() 拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和
Proxy
实例本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于满足条件的 age 属性以及其他属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
//如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用
const obj = {};
Object.defineProperty(obj, 'foo', {
value: 'bar',
writable: false,
});
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = 'baz';
}
};
const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"
//严格模式下,set代理如果没有返回true,就会报错
;
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
// 无论有没有下面这一行,都会报错
return false;
}
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'- deleteProperty()
deleteProperty
方法用于拦截delete
操作,如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
Reflect.deleteProperty(target,key)
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`无法删除私有属性`);
}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: 无法删除私有属性
//目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错- 取消代理
1
Proxy.revocable(target, handler);
使用场景
Proxy
其功能非常类似于设计模式中的代理模式,常用功能如下:- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
- 使用
Proxy
保障数据类型的准确性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let numericDataStore = { count: 0, amount: 1234, total: 14 };
numericDataStore = new Proxy(numericDataStore, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("属性只能是number类型");
}
return Reflect.set(target, key, value, proxy);
}
});
numericDataStore.count = "foo"
// Error: 属性只能是number类型
numericDataStore.count = 333
// 赋值成功- 声明了一个私有的
apiKey
,便于api
这个对象内部的方法调用,但不希望从外部也能够访问api._apiKey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23let api = {
_apiKey: '123abc456def',
getUsers: function(){ },
getUser: function(userId){ },
setUser: function(userId, config){ }
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
get(target, key, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} 不可访问.`);
} return Reflect.get(target, key, proxy);
},
set(target, key, value, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} 不可修改`);
} return Reflect.get(target, key, value, proxy);
}
});
console.log(api._apiKey)
api._apiKey = '987654321'
// 上述都抛出错误- 观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行; observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数
1
2
3
4
5
6
7
8
9
10
11const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
//观察者函数都放进Set集合,当修改obj的值,在会set函数中拦截,自动执行Set所有的观察者
怎么理解ES6中Generator;使用场景
介绍
执行
Generator
函数会返回一个遍历器对象,可以依次遍历Generator
函数内部的每一个状态形式上,
Generator
函数是一个普通函数,但是有两个特征:function
关键字与函数名之间有一个星号- 函数体内部使用
yield
表达式,定义不同的内部状态
1
2
3
4
5function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}使用
Generator
函数会返回一个遍历器对象,即具有Symbol.iterator
属性,并且返回给自己
1
2
3
4
5
6
7
8function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true- 通过
yield
关键字可以暂停generator
函数返回的遍历器对象的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
/**
上述存在三个状态:hello、world、return
通过next方法才会遍历到下一个内部状态,其运行逻辑如下:
1.遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
2.下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
3.如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
4.如果该函数没有return语句,则返回的对象的value属性值为undefined
*/
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
/**
done用来判断是否存在下个状态,value对应状态值
yield表达式本身没有返回值,或者说总是返回undefined
通过调用next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
*/
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
//正因为Generator函数返回Iterator对象,因此我们还可以通过for...of进行遍历
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
//原生对象没有遍历接口,通过Generator函数为它加上这个接口,就能使用for...of进行遍历了
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe异步解决方案
- 回调函数
1
2
3
4
5
6
7
8
9
10//回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数
fs.readFile('/etc/fstab', function (err, data) {
if (err) throw err;
console.log(data);
fs.readFile('/etc/shells', function (err, data) {
if (err) throw err;
console.log(data);
});
});
//readFile函数的第三个参数,就是回调函数,等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行- Promise 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
readFile('/etc/fstab').then(data =>{
console.log(data)
return readFile('/etc/shells')
}).then(data => {
console.log(data)
})
//这种链式操作形式,使异步任务的两段执行更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强- generator 函数
1
2
3
4
5
6
7//yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};- async/await
1
2
3
4
5
6
7//将上面Generator函数改成async/await形式,更为简洁,语义化更强了
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};区别:
1 | 通过上述代码进行分析,将promise、Generator、async/await进行比较: |
使用场景
Generator
是异步解决的一种方案,最大特点则是将异步操作同步化表达出来
1
2
3
4
5
6
7
8
9
10
11function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()
// 卸载UI
loader.next()- 包括
redux-saga
中间件也充分利用了Generator
特性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
function* mySaga() {
yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga;- 还能利用
Generator
函数,在对象上实现Iterator
接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7
怎么理解ES6中Module; 使用场景
介绍
模块,(Module),是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)。
两个基本的特征:外部特征和内部特征
- 外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能
- 内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码
为什么需要模块化
- 代码抽象
- 代码封装
- 代码复用
- 依赖管理
使用
ES6
模块内部自动采用了严格模式,这里就不展开严格模式的限制,毕竟这是ES5
之前就已经规定好模块功能主要由两个命令构成:
export
:用于规定模块的对外接口import
:用于输入其他模块提供的功能
- export
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//或
// 建议使用下面写法,这样能瞬间确定输出了哪些变量
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
//输出函数或类
export function multiply(x, y) {
return x * y;
};
//通过as可以进行输出变量的重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};- import
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37//使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
//同样如果想要输入变量起别名,通过as关键字
import { lastName as surname } from './profile.js';
//当加载整个模块的时候,需要用到星号*
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
// main.js
import * as circle from './circle';
console.log(circle) // {area:area,circumference:circumference}
//输入的变量都是只读的,不允许修改,但是如果是对象,允许修改属性
//如果不需要知道变量名或函数就完成加载,就要用到export default命令,为模块指定默认输出
//加载该模块的时候,import命令可以为该函数指定任意名字
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'动态加载
1 | /** |
怎么理解Decorator (装饰器); 使用场景
介绍
即装饰器,在不改变原类和使用继承的情况下,动态地扩展对象功能
优点:
- 代码可读性变强了,装饰器命名相当于一个注释
- 在不改变原有代码情况下,对原来功能进行扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//定义一个类,什么属性值,方法也没有
class soldier{
}
//定义一个方法,作为装饰器
function strong(target){
target.AK = true
}
//将装饰器装饰在类上
@strong
class soldier{
}
//调用类的AK属性
soldier.AK // true用法
Docorator
修饰对象为下面两种:- 类的装饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40/**
类的装饰
当对类本身进行装饰的时候,能够接受一个参数,即类本身
将装饰器行为进行分解,大家能够有个更深入的了解
*/
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
//下面@testable就是一个装饰器,target就是传入的类,即MyTestableClass,实现了为类添加静态属性
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
//如果想要传递参数,可以在装饰器外层再封装一层函数
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false- 类属性的装饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37/**
类属性的装饰
当对类属性进行装饰的时候,能够接受三个参数:
1.类的原型对象
2.需要装饰的属性名
3.装饰属性名的描述对象
*/
//首先定义一个readonly装饰器
function readonly(target, name, descriptor){
descriptor.writable = false; // 将可写属性设为false
return descriptor;
}
//使用readonly装饰类的name方法
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
//相当于以下调用
readonly(Person.prototype, 'name', descriptor);
//如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行
function dec(id){
console.log('evaluated', id);
return (target, property, descriptor) =>console.log('executed', id);
}
class Example {
@dec(1)
@dec(2)
method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1
//外层装饰器@dec(1)先进入,但是内层装饰器@dec(2)先执行注意项
装饰器不能用于修饰函数,因为函数存在变量声明情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
//编译阶段,变成下面
var counter;
var add;
@add
function foo() {
}
counter = 0;
add = function () {
counter++;
};
//意图是执行后counter等于 1,但是实际上结果是counter等于 0使用场景
基于
Decorator
强大的作用,我们能够完成各种场景的需求,下面简单列举几种:- 使用
react-redux
的时候,如果写成下面这种形式,既不雅观也很麻烦
1
2
3
4
5class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
//通过装饰器就变得简洁多了
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}- 将
mixins
,也可以写成装饰器,让使用更为简洁了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function mixins(...list) {
return function (target) {
Object.assign(target.prototype, ...list);
};
}
// 使用
const Foo = {
foo() { console.log('foo') }
};
@mixins(Foo)
class MyClass {}
let obj = new MyClass();
obj.foo() // "foo"- 讲讲
core-decorators.js
几个常见的装饰器
- 使用
1 | //@antobind |
ES6 class
class 没有声明提升
类方法 constructor
这个方法类似于其他语言,类的init方法,每次new实例时都会调用
关键字 extends (继承)
1 | class Son_of_Bullshit extends Bullshit{} |
关键字 super
1 | // 当我们获取父类属性时,需要用到super进行获取 |
关键字 static
在类里,静态属性、方法是属于类自身的,当调用时直接使用类调用即可
静态方法里的this是类而非实例对象,所以静态方法里面可以调用另外一个静态方法
关键字setter getter (设置属性和获取属性)
class实现单例模式
1 | // |