03-JS八股文

6/28/2022

# JS八股文(码路教育)

# 1. JavaScript垃圾回收是怎么做的?

所谓垃圾回收, 核心思想就是如何判断内存是否已经不再会被使用了, 如果是, 就视为垃圾, 释放掉
下面介绍两种常见的浏览器垃圾回收算法: 引用计数 和 标记清除法
1
2
IE采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。
如果没有任何变量指向它了,说明该对象已经不再需要了。

但它却存在一个致命的问题:循环引用。
如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
1
2
3
4
5
标记清除算法
现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
标记清除法:
    标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
    简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。
    凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)

总结:
    标记清除法:
    从js根部(全局对象), 出发, 能够访问到的对象, 普通变量, 函数等, 都是需要用的, 不会释放
    但是如果从js根部, 无法访问到, 无法触及 (谁都找不到这块空间), 这块空间就是垃圾, 需要被回收
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2. 谈谈你对 JavaScript 作用域链的理解?

作用域链研究的是数据在EC中的查找机制。

全局代码执行产生ECG。函数代码执行产生ECF。EC需要放在ECS中。

EC的作用是为了给当前代码提供数据的,查找数据先在自己的EC中查找,如果找不到,就去它的父EC中找,
如果还找不到,就去父的EC的父的EC中找,直到找到ECG。这人查找机制就是所谓的作用域链。
1
2
3
4
5
6

# 3. 谈谈你对闭包的理解?

闭包是一片不会被销毁的EC。它对EC中的数据有保护和保存的作用。

保护:防止像全局变量那样被污染,数据在函数内部,外界访问不了。
保存:可以像全局变量那样,延长变量的生命周期。

应用场景:
- 模块封装,在各模块规范(ES6)出现之前,都是用这样的方式防止变量污染全局
- 数据私有化
- 封装一些工具函数
- 在循环中创建闭包,防止取到意外的值。
- 防抖 、节流 、⽴即执⾏函数 、组合函数等等

闭包使用不当可能会导致内存泄露
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4. 谈谈你对原型链的理解?

原型链指的是查找对象上某个属性的查找机制。

查找某个属性,先在自己的私有属性中找,如果找不到,就沿着__proto__去它的原型对象(公有属性)上找,如果还找不到,就
继续沿着__proto__去它的原型对象的原型对象上找,直到找到null。这个查找机制就是所谓的原型链。

利用原型链可以实现继承,在vue源码中,Vue这个构造函数的原型对象上,也封装了大量的方法。
1
2
3
4
5
6

# 5. 说一下JS中的继承?

继承, 可以让多个构造函数之间建立关联, 便于管理和复用

# a. 不使用继承

// 分析需求:
// 人类, 属性: name, age   会说话
// 学生, 属性: name, age, className  会说话
// 工人, 属性: name, age, companyName  会说话

// 为什么要有继承:
// 继承: 将多个构造函数, 建立关联, 实现方便管理 和 方便复用

// 角度
// 1. 方法的继承
// 2. 实例化属性过程的复用

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function() {
    console.log('会说话')
}

function Student(name, age, className) {
    this.name = name
    this.age = age
    this.className = className
}
Student.prototype.sayHi = function() {
    console.log('会说话')
}

function Worker(name, age, companyName) {
    this.name = name
    this.age = age
    this.companyName
}
Worker.prototype.sayHi = function() {
    console.log('会说话')
}
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

# b. 继承之原型继承

原型继承: 通过改造原型链, 利用原型链的语法, 实现继承方法!

// 目标: 原型继承 => 继承方法
// 原型继承: 通过改造原型链实现继承, 利用原型链的特征实现继承,所谓的原型继承,就是在改造原型链

// 1. 定义Person构造函数
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.say = function() {
    console.log('人类会说话')
}

// 2. 定义Student构造函数
function Student(name, age, className) {
    this.name = name
    this.age = age
    this.className = className
}
// 3. 原型继承: 利用原型链, 继承于父级构造函数, 继承原型上的方法
// 语法: 子构造函数.prototype = new 父构造函数()
Student.prototype = new Person()
Student.prototype.study = function() {
    console.log('学生在学习')
}

let stu = new Student('张三', 18, '80期')
stu.say()
console.log(stu)
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

