Carmineprince's Blog
首页
    • HTML
    • CSS
    • JavaScript
    • Vue
    • React
    • TypeScript
    • Node
    • Flutter
    • Electron
    • Python
    • 运维
    • 重学前端
  • 分类
  • 标签
  • 归档

Ziqi Wang

胡思乱想程序员
首页
    • HTML
    • CSS
    • JavaScript
    • Vue
    • React
    • TypeScript
    • Node
    • Flutter
    • Electron
    • Python
    • 运维
    • 重学前端
  • 分类
  • 标签
  • 归档
  • 正则表达式快速使用列表
  • Array常用方法
  • JS回炉重造
    • 一、变量类型和计算
      • 1. 变量类型
      • 2. 变量计算
    • 二、原型和原型链
      • 1. class 和继承
      • 2. 类型判断instanceof
      • 3. 原型和原型链
    • 三、作用域和闭包
      • 1.作用域和自由变量
      • 2. 闭包
      • 3. this
    • 四、Array数组常用方法,包含ES6方法
      • ES5及以前老方法
      • ES6新增操作数组的方法
      • 10. includes
    • 五、异步和单线程
      • 1. 单线程和异步
      • 2. 应用场景
      • 3. callback hell和Promise
    • 六、异步-进阶
      • 1. event loop(事件循环/事件轮询)
      • 2. promise 进阶
      • 3. async/await
      • 4. 微任务/宏任务
    • 七、JS Web API
      • 1. DOM
      • 2. BOM(浏览器操作)
      • 3. 事件绑定
      • 4. ajax
      • 5. 存储
  • Layui form表单input获取回车事件
  • 需要背下来的内容
  • ES6实用场景
  • JavaScript相关
carmineprince
2021-10-20

JS回炉重造

# JS回炉重造

# 一、变量类型和计算

# 1. 变量类型

# (1).值类型vs引用类型

值类型

let a = 100
let b = a
a = 200
console.log(b) // 100
1
2
3
4

常见值类型

let a // undefined
const s = 'abc'
const n = 100
const b = true
const s = Symbol('s')
1
2
3
4
5

引用类型

let a = { age: 20}
let b = a
b.age = 21
console.log(a.age) // 21
1
2
3
4

常见引用类型

const obj = { x:100 }
const array = ['a','b','c']
const n = null // 特殊引用类型,指针指向为空地址

// 特殊引用类型, 但不用于存储数据,所以没有“拷贝、复制函数”这一说
function fn() {}
1
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'
1
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
}
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

# 2. 变量计算

# (1). 字符串拼接

const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const b1 = 100 + parseInt(10) // 110
const c = true + '10' // 'true10'
1
2
3
4

# (2). ==

100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true
1
2
3
4
5

== 会通过转换相等 注意:除了== null 之外,其他一律用 ===

// 除了== null 之外,其他一律用 ===,例如:
const obj = { x:100 }
if (obj.a == null) {}
// 相当于:
// if (obj.a === null || obj.a === undefined) {}
1
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
1
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
1
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
1
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
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

# 2. 类型判断instanceof

接上例

lihuanying instanceof Student       //true
lihuanying instanceof People        //true
lihuanying instanceof Object        //true

[] instanceof Array                 //true
[] instanceof Object                //true

{} instanceof Object                //true
1
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
1
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
1
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);
    }
//扩展自己的方法
}

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

# class的原型本质,怎么理解?

  • 原型和原型链的图示
  • 属性和方法的执行规则

# 三、作用域和闭包

# 1.作用域和自由变量

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6新增)
// ES6 块级作用域
if (true) {
    let x = 100
}
console.log(x)      //会报错
1
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
1
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
1
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
1
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}
1
2
3
4
5
6
7
8

箭头函数取值只取当前对象

箭头函数this取值

  • 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)
    }
}
1
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]
1
2
3
4
5

# 2. join()

用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的,默认使用','号分割,不改变原数组。

