# 前端手写题小册

# 1 / 函数防抖 「debounce」

防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。 那么与节流函数的区别直接看这个动画实现即可 (通过闭包)

const debounce = function(handle,delay){
    let timer = null // 存储定时器

    return (...args) => {
        clearTimeoute(timer)
        timer = setTimeout(() => {
            handle.apply(this, args)
        }, delay)
    }
}
1
2
3
4
5
6
7
8
9
10

适用场景:

  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

# 2 / 函数节流 「throttle」

节流函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效 (通过闭包)

const throttle = function(handle, delay = 500){
    let flag = true // 是否可以执行的标识
    
    return (...args) => {
         if(!flag) return

         flag = false
         setTimeout( () => {
            handle.apply(this, args)
            flag = true
         }, delay)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

适用场景:

  • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
  • 缩放场景:监控浏览器resize
  • 动画场景:避免短时间内多次触发动画引起性能问题

# 3 / 深克隆 「deepclone」

简单版 - 通过JSON

const newObj = JSON.parse(JSON.stringify(oldObj))
1

局限性:

  • 他无法实现对函数,RegExp等特殊对象的克隆
  • 会抛弃对象的constructor,所有的构造函数会指向Object
  • 对象有循环引用,会报错

面试版

/**
 * deep clone
 * @param  {[type]} parent object 需要进行克隆的对象
 * @return {[type]}        深克隆后的对象
 */

const deepClone = (parent) => {
    // 1 - 判断类型
    const isType = (obj, type) => {
        if(typeof obj !== 'object') return false
        
        const typeString = Object.prototype.toString.call(obj)  // like [object Object]
        let flag   // 判断当前类型是否是Array,Date或者RegExp
        switch(type) {
            case 'Array':
                flag = typeString === '[object Array]'
                break
            case 'Date':
                flag = typeString === '[object Date]'
                break
            case 'RegExp':
                flag = typeString === '[object RegExp]'
                break
            default:
                flag = false
        }
        return flag
    }

    // 处理正则
    const handleRegExp = reg => {
        var flags = ''
        if (re.global) flags += 'g'
        if (re.ignoreCase) flags += 'i'
        if (re.multiline) flags += 'm'
        return flags
    }

    // 维护两个存储循环引用的数组
    const parentsArr = []
    const childrenArr = []

    const clone = parent => {
        if( parent === null ) return null
        if( typeof parent !== "object" ) return parent  // 若不是对象类型,直接返回要克隆的对象

        let child, proto

        if(isType(parent, 'Array')){
            // 对数组做特殊处理
            child = []
        }else if(isType(parent, 'RegExp')){
            // 对正则对象做处理
            child = new RegExp(parent.source, handleRegExp(parent))
            if(parent.lastIndex) child.lastIndex = parent.lastIndex
        }else if (isType(parent, "Date")) {
            // 对Date对象做特殊处理
            child = new Date(parent.getTime());
        }else{
            // 核心:获取对象原型,利用Object.create()切断原型链,使克隆出来的对象不对原对象有影响
            proto = Object.getPrototypeOf(parent)
            child = Object.create(proto)
        }

        // 处理循环引用
        const index = parentsArr.indexOf(parent)
        if(index != -1){
            // 如果父数组存在本对象,说明之前已经被引用过,则直接返回此对象
            return childrenArr[index]
        }
        
        parentsArr.push(parent) // 之前未存在,则存入数组
        childrenArr.push(child)

        for(let i in parent){
            // 递归
            child[i] = clone(parent[i])
        }

        return child
    }

    return clone(parent)
}
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

局限性:

  • 一些特殊情况没有处理: 例如Buffer对象、Promise、Set、Map
  • 另外对于确保没有循环引用的对象,我们可以省去对循环引用的特殊处理,因为这很消耗时间

# 4 / Event Bus

event Bus 既是 node 中各个模块的基石,又是前端组件的依赖手段之一, 同时涉及了「订阅- 发布」的设计模式,是非常重要的基础

简单版

class EventEmeitter {
    constructor(){
        this._events = this._events || new Map()  // 用于存储事件/回调键值对
    }
}

// 触发名为 type 的事件
EventEmitter.prototype.emit = function(type, ...args){
    let handler

    // 从存储事件键值对的 _events 中读取对应事件回调
    handler = this._events.get(type)
    if(args.length > 0){
        handler.apply(this, args)
    }else{
        handler.call(this)
    }

    return true
}

// 监听名为 type 的事件 (添加事件)
EventEmitter.prototype.addListener = function(type, fun){
    // 将名为 type 的事件以及对应的 fun函数 存入 _events 中存储
    if(!this._events.get(type)){
        this._events.set(type, fun)
    }
}
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

面试版

class EventEmitter {
    constructor(){
        this._events = this._events || new Map() // 用于存储事件/回调键值对
    }
}

// 触发名为 type 的事件
EventEmitter.prototype.emit = function(type, ...args){
    let handler = this._events.get(type) // 获取对应事件名的函数清单
    
    if(Array.isArray(handler)){
        // 如果是数组说明type有多个监听者,需要依次触发里面的函数
        for(let i = 0; i < handler.length; i++){
            if(args.length > 0){
                handler[i].apply(this, args)
            }else{
                handler[i].call(this)
            }
        }
    }else{
        // 单个函数的情况则直接触发
        if(args.length > 0){
            handler.apply(this, args)
        }else{
            handler.call(this)
        }
    }

    return true
}

// 监听名为 type 的事件
EventEmitter.prototype.addListener = function(type, fun){
    const handler = this._events.get(type) // 获取对应事件名的函数清单

    if(!handler){
        // 如果没有该 type 的键,则直接存入
        this._events.set(type, fun)
    }else if(handler && typeof handler === 'function'){
        // 如果存在该键,且类型是函数,则多个监听者需要更换用数组存储
        this._events.set(type, [handler, fun])
    }else{
        // 存在该键,且已经是多个监听者的情况,直接push函数进数组
        handler.push(fun)
    }
}

// 移除名为 type 的 fn 事件
EventEmitter.prototype.removeListener = function(type, fun){
    const handler = this._events.get(type) // 获取对应事件名的函数清单

    if(handler && typeof handler === 'function'){
        // 如果事件名存在且是函数,只备监听一次,直接delete
        this._events.delete(type)
    }else{
        let position

        // 如果handler是数组,说明被监听多次,要找到对应的函数
        for(let i = 0; i < handler.length; i++){
            if(handler[i] === fun){
                position = i
            }else{
                position = -1
            }
        }

        // 如果找到匹配的函数,从数组中清除
        if(position !== -1){
            // 找到数组对应的位置,直接清除此回调
            handler.splice(position, 1)

            // 如果清除后只有一个函数,那么将数组类型转变为函数类型保存
            if(handler.length === 1){
                this._events.set(type, handler[0])
            }
        }else{
            return this
        }
    }
}
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

# 5 / instanceof

instanceof 原理是通过原型链进行查找,A instanceof B,在A的原型链中层层查找,查找是否有原型等于B.prototype

// A instanceof B  → left 表示 A, right 表示 B
function myInstanceof(left, right){
    var R = right.prototype, // 获取 right 的原型对象
        L = left.__proto__  // 获取 left 的原型对象
    
    while(true){
        if(L === null) return false
        if(L === R){
            return true
        }
        L = L.__proto__ // 找不到且未到顶端,原型链继续向上
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6 / New

首先理解 new 操作符都做了些什么事

  • 创建了一个全新的对象
  • 这个全新的对象被执行原型连接(proto)
  • 将this指向新创建的对象(对象赋值给this)
  • 执行这个构造函数中的所有方法
  • 如果函数没有返回对象类型Object,那么new表达式中的函数调用将返回该对象引用
function objectFactory() {
    const obj = new Object()
    const Constructor = [].shift.call(arguments) // 删除并获取arguments第一项

    obj.__proto__ = Constructor.prototype

    const returnObj = Constructor.apply(obj, arguments)  // 执行构造函数 并获取返回值来判断是否有返回值

    return typeof returnObj === "object" ? returnObj : obj // 如果函数没有返回对象类型Object,那么new表达式中的函数调用将返回该对象引用
}

function Person(name){
    this.name = name
}

objectFactory(Person, 'chicago')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 7 / call

首先理解 call 需要做什么

  • 将函数设为对象的属性
  • 执行 & 删除 这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入函数, 默认指向window
Function.prototype.myCall = function(Argument){
    // 判断this指向的是否为function
    if (typeof this !== 'function') {
        throw new TypeError('this should be a function')
    }

    // Argument不存在时(不传参),指向window
    Argument = Argument || window
    Argument.fn = this   // this指向调用 myCall 的函数

    const args = [...arguments].slice(1) 
    const result = Argument.fn(...args) // 传入参数 执行调用 myCall 的这个函数

    delete Argument.fn
    return result
}

function sayHi(name){
    console.log(this.a + name)
}

var obj = {
    a: 'A'
}

sayHi.myCall(obj,'chicago') // Achicago
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

# 8 / apply

原理与call相似,只是处理参数方式不同

Function.prototype.myApply = function(Argument){
    // 判断this指向的是否为function
    if (typeof this !== 'function') {
        throw new TypeError('this should be a function')
    }

    // Argument不存在时(不传参),指向window
    Argument = Argument || window
    Argument.fn = this // this指向调用myCall的函数

    let result
    // 因为 myApply 接受的第二个参数是数组,需判断有没有第二个参数的情况
    if(arguments[1]){
        // 有参数
        result = Argument.fn(...arguments[1])
    }else{
        // 没参数
        result = Argument.fn()
    }

    delete Argument.fn
    return result
}

var obj1 = {
    a: 1
}
var obj2 = {
    a: 2,
    say: function() {
        console.log(this.a)
    }
}
obj2.say.myApply(obj1)
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

# 9 / bind

首先理解 bind 需要做什么

  • 区别call,applybind返回的是一个新函数
  • bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效
Function.prototype.myBind = function(Argument) {
    // 判断this指向的是否为function
    if (typeof this !== 'function') {
        throw new TypeError('this should be a function')
    }

    const args = [].slice.call(arguments, 1) // 获取除第一个参数之外的参数
    const self = this // this指向调用myCall的函数

    const savePrototype = function(){} // 用于保存原函数原型对象

    const bind = function() {
        // this instanceof savePrototype 判断是否使用了 new 来调用 myBind
        // 如果是通过 new 来调用,this则指向这个实例
        // 如果不是,就改变 this 的指向,让 this 指向指定的对象
        return self.apply(this instanceof savePrototype ? this : Argument, args.concat([].slice.call(arguments)))
    }

    if (this.prototype) {
        savePrototype.prototype = this.prototype
    }
    
    bind.prototype = new savePrototype()
    return bind
}

var obj1 = {
    a: 1
}
var obj2 = {
    a: 2,
    say: function() {
        console.log(this.a)
    }
}

const bind = obj2.say.myBind(obj1)
bind()
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

# 10 / Object.create

Object.create()用于创建一个新对象,那么我们可以通过现有的对象来提供新创建的对象

function create(proto){
    function newObj(){}

    newObj.prototype = proto

    return new newObj()
}
1
2
3
4
5
6
7

# 11 / 实现类的继承

组合继承 → 原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性和方法的继承 优缺点:

  • 可传参,不会与父类共享引用属性,可以复用父类的函数
  • 继承父类时调用父类构造函数,导致子类上会多出一些不需要的父类属性,存在浪费
function Parent(name) {
    this.name = name
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(name){
    Parent.call(this, name)  // 借用构造函数
}
Child.prototype = new Parent() // 原型链继承
1
2
3
4
5
6
7
8
9
10

寄生组合继承 优点:

  • 解决了组合继承中无用的父类属性问题
function Parent(name) {
    this.name = name
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(name){
    Parent.call(this, name)
}
Child.prototype = Object.create(Parent.prototype, {
    constructor:{
        value: Child,
        enumerable: false,
        writable: true,
        configurable: true
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 12 / JSON.parse

此方式属于'黑魔法',极易受到XSS攻击

var json = '{"name" : "chicago", "age": 25}'
var obj = eval("(" + json + ")")
1
2

# 13 / Promise

  • 1 / Promise 的声明 ( class
    • 由于new Promise((resolve, reject) => {}),所以传入一个参数(函数)→ 函数我们称为executor
    • executor有两个参数,一个叫resolve,一个叫reject
    • 由于resolvereject可执行,所以都是函数,我们用let来声明
class Promise{
    constructor(executor){
        let resolve = () => {}
        let reject = () => {}

        // 立即执行
        executor(resolve, reject)
    }
}
1
2
3
4
5
6
7
8
9
  • 2 / 解决基本状态
    • Promise存在三个状态:pending,fulfilled,rejected
    • pending(等待态)为初始状态,可以转化为fulfilled或者rejected
    • 成功时,不可转化为其他状态,且必须有一个不可改变的值(value)
    • 失败时,不可转化为其他状态,且必须有一个不可改变的原因(reason)
    • new Promise((resolve, reject) => { resolve(value) }) resolve为成功,接受参数value,状态修改为fulfilled,不可再次改变
    • new Promise((resolve, reject) => { reject(reason) }) reject为失败,接受参数reason,状态修改为rejected,不可再次改变
    • executor函数报错,直接执行reject()
class Promise{
    construtor(executor){
        // 初始化state为pending等待态
        this.state = 'pending'
        // 存储成功的值
        this.value = undefined
        // 存储失败的原因
        this.reason = undefined

        let resolve = value => {
            // state改变,resolve调用就会失败
            if(this.state = 'pending'){
                // resolve调用后,state转变为成功态
                this.state = 'fulfilled'
                // 存储成功的值
                this.value = value
            }
        }

        let reject = reason => {
            // state改变,resolve调用就会失败
            if(this.state = 'pending'){
                // resolve调用后,state转变为失败态
                this.state = 'rejected'
                // 存储成功的值
                this.reason = reason
            }
        }

        // 如果executor执行报错,直接执行reject
        try{
            executor(resolve, reject)
        }catch(err){
            reject(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
28
29
30
31
32
33
34
35
36
37
  • 3 / 实现 then 方法
    • Promise有一个then方法,里面有两个参数,onFulfilled,onRejected,成功有成功的值,失败有失败的原因
    • 当状态statefulfilled,则执行onFilfilled,传入this.value。当状态staterejected,则执行onRejected,则传入this.reason
    • onFulfilledonRejected如果它们是函数,则分别在状态为fulfilledrejected之后被调用,valuereason依次作为它们的第一个参数
class Promise{
    constructor(executor){ ... }

    // then 方法,接受2个参数, onFulfilled,onRejected
    then(onFulfilled, onRejected){
        // 状态为fulfilled,执行onFulfilled
        if(this.state === 'fulfilled'){
            onFulfilled(this.value)
        }
        // 状态为rejected,执行onRejected
        if(this.state === 'rejected'){
            onRejected(this.value)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 4 / 解决异步实现
    • 现在基本可以实现简单的同步代码,但是当resolvesetTimeout内执行,thenstate还处于等待态,我们就需要在then调用的时候,将成功和失败存到各自的数组,一旦resolve或者reject,就调用它们
    • 类似于发布订阅,先将then里面的两个函数存储起来,由于一个Promise可以有多个then,所以存在同一个数组内
    • 成功或失败的时候,通过forEach调用它们
class Promise{
    constructor(executor){
        // 初始化state为pending等待态
        this.state = 'pending'
        // 存储成功的值
        this.value = undefined
        // 存储失败的原因
        this.reason = undefined
        // + 成功回调存放的数组
        this.onResolvedCallbacks = []
        // + 失败回调存放的数组
        this.onRejectedCallbacks = []

        let resolve = value => {
            // state改变,resolve调用就会失败
            if(this.state = 'pending'){
                // resolve调用后,state转变为成功态
                this.state = 'fulfilled'
                // 存储成功的值
                this.value = value
                // + 一旦resolve执行,调用成功数组的函数
                this.onResolvedCallbacks.forEach( fn => fn() )
            }
        }

        let reject = reason => {
            // state改变,resolve调用就会失败
            if(this.state = 'pending'){
                // resolve调用后,state转变为失败态
                this.state = 'rejected'
                // 存储成功的值
                this.reason = reason
                // + 一旦reject执行,调用成功数组的函数
                this.onRejectedCallbacks.forEach( fn => fn() )
            }
        }

        // 如果executor执行报错,直接执行reject
        try{
            executor(resolve, reject)
        }catch(err){
            reject(err)
        }

        // then 方法,接受2个参数, onFulfilled,onRejected
        then(onFulfilled, onRejected){
            // 状态为fulfilled,执行onFulfilled
            if(this.state === 'fulfilled'){
                onFulfilled(this.value)
            }
            // 状态为rejected,执行onRejected
            if(this.state === 'rejected'){
                onRejected(this.value)
            }
            // + 状态为pending
            if(this.state === 'pending'){
                // onFulfilled传入成功回调数组
                this.onResolvedCallbacks.push(() => {
                    onFulfilled(this.value)
                })
                // onRejected传入失败回调数组
                this.onRejectedCallbacks.push(() => {
                    onRejected(this.reason)
                })
            }
        }
    }
}
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
  • 5 / 解决链式调用
    • 我们常常用到new Promise().then().then(),这就是链式调用,用来解决回调地狱
    • 为了达成链式,我们默认在第一个then里返回一个Promise,就是在then方法里面返回一个新的Promise,称为promiseTwopromiseTwo = new Promise((resolve, reject) => {})
      • 将这个promiseTwo返回的值传递给下一个then
      • 如果返回一个普通的值,则将普通的值传递给下一个then
    • 当我们在第一个then中返回了一个参数(参数未知,需判断)。这个返回出来的新的promise就是onFulfilled()或者onRejected()的值
    • 规定onFulfilled()onRejected()的值,即第一个then返回的值,叫做x,判断x的函数叫做resolvePromise
      • 首先要看x是不是promise
      • 如果是promise,则取它的结果,作为新的promiseTwo成功的结果
      • 如果是普通值,直接作为promiseTwo成功的结果
      • 所以要比较x和promiseTwo
      • resolvePromise的参数有promiseTwo(默认返回的promise),x(我们自己return的对象),resolvereject
      • resolverejectpromiseTwo
class Promise{
    constructor(executor){
        this.status = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []

        let resolve = value => {
            if(this.status === 'pending'){
                this.status = 'fulfilled'
                this.value = value
                this.onResolvedCallbacks.forEach( fn => fn() )
            }
        }
        let reject = reason => {
            if(this.status === 'pending'){
                this.status = 'rejected'
                this.reason = reason
                this.onRejectedCallbacks.forEach( fn => fn())
            }
        }

        try{
            executor(resolve, reject)
        }catch(err){
            reject(err)
        }
    }

    then(onFulfilled, onRejected){
        // + 声明返回的promiseTwo
        let promiseTwo = new Promise((resolve, reject) => {
            if(this.status === 'fulfilled'){
                let x = onFulfilled(this.value)
                // resolvePromise函数处理自己返回的promise和默认的promiseTwo的关系
                resolvePromise(promiseTwo, x, resolve, reject)
            }
            if(this.status === 'rejected'){
                let x = onRejected(this.reason)
                resolvePromise(promiseTwo, x, resolve, reject)
            }
            if(this.status === 'pending'){
                this.onResolvedCallbacks.push( () => {
                    let x = onFulfilled(this.value)
                    resolvePromise(promiseTwo, x, resolve, reject)
                })
                this.onRejectedCallbacks.push( () => {
                    let x = onRejected(this.reason)
                    resolvePromise(promiseTwo, x, resolve, reject)
                })
            }
        })

        // 返回 promise,完成链式
        return PromiseTwo
    }
}
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
  • 6 / 实现 resolvePromise 函数
    • 规定了一段代码,让不同的promise代码相互套用,叫做resolvePromise
    • 如果 x === promiseTwo,则是会造成循环引用,自己等待自己完成,则报循环引用错误,例如这段代码
      let p = new Promise(resolve => {
          resolve(0);
      });
      var p2 = p.then(data => {
          // 循环引用,自己等待自己完成,一辈子完不成
          return p2;
      })
      /* Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise> */
      
      1
      2
      3
      4
      5
      6
      7
      8
    • 1 · 判断x
      • x 不能是 null
      • x 是普通值,则直接 resolve(x)
      • x 是对象或者函数(包括Promise),let then = x.then
    • 2 · 当x是对象或者函数(默认promise
      • 声明了then
      • 如果取then报错,则走reject()
      • 如果then是个函数,则用call执行then,第一个参数是this,后面则是成功的回调和失败的回调
      • 如果成功的回调还是返回promise,就递归继续解析
      • 成功和失败只能调用一个,所以设定一个called来防止多次调用
function resolvePromise(promiseTwo, x, resolve, reject){
    // 循环引用则报错
    if(x === promiseTwo){
        // 直接reject报错
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }

    // 防止多次调用
    let called

    // x 不是null 且 x 是对象或者函数
    if(x != null && (typeof x === 'object' || typeof x === 'function')){
        try{
            // 声明 then ,并且让它为 x.then
            let then = x.then
            // 如果then是函数,就默认是promise了
            if(typeof then === 'function'){
                // 让then执行,第一个参数是this,后面是成功的回调和失败的回调
                then.call(x, success => {
                    // 成功和失败只能调用一个
                    if(called) return
                    called = true
                    // resolve的结果如果依旧是promise,那就继续解析
                    resolvePromise(promiseTwo, success, resolve, reject)
                }, err => {
                    if(called) return
                    called = true
                    // 失败了就直接reject
                    reject(err) 
                })
            }else{
                // 如果不是函数,直接resolve即可
                resolve(x) 
            }
        }catch(error){
            // 也属于失败
            if(called) return
            called = true
            // 获取then出错了那就不要在执行
            reject(error)
        }
    }else{
        resolve(x)
    }
}
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
  • 7 / 解决其他问题
    • 规定onFulfilledonRejected都是可选参数,如果它们不是函数,必须被忽略
      • onFulfilled返回一个普通的值,成功时直接等于value => value
      • onRejected返回一个普通的值,失败时如果直接等于value => value,则会跑到下一个then中的onFulfilled,所以直接扔出一个错误err => throw err
    • 规定onFulfilledonRejected不能同步被调用,必须异步调用,我们就用setTimeout解决异步问题
      • 如果onFulfilled或者onRejected报错,则直接返回reject()
class Promise{
    constructor(executor){
        this.status = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []

        let resolve = value => {
            if(this.status === 'pending'){
                this.status = 'fulfilled'
                this.value = value
                this.onResolvedCallbacks.forEach( fn => fn() )
            }
        }
        let reject = reason => {
            if(this.status === 'pending'){
                this.status = 'rejected'
                this.reason = reason
                this.onRejectedCallbacks.forEach( fn => fn())
            }
        }

        try{
            executor(resolve, reject)
        }catch(err){
            reject(err)
        }
    }

    then(onFulfilled, onRejected){
        // + onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        // + onRejected如果不是函数,就忽略onRejected,直接抛出错误
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }

        let promiseTwo = new Promise((resolve, reject) => {
            if(this.status === 'fulfilled'){
                // + 异步
                setTimeout( () => {
                    try{
                        let x = onFulfilled(this.value)
                        resolvePromise(promiseTwo, x, resolve, reject)
                    }catch(err){
                        reject(err)
                    }
                },0)
            }

            if(this.status === 'rejected'){
                setTimeout( () => {
                    try{
                        let x = onRejected(this.reason)
                        resolvePromise(promiseTwo, x, resolve, reject)
                    }catch(err){
                        reject(err)
                    }
                }, 0)
            }

            if(this.status === 'pending'){
                this.onResolvedCallbacks.push( () => {
                    setTimeout(() => {
                        try{
                            let x = onFulfilled(this.value)
                            resolvePromise(promiseTwo, x, resolve, reject)
                        }catch(err){
                            reject(err)
                        }
                    },0)
                })

                this.onRejectedCallbacks.push( () => {
                    setTimeout(() => {
                        try{
                            let x = onRejected(this.reason)
                            resolvePromise(promiseTwo, x, resolve, reject)
                        }catch(err){
                            reject(err)
                        }
                    },0)
                })
            }
        })
        // 返回promise,完成链式
        return promiseTwo
    }
}
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
87
88
  • 8 / 完整代码

完整版 附 catch(), resolve(),reject(), rece(), all()

class Promise{
    constructor(executor){
        this.status = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []

        let resolve = value => {
            if(this.status === 'pending'){
                this.status = 'fulfilled'
                this.value = value
                this.onResolvedCallbacks.forEach( fn => fn())
            }
        }
        let resolve = reason => {
            if(this.status === 'pending'){
                this.status = 'rejected'
                this.reason = reason
                this.onRejectedCallbacks.forEach( fn => fn())
            }
        }

        try{
            executor(resolve, reject)
        }catch(err){
            reject(err)
        }
    }

    then(onFulfilled, onRejected){
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }

        let promiseTwo = new Promise( (resolve, reject) => {
            if(this.status === 'fulfilled'){
                setTimeout( () => {
                    try{
                        let x = onFulfilled(this.value)
                        resolvePromise(promiseTwo, x, resolve, reject)
                    }catch(err){
                        reject(err)
                    }
                }, 0)
            }

            if(this.status === 'rejected'){
                setTimeout( () => {
                    try{
                        let x = onRejected(this.reason)
                        resolvePromise(promiseTwo, x, resolve, reject)
                    }catch(err){
                        reject(err)
                    }
                }, 0)
            }

            if(this.status === 'pending'){
                this.onResolvedCallbacks.push( () => {
                    setTimeout(() => {
                        try{
                            let x = onFulfilled(this.value)
                            resolvePromise(promiseTwo, x, resolve, reject)
                        }catch(err){
                            reject(err)
                        }
                    }, 0)
                })
                this.onRejectedCallbacks.push( () => {
                    setTimeout( () => {
                        try{
                            let x = onRejected(this.reason)
                            resolvePromise(promiseTwo, x, resolve, reject)
                        }catch(err){
                            reject(err)
                        }
                    }, 0)
                })
            }
        })

        return promiseTwo       
    }

    catch(fn) {
        return this.then(null ,fn)
    }
}

function resolvePromise(promiseTwo, x, resolve, reject){
    if(x === promiseTwo){
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }

    let called
    if(x != null && (typeof x === 'object' || typeof x === 'function')){
        try{
            let then = x.then
            if(typeof then === 'function'){
                this.call(x, success => {
                    if(called) return
                    called = true
                    resolvePromise(promiseTwo, success, resolve, reject)
                }, err => {
                    if(called) return
                    called = true
                    reject(err)
                })
            }else{
                resolve(x)
            }
        }catch(error){
            if(called) return
            called = true
            reject(error)
        }
    }else{
        resolve(x)
    }
}


// resolve()
Promise.resolve = function(value){
    return new Promise((resolve, reject) => {
        resolve(val)
    })
}

// reject()
Promise.reject = function(reason){
    return new Promise((resolve, reject) => {
        reject(reason)
    })
}

// race
Promise.race = function(promises){
    return new Promise((resolve, reject) => {
        for(let i = 0; i < promises.length; i++){
            promises[i].then(resolve, reject)
        }
    })
}

// all() → 获取所有的promise,都执行then(),把结果放到数组里,一起返回
Promise.all = function(promises){
    let resultArr = []
    let i = 0
    function processData(index, data){
        resultArr[index] = data
        i++
        if(i == promises.length){
            resolve(resultArr)
        }
    }

    return new Promise((resolve, reject) => {
        for(let i = 0; i < promises.length; i++){
            promises[i].then( data => {
                processData(i, data)
            }, reject)
        }
    })
}
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

# 14 / 解析 URL Params 对象

先看下最终的结果

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 
{ user: 'anonymous',
  id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
  city: '北京', // 中文需解码
  enabled: true, // 未指定值得 key 约定为 true
}
*/
1
2
3
4
5
6
7
8
9
function parseParam(url){
    const paramsStr = /.+\?(.+)$/.exec(url)[1] // 将 ? 后面的字符串取出来
    const paramsArr = paramsStr.split('&')  // 将字符串以 & 分割后存到数组中
    let paramsObj = {} // 最后返回对象
    
    // 将 param 存到对象中
    paramsArr.forEach( param => {
        if(param.indexOf('=') != -1){
            // 处理带有value的参数
            let [key, val] = param.split('=') // 分割 key 和 value
            val = decodeURIComponent(val)  // decodeURIComponent() 函数可对 encodeURIComponent() 函数编码的 URI 进行解码。
            val = /^\d+$/.test(val) ? parseFloat(val) : val // 判断是否转为数字

            if(paramsObj.hasOwnProperty(key)){
                // 如果对象有 key,则添加一个值
                paramsObj[key] = [].concat(paramsObj[key], val)
            }else{
                // 如果对象没有这个 key,创建 key 并设置值
                paramsObj[key] = val
            }
        }else{
            // 处理没有value的参数
            paramsObj[param] = true // 默认给与这个key一个true值,表示存在这个key
        }
    })

    return paramsObj
}
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
  • 补:使用 正则 + 归并
function ParseUrlSearch(url){
    return /.+\?(.+)$/.exec(url)[1].split('&').reduce( (total, value) => {
        const [key, val] = value.split('=')
        total[key] = decodeURIComponent(val)
        return total
    }, {})
}
1
2
3
4
5
6
7

# 15 / 实现模板引擎

先看下最终的结果

let template = `我是{{name}}, 年龄{{age}}, 性别{{sex}}`
let data = {
    name: '姓名',
    age:18
}
render(template, data) // 我是姓名,年龄18,性别undefined
1
2
3
4
5
6
function render(template, data){
    const reg = /\{\{(\w+)\}\}/    // 模板字符串正则,匹配{{}}中的子字符串
    if(reg.test(template)){
        // 判断模板里是否有模板字符串
        const str = reg.exec(template)[1]   // 获取当前模板里第一个模板字符串的字符片段
        template = template.replace(reg, data[str])  // 将data中的对应str的属性值替换到模板上
        return render(template, data)  // 递归调用,直至template里没有模板字符串{{}}
    }
    return template // 如果模板里已经没有模板字符串,则直接返回
}
1
2
3
4
5
6
7
8
9
10

# 16 / 转化为驼峰式

效果如下

var s1 = "get-element-by-id"

// 转化为 getElementById
1
2
3
function turnToHump(str){
    return s.replace(/-\w/g, function(x){  // 匹配所有符合 '-?' 的子字符串,并用 ?.slice(1).toUpperCase() 替换它
        return x.slice(1).toUpperCase()
    })
}
1
2
3
4
5

# 17 / 查找字符串中出现最多的字符和个数

例如:'abbcccddddd' 出现最多的是d,出现了5次

let str = 'abcabcabcbbccccc'
let sum = 0   // 出现最多次数
let char = ''  // 出现最多字符

str = str.split('').sort().join('') // 按一定次序排序

let reg = /(\w)\1+/g

str.replace(reg, (x, y, z) => {
    // x 字符串, y 字符, z 个数
    if(sum < z){
        sum = z
        char = y      
    }
})

console.log(`字符最多的是${char},出现了${sum}`)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 18 / 字符串查找

使用最基本的遍历来实现判断字符串a是否被包含在字符串b中,并返回第一次出现的位置(找不到则返回-1)

a = 'ab'; b = 'cccabccc'  // 返回 3
a = 'c'; b = 'aaaaa'  // 返回 -1
isInclude(a, b)
1
2
3
function isInclude(a, b){
    for(let i in b){
        if(a[0] === b[i]){
            let tmp = true
            for(let j in a){
                if(a[j] !== b[parseInt(i) + parseInt(j)]){
                    tmp = false
                }
            }
            if(tmp){
                return i
            }
        }
    }
    return -1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 19 / 实现千位分隔符

// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
1
2
3
4
function parseToMoney(num){
    num = parseFloat(num.toFixed(3)) // 保留三位小数

    let [integer, decimal] = String.prototype.split.call(num, '.')  // 转化为字符串并且以 '.' 分割整数部分(integer)和小数部分(decimal)

    integer = integer.replace(/\d(?=(\d{3})+$)/g, (x) => {
        return x + ','
    })
    return decimal ? integer + '.' + decimal : integer
}
1
2
3
4
5
6
7
8
9
10

# 20 / 判断是否是电话号码

function isPhone(phoneNum){
    var reg = /^1[34578]\d{9}$/

    return reg.test(phoneNum)
}
1
2
3
4
5

# 21 / 判断是否是邮箱

function isEmail(email){
    var reg = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/

    return reg.test(email)
}
1
2
3
4
5

# 22 / 判断是否是身份证

function isIDcard(idcard){
    var reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/

    return reg.test(idcard)
}
1
2
3
4
5

# 23 / 手写快速排序

快速排序的思想:

  • 从数组中取出一个数作为基准
  • 在原数组中进行移动,将大于基准的数放在基准右边,小于基准的数放在基准左边,在基准左右形成两个子数组
  • 在左右子数组中反复执行,直到所有子数组只剩下一个数
function quickSort(array){
    if(array.length <= 1){
        return array
    }
    var middleIndex = Math.floor(array.length / 2)
    var middleNum = array.splice(middleIndex, 1)[0]
    var left = []
    var right = []
    for(var i = 0; i < array.length; i++){
        if(array[i] < middleNum){
            left.push(array[i])
        }else{
            right.push(array[i])
        }
    }

    return quickSort(left).concat([middleNum], quickSort(right))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 24 / 手写冒泡排序

冒泡排序思想

  • 比较两个相邻的元素,如果后一个比前一个大,则交换位置
  • 第一轮结束时最后一个元素是最大的一个
  • 进行下一轮比较,由于上一轮已经把最大值移至最后,所以最后一个元素不用比较
function bubbleSort(nums){
    for(var i = 0; i < nums.length - 1; i++){
        for(var j = 0; j < nums.length - 1 - i; j++){
            if(nums[j] > nums[j + 1]){
                var temp = nums[j]
                nums[j] = nums[j + 1]
                nums[j + 1] = temp
            }
        }
    }

    return nums
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 25 / 手写 jsonP

/**
 *  jsonp 返回 Promise 对象
 *  参数url,data:json对象, callback函数
*/
function jsonp(url, data = {}, callback = 'callback'){
    // 处理json对象,拼接url
    data.callback = callback
    let params = []
    for(let key in data){
        params.push(key + '=' + data[key])
    }
    
    // 创建script元素
    let script = document.crateElement('script')
    script.src = params.length > 0 ? url + '?' + params.join('&') : url
    document.body.appendChild(script)

    // 返回Promise
    return new Promise((resolve, reject) => {
        window[callback] = data => {
            try{
                resolve(data)
            }catch(e){
                reject(e)
            }finally{
                // 移除script元素
                script.parentNod.removeChild(script)
            }
        }
    })
}

jsonp('http://xxx/xxx/xxx', {
    username:'xxx'
}, 'jsoncallback').then(data => {
    console.log(data)
})
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

# 26 / 手写数组扁平化

· 通过数组 reduce 归并 + 递归

function Flat(array){
    return array.reduce((total, value) => {
        return total.concat(Array.isArray(value) ? Flat(value) : value)
    }, [])
}
1
2
3
4
5

# 27 / 数组成员个数统计

· 例如 [0, 1, 1, 2, 2, 2] => { 0: 1, 1: 2, 2: 3 }

function Count(arr = []){
    return arr.reduce( (total, value) => {
        total[value] = (total[value] || 0) + 1
        return total
    }, {})
}
/*
    装B用法,逗号表达式
    function Count(arr = []){
        return arr.reduce( (total, value) => ( total[value] = ( total[value] || 0 ) + 1, total), {})
    }
*/
1
2
3
4
5
6
7
8
9
10
11
12