# 发布订阅

class EventEmitter{

    constructor(){
        this.events={}
    }

    emit(eventName,...args){
        this.events[eventName]&&this.events[eventName].forEach(cb=>cb(...args))
    }

    on(eventName,callback){
        if(this.events[eventName]){
            this.events[eventName].push(callback)
        }else{
            this.events[eventName]=[callback]
      }
    }

    once(eventName,callback){
        let fun =function(){
            callback()
            this.remove(eventName,fun)
        }
        this.on(eventName,fun)
    }

    remove(eventName,callback){
       this.events[eventName]&&this.events[eventName].filter((cb)=>cb!=callback)
    }

    clear(){
        this.events={}
    }
}

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

# LRUCache

class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.map = new Map();
    }
    get(key) {
        if (this.map.has(key)) {
            // get表示访问该值
            // 所以在访问的同时,要将其调整位置,放置在最后
            const temp = this.map.get(key);
            // 先删除,再添加
            this.map.delete(key);
            this.map.set(key, temp);
            // 返回访问的值
            return temp;
        } 
            // 不存在,返回-1
            return -1;
    }
    put(key, value) {
        // 要将其放在最后,所以若存在key,先删除
        if (this.map.has(key)) this.map.delete(key);
        else if (this.map.size >= this.capacity) {
            // 若超出范围,将map中头部的删除
            // map.keys()返回一个迭代器
            // 迭代器调用next()方法,返回包含迭代器返回的下一个值,在value中
            this.map.delete(this.map.keys().next().value);
        }
          // 设置key、value
        this.map.set(key, value);
    }
}
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

# New

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

  • 原理
    • 新建一个空对象
    • 链接到原型
    • 绑定this
    • 返回该对象
function myNew() {
// 1.新建一个空对象
let obj = {}
// 2.获得构造函数
let con = [].shift.call(arguments)
// 3.链接原型,实例的 __proto__ 属性指向构造函数的 prototype
obj.__proto__ = con.prototype
// 4.绑定this,执行构造函数
let res = con.apply(obj, arguments)
// 5.返回新对象
return typeof res === 'object' ? res : obj
}

function Person(name) {
    this.name = name
}
let person = myNew(Person,'test')
console.log(person) //{name: "test"}
console.log(typeof person === 'object') //true
console.log(person instanceof Person) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# instanceof

instanceof 用于检测构造函数的prototype是否在实例的原型链上,需要注意的是instanceof只能用来检测引用数据类型,对于基本数据检测都会返回false

  • 原理

通过循环检测实例的__proto__属性是否与构造函数的prototype属性相等

/**
 * instanceof 用于检测构造函数的prototype是否在实例的原型链上
 */
function myInstanceof(left, right) {
    // 先排除基本数据类型
    if(typeof left !== 'object' || left === null) return false
    let proto = left.__proto__
    while(proto) {
        if(proto === right.prototype) return true
        proto = proto.__proto__
    }
    return false
}

function Person() {}
let person = new Person()
console.log(myInstanceof(person,Person)) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# apply/call/bind

# apply

apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

Function.prototype.myApply = function(context) {
    var context = context || window // 获取需要绑定的this
    context.fn = this // 获取需要改变this的函数
    const arg = arguments[1] // 获取传递给函数的参数

    if(!(arg instanceof Array)) {
        throw Error('参数需要是一个数组')
    }
    const res = context.fn(...arg) // 执行函数
    delete context.fn // 删除该方法
    return res // 返回函数返回值
}
function say(a,b,c) {
    console.log(this.name,a,b,c)
}
say.myApply({name:'nanjiu'},[1,2,3]) //nanjiu 1 2 3
say.apply({name:'nanjiu'},[1,2,3]) //nanjiu 1 2 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

Function.prototype.myCall = function(context) {
    var context = context || window // 获取需要改变的this
    context.fn = this // 获取需要改变this的函数
    const args = [...arguments].slice(1) // 获取参数列表
    const res = context.fn(...args) // 将参数传给函数并执行
    delete context.fn // 删除该方法
    return res // 返回函数返回值
}

function say(a,b,c) {
    console.log(this.name,a,b,c)
}
say.myCall({name:'nanjiu'},1,2,3) //nanjiu 1 2 3
say.call({name:'nanjiu'},1,2,3) //nanjiu 1 2 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

Function.prototype.myBind = function(context) {
    var context = context || window //获取需要改变的this
    context.fn = this  // 获取需要改变this的函数

    //获取函数参数
    const args = [...arguments].slice(1)
    // 与apply,call不同的是这里需要返回一个函数
    return () => {
        return context.fn.apply(context,[...args])
    }

}

function say(a,b,c) {
    console.log(this.name,a,b,c)
}
say.bind({name: 'nanjiu'},1,2,3)() //nanjiu 1 2 3
say.myBind({name: 'nanjiu'},1,2,3)() //nanjiu 1 2 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 函数柯里化(Currying)

先来理解一下什么是函数柯里化,上面文绉绉的内容可能不是那么容易理解,我们还是直接上代码来理解吧