# c. 继承之组合继承

// 组合继承: 两种技术的组合, 原型链技术, 借用构造函数(call)结合, 发挥二者之长, 实现继承的方式
// 1. 原型链技术: 改造原型链, 实现继承方法
//    Student.prototype = new Person()
// 2. 实例属性的构造过程没有得到复用, 可以用借用构造函数的方式, 实现复用
//    Person.call(this, name, age)

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function() {
    console.log('会说话')
}

function Student(name, age, className) {
    // 不仅要执行Person构造函数, 且要让执行构造函数时的this指向创建出来的实例stu
    // call
    // 1. 调用函数
    // 2. 改变函数执行时的this指向
    Person.call(this, name, age)
    this.className = className
}
Student.prototype = new Person()

const stu = new Student('zs', 7, '一年级一班')
stu.sayHi()
console.log(stu)

// 方法通过 原型继承
// 属性通过 父构造函数的.call(this, name, age)
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

# d. 继承之寄生组合继承

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function() {
    console.log('会说话')
}

function Student(name, age, className) {
    // 不仅要执行Person构造函数, 且要让执行构造函数时的this指向创建出来的实例stu
    // call
    // 1. 调用函数
    // 2. 改变函数执行时的this指向
    Person.call(this, name, age)
    this.className = className
}
// 构造函数没有必要执行, 我们只需要的是原型链
Student.prototype = Object.create(Person.prototype)

const stu = new Student('zs', 7, '一年级一班')
stu.sayHi()
console.log(stu)

// 总结:
// Object.create() 以参数的对象, 作为新建对象的__proto__属性的值, 返回新建的对象
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

# e. 继承之ES中的继承

// function Person (name, age) {
//   this.name = name
//   this.age = age
// }
// Person.prototype.sayHi = function() {}
// Person.prototype.jump = function() {}

// 人类
class Person {
    // 类似于之前的构造函数
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    // 底层 => 这两个方法, 就是添加到 Person.prototype 上
    sayHi() {
        console.log('你好哇')
    }
    jump() {
        console.log('会跳')
    }
}
const p = new Person('zs', 18)
console.log(p)

// 继承关键字 => extends
// 老师类
class Teacher extends Person {
    // 如果没有提供构造函数, 在继承时, 会默认自动借调父构造函数
    constructor(name, age, lesson) {
        // 你写的构造函数中, 没有借调父构造函数
        super(name, age) // 触发调用父构造函数, 进行实例的属性初始化
        this.lesson = lesson
    }
    teach() {
        console.log('会教书')
    }
}
const teacher = new Teacher('zs', 18, '教体育')
console.log(teacher)
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

# 6. 谈谈你对this的理解?

绑定规则如下:
    绑定一:默认绑定; 
    绑定二:隐式绑定; 
    绑定三:显示绑定; 
    绑定四:new绑定;

1,默认绑定:独立函数调用就是所谓的默认绑定,独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
2,隐式绑定:通过某个对象进行调用的,也就是它的调用位置中,是通过某个对象发起的函数调用
3,显示绑定:通过call,apply,bind去绑定
4,new绑定: new的原理

优先级:
    1,默认规则的优先级最低
    2,显示绑定优先级高于隐式绑定
    3,new绑定优先级高于隐式绑定 
    4,new绑定优先级高于bind 
    5,new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
    6,箭头函数中的this需要向上找一层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 7. 什么是浏览器事件环?

事件环:
1)先执行同步代码
2)如果遇到一个宏任务,会把这个任务放到一个宏任务队列,如果遇到一个微任务,就把这个微任务放到微任务任务中。
3)当同步代码执行完毕后,先去清空微任务队列。
4)当微任务队列清空完毕后,从宏任务队列中取出一个宏任务,去执行,在执行过程中,你的宏任务中可能还有同步代码或宏任务或微任务,重复上面的步骤,执行完一个宏任务,肯定要清空微任务队列。这就是所谓的事件环

宏任务:ajax,setTimeout,setInterval,DOM事件监听,UI渲染....
微任务:promies中的then回调  Mutaion Observer ...
1
2
3
4
5
6
7
8

# 8. 什么是防抖?什么是节流?有什么应用场景?

