笔试题

数组去重

function unique(arr) {
  return [...new Set(arr)];
}

function unique(arr) {
  // 当前值 下标 数组
  return arr.filter((cur, index, array) => {
    return array.indexOf(cur) === index;
  })
}

function unique(arr){
  return arr.reduce((pre,cur)=>{
    if(!pre.includes(cur)){
      return pre.concat(cur)
    }else{
      return pre
    }
  },[])
}

数组扁平化

function flatten(arr){
    return arr.reduce((pre,cur) => {
        return pre.concat(Array.isArray(cur) ? flatten(cur):cur)
    },[])
}

function flatten(arr){
    return [].concat(...arr.map(item => {
        return Array.isArray(item) ? flatten(item) : item
    }))
}

防抖函数 (debounce)

  • 定义:每次事件触发则删除原来的定时器,建立新的定时器。跟王者荣耀的回城功能类似,你反复触发回城功能,那么只认最后一次,从最后一次触发开始计时

  • 场景:

    • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
    • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
function debounce(fn, delay){
    let timer = null
    return function(...args){
        if(timer) clearTimeout(timer)

        timer = setTimeout(function(){
            fn.apply(this, args)
        },delay)
    }
}

节流函数 (throttle)

  • 定义:如果在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器任务。这就好比公交车,10 分钟一趟,10 分钟内有多少人在公交站等我不管,10 分钟一到我就要发车走人!
  • 场景
    • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
    • 缩放场景:监控浏览器 resize
    • 动画场景:避免短时间内多次触发动画引起性能问题
function throttle(fn, interval){
    let flag = true

    return function(...args){
        if(!flag) return
        flag = false

        setTimeout(()=>{
            fn.apply(this, args)
            flag = true
        },interval)
    }
}

洗牌算法和获取随机数

function getRandom(max,min){
    return Math.floor(Math.random() * (max - min + 1) + min)
}

function shuffle(arr){
    let _arr = arr.slice()

    for(let i=0;i<_arr.length;i++){
        let el = _arr[i], random = getRandom(0,i)
        
        _arr[i] = _arr[random]
        _arr[random] = el
    }

    return _arr
}

实现 instanceof

function myInstanceof(L, R){
    let RP = R.prototype
    L = L.__proto__

    while(true){
        if(L === null) return false

        if(L === RP) return true

        L = L.__proto__
    }
}

// test case
myInstanceof([], Object)    // true

实现 new

function myNew(constructor, ...args){
    let obj = Object.create(constructor.prototype)

    let res = constructor.apply(obj, args)

    return res instanceof Object ? res : obj
}


// test case
function Person(age, name){
    this.age = age
    this.name = name

    console.log('Person name & age', this)
}

let person = myNew(Person, [12, 'joe'])

person.__proto__ === Person.prototype
person.constructor === Person

call & apply & bind

function myCall(ctx, ...args){
    ctx = ctx || window

    let caller = Symbol('caller')
    ctx[caller] = this

    let res = ctx[caller](...args)
    delete ctx.caller

    return res
}

// test case
function Parent(age){
  this.age = age;
}

// 定义子
function Child(age,name){
    Parent.myCall(this,age);
    this.name = name
}

let child = new Child(12,'Lee')
console.log(child);

自增函数

let count = (function (){
    let num = 0

    return function(){
        return num++
    }
})()

// test case
count() // 0 
count() // 1 
count() // 2 

sleep

function sleepEs5(cb, delay){
    if(typeof cb === 'function'){
        setTimeout(cb, delay)
    }
}

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

sleep(3000).then(()=> {
    console.log('ok')
})

forEach

arr.forEach(function(currentValue, currentIndex, arr) {}, thisArg)

//currentValue  必需。当前元素
//currentIndex  可选。当前元素的索引
//arr           可选。当前元素所属的数组对象。
//thisArg       可选参数。当执行回调函数时,用作 this 的值。

Array.prototype._forEach = function(fn, thisArgs){
    let arr = Array.prototype.slice.call(this)

    for(let i=0;i<arr.length;i++){
        fn.call(thisArgs, arr[i], i, arr)
    }
}

let arr = [1,2,3,4]

