美文网首页
手写代码面试题

手写代码面试题

作者: scrollHeart | 来源:发表于2024-01-30 16:39 被阅读0次

手写获取数组的重复元素,要求尽可能用多种方法实现

Let arr = [11, 23, 26, 23, 11, 9]
Const filterUnique = arr => arr.filter(i => arr.indexOf(i) !== arr.lastIndexOf(i))
console.log(filterUnique(arr))  // [11, 23, 23, 11]

手写 Array.prototype.reduce()

array.reduce(): 接收两个参数,一个处理数据的函数,一个是初始值
接收的处理数据函数,可接收4个参数(total, curValue, curIndex, array)
total(上一次调用回调返回的值,或者是提供的初始值)
curValue(数组中当前被处理的元素)
curIndex(当前元素在数组中的索引)
array(调用reduce的数组)

手动实现

  1. 定义一个函数myfunction(callback, initValue)
    当this或者callback不为正确语法,抛出错误;this指向调用reduce的数组
    把调用的值通过object(this)包装成一个类对象。

Array.prototype.reduce.call(1212, (pre,cut) =>{return ‘’ + pre+cur}, 0)

把手写的myReduce写入Array的原型链上Array.prototype.MyReduce = MyReduce;

function MyReduce(fun, initValue){
    if (this === null){
        // this 不存在,抛出错误
        throw new TypeError(‘called on null or undefined’)
    }
    if (typeof fun !== ‘function’) {
        // fun 不是function时,抛出错误
        throw new TypeError(fun + ‘is not a function’);
    }
    const value = Object(this);
    let preValue, curValue, curIndex;
    if(initValue !== undefined){
        preValue = initValue;
        curValue = arr[0];
        curIndex = 0;
    } else {
        preValue = arr[0];
        curValue = arr[1];
        curIndex = 1
    }
    for (let I = curIndex; I < value.length; I++){
        const item = value[I];
        preValue = fun(preValue, item, I, arr);
    }
    return preValue;
}

Function setReduce(preValue, curValue, index, arr){
    return `${preValue} - ${curValue}`
}

// 把方法写入原型链上
Array.prototype.MyReduce = MyReduce;
2.Array.prototype.myReduce = function(callbackFn, initialValue) {
// 处理回调类型异常
    if(Object.prototype.toString.call(callbackFn) !== ‘[object Function]’) {
        throw new TypeError(callbackFn + ‘is not a function’)
    }
    
    var acc = initialValue || this[0];
    var startIndex = initialValue ? 0 : 1;

    for(var I = startIndex, Len = this.length; I < Len; I++){
        acc = callbackFn(acc, this[I], I, this)
    }
    return acc;
}

Const arr = [1, 2, 3, 4]
arr.myReduce((total, cur) => total + cur);  // 10
arr.myReduce((total, cur) => total + cur, 10);  // 20
Arr.myReduce(’555’);  // 555 is not a function

手写 Promise

先回顾下最简单的Promise使用方式:

const p1 = new Promise((resolve, reject) => {
    console.log(‘create a promise’)
    resolve(‘成功了’)
})
console.log(‘after new promise’)

const p2 = p1.then(data => {
    console.log(data)
    throw new Error(’失败了’)
})

const p3 = p2.then(data => {
    console.log(‘success’, data)
}, err => {
    console.log(‘faild’, err) 
})

promise有三个状态:pending, fulfilled, or rejected
New promise时, 需要传递一个executor()执行器,立即执行
executor接受两个参数,分别是resolve和reject;
promise 的默认状态:pending
Promise 有一个value保存成功状态的值,可以是undefined/thenable/promise
Promise 有一个reason保存失败状态的值,
promise 只能从pending 到 rejected, 或者从 pending 到 fulfilled, 状态一旦确认,就不会再改变
promise 必须有一个then方法,then 接收两个参数,分别是promise成功的回调onFulfilled, 和 promise 失败的回调onRejected
若调用then, promise 已经成功,执行onFulfilled, 参数是promise 的value
若调用then,promise 已经失败,执行onRejected,参数是promise 的reason
then 抛出异常,把异常作为参数,传递下一个then的失败回调onRejected

