Js面试手写系列


手写call

手写Promise
手写call
手写apply
手写bind
手写instanceof
数组的复合和拍平
手写Ajax
节流和防抖
手写EventEmitter
函数柯里化
手写LRU缓存机制
手写router路由
手写图片懒加载
手写node获取当前目录下的文件

手写Promise.all()

// 手写promise.all
function PromiseAll(promises) {
    return newPromise((resolve, reject) => {
        //第一个坑, 如果不是一个数组, 返回的应该是错误信息
        if(!Array.isArray(promises)) {
            return reject(newError('传入的参数必须得是数组格式!'))
        }
        let  res = []
        let count = 0
        const promiseLength = promises.length
        promises.forEach((promise, index) => {
            // 第二个点, 这里应该是要判断promise是否是promise格式的, 因为可能是数字等其他格式。
Promise.resolve(promise).then(result => {
                // 第三个点, 这里应该要有一个计数器, 因为promiseall是按照顺序返回的, 使用i就不是按照顺序了
                count ++
                res[index] = result
                // 第四个点, 这里如果使用length会有错, 因为, js中数组, 如果有第7个前面没有, 长度还是7(前面是undefined)
                if(count === promiseLength) resolve(res)
            }).catch(e => reject(e))
        })
    })
}

手写Promise

  • 手写Promise
class PromiseV2 {

    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'

    // 构造器, 会在类被创建的时候自动执行
    constructor(exector) {
        //设置初始化状态
        this.value = null
        this.status = PromiseV2.PENDING
        // 没有立即执行的回掉函数, 被压进这个数组中
        this.callbacks = []
        try {
            exector(this.resolve.bind(this), this.reject.bind(this))
        } catch (err) {
            this.reject(err)
        }
    }

//    两个类方法
    resolve(value) {
        // 给个判断,如果不是pending状态, 就不改变状态。 保证状态一旦改变,就不进行修改了。
        if (this.status === PromiseV2.PENDING) {
            setTimeout(() => {
                this.value = value
                this.status = PromiseV2.FULFILLED
                this.callbacks.map(callback => {
                    callback.onFulfilled(value)
                })
            })
        }
    }

    reject(reason) {
        if (this.status === PromiseV2.PENDING) {
            setTimeout(() => {
                this.value = reason
                this.status = PromiseV2.REJECTED
                this.callbacks.map(callback => {
                    callback.onRejected(reason)
                })
            })
        }
    }

    then(onFulfilled, onRejected) {
        console.log(onFulfilled)
        // 里面的三种状态和下面的两个方法是相关联的
        if (typeof onFulfilled !== 'function') {
            // onFulfilled 定义为一个直接返回参数的函数
            onFulfilled = value => value
        }
        if (typeof onRejected !== 'function') {
            onRejected = reason => reason
        }

        // 因为then是链式回调的, 所以,then 应该也返回的是一个Promise
        return new PromiseV2((resolve, reject) => {
            // 如果是回调, 那么就直接压进去, 等着后来拿出来执行
            if (this.status === PromiseV2.PENDING) {
                this.callbacks.push({
                    onFulfilled: data => {
                        this.parse(onFulfilled(this.value), resolve, reject)
                    },
                    onRejected: err => {
                        this.parse(onRejected(this.value), resolve, reject)
                    }
                })
            }

            //    如果是fulfilled, 直接onFulfilled
            //    其实这里就相当于使用了, if(onFulfilled)
            if (this.status === PromiseV2.FULFILLED) {
                setTimeout(() => {
                    this.parse(onFulfilled(this.value), resolve, reject)
                })
            }
            //    如果是rejected 直接onRejected
            //    其实这里就相当于使用了, if(onRejected)
            if (this.status === PromiseV2.REJECTED) {
                setTimeout(() => {
                    this.parse(onRejected(this.value), resolve, reject)
                })
            }
        })
    }

    // parse类函数, 抽离相同的函数部分
    parse(result, resolve, reject) {
        try {
            if (result instanceof PromiseV2) {
                result.then(resolve, reject)
            } else {
                resolve(result)
            }
        } catch (err) {
            reject(err)
        }
    }
}
  • 包含all和race的完整版本:
