JS回炉重造
# JS回炉重造
# 一、变量类型和计算
# 1. 变量类型
# (1).值类型vs引用类型
值类型
let a = 100
let b = a
a = 200
console.log(b) // 100
2
3
4
常见值类型
let a // undefined
const s = 'abc'
const n = 100
const b = true
const s = Symbol('s')
2
3
4
5
引用类型
let a = { age: 20}
let b = a
b.age = 21
console.log(a.age) // 21
2
3
4
常见引用类型
const obj = { x:100 }
const array = ['a','b','c']
const n = null // 特殊引用类型,指针指向为空地址
// 特殊引用类型, 但不用于存储数据,所以没有“拷贝、复制函数”这一说
function fn() {}
2
3
4
5
6
# (2).typeof 运算符
- 识别所有值类型
- 识别函数
- 判断是否引用类型(不可再细分)
let a;
typeof a // 'undefined'
const str = 'abc';
typeof str // 'string'
const n = 100;
typeof n // 'number'
const b = true;
typeof b // 'boolean'
const s = Symbol('s')
typeof s // 'symbol'
// 能判断函数
typeof console.log // 'function'
typeof function () {} // 'function'
// 能识别引用类型(不能再继续识别)
typeof null // 'object'
typeof ['a','b'] // 'object'
typeof { x:100 } // 'object'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# (3).深拷贝
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'qingdao'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city) // qingdao
console.log(obj2.address.city) // shanghai
console.log(obj1.arr[0]) // a
console.log(obj2.arr[0]) // a1
/**
* 深拷贝
* @param obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是null, 或者不是对象和数组,直接返回
return obj
}
//判断原始类型,初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证key 不是原型的属性。
if (obj.hasOwnProperty(key)) {
// 递归
result[key] = deepClone(obj[key])
}
}
return result
}
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
# 2. 变量计算
# (1). 字符串拼接
const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const b1 = 100 + parseInt(10) // 110
const c = true + '10' // 'true10'
2
3
4
# (2). ==
100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true
2
3
4
5
== 会通过转换相等 注意:除了== null 之外,其他一律用 ===
// 除了== null 之外,其他一律用 ===,例如:
const obj = { x:100 }
if (obj.a == null) {}
// 相当于:
// if (obj.a === null || obj.a === undefined) {}
2
3
4
5
# (3). if语句和逻辑运算
- truly变量: !!a === true 的变量
- falsely变量: !!a === false的变量 两次非运算后的布尔值
以下是falsely变量。除此之外都是truly变量:
!!0 === false
!!Nan === false
!!'' === false
!!null === false
!!undefined === false
!!! false === false
2
3
4
5
6
if语句在判断的是否判断的是truly变量或falsely变量,而不是直接判断true or false,与C语言不同
逻辑判断
console.log(10 && 0) // 0,0是falsly变量,所以直接返回了
console.log('' || 'abc') // 'abc'
console.log(!window.abc) // true
2
3
# 二、原型和原型链
# 1. class 和继承
# class
- constructor
- 属性
- 方法
// 类
class Student {
constructor(name, number) {
this.name = name
this.number = number
// this.gender = 'male'
}
sayHi() {
// es6新语法,反引号,反引号包裹,内部变量使用${}形式
console.log(`姓名:${this.name},学号:${this.number}`)
}
}
// 通过类声明对象/实例
const lihuanying = new Student('李焕英',100)
console.log(lihuanying.name) //李焕英
console.log(lihuanying.number) //100
lihuanying.sayHi() //姓名:李焕英,学号:100
const jiaxiaoling = new Student('贾晓玲',101)
jiaxiaoling.sayHi() //姓名:贾晓玲,学号:101
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 继承
- extends
- super
- 扩展或重写
// 父类
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat somethind`)
}
}
// 子类
class Student extends People {
constructor(name, number) {
super(name);
this.number = number
}
sayHi() {
console.log(`姓名:${this.name},学号:${this.number}`)
}
}
// 子类
class Teacher extends People {
constructor(name,major) {
super(name);
this.major = major
}
teach() {
console.log(`${this.name} 教授${this.major}`)
}
}
const lihuanying = new Student('李焕英',100)
console.log(lihuanying.name) //李焕英
console.log(lihuanying.number) //100
lihuanying.sayHi() //姓名:李焕英,学号:100
lihuanying.eat() //姓名:贾晓玲,学号:101
const wanglaoshi = new Teacher('王老师','数学')
wanglaoshi.teach() //王老师 教授数学
wanglaoshi.eat() //王老师 eat somethind
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
# 2. 类型判断instanceof
接上例
lihuanying instanceof Student //true
lihuanying instanceof People //true
lihuanying instanceof Object //true
[] instanceof Array //true
[] instanceof Object //true
{} instanceof Object //true
2
3
4
5
6
7
8
# 3. 原型和原型链
# 原型
// class 实际上是函数,可见是语法糖
typeof People // 'function'
typeof Student // 'function'
// 隐式原型(__proto__)和显示原型(prototype)
console.log(lihuanying.__proto__)
console.log(Student.prototype)
console.log(lihuanying.__proto__ === Student.prototype) //true
2
3
4
5
6
7
8
- 每个class都有显示原型prototype
- 每个实例都有隐式原型__proto__
- 实例的__proto__指向对应class的prototype
# 基于原型的执行规则
- 获取属性lihuanying.name 或执行 lihuanying.sayhi()时
- 先在自身属性和方法寻找
- 如果找不到则自动去__proto__中寻找
# 原型链
console.log(Student.prototype.__proto__)
console.log(People.prototype)
console.log(People.prototype === Student.prototype.__proto__) //true
2
3
判断是否是自己的属性hasOwnProperty()
instanceof 是根据原型链查找,是否能找到匹配的显式原型
class是ES6语法规范,由ECMA委员会发布 ECMA只是规定语法规则,即我们代码的书写规范,不规定如何实现 以上实现方式都是V8引擎的实现方式,也是主流的
# 手写一个简易的JQuery,考虑插件和可扩展性
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
// 类似于数组,类数组
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
}
const $p = new jQuery('p')
$p.get(1)
$p.each((elem) => console.log(elem.nodeName))
$p.on('click', () => alert(clicked))
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
$p.dialog('lkj')
// "造轮子"
class myJQuery extends jQuery {
constructor(selector) {
super(selector);
}
//扩展自己的方法
}
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
# class的原型本质,怎么理解?
- 原型和原型链的图示
- 属性和方法的执行规则
# 三、作用域和闭包
# 1.作用域和自由变量
- 全局作用域
- 函数作用域
- 块级作用域(ES6新增)
// ES6 块级作用域
if (true) {
let x = 100
}
console.log(x) //会报错
2
3
4
5
自由变量
- 一个变量在当前作用域没有定义,但被使用了
- 向上级作用域,一层一层依次寻找,直到找到为止
- 如果到全局作用域都没找到,则报错 xx is not defined
# 2. 闭包
作用域应用的特殊情况,有两种表现
- 函数作为参数被传递
function create() {
let a = 100
return function () {
console.log(a)
}
}
let fn = create()
let a = 200
fn() // 100
2
3
4
5
6
7
8
9
- 函数作为返回值被返回
function print(fn) {
let a = 100
fn()
}
let a = 100
function fn() {
console.log(a)
}
print(fn) // 100
2
3
4
5
6
7
8
9
闭包: 自由变量的查找,是在函数定义的地方,向上级作用域查找 不是在执行的地方!!!
# 实际开发中闭包有何应用?
- 隐藏数据
如做一个简单cache工具(缓存工具)
// 闭包隐藏数据, 只提供API
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('a',100)
console.log(c.get('a')) // 100
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3. this
# this赋值的方式
- 作为普通函数被调用(window)
- 使用call apply bind(传入什么绑定什么)
- 作为对象方法被调用(对象本身)
- 在class方法中调用(当前实例本身)
- 箭头函数(找上级作用域的值)
this取什么值,是在函数执行的时候确定的,不是在定义的时候确定的
function fn1() {
console.log(this)
}
fn1() // window
fn1.call({ x: 100 }) // { x: 100 }
const fn2 = fn1.bind({ x: 200 })
fn2() // { x: 200}
2
3
4
5
6
7
8
箭头函数取值只取当前对象
- this的不同应用场景,如何取值?
# 如何手写bind函数?
Function.prototype.bind1 = function() {
// 将参数解析为数组
const args = Array.prototype.slice.call(arguments)
// 获取 this (取出数组第一项,数组剩余的就是传递的参数)
const t = args.shift()
const self = this // 当前函数
// 返回一个函数
return function () {
// 执行原函数,并返回结果
return self.apply(t,args)
}
}
2
3
4
5
6
7
8
9
10
11
12
# 四、Array数组常用方法,包含ES6方法
# ES5及以前老方法
# 1.concat()
用于连接两个或多个数组。该方法不会改变现有的数组,仅会返回被连接数组的一个副本。
var arr1 = [1,2,3];
var arr2 = [4,5];
var arr3 = arr1.concat(arr2);
console.log(arr1); //[1, 2, 3]
console.log(arr3); //[1, 2, 3, 4, 5]
2
3
4
5
# 2. join()
用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的,默认使用','号分割,不改变原数组。
var arr = [2,3,4];
console.log(arr.join()); //2,3,4
console.log(arr); //[2, 3, 4]
2
3
# 3. push()
向数组的末尾添加一个或多个元素,并返回新的长度。末尾添加,返回的是长度,会改变原数组。
var a = [2,3,4];
var b = a.push(5);
console.log(a); //[2,3,4,5]
console.log(b); //4
push方法可以一次添加多个元素push(data1,data2....)
2
3
4
5
# 4. pop()
用于删除并返回数组的最后一个元素。返回最后一个元素,会改变原数组。
var arr = [2,3,4];
console.log(arr.pop()); //4
console.log(arr); //[2,3]
2
3
# 5. shift()
用于把数组的第一个元素从其中删除,并返回第一个元素的值。返回第一个元素,改变原数组。
var arr = [2,3,4];
console.log(arr.shift()); //2
console.log(arr); //[3,4]
2
3
# 6. unshift()
可向数组的开头添加一个或更多元素,并返回新的长度。返回新长度,改变原数组。
var arr = [2,3,4,5];
console.log(arr.unshift(3,6)); //6
console.log(arr); //[3, 6, 2, 3, 4, 5]
2
3
# 7. slice()
返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。返回选定的元素,该方法不会修改原数组。
var arr = [2,3,4,5];
console.log(arr.slice(1,3)); //[3,4]
console.log(arr); //[2,3,4,5]
2
3
# 8. splice()
可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。splice() 方法会直接对数组进行修改。
var a = [5,6,7,8];
console.log(a.splice(1,0,9)); //[]
console.log(a); // [5, 9, 6, 7, 8]
var b = [5,6,7,8];
console.log(b.splice(1,2,3)); //[6, 7]
console.log(b); //[5, 3, 8]
2
3
4
5
6
# 9. sort()
按照 Unicode code 位置排序,默认升序
var fruit = ['cherries', 'apples', 'bananas'];
fruit.sort(); // ['apples', 'bananas', 'cherries']
var scores = [1, 10, 21, 2];
scores.sort(); // [1, 10, 2, 21]
2
3
4
5
# 10. reverse()
用于颠倒数组中元素的顺序。返回的是颠倒后的数组,会改变原数组。
var arr = [2,3,4];
console.log(arr.reverse()); //[4, 3, 2]
console.log(arr); //[4, 3, 2]
2
3
# 11. indexOf 和 lastIndexOf
都接受两个参数:查找的值、查找起始位置 不存在,返回 -1 ;存在,返回位置。indexOf 是从前往后查找, lastIndexOf 是从后往前查找。
indexOf:
var a = [2, 9, 9];
a.indexOf(2); // 0
a.indexOf(7); // -1
if (a.indexOf(7) === -1) {
// element doesn't exist in array
}
2
3
4
5
6
7
lastIndexOf:
var numbers = [2, 5, 9, 2];
numbers.lastIndexOf(2); // 3
numbers.lastIndexOf(7); // -1
numbers.lastIndexOf(2, 3); // 3
numbers.lastIndexOf(2, 2); // 0
numbers.lastIndexOf(2, -2); // 0
numbers.lastIndexOf(2, -1); // 3
2
3
4
5
6
7
# 12. every() 和 some()
every 对数组的每一项都运行给定的函数,每一项都返回 ture,则结果返回 true
function isBigEnough(element, index, array) {
return element < 10;
}
[2, 5, 8, 3, 4].every(isBigEnough); // true
2
3
4
some
对数组的每一项都运行给定的函数,任意一项都返回 ture,则返回 true
function compare(element, index, array) {
return element > 10;
}
[2, 5, 8, 1, 4].some(compare); // false
[12, 5, 8, 1, 4].some(compare); // true
2
3
4
5
# 13. filter()
对数组的每一项都运行给定的函数,返回 结果为 ture 的项组成的数组
var words = ["spray", "limit", "elite", "exuberant", "destruction", "present", "happy"];
var longWords = words.filter(function(word){
return word.length > 6;
});
// Filtered array longWords is ["exuberant", "destruction", "present"]
2
3
4
5
6
# 14.map()
对数组的每一项都运行给定的函数,返回每次函数调用的结果组成一个新数组
var numbers = [1, 5, 10, 15];
var doubles = numbers.map(function(x) {
return x * 2;
});
// doubles is now [2, 10, 20, 30]
// numbers is still [1, 5, 10, 15]
2
3
4
5
6
# 15.forEach数组遍历
const items = ['item1', 'item2', 'item3'];
const copy = [];
items.forEach(function(item){
copy.push(item)
});
2
3
4
5
# ES6新增操作数组的方法
# 1. find()
传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。
const arr = [1, "2", 3, 3, "2"]
console.log(arr.find(n => typeof n === "number")) // 1
2
# 2. findIndex()
传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的下标,终止搜索。
const arr = [1, "2", 3, 3, "2"]
console.log(arr.findIndex(n => typeof n === "number")) // 0
2
# 3. fill()
用新元素替换掉数组内的元素,可以指定替换下标范围。
arr.fill(value, start, end)
# 4. copyWithin()
选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围。
arr.copyWithin(target, start, end)
const arr = [1, 2, 3, 4, 5]
console.log(arr.copyWithin(3))
// [1,2,3,1,2] 从下标为3的元素开始,复制数组,所以4, 5被替换成1, 2
const arr1 = [1, 2, 3, 4, 5]
console.log(arr1.copyWithin(3, 1))
// [1,2,3,2,3] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,所以4, 5被替换成2, 3
const arr2 = [1, 2, 3, 4, 5]
console.log(arr2.copyWithin(3, 1, 2))
// [1,2,3,2,5] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,结束位置为2,所以4被替换成2
2
3
4
5
6
7
8
9
10
# 5. from
将类似数组的对象(array-like object)和可遍历(iterable)的对象转为真正的数组
const bar = ["a", "b", "c"];
Array.from(bar);
// ["a", "b", "c"]
Array.from('foo');
// ["f", "o", "o"]
2
3
4
5
6
# 6. of
用于将一组值,转换为数组。这个方法的主要目的,是弥补数组构造函数 Array() 的不足。因为参数个数的不同,会导致 Array() 的行为有差异。
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
Array(7); // [ , , , , , , ]
Array(1, 2, 3); // [1, 2, 3]
2
3
4
5
6
7
8
# 7. entries() 返回迭代器:返回键值对
//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.entries()) {
console.log(v)
}
// [0, 'a'] [1, 'b'] [2, 'c']
//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.entries()) {
console.log(v)
}
// ['a', 'a'] ['b', 'b'] ['c', 'c']
//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.entries()) {
console.log(v)
}
// ['a', 'a'] ['b', 'b']
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 8. values() 返回迭代器:返回键值对的value
//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.values()) {
console.log(v)
}
//'a' 'b' 'c'
//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.values()) {
console.log(v)
}
// 'a' 'b' 'c'
//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.values()) {
console.log(v)
}
// 'a' 'b'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 9. keys() 返回迭代器:返回键值对的key
//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.keys()) {
console.log(v)
}
// 0 1 2
//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.keys()) {
console.log(v)
}
// 'a' 'b' 'c'
//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.keys()) {
console.log(v)
}
// 'a' 'b'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 10. includes
判断数组中是否存在该元素,参数:查找的值、起始位置,可以替换 ES5 时代的 indexOf 判断方式。indexOf 判断元素是否为 NaN,会判断错误。
var a = [1, 2, 3];
a.includes(2); // true
a.includes(4); // false
2
3
# 五、异步和单线程
- 同步和异步的区别是什么?
- 手写用Promise加载一张图片
- 前端使用异步的场景有哪些?
# 1. 单线程和异步
- JS是单线程语言,只能同时做一件事儿
- 浏览器和nodejs已支持JS启动进程,如Web Worker
- JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
- 遇到等待(网络请求,定时任务)不能卡住
- 需要异步(解决单线程等待的问题)
- 使用回调callback函数形式
# (1)异步举例
console.log(100)
setTimeout(function() {
console.log(200)
},1000)
console.log(300)
// 100
// 300
// 200
2
3
4
5
6
7
8
# (2)同步举例
console.log(100)
alert(200)
console.log(300)
// 100
// 200,弹窗并阻塞。点击确定后,执行后续操作
// 300
2
3
4
5
6
# (3)同步和异步
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
# 2. 应用场景
- 网络请求,如ajax图片加载
- 定时任务,如setTimeout
# (1)网络请求
// ajax
console.log('start')
$.get('./data1.json',function(data1){
console.log(data1)
})
console.log('end')
2
3
4
5
6
# (2)图片加载
console.log('start)
let img = document.createElement('img')
img.onload = function () {
console.log('loaded')
}
img.src = '/xxx.png'
console.log('end')
2
3
4
5
6
7
# (3)定时、循环
// setTimeout
console.log(100)
setTimeout(function () {
console.log(200)
},1000)
console.log(300)
// 100,300,200
// setInterval
console.log(100)
setInterval(function () {
console.log(200)
},1000)
console.log(300)
// 100,300,200,200,200...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3. callback hell和Promise
# (1)callback hell
// 获取第一份数据
$.get(url1,(data1)) => {
console.log(data1)
// 获取第二份数据
$.get(url2,(data2)) => {
console.log(data2)
// 获取第三份数据
$.get(url3,(data3)) => {
console.log(data3)
// 还可能获取更多的数据
})
})
})
2
3
4
5
6
7
8
9
10
11
12
13
# (2)Promise
function getDate(url) {
return new Promise((resolve, reject)) => {
$.ajax({
url,
success(data) {
resolve(data)
},
error(err) {
reject(err)
}
})
}
}
const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'
getData(url1).then(data1 => {
console.log(data1)
return getData(url2)
}).then(data2 => {
console.log(data2)
return getData(url3)
}).then(data3 => {
console.log(data3)
}).catch(err => console.error(err))
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
Promise解决了callback hell嵌套的情形,实现了串联的形式。
# 六、异步-进阶
- 请描述event loop(事件循环/事件轮询)的机制,可画图
- 什么事宏任务和微任务,两者有什么区别?
- Promise有哪三种状态?如何变化?
- async/await语法
- promise和setTimeout的顺序
# 1. event loop(事件循环/事件轮询)
- JS是单线程运行的
- 异步要基于回调来实现
- event loop就是异步回调的实现原理
JS是如何执行的?
- 从前到后,一行一行执行的。
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
# (1) 简单异步示例:
console.log('Hi')
setTimeout(function cb1() {
console.log('cb1') // cb 即callback
},5000)
console.log('Bye')
// Hi,Bye,cb1
2
3
4
5
6
# (2) event loop 过程:
- 同步代码,一行一行放在Call Stack执行
- 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
- 时机到了,就移动到Callback Queue
- 如Call Stack为空(即同步代码执行完)EventLoop开始工作
- 轮训查找Callback Queue,如有则移动到Call Stack执行
- 然后继续轮询查找(永动机一样)
# (3) DOM事件和event loop
<button id="btn1">提交</button>
<script>
console.log('Hi')
$('#btn1').click(function(e) {
console.log('button clicked')
})
console.log('Bye')
</script>
2
3
4
5
6
7
8
- 异步(setTimeout,ajax等)使用回调,基于event loop
- DOM事件也使用回调,基于event loop
注意:DOM事件并不是异步
# 2. promise 进阶
# (1)三种状态
- pending(过程中) fulfilled(解决了) rejected(失败了)
- pending ->fulfilled或pending->rejected
- 过程不可逆
注意:resolve()后是fulfilled状态
# (2)状态的表现和变化
- pending状态,不会触发then和catch
- fulfilled状态,会触发后续的then回调函数
- rejected状态,会触发后续的catch回调函数
# (3)then和catch对状态的影响
- then正常返回resolved,里面有报错则返回rejected
- catch正常返回resolved,里面有报错则返回rejected
# (4)题目
// 第一题
Promise.resolve().then(()=>{
console.log(1)
}).catch(()=>{
console.log(2)
}).then(()=>{
console.log(3)
})
// 1,3
// 第二题
Promise.resolve().then(()=>{
console.log(1)
throw new Error('erro1')
}).catch(()=>{
console.log(2)
}).then(()=>{
console.log(3)
})
// 1,2,3
// 第三题
Promise.resolve().then(()=>{
console.log(1)
throw new Error('erro1')
}).catch(()=>{
console.log(2)
}).catch(()=>{
console.log(3)
})
// 1,2
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
# 3. async/await
####(1)基础
- 异步回调 callback hell 风险
- Promise then catch 链式调用,单也是基于回调函数
- async/await是同步语法,彻底消灭回调函数
function loadImg(src) {
// pending
const p = new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img) // resolved
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`)
reject(err) // rejected
}
img.src = src
})
return p
}
const src1 = 'https://carmineprince.oss-cn-qingdao.aliyuncs.com/one_red.png'
const src2 = "https://carmineprince.oss-cn-qingdao.aliyuncs.com/one.png"
// 执行await函数时必须使用async函数包裹
// await可以执行promise和async函数
async function loadImg1() {
const img1 = await loadImg(src1)
return img1
}
async function loadImg2() {
const img2 = await loadImg(src2)
return img2
}
//匿名函数
!(async function () {
// 同步的写法,执行了异步的代码
const img1 = await loadImg1(src1)
console.log(img1.height, img1.width)
const img2 = await loadImg2(src2)
console.log(img2.height, img2.width)
})()
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
- 执行await函数时必须使用async函数包裹
- await可以执行promise和async函数
####(2)async/await 和 Promise的关系
async/await 是消灭异步回调的终极武器 但和Promise并不互斥 两者相辅相成
- 执行async函数,返回的是Promise对象
- await相当于Promise的then
- try...catch可捕获异常,代替了Promise的catch
// 匿名async函数
!(async function () {
const p4 = Promise.reject('err') // rejected
try {
const res = await p4 // Promise then
} catch (ex) {
console.log(ex) // try...catch,相当于 Promise的catch
}
})()
2
3
4
5
6
7
8
9
# (3) 异步的本质
- async/await是消灭异步回调的终极武器
- JS还是单线程,还得是有异步,还得是基于event loop
- async/await只是一个语法糖
async function async1() {
console.log('async1 start') // 2
await async2()
// await 的后面,都可以看做是callback里的内容,即异步
// 类似,event loop,setTimeout(cb1)
// setTimeout(function () { console.log('async1 end') })
// Promise.resoleve().then(()=>{ console.log('async1 end') })
console.log('async1 end') // 5
await async3()
// 下面一行是异步回调的内容
console.log('async1 end 2') // 7
}
async function async2 () {
console.log('async2') // 3
}
async function async3 () {
console.log('async3') // 6
}
console.log('script start') // 1
async1()
console.log('script end') // 4
// 同步代码已经执行完(event loop)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# for...of
- for..in(以及forEach for) 是常规的同步遍历
- for..of常用于异步的遍历
for..of是等待异步执行结束后,在执行下一个遍历 for..in(以及forEach for)都是同时执行。
# 4. 微任务/宏任务
# 宏任务 macroTask
- setTimeout
- setInterval
- Ajax
- Dom事件
# 微任务 microTask
- Promise
- async/await
微任务执行时机比宏任务要早
# event-loop和DOM渲染的关系
- 每次CallStack清空(即每次轮训结束),即同步任务执行完
- 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
- 然后再去触发下一次Event Loop
# 微任务和宏任务的区别
- 宏任务:DOM渲染后触发,如setTimeout
- 微任务:DOM渲染前触发,如Promise
# 从 event loop解释,为何微任务比宏任务早
- 微任务是ES6语法规定的:存放在micro task queue
- 宏任务是由浏览器规定的:存放在 Web APIs
# 七、JS Web API
- JS基础知识,规定语法(ECMA 262标准)
- JS Web API,网页操作的API(W3C标准)
- 前者是后者的基础,两者结合才能真正实际应用
# 1. DOM
vue和React框架应用广泛,封装了DOM操作 但DOM操作一直都会是前端工程师的基础、必备知识 只会vue而不动DOM操作,不会长久
- DOM是属于那种数据结构
- DOM操作的常用API
- attr和property的区别
- 一次性插入多个DOM节点,考虑性能
# (1)DOM的本质(Document Object model)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<p>this is p</p>
</div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
DOM的本质是节点树,是XML文档,但是规定了对应的名称
# (2)DOM节点操作
- 获取DOM节点
const div1 = document.getElementById('div1') // 元素
const divList = document.getElementsByTagName('div') // 集合
console.log(divList.length)
console.log(divList[0])
const containerList = document.getElementsByClassName('.container') //集合
const pList = document.querySelectorAll('p') // 集合
2
3
4
5
6
- property
const pList = document.querySelectorAll('p') // 集合
const p = pList[0]
console.log(p.style.width) //获取样式
p.stype.width = '100px' //修改样式
console.log(p.className) // 获取class
p.className = 'p1' // 修改class
// 获取nodeName 和 nodeType
console.log(p.nodeName)
console.log(p.nodeType)
2
3
4
5
6
7
8
9
10
property 修改对象属性,不会提现到html结构中
- attribute
const pList = document.querySelectorAll('p') // 集合
const p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name','carmineprince')
p.getAttribute('style')
p.setAttribute('style','font-size:30px;')
2
3
4
5
6
attribute 修改html属性,会改变html结构
property和attribute都可能引起DOM重新渲染 注意:非必要条件下,首选property
# (3)DOM结构操作
- 新增/插入节点
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)
// 移动节点:新增已有元素,会移动该节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)
2
3
4
5
6
7
8
9
10
11
- 获取子元素列表,获取父元素
// 获取子元素列表
const div1 = document.getElementById('div1')
const child = div1.childNodes
// 获取父元素
const div1 = document.getElementById('div1')
const parent = div1.parentNode
2
3
4
5
6
7
- 删除子元素
const div1 = document.getElementById('div1')
const child = div1.childNodes
div1.removeChild(child[0])
2
3
# (4)DOM性能
DOM操作非常“昂贵”,避免频繁的DOM操作 对DOM查询做缓存 将频繁操作改为一次性操作
- DOM查询做缓存
// 不缓存DOM查询结果
for (let = 0; i < document.getElementsByTagName('p').length; i++) {
// 每次循环,都会计算length,频繁进行DOM查询
}
// 缓存DOM查询结果
const pList = document.getElementsByTagName('p')
const length = pList length
for (let i = 0; i < length; i++) {
// 缓存length, 只进行一次DOM查询
}
2
3
4
5
6
7
8
9
10
11
- 将频繁操作改为一次性操作
const listNode = document.getElementById('list')
// 创建一个文本片段, 此时还没有插入到DOM数中
const frag = document.createDocumentFragment()
// 执行插入
for (let x = 0; x < 10; x++) {
const li = document.createElement("li")
li.innerHTML = 'List item' + x
frag.appendChild(li)
}
// 都完成之后,再插入到DOM树中
listNode.appendChild(frag)
2
3
4
5
6
7
8
9
10
11
12