arr._forEach((item, index, arr) => {
    console.log(item, index, arr)
})

filter

Array.prototype._filter = function(fn, thisArgs){
    let arr = Array.prototype.slice.call(this)
    let res = []

    for(let i=0;i<arr.length;i++){
        fn.call(thisArgs, arr[i], i, arr) && res.push(arr[i])
    }

    return res
}

let arr = [1,2,3,4]
arr._filter((i) => i > 2)   [3,4]

map

Array.prototype._map = function(fn, thisArgs){
    let res = []
    thisArgs = thisArgs ?? []

    this.reduce((pre, cur, index, arr) => {
        res.push(fn.call(thisArgs,cur, index, arr))
    }, [])

    return res
}

let arr = [1,2,3,4]
arr.map((item) => item + 1) // [2,3,4,5]

promise

// promise 的三种组状态
const STATUS = {
    PENDING: 'PENDING',
    FULLFIlLED: 'FULLFIlLED',
    REJECTED: 'REJECTED',
}

class Promise {
    constructor(run){
        // init status is pending
        this.status = STATUS.PENDING

        // success & fail results
        this.value = undefined
        this.reason = undefined

        // callbacks
        this.onFullfilledCallbacks = []
        this.onRejectedCallbacks = []

        const resolve = value =>{
            if(this.status === STATUS.PENDING){
                this.status = STATUS.FULLFILED
                this.value = value

                this.onFullfilledCallbacks.forEach((fn) => fn(this.value))
            }
        }

        const reject = value => {
            if(this.status === STATUS.PENDING){
               this.status = STATUS.REJECTED
               this.reason = value

                this.onRejectedCallbacks.forEach((fn) => fn(this.reason))
            }
        }

        try {
            run(resolve, reject)
        } catch(e){
            reject(e)
        }
    }