防抖 和 节流都是为了避免频繁触发一些回调函数。频繁触发,产生大量EC或大量的请求,造成浏览器性能低下或服务器压力太大。

防抖应用场景:
  1)当用户进行了某个行为(例如点击)之后。不希望每次行为都会触发方法,而是行为做出后,一段时间内没有再次重复行为,才给用户响应
  2)如:百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发
防抖实现原理: 每次触发事件时设置一个延时调用方法,并且取消之前的延时调用方法。(每次触发事件时都取消之前的延时调用方法)

节流应用场景: 用户进行高频事件触发(滚动),但在限制在n秒内只会执行一次。
节流实现原理: 
  1)每次触发时间的时候,判断当前是否存在等待执行的延时函数
  2)如:就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。

区别:节流不管事件触发多频繁保证在一定时间内一定会执行一次函数。防抖是只在最后一次事件触发后才会执行一次函数
1
2
3
4
5
6
7
8
9
10
11
12
13

# 9. 说说什么是事件委托?

事件委托,就是利用了事件冒泡的机制,在较上层位置的元素上添加一个事件监听函数,
来管理该元素及其所有子孙元素上的某一类的所有事件。

适用场景:在绑定大量事件的时候,可以选择事件委托

事件委托可以减少事件注册数量,节省内存占⽤!
当新增⼦元素时,⽆需再次做事件绑定,因此非常适合动态添加元素 (vue解析模板时, 会对新创建的元素, 额外进行绑定的)
1
2
3
4
5
6
7

# 10. 什么是柯里化函数?什么是组合函数?

柯里化:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数,这个过程,就是柯里化。是函数式编程的重要组成部分。

柯⾥化的作⽤:
1)单⼀职责:每⼀个函数只⽤处理传⼊的单个参数,每个函数的职责单⼀⽽且确定
2)参数复⽤:可以拿到每⼀层函数执⾏的返回值作为⼀个新的函数,复⽤已经传⼊过的参数。

组合函数:将多个函数使用compoose组合在一起,返回一个新函数。避免函数的嵌套调用。也是函数式编程的重要组成部分。

组合函数的作⽤:
1)减少重复代码的编写,提⾼代码的复⽤性,便于开发
2)可以对任意个函数进⾏组合,返回新的具有多个被组合函数功能的新函数
1
2
3
4
5
6
7
8
9
10
11

# 11. 项目开发过程中,都使用到了哪些ES6新特性?

1, 项目中使用let或const
2, 解构赋值,数组解构赋值,对象解构赋值,函数参数解构赋值,减少代码量
3,展开运算符
4,rest运算符
5,箭头函数
6,对象的中属性的简写,方法的简写
7,Object.assign( )方法
8,Symbol
9,Proxy vue3中数据响应式的核心
10,Set和Map集合
1
2
3
4
5
6
7
8
9
10

# 12. 介绍下 Set、Map的区别?

Set
    成员不能重复
    只有键值没有键名,类似数组
    可以遍历,方法有add, delete,has
Map
    本质上是健值对的集合,类似集合
    可以遍历,可以跟各种数据格式转换

应用场景:Set用于数据重组,Map用于数据储存
1
2
3
4
5
6
7
8
9

# 13. 说一下,JS中的模块化?

模块化要解决的问题:1)加载顺序  2)污染全局

1,没有模块化之前,是使用IIFE来解决
2,后面就有了大量JS插件,开发过程插件化,只给用户提供一个配置
3,后面有了AMD和CMD规范
4,NodeJS诞生,带来了前所未有的模块化体验,采用了CommoJS规范
5,ES官方的权威模块化规范
1
2
3
4
5
6
7

# 14. 异步加载JS的方式有哪些?

defer并行加载js文件,会按照页面上script标签的顺序执行
async并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行
1
2

# 15. JS数据类型?

在ES5的时候,我们认知的数据类型确实是 6种:Number、String、Boolean、undefined、object、Null。

ES6 中新增了一种 Symbol 。这种类型的对象永不相等,即始创建的时候传入相同的值,可以解决属性名冲突的问题,做为标记。
1
2
3

# 16. 如何实现深拷贝?

<!-- 深拷贝-深拷贝函数的循环引用 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
</head>