// 三个状态: pending, fulfilled, rejected
Const pending = ‘pending’;
Const fulfilled = ‘fulfilled’;
Const rejected = ‘rejected’;
Class Promise {
    constructor(executor){
        // 默认状态为pending
        this.status = pending;
        // 存放成功状态的值,默认为undefined
        this.value = undefined;
        // 存放失败状态的值,默认为undefined
        this.reason = undefined;

        // 调用此方法就是成功
        let resolve = (value) => {
            // 状态为pending时可以更新状态,防止executor中调用两次resolve/reject方法
            if (this.status === pending){
                this.status = fulfilled;
                this.value = value;
            }

        }
        // 调用此方法就失败
        let reject = (reason) => {
            // 状态为pending时可以更新状态,防止executor中调用两次resolve/reject方法
            if (this.status === pending){
                this.status = rejected;
                this.reason = reason;
            }

        }

        try {
            executor(resolve, reject)
        } catch(error) {
            // 发生异常时执行失败逻辑
            reject(error)
        }
        //包含一个then,接收两个参数onFulfilled, onRejected

        then(onFulfilled, onRejected){
            if(this.status === fulfilled) {
                onFulfilled(this.value)
            }
            if(this.status === rejected) {
                onRejected(reason)
            }
        }
    }

}

手写 apply

Function.prototype.myApply = function(context){
    if(context === undefined || context === null){
        context = window
    } else {
        context = Object(context)
    }
    // 判断是否为类数组对象
    function isArrayLike(o) {
        if (
            o &&                                      // o 不是null,undefined等
            typeof o === ‘object’ &&    // o是对象
            isFinite(o.length) &&           // o.length是有限数值
            o.length >= 0 &&                // o.length为非负值
            o.length === Math.floor(o.length) &&      // o.length是整数
            o.length < Math.pow(2,32)
        ){
            return true
        }
        return false
    }
    const fn = Symbol(‘uniqueFn’)
    context[fn] = this
    let args = arguments[1]
    let result  
    if (!Array.isArray(args) && isArrayLike(args)){
        throw new Error(‘CreateListFromArrayLike called on non-object’)  // 第二个参数不为数组且不为类对象数组
    } else {
        args = Array.from(args)     // 转为数组
        result = context[fn](…args)
    }
    delete context[fn]
    return result
}

手写 call 函数

call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数

Function.prototype.myCall = function(context){
    if(context === undefined || context === null){
        context = window // 指定null 和 undefined 的this值会自动指向全局对象
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的this会指向该原始值的实例对象 
    }
    const fn = Symbol(‘uniqueFn’)  // 用Symbol是防止跟上下文的原属性冲突
    context[fn] = this
    let arg = […arguments].slice(1)
    let result = context[fn](…arg)
    delete context[fn]
    return result
}

手写bind方法

实现bind要额外考虑一个问题:方法一个绑定函数也能使用new操作符创建对象:这种行为像把原函数当成构造器,提供的this值被忽略,同时调用时的参数被提供给模拟函数

Function.prototype.myBind = function(context){
    if (typeof this !== ‘function’){
        throw new Error(‘Expected this is a function’)
    }
    let self = this
    let args = […arguments].slice(1)
    let fn = function(…innerArg){
        const isNew = this instanceof fn     // 返回的fn是否通过new调用
        return self.apply(isNew ? This : Object(context), args.concat(innerArg)). // new 调用就绑定到this上,否则绑定到传入的context上
    }
    // 复制原函数的prototype给fn,一些情况下函数没有prototype,比如箭头函数
    if(self.prototype){
        fn.prototype = Object.create(self.prototype)
    }
    return fn    // 返回拷贝的函数
}

手写 new