// 假如有这样一个函数
function add (a,b,c) {
    console.log(a+b+c)
}
add(1,2,3) //6
/**
 * 我们希望可以通过add(1,2)(3)或add(1)(2)(3)或add(1)(2,3)这样调用也能够得倒正确的计算结果
 这就是函数柯里化的简单应用
 */
1
2
3
4
5
6
7
8
9
  • 代码实现
function curry(fn, curArgs) {
    const len = fn.length  // 需要柯里化函数的参数个数
    curArgs = curArgs || []

    return function() {
        let args = [].slice.call(arguments) // 获取参数
        args = curArgs.concat(args) //拼接参数
        // 基本思想就是当拼接完的参数个数与原函数参数个数相等才执行这个函数,否则就递归拼接参数
        if(args.length < len) {
            return curry(fn, args)
        }else{
            return fn.apply(this, args)
        }
    }
}

let fn = curry(function(a,b,c){
    console.log([a,b,c])
})
fn(1,2,3) // [ 1, 2, 3 ]
fn(1,2)(3) // [ 1, 2, 3 ]
fn(1)(2,3) // [ 1, 2, 3 ]
fn(1)(2)(3) // [ 1, 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

# 深拷贝(deepClone)

在拷贝的时候判断一下属性值的类型,如果是对象,递归调用深拷贝函数

  • 普通版
/**
 * 在拷贝的时候判断一下属性值的类型,如果是对象,递归调用深拷贝函数
 */

function deepClone(obj, cache=new Map()) {
    // 基本数据类型直接返回
    if(typeof obj !== 'object' || obj === null) return obj
    // 防止循环引用
    const cacheTarget = cache.get(obj)
    // 已经存在就直接返回
    if(cacheTarget) return cacheTarget

    let newObj = obj instanceof Array ? [] : {} // 新建一个对象

    cache.set(obj, newObj)
    // 遍历原对象
    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
        }
    }
    return newObj
}
const obj = {
    name: 'test'
}
const obj1 = obj
const obj2 = deepClone(obj)
console.log(obj1===obj) //true
console.log(obj2===obj) //false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  • 高级版

考虑 Function|RegExp|Date|Map|Set 等类型

const _completeDeepClone = (target, map = new Map()) => {
    // 补全代码
    //参数如果为空
    if(target===null) return target;
    //参数如果不是对象类型,而是基本数据类型
    if(typeof target!=='object') return target;
    //参数为其他数据类型
    const cons = target.constructor;
    if(/^(Function|RegExp|Date|Map|Set)$/i.test(cons)) return new cons(target);
    const cloneTarget = Array.isArray(target)? []:{};
    //如果存在循环引用,直接返回当前循环引用的值,否则,将其加入map,
    if(map.get(target)) return map.get(target);
    map.set(target,cloneTarget);
    for(let key in target){
        cloneTarget[key] = _completeDeepClone(target[key],map)
    }
    return cloneTarget;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# sleep

实现一个函数,n秒后执行指定函数

function sleep(func, delay) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(func())
        }, delay)
    })
}

function say(name) {
    console.log(name)
}
async function go() {
    await sleep(()=>say('nanjiu'),1000) //过一秒打印nanjiu
    await sleep(()=>say('前端南玖'),2000) // 再过两秒打印前端南玖
}
go()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 解析URL参数

function queryData(key) {
    let url = window.location.href,obj = {}
    let str = url.split('?')[1] // 先拿到问号后面的所有参数
    let arr = str.split('&') // 分割参数
    for(let i=0; i< arr.length; i++) {
        let kv = arr[i].split('=')
        obj[kv[0]] = decodeURIComponent(kv[1])
    }
    console.log(url,obj)
    // {a: '1', b: '2', c: '3', name: 'aa'}
    return obj[key]

}
//http://127.0.0.1:5500/src/js/2022/%E6%89%8B%E5%86%99/index.html?a=1&b=2&c=3&name=%E5%8D%97%E7%8E%96
console.log(queryData('name')) // aa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 数组API实现

JavaScript之数组 (opens new window)

# 继承

  • 寄生组合式继承(call+Object.create)
        function Human(name) {
                this.name = name
                this.kingdom = 'animal'
                this.color = ['yellow', 'white', 'brown', 'black']
            }
            Human.prototype.getName=function(){
                return this.name
            }
            function Chinese(name,age) {
                //核心步骤一:借用构造函数继承,继承父类的属性
                Human.call(this,name)
                this.color = 'yellow'
                this.age=age
            }
            Chinese.prototype = Object.create(Human.prototype);     //创建对象,创建父类原型的一个副本
            Chinese.prototype.constructor= Chinese                      //增强对象,弥补因重写原型而失去的默认的 constructor 属性
            Chinese.prototype.getAge=function(){
                return this.age
            }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

JavaScript之继承 (opens new window)

# Promise

JavaScript之异步 (opens new window)

# 参考

40+ 大厂常考 JS 手写题系列 (opens new window)