<body>
    <script>
        function isObject(value) {
            const valueType = typeof value
            return (value !== null) && (valueType === "object" || valueType === "function")
        }
    </script>
    <script>
        // 深拷贝函数
        // let map = new WeakMap()
        function deepCopy(originValue, map = new WeakMap()) {
            // const map = new WeakMap()

            // 0.如果值是Symbol的类型
            if (typeof originValue === "symbol") {
                return Symbol(originValue.description)
            }

            // 1.如果是原始类型, 直接返回
            if (!isObject(originValue)) {
                return originValue
            }

            // 2.如果是set类型
            if (originValue instanceof Set) {
                const newSet = new Set()
                for (const setItem of originValue) {
                    newSet.add(deepCopy(setItem))
                }
                return newSet
            }

            // 3.如果是函数function类型, 不需要进行深拷贝
            if (typeof originValue === "function") {
                return originValue
            }

            // 4.如果是对象类型, 才需要创建对象
            if (map.get(originValue)) {
                return map.get(originValue)
            }
            const newObj = Array.isArray(originValue) ? [] : {}
            map.set(originValue, newObj)
            // 遍历普通的key
            for (const key in originValue) {
                newObj[key] = deepCopy(originValue[key], map);
            }
            // 单独遍历symbol
            const symbolKeys = Object.getOwnPropertySymbols(originValue)
            for (const symbolKey of symbolKeys) {
                newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map)
            }

            return newObj
        }

        const info = {
            name: "wc",
            age: 18,
            friend: {
                name: "kobe",
                address: {
                    name: "xq",
                    detail: "bj"
                }
            },
            // self: info
        }
        info.self = info

        let newObj = deepCopy(info)
        console.log(newObj)
        console.log(newObj.self === newObj);
    </script>
</body>

</html>
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
76
77
78
79
80
81
82
83
84
85

# 17. 在 script 标签上使用 defer 和 async 的区别是什么?

明确: defer 和 async 的使用, 可以用于提升网页性能

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 1. 默认的写法, 会阻塞后面结构的加载和解析 -->
    <!-- <script src="https://baidu.com/ajax/libs/vue/2.6.12/vue.js"></script>
  <script src="https://baidu.com/ajax/libs/element-ui/2.15.0/index.js"></script> -->

    <!-- 2. async 异步加载文件, 不会阻塞, 脚本加载完, async立刻执行 -->
    <!--    async 使用场景: 适合于不依赖于其他js文件的脚本加载 -->
    <!-- <script async src="https://baidu.com/ajax/libs/vue/2.6.12/vue.js"></script>
  <script async src="https://baidu.com/ajax/libs/element-ui/2.15.0/index.js"></script> -->

    <!-- 3. defer 异步加载文件, 不会阻塞, 脚本加载完, 不会立刻执行, 会等一等, 
          等dom结构的加载, 且等上面的defer的脚本先执行, 它再执行 (保证顺序)
  -->
    <!--    如果有依赖关系, 用defer  -->
    <script defer src="https://baidu.com/ajax/libs/vue/2.6.12/vue.js"></script>
    <script defer src="https://baidu.com/ajax/libs/element-ui/2.15.0/index.js"></script>
</head>

<body>

    <div>555</div>

    <script>
        console.log('555')
    </script>
</body>

</html>
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

# 18. 封装一个数据类型检测的方法

# a. typeof检测

<script>
    // typeof
    console.log(typeof(123)) // number
    console.log(typeof("hello")) // string
    console.log(typeof(true)) // boolean
    console.log(typeof(undefined)) // und
    console.log(typeof(null)) // object  不准确
    console.log(typeof
        function() {}) // funciton
    console.log(typeof {}) // object
    console.log(typeof ["a"]) // object 不准确
    console.log(typeof NaN) // number
    // 特点:对于基本数据类型和函数的检测比较准确,对于引用数据类型的检测不准确
    // 使用的话,非常简单
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# b. instanceof检测

<script>
    // instanceof
    let a = 1;
    let b = "hello";
    console.log(a instanceof Number); // false
    console.log(b instanceof String); // false
    console.log({}
        instanceof Object); // true
    console.log([] instanceof Array); // true
    console.log(function() {}
        instanceof Function); // true

    console.log(function() {}
        instanceof Object); // true
    console.log([] instanceof Object); // true

    // 特点:检测引用数据类型还可以,检测基本数据类型不给力
    // 在JS中,所有的引用数据类型,都是Object类的实例,如果判断任何一个引用数据类型是否是Object的实例,结果都是true
    // 有时候,人为可以修改原型链,导致检测结果不准确
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# c. constructor检测