New 到底做了什么?

  • 创建一个新的对象
  • 继承父类原型上的方法
  • 添加父类的属性到新的对象上并初始化,保存方法的执行结果
  • 如果执行结果有返回值并且是一个对象,返回执行的结果,否则,返回新创建的对象
Function _new(obj, …rest){
    // 基于obj的原型创建一个新的对象
    const newObj = Object.create(obj.prototype);
    // 添加属性到新创建的newObj上,并获取obj函数执行的结果
    const result = obj.apply(newObj, rest);
    // 如果执行结果有返回值并且是一个对象,返回执行的结果,否则,返回新创建的对象
    return typeof result === ‘object’ ? result : newObj;
}

手写深拷贝

基础版本:

function deepClone(target){
    let result;
    if (typeof target === ‘object’){
        if(Array.isArray(target)){
            result = []
            for(let I in target){
                result.push(deepClone(target[I]))
            }
        }
        else {
            result = {}
            for (let key in target){
                result[key] = target[key]
            }
        }
    } else {
        result = target;
    }
    return result;
}
Let A = [1,2,3, {a:1, b:2}]
Let B = deepClone(A)
B[3].a = 99
console.log(A).   // [1,2,3,[4,5]]
console.log(B)    // [1,2,3,{a:99, b: 2}]

改用class类写

class DeepClone{
    constructor(){
        cloneVal: null;
    }
    clone(val, map = new WeakMap()){
        let type = this.getType(val);    // 当是引用类型的时候先拿到其确定的类型
        if(this.isObj(val)){
            switch(type){
                case ‘date’:              // 日期类型重新new 一次传入之前的值,data实例化本身结果不变
                    return new Date(val);
                    break;
                case ‘regexp’:       // 正则类型直接new 一个新的正则传入source和flags即可
                    return new RegExp(val.source, val.flags);
                    break;
                case ‘function’:    // 若是函数类型是直接通过function包裹返回一个新的函数,并且改变this指向
                    return new RegExp(val.source, val.flags);
                    break;
                default: 
                    this.cloneVal = Array.isArray(val) ? [] : {};
                    if (map.has(val)) return map.get(val)
                    map.set(val, this.cloneVal)
                    for(let key in val){
                        if (val.hasOwnProperty(key)) {. // 判断是不是自身的key
                            this.cloneVal[key] = new DeepClone().clone(val[key], map);
                        }
                    }
                    return this.cloneVal;
            }
        } else {
            return val;    // 当是基本数据类型的时候直接返回
        }
    }
    /** 判断是否是引用类型*/
    isObj(val) {
        return (typeof val == ‘object’ || typeof val == ‘function’) && val != null
    }
    /**获取类型*/
    getType(data){
        var s = Object.prototype.toString.call(data);
        return s.match(/\[object(.*?)\]/)[1].toLowerCase();
    }
}
/** 测试*/
var a = {
    a: 1,
    b: true,
    c: undefined,
    d: null,
    e: function(alb){
        return a + b
    },
    f: /\W+/gi,
    time: new Date(),
}
Const deepClone = new DeepClone()
Let b = deepClone.clone(a)
console.log(b)

手写虚拟 dom 转换成真实 dom

{
    tag: ‘DIV’,
    attrs: {
        id: ‘app’
    },
    children: [
        {
            tag: ’SPAN’,
            children: [
                {tag: ‘A’, children: []}
            ]
        },
        {
            tag: ’SPAN’,
            children: [
                {tag: ‘A’, children: []},
                {tag: ‘A’, children: []}
            ]
        }
    ]
}

// 把上述虚拟Dom转化成下方真实Dom

<div id=“app”>
    <span>
        <a></a>
    </span>
    <span>
        <a></a>
        <a></a>
    </span>
</div>

// 真正的渲染函数