class PromiseV3 {
    staticPENDING= 'pending'
    staticFULFILLED= 'fulfilled'
    staticREJECTED= 'rejected'

    constructor(executor) {
        this.value = ''
        this.status = PromiseV3.PENDING
this.callbacks = []
        if (typeof executor === "function") {
            try {
                executor(this.resolve.bind(this), this.reject.bind(this))
            } catch (e) {
console.log(e)
            }
        } else {
            try {
console.log(executor)
            } catch (e) {
                this.reject(e)
            }
        }

    }

    resolve(value) {
        this.value = value
        this.status = PromiseV3.FULFILLED
this.callbacks.map(callback => {
            callback.onfulfilled(value)
        })
    }

    reject(reason) {
console.log('reject')
    }

    then(onFulfilled, onRejected) {
        if (this.value === PromiseV3.PENDING) {
            try {
                this.callbacks.push({
                    onFulfilled: (data) => {
                        try {
                            this.resolve(data)
                        } catch (e) {
                            this.reject(e)
                        }
                    },
                    onRejected: (reason) => {
                        try {
                            this.reject(reason)
                        } catch (e) {
                            this.reject(e)
                        }
                    }
                })
            } catch (e) {
                this.reject(e)
            }
        }
        if (this.status === PromiseV3.FULFILLED) {
            setTimeout(() => {
                try {
                    onFulfilled(this.value)
                } catch (e) {
                    onRejected(e)
                }
            })
        }
        if (this.status === PromiseV3.REJECTED) {
            setTimeout(() => {
                try {
                    onRejected(this.value)
                } catch (e) {
                    onRejected(e)
                }
            })
        }
    }

    staticresolve(value) {
        return new PromiseV3((resolve, reject) => {
            if (value instanceof PromiseV3) {
                value.then(resolve, reject)
            } else {
                resolve(value)
            }
        })
    }

    staticreject(reason) {
        return new PromiseV3((resolve, reject) => {
            if (reason instanceof PromiseV3) {
                reason.then(resolve, reject)
            } else {
                reject(reason)
            }
        })
    }
    staticall(promises) {
        const values = []
        return new PromiseV3((resolve, reject) => {
                promises.forEach(promise => {
                    promise.then(value => {
                        values.push(value)
                        if(promises.length === values.length){
                            resolve(values)
                        }
                    }, reason => {
                        reject(reason)
                    })
                })
        })
    }
    staticrace(promises) {
        return new PromiseV3((resolve, reject) => {
            promises.map(promise => {
                promise.then(value => {
                    resolve(value)
                }, reason => {
                    reject(reason)
                })
            })
        })
    }
}

// const p = new PromiseV3()

手写call

Function.prototype.myCall = function(context, args) {
	context = context || window

	const symbol = Symbol('fn')
	context[symbol] = this

// 这里相对于apply来说, 就是需要将参数展开, 不是数组的形式执行的
	context[symbol](...args)
// 别忘记删除了, 防止污染
delete context[symbol]

}

手写apply

Function.prototype.myApply = (context, args) => {
// 1. 上下文
	context = context || window

// 2. 定义对象,改变this
	const symbol = Symbol('fn')
	context[symbol] = this

// 3. 带入参数, 执行方法
	context[symbol](args)
	delete context[symbol]
}

手写bind

Function.prototype.myBind=(context, args)=>{
			context = context || window
			const symbol = Symbol('fn')

			context[symbol] = this

			return function(_args){
			const finArgs = [...args, ..._args]

			// bind里面的参数是数组的格式, 不用展开
			context[symbol](args)
			delete context[symbol]
	}
}

手写instanceof

/**
 * 先确定使用, 再进行手写instanceof
 * 1 左右两边, 左边如果是普通对象, 就直接返回false
 * 2 左边是引用对象, 再向左边的隐式对象上找, 只要有就一直找
 */
const a = function (){}
console.log(a instanceof Function ) // trues

function instanceofV1(L, R) {
    // 如果是除了null以外的普通对象, 就直接返回false
    const baseType = [ 'undefined', 'boolean','number', 'string', 'symbol']
    if(baseType.includes(typeof L)) return false

    let RP = R.prototype
    while (true){
        if(L === null){ // 找到最高层, 没有, 返回false
            return false
        }
        if(L === RP){  // 如果隐式对象和R的原型相等, 返回 true
            return true
        }
        L = L.__proto__ // 如果没有, 那么就继续向上查找
    }
}