<script>
    // constructor

    let n = new Number(110);
    // 人为修改constructor,检测的结果就不准确了
    n.constructor = Array;
    console.log(n.constructor === Number) // true
    console.log({}.constructor === Object) // true
    console.log([].constructor === Array) // true

    console.log([].constructor === Number) // false

    // console.log(123.constructor === Number)

    // 特点:检测引用数据类型还可以,检测基本数据类型不能检测
    // 人为修改constructor,检测的结果就不准确了
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# d. toString检测

<script>
    // toString

    console.log(Object.prototype.toString.call(123)) // [object Number]
    console.log(Object.prototype.toString.call("hello")) // [object String]
    console.log(Object.prototype.toString.call(true)) // [object Boolean]
    console.log(Object.prototype.toString.call([])) // [object Array]
    console.log(Object.prototype.toString.call({})) // [object Object]
    console.log(Object.prototype.toString.call(function() {})) // [object Function]
    console.log(Object.prototype.toString.call(/abc/)) // [object RegExp]
    let d = new Date()
    console.log(Object.prototype.toString.call(d)) // [object Date]
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

# e. 封装方法

<script>
    // 封装一个方法,实现数据类型的检测  方法的核心,还是toString
    function checkType(date) {
        var type = Object.prototype.toString.call(date);
        type = type.substr(8, type.length - 9);
        return type.toLowerCase();
    };

    // var x=checkType(123);
    var x = checkType("hello");
    console.log(x); //number
</script>
1
2
3
4
5
6
7
8
9
10
11
12

# 19. 手写 事件总线(发布订阅模式)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>

    <button class="nav-btn">nav button</button>

    <script>
        // 类EventBus -> 事件总线对象
        class MyEventBus {
            constructor() {
                this.eventMap = {}
            }

            on(eventName, eventFn) {
                let eventFns = this.eventMap[eventName]
                if (!eventFns) {
                    eventFns = []
                    this.eventMap[eventName] = eventFns
                }
                eventFns.push(eventFn)
            }

            off(eventName, eventFn) {
                let eventFns = this.eventMap[eventName]
                if (!eventFns) return
                for (let i = 0; i < eventFns.length; i++) {
                    const fn = eventFns[i]
                    if (fn === eventFn) {
                        eventFns.splice(i, 1)
                        break
                    }
                }

                // 如果eventFns已经清空了
                if (eventFns.length === 0) {
                    delete this.eventMap[eventName]
                }
            }

            emit(eventName, ...args) {
                let eventFns = this.eventMap[eventName]
                if (!eventFns) return
                eventFns.forEach(fn => {
                    fn(...args)
                })
            }
        }

        // 使用过程
        const eventBus = new MyEventBus()

        // aside.vue组件中监听事件
        eventBus.on("navclick", (name, age, height) => {
            console.log("navclick listener 01", name, age, height)
        })

        const click = () => {
            console.log("navclick listener 02")
        }
        eventBus.on("navclick", click)

        setTimeout(() => {
            eventBus.off("navclick", click)
        }, 5000);

        eventBus.on("asideclick", () => {
            console.log("asideclick listener")
        })

        // nav.vue
        const navBtnEl = document.querySelector(".nav-btn")
        navBtnEl.onclick = function() {
            console.log("自己监听到")
            eventBus.emit("navclick", "wc", 18, 1.88)
        }
    </script>

</body>

</html>
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
76
77
78
79
80
81
82
83
84
85
86

# 20. 用setTimeout实现setInterval

题目描述:setinterval 用来实现循环定时调用 可能会存在一定的问题 能用 settimeout 解决吗

function mySetTimout(fn, delay) {
    let timer = null
    const interval = () => {
        fn()
        timer = setTimeout(interval, delay)
    }
    setTimeout(interval, delay)
    return {
        cancel: () => {
            clearTimeout(timer)
        }
    }
}