function _render(vnode){
    // 如果是数字类型转化为字符串
    if(typeof node === ’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;
}

手写 assign,要考虑全面,包括 symbol 也要考虑在内

let obj1 = {
    a: 1,
    b: 2
}
obj1[Symbol(‘abc’)] = ‘abc’

let obj2 = {
    a: 3,
    d: 5
}

obj2[Symbol(‘abc’)] = ‘abc’
function myAssign(…obj){
    let res = {}
    for(let obj of objs) {
        for(let prop of Reflect.ownKeys(obj)) {
            res[prop] = obj[prop]
        }
    }
    return res
}
let obj3 = myAssign(obj1, obj2)
console.log(obj3)

手写 ES6 的模板字符串

// 1.创建一个正则表达式
    let args =  //
// 2.从${}中取值
    let args = /${}/
// 3.由于$, {, }都是有特殊含义的,需要转义
let args = /\$\{\}/

// 4.现在要将${}中的变量名取到,需要使用()
/* 正则圆括号:
(1)在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰
(2)取匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到
*/

let args = /\$\{()\}/

// 5.变量名可能是任意的字符, 匹配任意的是. 变量的名称可能是多个字符,需要用到 + 还需要全局匹配
let args = /\$\{(.+)\}/g

// 6.步骤5中的正则,是贪婪模式的,如果存在多个${}, 那第一次把所有匹配到的内容都拿到,是这样的
// 惰性的正则(加?可以使其变为惰性的,取到1次就不再取了)
let args = /\$\{(.+?)\}/g
// 实现strParse函数
function strParse(string){
    let args = /\$\{(.+?)\}/g
    // replace方法有两个参数,第二个参数是要替换的内容,或者是一个返回字符串的函数,这个函数有几个参数
    return string.replace(args, function(){
        return eval(arguments[1])
    })
}

// 测试一下

Let esStr = ‘第一个字符串是${str1}, 第二个字符串是${str2}’
Let str = strParse(esStr)
console.log(str) // 第一个字符串是abc,第二个字符串是def

手写发布订阅模式,订阅,触发,移除

发布-订阅模式其实是一种对象间一对多的依赖关系 ,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知,订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel), 当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码

实现思路:
1.创建一个EventEmitter类
2.在该类上创建一个事件中心(Map)
3.on方法用来把函数fn都加到事件中心中(订阅者注册事件到调度中心)
4.emit方法取到arguments里第一个当做event,根据event值去执行对应事件中心中的函数(发布者发布事件到调度中心,调度中心处理代码)
5.off方法可根据event值取消订阅
6.once方法只监听一次,调用完毕后删除缓存函数(订阅一次)
7.注册一个newListener用于监听新的事件订阅

第一步,创建一个类,并初始化一个事件存储中心

class EventEmitter{
    // 用来存放注册的事件和回调
    constructor(){
        this._events = {};
    }
}

第二步,实现事件的订阅方法on(将事件回调函数存储到对应的事件上)

class EventEmitter{
    // 用来存放注册的事件与回调
    constructor(){
        this._events = {}
    }
    on(eventName, callback){
        // 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
        const callbacks = this._events[eventName] || [];
        callbacks.push(callback);
        this._events[eventName] = callbacks
    }
}

第三步,实现事件的发布方法emit(基本思路:获取到事件对应的回调函数依次执行)

class EventEmitter{
    // 用来存放注册的事件和回调
    constructor(){
        this._events = {}
    }
    // args用于收集发布事件时传递的参数
    emit(eventName, …args){
        const callbacks = this._events[eventName] || [];
        callbacks.forEach(cb => cb(…args))
    }
}

第四步,实现事件的取消订阅的方法off(找到事件对应的回调函数,删除对应的回调函数)

class EventEmitter{
    // 用来存放注册的事件和回调
    constructor(){
        this._events = {}
    }
    off(eventName, callback){
        const callbacks = this._events[eventName] || []
        const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback // 用于once的取消订阅)
        this._events[eventName] = newCallbacks
    }
}

第五步,实现事件的单次订阅方法once(基本思路:1先注册 2.事件执行后取消订阅)