    then(onFullfilled, onRejected){
        onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {
            throw new Error(reason instanceof Error ? reason.message : reason)
        }

        const self = this

        return new Promise((resolve, reject) => {
            if(self.status === STATUS.PENDING){
                self.onFullfilledCallbacks.push(() => {
                    try {
                        setTimeout(() => {
                            const res = onFullfilled(self.value)

                            res intanceof Promise ? res.then((resolve, reject)) : resolve(res) 
                        })
                    } catch(e) {
                        reject(e)
                    }
                })

                self.onRejectedCallbacks.push(() => {
                    try {
                            setTimeout(() => {
                                const res = onRejected(self.reason)

                                res instanceof Promise ? res.then((resolve, reject)) : reject(res)
                    } catch(e){
                        reject(e)
                    }
                )}

            } else if (self.status === STATUS.FULFILLED){
                try {
                        setTimeout(() => {
                            const res = onFullfilled(self.value)

                            res intanceof Promise ? res.then((resolve, reject)) : resolve(res) 
                    })
                } catch(e) {
                        reject(e)
                    }
            } else if((self.status === STATUS.REJECTED){
                setTimeout(()=> {
                    try{
                        const res = onFullfilled(self.reason)

                        res instanceof  Promise ? res.then(resolve,reject) : reject(res)
                    }catch(e){
                        reject(e)
                    }
                })
            }
        })
            
    },

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

    static resolve(value) {
        if(value of Promise){
            return value
        } else {
            return new Promise((resolve,reject) => resolve(value))
        }
    }

    staic reject(value){
        return new Promise((resolve,reject) => {
            reject(value)
        })
    }
}

Promise.prototype.finally = function(cb){
    this.then(value => {
        return Promise.resolve((cb())).then(() => {
            return value 
        })
    }, err => {
        return Promise.resolve(cb()).then(()=> {
            throw error
        })
    }
    )
}

Promise 实现网络超时判断

const upload = (url, params) => {
    return Promise.race([
        uploadPrommise(url, params),
        uploadSetTiemout(100)
    ])
}

function uploadPrommise(url, params){
    return new Promise((resolve,reject) =>  {
        fetch(url,{
            headers: {'Content-Type': 'multipart/form-data'}, // 以formData形式上传文件
            withCredentials: true
        }).then((data) => {
            if(data.code === 200){
                resolve(data)
            } else {
                reject(data)
            }
        })
    })
}

function uploadSetTiemout(delay){
    return new Promise((resolve, reject) => {
        setTimeout(()=> {
            reject({timeoutMsg: '上传超时'})
        }, delay)
    })
}

批量请求函数

  • 要求最大并发数 maxNum
  • 每当有一个请求返回,就留下一个空位,可以增加新的请求
  • 所有请求完成后,结果按照 urls 里面的顺序依次打出
function mutilyRequest(urls, limit){
    let length = urls.length
    let count = 0
    let results = Array(length).fill(false)

    return new Promise((resolve, reject) => {
        while(count < limit){
            next()
        }

        function next(){
            let current = count++

            if(current >= length){
                !results.includes(false) && resolve(results)
                return 
            }

            let url = urls[current]

            console.log(`current request starting, url is: ${url}, this is ${current} request`)

            request(url).then(data => {
                results[current] = data

                console.log(`current request ${current}: ${url} has finished`)

                if(current < length){
                    next()
                }
            }).catch((e) => {
                results[current] = e

                console.log(`current request ${current}: ${url} error`)

                if(current<length){
                    next()
                }
            })
        }
    })
}

function request(url){
    return new Promise((resolve, reject) => {
        let res
        fetch(url,{
            headers: {
                'connect-src' : self
            }
        }).then(data => {
            res = data
        })
        resolve(`current request is', ${url}, res is : ${res}`)
        // fetch(url)
    })
}

let urls = [
    'https://cat-fact.herokuapp.com/facts',
    'https://cat-fact.herokuapp.com/facts',
    'https://cat-fact.herokuapp.com/facts',
    '3',
    'https://cat-fact.herokuapp.com/facts',
    '5',
    'https://cat-fact.herokuapp.com/facts',
    '7',
]

mutilyRequest(urls, 3).then((res) => {
    console.log('res: ', res)
})

Vue 双向绑定

  • 对象,包含复杂对象
  • 值类型
  • 数组,改造原型

TODO:

function defineReactive(data, key, value) {
    Object.defineProperty(data, key, {
        get: function(val){
            return value
        },

        set: function(newVal){
            observer(value)

            if(value == newVal) {
                return
            }

            val = newVal
        }
    })
}

// const originArrayPrototype = Array.prototype
// const newArrayObj = Object.create(originArrayPrototype)

// ['push','shift','unshift','pop'].forEach((name) => {
//     console.log('update', name)
//         newArrayObj[name] = function(){
//         console.log('update', name)
//         originArrayPrototype[name].call(this,...arguments)
//     }
// })


function observer(target){
    if(typeof target !== 'object' || target === null){
        return target
    }

    // if(target instanceof Array){
    //     target.__proto__ = newArrayObj
    // }

    for(let key in target){
        defineReactive(target, key, target[key])
    }
}

let obj = {
    num: 1
}

observer(obj)

obj.num = 3

虚拟 dom 过程 (html 转为 json 格式)

const str1 = '<div>1<span>2<a>3</a>4</span>5<span>6<a>7</a>8<a>9</a>10</span>11</div>';
function Dom2JSON(str) {
    str = str.split('<').map(x => x.split('>'));
    let res = [],stack = [],temp = {},cur = {},key = 0;
    // 获取树形结构
    for(let i = 1;i < str.length; i++) {
        if (str[i][0].indexOf('/') === -1) {
            temp = {};
            temp['key'] = key++;
            temp['tag'] = str[i][0];
            temp['value'] = str[i][1];
            temp['children'] = [];
            temp['parent'] = stack.length === 0 ? 0 : stack[0]['key'];
            stack.unshift(temp);
        } else {
            cur = stack.shift();
            // 当前元素为根元素时栈为空
            stack.length !== 0 && (stack[0]['value'] = stack[0]['value'] + cur['value'] + str[i][1]);
            res.unshift(cur);
        }
    }
    // 使得遍历时索引与key值匹配
    res = res.sort((x, y) => x['key'] - y['key']);
    for (let i = res.length - 1;i > 0;i--) {
        temp = {};
        temp['tag'] = res[i]['tag'];
        temp['value'] = res[i]['value'];
        temp['children'] = res[i]['children'];
        res[res[i]['parent']]['children'].unshift(temp);
    }
    res = res[0];
    delete res['parent'];
    delete res['key'];
    return JSON.parse(JSON.stringify(res));
}

console.log(Dom2JSON(str1));

实现一个 ajax

const ajax = (url, options = {
    method: 'GET',
    aysnc: true,
    data: null
}) => {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest()

        xhr.onreadystatechange = () => {
            if(xhr.readState === 4){
                if(xhr.status === 200){
                    resolve(JSON.parse(xhr.resonseText))
                } else if(xhr.status > 400) (
                    reject('xhr error occured')
                )
            }
        }

        const { method, async, data } = options

        xhr.open(method,url, async)

        xhr.send(data)
    })
}

ajax('https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/send/bcd.json')

红绿灯

3s 打印 red,2s 打印 green,1s 打印 yellow

async function setColor(color, time){
    return new Promise((resolve) => {
        setTimeout(()=> {
            console.log(color)
            resolve()
        }, time)
    })
}

async function getter(){
    await setColor('red', 1000)
    await setColor('green', 2000)
    await setColor('yellow', 3000)
}

柯里化

function cury(fn, ...args){
    // 判断参数的长度是否已经满足函数所需参数的长度 够了就执行,否则新增参数然后返回函数
    return fn.length <= args.length ? fn(...args) : cury.bind(null, fn, ...args)
}

let add = cury((a,b,c) => a+b+c)

// test case
console.log(add(1,2,3))
console.log(add(1,2)(3))
console.log(add(1)(2)(3))

设计模式

单例模式

class Single {
    constructor(name){
        this.name = name
        this.instance = null
    }