// 测试
const {
    cancel
} = mySetTimout(() => console.log(888), 1000)
setTimeout(() => {
    cancel()
}, 4000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 21. 用setInterval实现setTimeout

function mySetInterval(fn, delay) {
    const timer = setInterval(() => {
        fn()
        clearInterval(timer)
    }, delay)
}

// 测试
mySetInterval(() => console.log(888), 1000)
1
2
3
4
5
6
7
8
9

# 22. 实现一个compose函数

function fn1(x) {
    return x + 1;
}

function fn2(x) {
    return x + 2;
}

function fn3(x) {
    return x + 3;
}

function fn4(x) {
    return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a)
console.log(a(1)); // 1+2+3+4=11

function compose(...fn) {
    if (fn.length === 0) return (num) => num
    if (fn.length === 1) return fn[0]
    return fn.reduce((pre, next) => {
        return (num) => {
            return next(pre(num))
        }
    })
}
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

# 23. 实现一个科里化函数

const add = (a, b, c) => a + b + c;
const a = currying(add, 1);
console.log(a(2, 3)) // 1 + 2 + 3=6

function currying(fn, ...args1) {
    // 获取fn参数有几个
    const length = fn.length
    let allArgs = [...args1]
    const res = (...arg2) => {
        allArgs = [...allArgs, ...arg2]
        // 长度相等就返回执行结果
        if (allArgs.length === length) {
            return fn(...allArgs)
        } else {
            // 不相等继续返回函数
            return res
        }
    }
    return res
}

// 测试:
const add = (a, b, c) => a + b + c;
const a = currying(add, 1);
console.log(a(2, 3))
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

# 24. 将DOM转化成树结构对象

&lt;div>&lt;span>&lt;/span> &lt;ul>&lt;li>&lt;/li>&lt;li>&lt;/li>&lt;/ul>&lt;/div>

将上方的DOM转化为下面的树结构对象

{
    tag: 'DIV',
    children: [{
            tag: 'SPAN',
            children: []
        },
        {
            tag: 'UL',
            children: [{
                    tag: 'LI',
                    children: []
                },
                {
                    tag: 'LI',
                    children: []
                }
            ]
        }
    ]
}

function dom2tree(dom) {
    const obj = {}
    obj.tag = dom.tagName
    obj.children = []
    dom.childNodes.forEach(child => obj.children.push(dom2tree(child)))
    return obj
}
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

# 25. 将树结构转换为DOM

{
    tag: 'DIV',
    children: [{
            tag: 'SPAN',
            children: []
        },
        {
            tag: 'UL',
            children: [{
                    tag: 'LI',
                    children: []
                },
                {
                    tag: 'LI',
                    children: []
                }
            ]
        }
    ]
}

将上方的树结构对象转化为下面的DOM

    <
    div >
    <
    span > < /span> <
ul >
    <
    li > < /li> <
li > < /li> < /
ul > <
    /div>

// 真正的渲染函数
function _render(vnode) {
    // 如果是数字类型转化为字符串
    if (typeof vnode === "number") {
        vnode = String(vnode);
    }
    // 字符串类型直接就是文本节点
    if (typeof vnode === "string") {
        return document.createTextNode(vnode);
    }
    // 普通DOM
    const dom = document.createElement(vnode.tag);
    if (vnode.attrs) {
        // 遍历属性
        Object.keys(vnode.attrs).forEach((key) => {
            const value = vnode.attrs[key];
            dom.setAttribute(key, value);
        });
    }
    // 子数组进行递归操作
    vnode.children.forEach((child) => dom.appendChild(_render(child)));
    return dom;
}
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

# 26. 手写call的实现原理

<script>
    // call的原理
    (function() {
        function mlcall(context) {
            context = context ? Object(context) : window;
            // this就是fn  
            // this()  fn()
            context.f = this;
            let args = []; // 收集函数的参数
            for (var i = 1; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            let res = context.f(...args)
            delete context.f;
            return res;
        }
        Function.prototype.mlcall = mlcall;
    }())

    function fn(num1, num2) {
        console.log(this);
        return num1 + num2
    }
    let obj = {
        name: "码路"
    }
    // 1)改变fn中this的指向
    // 2)call可以让函数调用
    // 3)返回函数调用的结果
    // let res = fn.call(obj, 6, 8)
    let res = fn.mlcall(obj, 6, 8)
    console.log(res)
</script>
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

# 27. 手写apply的实现原理

<script>
    // apply的原理
    (function() {
        function mlapply(context, args) {
            context = context ? Object(context) : window;
            // this  表示fn
            // this() 不OK
            context.f = this;
            if (!args) {
                return context.f();
            }
            let res = context.f(...args)
            delete context.f;
            return res;
        }
        Function.prototype.mlapply = mlapply;
    }())

    function fn(num1, num2) {
        console.log(this);
        return num1 + num2
    }
    let obj = {
        name: "码路"
    }
    // 1)改变fn中this的指向
    // 2)apply可以让函数调用
    // 3)返回函数调用的结果
    // let res = fn.apply(obj, [6, 8])
    let res = fn.mlapply(obj, [6, 8])
    console.log(res)
</script>
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

# 28. 手写bind的实现原理

<script>
    // bind实现原理
    (function() {
        function mlbind(context) {
            let bindArgs = Array.prototype.slice.call(arguments, 1);
            // this 表示fn  this()不OK
            let that = this;

            function gn() {
                let args = Array.prototype.slice.call(arguments);
                return that.apply(context, bindArgs.concat(args))
            }
            return gn;
        }
        Function.prototype.mlbind = mlbind;
    })()

    function fn(num1, num2) {
        console.log(this);
        return num1 + num2;
    }
    let obj = {
        name: "码路"
    }
    // 1)改变fn中this的指向
    // 2)返回一个绑定this后的函数
    // let res = fn.bind(obj, 1)
    let res = fn.mlbind(obj, 1)
    console.log(res(2));
</script>
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

# 29. 手写new的实现原理

< script >
    function Dog(name) {
        this.name = name;
        // return {a:1}
        return function() {
            console.log("xx");
        }
    }
Dog.prototype.bark = function() {
    console.log('wangwang');
}
Dog.prototype.sayName = function() {
    console.log('my name is ' + this.name);
}
// (1)在构造器内部创建一个新的对象
// (2)这个对象内部的__proto__属性会被赋值为该构造函数的prototype属性;
// (3)让构造器中的this指向这个对象
// (4)执行构造器中的代码
// (5)如果构造器没有返回对象或函数,则返回上面的创建出来的对象
// let malu = new Dog('码路');
// console.log(malu);
// malu.sayName();
// malu.bark();
function _new(Ctor, ...args) {
    //=>完成你的代码
    if (!Ctor.hasOwnProperty("prototype")) {
        throw new TypeError("Ctor is not a constructor")
    }
    let obj = Object.create(Ctor.prototype)
    let result = Ctor.apply(obj, args)
    if (result !== null && (typeof result == "object" || typeof result == "function")) {
        return result;
    }
    return obj;
}
let malu = _new(Dog, '码路');
console.log(malu);
// malu.bark(); //=>"wangwang"
// malu.sayName(); //=>"my name is 码路"
// console.log(malu instanceof Dog); //=>true
<
/script>
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

# 30. 手写 instanceof 关键字

function instanceOf(father, child) {
    const fp = father.prototype
    var cp = child.__proto__
    while (cp) {
        if (cp === fp) {
            return true
        }
        cp = cp.__proto__
    }
    return false
}
1
2
3
4
5
6
7
8
9
10
11

# 31. 手写实现深copy

<script>
    // 实现深copy
    function deepClone(target, weakMap = new WeakMap()) {
        if (target == null) return target;
        if (target instanceof Date) return new Date(target);
        if (target instanceof RegExp) return new RegExp(target)
        // .....
        if (typeof target !== "object") return target;
        let cloneTarget = new target.constructor;
        if (weakMap.get(target)) {
            return weakMap.get(target)
        }
        weakMap.set(target, cloneTarget)
        for (let key in target) {
            if (target.hasOwnProperty(key)) {
                cloneTarget[key] = deepClone(target[key], weakMap)
            }
        }
        return cloneTarget;
    }
    let obj = {
        name: "码路",
        address: {
            city: "北京"
        }
    };
    obj.xxx = obj; // 循环引用
    let newObj = deepClone(obj);
    console.log(newObj); < /script>
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
Last Updated: 12/25/2022, 10:02:14 PM