console.log(instanceofV1(a, Function)); // true

数组的复合和拍平

  1. 先将数组转成二维数组
 // 这是将一维数组转成二维数组
 let newArr= []
 function arr2(){
 	for(let i = 0; i<arr.length;){
 		newArr.push(arr.slice(i, i+=5))
 	}
 }
  1. 将二维数组拍平
 // 将二维数组转成一维数组, 拍平

 function flat(arr) {
 	for(const item of arr){
 // 如果是数组, 递归再来一次. 
 // 和对象的深拷贝是一样的. 
 			if(Array.isArray(item)){
 				flat(item)
 		}else {
 				newArr.push(item)
 		}
 	}
 }
  1. Es6有专门的flat拍平的方法
 let arr = [
   [ 1, 2, 3, 4, 5 ],
   [ 6, 7, 8, 9, 10 ],
   [ 11, 12, 13, 14, 15 ],
   [ 16, 17, 18, 19, 20 ]
 ]
 const newArr = arr.flat()
 输出: 
 /*
 [
    1,  2,  3,  4,  5,  6,  7,
    8,  9, 10, 11, 12, 13, 14,
   15, 16, 17, 18, 19, 20
 ]
 */
  1. toString 方法
 // 将数组 toString方法后, 输出的是纯内容了, 自动将内部数组去掉了

 const arr1 = [1, [2, 3, 4, 5], 6]
 arr.toString(arr1).split(',').map(item => parseInt(item))

手写Ajax

/**
 * 封装ajax
 */

class Ajax {
    constructor(executor) {
        this.xhr= null
        executor(this.createXhr)
    }
    createXhr() {
        if(window.XMLHttpRequest) {
            this.xhr = newXMLHttpRequest()
        }else {
            this.xhr = newActiveXObject()
        }
    }

    staticget(method, url, params){
        this.xhr(method, url, params)
        this.xhr.onReadystatechange=()=> {
            if(this.xhr.readySate === 2){
console.log('接受到响应头')
            }else if(this.xhr.readyState === 3) {
console.log('响应体加载中')
            }else  if(this.xhr.readyState === 4) {
console.log('响应体加载完成!这里进行后续操作!')
            }

        }
    }
}

/*
* 0: 建立来xhr, 还没发起请求,还没发起open请求。
* 1: 进行连接, open方法被调用
* 2: 连接完成, send方法被调用, 接受响应头
* 3: 响应体的返回
* 4: 响应体下载完毕, 可以进行其他的操作了。
*
*
* ajax的第三个参数为 是否异步, 默认的是 true,
* 也就是不用等待这完成, 可以先执行下面的任务, 不用等待
*
* 如果第三个参数设置为false, 那么就是同步的, 必须等待这个玩意儿没收到数据, 才能进行后续的其他操作。
*
* */

function get() {
    let xhr;

    xhr.open('method', 'url', 'isAsync')
    xhr.send()
    if(xhr.onreadystatechange){
        if(xhr.readyState === 4){
console.log('获取到数据')
        }
    }
}

function post() {
    let xhr;

    xhr.open('method', 'url', 'isAsync')
    // 设置请求头的格式, 告诉服务器传输的数据格式是什么。
    xhr.setRequestHeader("content-Type","application/x-www-form-urlencoded")
    // 这里使用的是和url中query一样的格式
    xhr.send('name=ck&age=20')

    if(xhr.onreadystatechange) {
        if(xhr.readyState === 4) {
console.log(xhr.responseText)
        }
    }
}
/*
* post方法, 或者传值的三种方法:
* 1. query格式: name=ck&age=18
* 2. 对象的字符串格式: {"name":"ck", "age":18}
* 2. JSON.stringfy({name:"ck", age:18}), 将方法二中的参数转成字符串格式的
* */

手写EventEmitter

/**
 * eventEmitter 的使用,
 * on(eventName, fn), 将事件绑定到eventEmitter中,
 * once(eventName, fn), 注册一个只会调用一次的事件
 * emit(eventName, args), 触发eventName事件, 后面是携带的参数
 * remove(eventName), 移除事件队列中的指定事件名的事件
 */