class EventEmitter{
    // 用来存放注册的事件与回调
    constructor(){
        this._events = {}
    }
    once(eventName, callback){
        // 由于需要在回调函数执行后,取消订阅当前事件,需要对传入的回调函数做一层包装,然后绑定包装后的函数
        const one = (…args) => {
            // 执行回调函数
            callback(…args)
            // 取消订阅当前事件
            this.off(eventName, one)
        }
        // 考虑:如果当前事件在未执行,被用户取消订阅,能否取消

        // 由于:订阅事件时,修改了原回调函数的引用,所以,用户触发off时不能找到对应回调函数
        // 所以,需要在当前函数与用户传人的回调函数做一个绑定,通过自定义属性来实现
        one.initialCallback = callback;
        this.on(eventName, one)
    }
}

第六步,注册一个newListener用于监听新的事件订阅(在用户注册事件时,发布一下newListener事件)

class EventEmitter{
    // 用来存放注册的事件和回调
    constructor(){
        this._events = {}
    }
    on(eventName, callback){
        // 如果绑定事件不是newListener就触发回调
        if (this._events[eventName]){
            if(this.eventName !== ‘newListener’){
                this.emit(‘newListener’, eventName)
            }
        }
        // 由于一个事件可能注册多个回调函数,使用数组来存储事件队列
        const callbacks = this._events[eventName] || [];
        callbacks.push(callback);
        this._events[eventName] = callbacks
    }
}

手写斐波那锲数列

// count: 指的是第几个
// 返回值是斐波那锲数列第count项的值
function fibonacci(count){
    if (count === 1 || count === 2) return 1
    return  fibonacci(count - 2) + fibonacci(count -1)
}
// for循环版(比前者空间复杂度更低)
function fibonacci(n){
    let n1 = 1; n2 = 1;
    for(let I = 2; I < n; I++){
        [n1, n2] = [n2, n1+n2]
    }
    return n2
}
for(let I = 1; I <=10, I++){
    console.log(fibonacci(I))
}

手写防抖和节流

防抖:当事件触发时,相应函数的不会被立即触发,而是会被推迟执行
只有等待一段时间后也没有再次触发该事件,才会真正执行响应函数

输入框频繁输入
频繁点击按钮,触发某个事件
监听浏览器滚动事件
监听用户缩放浏览器resize事件

简易版防抖函数:

// 第一个参数是需要进行防抖处理的函数,第二个参数是延迟时间,默认是1s,
function debounce(fn, delay = 1000){
// 实现防抖函数的核心是使用setTimeout
    // time变量用于保存setTimeout返回的id
    let time = null
    function _debounce(…args){
        // 如果time不为0,有定时器存在,将该定时器清除
        if(time !== null){
            clearTimeout(time)
        }
        time = setTimeout(() => {
            // 使用apply改变fn的this, 同时将参数传递给fn
            fn.apply(this, args)
        }, delay)
    }
    // 防抖函数会返回另一个函数,该函数才是真正被调用的函数
    return _debounce
}

什么是节流
节流函数会按照一定的频率来执行函数
节流类似于技能cd,不管按了多少次,必须等cd结束后才能释放技能,如果在cd时间段,不管触发了几次事件,只会执行一次,只有当下一次cd转换,才会再次执行

  • 实现节流函数
// interval 间隔时间,也就是cd的长短
function throttle(fn, interval){
    // 该变量用于记录上一次函数的执行事件
    let lastTime = 0
    
    const _throttle = function(…args){
        // 获取当前时间
        const nowTime = new Date().getTime()
        // cd剩余时间
        const remainTime = nowTime - lastTime
        // 如果剩余时间大于间隔时间,可以再次执行函数
        if(remainTime - interval >= 0){
            fn.apply(this, args)
            // 将上一次函数执行的时间设置为nowTime, 这样下次才能重新进入cd
            lastTime = nowTime
        }
    }
    // 返回_throttle函数
    return _throttle
}

相关文章

网友评论

      本文标题:手写代码面试题

      本文链接:https://www.haomeiwen.com/subject/yzanodtx.html