var arr = [2,3,4];
console.log(arr.join());  //2,3,4
console.log(arr);  //[2, 3, 4]
1
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....)
1
2
3
4
5

# 4. pop()

用于删除并返回数组的最后一个元素。返回最后一个元素,会改变原数组。

var arr = [2,3,4];
console.log(arr.pop()); //4
console.log(arr);  //[2,3]
1
2
3

# 5. shift()

用于把数组的第一个元素从其中删除,并返回第一个元素的值。返回第一个元素,改变原数组。

var arr = [2,3,4];
console.log(arr.shift()); //2
console.log(arr);  //[3,4]
1
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]
1
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]
1
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]
1
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]
1
2
3
4
5

# 10. reverse()

用于颠倒数组中元素的顺序。返回的是颠倒后的数组,会改变原数组。

var arr = [2,3,4];
console.log(arr.reverse()); //[4, 3, 2]
console.log(arr);  //[4, 3, 2]
1
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
}
1
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
1
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
1
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
1
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"]
1
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]
1
2
3
4
5
6

# 15.forEach数组遍历

const items = ['item1', 'item2', 'item3'];
const copy = [];    
items.forEach(function(item){
  copy.push(item)
});
1
2
3
4
5

# ES6新增操作数组的方法

# 1. find()

传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。

const arr = [1, "2", 3, 3, "2"]
console.log(arr.find(n => typeof n === "number")) // 1
1
2

# 2. findIndex()

传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的下标,终止搜索。

const arr = [1, "2", 3, 3, "2"]
console.log(arr.findIndex(n => typeof n === "number")) // 0
1
2

# 3. fill()

用新元素替换掉数组内的元素,可以指定替换下标范围。

arr.fill(value, start, end)
1

# 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
1
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"]
1
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]
1
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']
1
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'
1
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'
1
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
1
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
1
2
3
4
5
6
7
8

# (2)同步举例

console.log(100)
alert(200)
console.log(300)
// 100 
// 200,弹窗并阻塞。点击确定后,执行后续操作
// 300
1
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')
1
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')
1
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...
1
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)
            // 还可能获取更多的数据
        })
    })
})
1
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))

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

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
1
2
3
4
5
6

# (2) event loop 过程:

  1. 同步代码,一行一行放在Call Stack执行
  2. 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
  3. 时机到了,就移动到Callback Queue
  4. 如Call Stack为空(即同步代码执行完)EventLoop开始工作
  5. 轮训查找Callback Queue,如有则移动到Call Stack执行
  6. 然后继续轮询查找(永动机一样)

event loop图示

# (3) DOM事件和event loop

<button id="btn1">提交</button>
<script>
console.log('Hi')
$('#btn1').click(function(e) {
    console.log('button clicked')
})
console.log('Bye')
</script>
1
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
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

# 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)
})()
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
  • 执行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
    }
})()
1
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)
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

# 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渲染的关系

  1. 每次CallStack清空(即每次轮训结束),即同步任务执行完
  2. 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
  3. 然后再去触发下一次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操作,不会长久

  1. DOM是属于那种数据结构
  2. DOM操作的常用API
  3. attr和property的区别
  4. 一次性插入多个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>
1
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') // 集合
1
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)
1
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;')
1
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)
1
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
1
2
3
4
5
6
7
  • 删除子元素
const div1 = document.getElementById('div1')
const child = div1.childNodes
div1.removeChild(child[0])
1
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查询
}
1
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)
1
2
3
4
5
6
7
8
9
10
11
12

# 2. BOM(浏览器操作)

# 3. 事件绑定

# 4. ajax

# 5. 存储

#回顾#javascript
上次更新: 10/21/2021, 1:52:09 PM
Array常用方法
Layui form表单input获取回车事件

← Array常用方法 Layui form表单input获取回车事件→

最近更新
01
pc端rem配置
03-02
02
使用动态变量ts报错的解决
02-25
03
React Hook详解
02-18
更多文章>
Theme by Vdoing | Copyright © 2021-2022 Carmineprince | 鲁ICP备2021046263号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式