class EventBus{
    constructor(max) {
        this.events = {}
        this.maxListener = max || Infinity
        console.log(this.maxListener)
    }

    on(event, cb){
        // 先要声明一个数组, 如果当前事件不存在的话, 初始化一个数组
        if(!this.events[event]) {
            this.events[event] = []
        }
        if(this.maxListener !== Infinity && this.events[event].length > this.maxListener) {
            console.log(`事件${event}超过最大监听数`)
            return this;
        }
        this.events[event].push(cb)
        return this;
    }
    once(event, cb){
        const fn = (...args) => {
            this.off(event, fn)
            cb.call(this, args)
        }
        this.on(event, fn)

        return this;
    }
    emit(event, ...args){
        const cbs = this.events[event]
        if(!cbs) {
            console.log('没有该事件')
            return this;
        }
        cbs.forEach(cb => cb.call(this, args))
        return  this;
    }
    off(event, cb){
        // 一个事件上可以挂载好多的函数, 如果没有函数, 那么就删除该事件上的所有函数
        if(!cb) {
            this.events[event] = null
        }else{
            // 删除事件上指定的cb
            this.events[event] = this.events[event].filter(item => item !== cb)
        }
        return this;
    }
}

const events = new EventBus(2);
events.on('sum', (a, b) => {console.log(a + b)})
events.on('sum', (a, b) => {console.log(a + b + 1)})
events.on('sum', (a, b) => {console.log(a + b + 2)})
events.emit('sum', 1, 2)

函数柯里化

// 传入的参数是函数, 然后获取第二个参数起的参数. 
function curry(fn){
	const oldArg = Array.prototype.slice.call(arguments, 1)
	return function (){
			// 合并两个参数, 然后调用传入的那个目标函数进行调用
			const newArgs = Array.prototype.slice.call(arguments)
			const finalArg = [...oldArg, ...newArgs]
			fn.call(null, finalArgs)	
	}
}

手写LRU

  • 先弄清楚LRU的使用:
    • 对使LRU最近最先使用:
    • 如果被使用到了, 就那到第一个位置, 否则, 就按照使用顺序入队.
/**
 * LRU 缓存机制, 最近最新使用, 也就是淘汰最早使用的,
 * 比如一个浏览器, 浏览了100个网页, 那么, 假如需要缓存80个网页, 怎么做呢
 *
 */

class LRU {
    constructor(opacity) {
// 设置我们要缓存的容器大小
this.opacity = opacity;
        this.list = []
    }

    put(key, value) {
// 如果队列中有该缓存, 将原来位置删掉, 将其放置到最新的位置
if(this.isNodeInList(key)){
            this.remove(key)
            this.list.unshift({key:value})
        }else {
            if(this.isFull()){
                this.list.pop()
                this.list.unshift({key:value})
            }
        }
    }

// 获取指定的节点, 并将该节点放到最新的位置, 因为这里已经被使用过了, 所以, 需要更新到最前面。
    // 其他的好像都是和cache一样进行普通的处理, 但是这里需要的是,使用过后, 需要进行更新操作。
get(key){
        if(this.isNodeInList(key)) {
            const node = this.remove(key)
            this.list.unshift(key, node)
        }else {
            return  -1;
        }
    }

// 删除队列中的指定节点
remove(key) {
        for(let i=0; i<this.list.length; i++) {
            if(this.list[i].key === key) {
                const node = this.list.splice(i, 1)
// 删除的哪一个, 返回删除的key
return node.key
            }
        }
    }

// 判断目标节点是否在队列中
isNodeInList(key) {
        for(let item of this.list) {
            if(item.key === key) {
                return item
            }
        }
        return false;
    }
// 判断队列是否满了
isFull(){
        return this.list.length === this.opacity
    }
}