    static getInstance(){
        if(!this.instance){
            this.instance = new Single(name)
        }

        return this.instance
    }
}

let a = Single.getInstance('a')
let b = Single.getInstance('b')

console.log(a === b)

观察者模式

// 四种状态:sub, pub, remove, removeAll
class Event {
    constructor(){
        this.events = {}
    }

    sub(name, callback){
        if(!this.events[name]){
            this.events[name] = [callback]
        } else {
            this.events[name].push(callback)
        }
    }

    pub(name){
        if(this.events[name]){
            this.events[name].forEach((callback) => callback())
        }
    }

    remove(name, callback){
        if(this.events[name]){
            this.events[name] = this.events[name].filter((fn) => fn !== callback)
        }
    }

    removeAll(name){
        if(this.events[name]){
            this.events[name] = []
        }
    }
}

// test case 
let events = new Event()

function logger(){
    console.log(...arguments)
}

function getTime(){ return new Date()}

function makeArray(){
    return Array(10).fill(false)
}

events.sub('logger', logger)
events.sub('logger', getTime)
events.sub('makeArray', makeArray)

events.pub('logger')
events.pub('makeArray')

events.remove('makeArray', makeArray)

events.sub('makeArray', makeArray)

events.removeAll('logger')

console.log(events)

发布订阅模式

  • on
  • emit
  • remove
  • once
class EventEmitter {
    constructor(){
        this.events = {}
    }

    on(name, cb){
        if(!this.events[name]){
            this.events[name] = [cb]
        } else {
            this.events[name].push(cb)
        }
    }

    emit(name){
        this.events[name] && this.events[name].forEach(fn => fn())
    }

    remove(name, cb){
        if(this.events[name]){
            this.events[name] = this.events[name].filter(fn => fn !== cb)
        }
    }

    once(name, cb) {
        let fn = () => {
            cb()
            this.remove(name, cb)
        }

        this.on(name, fn)
    }
}

// test case

let em = new EventEmitter();
let workday = 0;

em.on("work", function() {
    workday++;
    console.log("work everyday");
});

em.once("love", function() {
    console.log("just love you");
});

function makeMoney() {
    console.log("make one million money");
}

em.on("money",makeMoney)


let time = setInterval(() => {
    em.emit("work");
    em.remove("money",makeMoney);
    em.emit("money");
    em.emit("love");
    if (workday === 5) {
        console.log("have a rest")
        clearInterval(time);
    }
}, 1);
上次更新:
贡献者: Joe