class LRUV2 {
    constructor(opacity) {
        this.opcacity = opacity
        this.list = []
    }
    put(key,value) {
        if(this.isNodeInList(key)) {
            this.remove(key)
            this.list.unshift({key:value})
        }else {
            this.list.unshift({key:value})
        }
    }
    get(key) {
        if(!this.isNodeInList(key)) return null
        for(let item of this.list) {
            if(item.key === key) {
// 在原有位置删除node
const node = this.remove(key)
// 将删除后的node入队
this.list.unshift(node)
// 最后返回这个node, 就是最近使用的一个
return  node;
            }
        }
    }
    remove(key) {
        if(this.isNodeInList(key)) {
            for(let i=0; i<this.list.length; i++) {
                if(this.list[i].key === key) {
                    const node = this.list[i]
                    this.list.splice(i, 1)
                    return node
                }
            }
        }else {
            return  -1;
        }
    }
    isNodeInList(key){
        for(let i =0; i<this.list.length; i++) {
            if(this.list[i].key=== key) {
                return true
            }
        }
        return false;
    }
    isFull(){
        return this.list.length === this.opcacity;
    }
}

class LUR{
    constructor(maxListener) {
        this.maxListener = maxListener
        this.events = []
    }

    put(key, value){
        if(this.isNodeInList(key)){
            this.remove(key)
            this.events.unshift({key, value})
        }else {
            this.events.unshift({key, value})
        }
    }

    get(key){
        if(this.isNodeInList(key)){
            this.remove(key)
            this.events.unshift({key, value})
        }else {
            console.log('该方法不存在哦~')
        }

    }

    remove(key){
        if(this.isNodeInList(key)){
            this.events.forEach((item,index) => {
                item.key === key && this.events.splice(index, 1)
            })
        }else {
            console.log('该方法不存在哦!')
        }
    }

    isFull(){
        return this.events.length === this.maxListener
    }

    isNodeInList(key){
        this.events.map(item => {
            return item.key === key
        })
    }
}

用NodeJs获取目录下的所有文件

const file = require('fs')
const Path = require('path')

//TODO: 获取当前目录下的所有文件。 递归和非递归两种方式
function getAllFiles(path) {
    const res = []
    F(path)

    function F(path) {
        file.readdir(Path.resolve(__dirname, path), (err, files) => {
            if (!err) {
                files.map(item => {
                    (function (item) {
                        file.stat(item, (err1, stats) => {
                            if (!err) {
                                if (stats.isFile()) {
                                    res.push(item)
                                } else if (stats.isDirectory() && item !== 'node_modules') {
                                    F(item)
                                }
                                console.log(res)
                            }
                        })
                    })(item)
                })
            }
        })
    }

}

getAllFiles(__dirname)

手写路由Router

  • 基于window路由
class Router {
/**
     * options: {
     *     path:'/index',
     *     component: Main
     * }
     *
     * 基于js原生的window.addEventListener('load')
     * window.addEventListener('hashchange', function(){}, isScrape)// 是否是在捕获阶段执行
     */
constructor() {
        this.routes = {}
        this.currentHash = ''
        this.refreshRoute = this.refreshRoute.bind(this)
        this.init()
    }

    init() {
        window.addEventListener('load', this.storeRoute, false)
        window.addEventListener('hashchange', this.refreshRoute, false)
    }

    storeRoute(path, cb) {
        this.routes[path] = cb || function () {
        }
    }

    refreshRoute() {
        this.currentHash = location.hash.slice(1) || '/'
        this.routes[this.currentHash]()
    }

}

  • 基于vue-router
class RouterVueRouter{
    constructor(options) {
        this.options= options
        this.routes = {}
        this.currentHash = ''
// 创建hash表
this.createRouterMap.bind(this,options)
// 初始化, 用于监听页面路由改变
this.init()
    }
    init() {
        window.addEventListener('load', this.setHash.bind(this), false)
        window.addEventListener('hashchange', this.setHash.bind(this), false)
    }
    createRouterMap(){
        this.options.map(item => {
            this.routes[item.path] = item.component
        })
    }
// 获取当前的hash
getHash() {
        return location.hash.slice(1) || '/'
    }
// 设置当前hash
setHash() {
        this.currentHash  = this.getHash()
    }
}

手写数据双向绑定

let obj = {}
let OInput = document.querySelector('#input')
let OSpan =  document.querySelector('#span')

Object.defineProperties(obj,'text', {
    configurable:true,
    enumerable:true,
    get(){
        console.log('获取到了数据')
    },
    set(val) {
        console.log('数据更新了')
        OInput.value = val
        OSpan.innerHTML = val
    }
})

OInput.addEventListener('keyup', function (e){
    obj.text = e.target.value
})

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值