寒冬三年经验前端面试总结含头条百度饿了么滴滴等之手写题二

33次阅读

共计 4448 个字符,预计需要花费 12 分钟才能阅读完成。

前言

不论是寒冬还是暖冬,找工作之前都需要做好充足的准备,面试的时候才能做到游刃有余。此文是把我最近找工作准备的以及笔试面试中涉及到的手写题做一个总结。给自己,也给需要的同学。

手写题是比较好准备的一个环节,大部分公司考察的题也就那么多,大都不会超出范围。

本文是手写题系列的第二篇文章。


往期:

  1. “ 寒冬 ” 三年经验前端面试总结(含头条、百度、饿了么、滴滴等)
  2. “ 寒冬 ” 三年经验前端面试总结(含头条、百度、饿了么、滴滴等)之手写题(一)

实现 eventEmitter

观察者模式是我们工作中经常能接触到的一种设计模式。用过 jquery 的应该对这种设计模式都不陌生。eventEmitternode 中的核心,主要方法包括on、emit、off、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,...arg){if(this.events[name]){this.events[name].forEach(fn => {fn.call(this,...arg)
            })
        }
    }
    off(name,cb){if(this.events[name]){this.events[name] = this.events[name].filter(fn => {return fn != cb})
        }
    }
    once(name,fn){var onlyOnce = () => {fn.apply(this,arguments);
            this.off(name,onlyOnce)
        }
        this.on(name,onlyOnce);
        return this;
    }
}

实现继承

继承是一个万年不变的考点。从 ES5 到 ES6,有许多继承方法。专门看有关继承的文章,一般都会从最基础的 prototype 原型链继承 到 借用父类构造函数的 call 继承 到二者的结合说起。本文只给出终极方法,如果想了解其他方法的话,可以自行搜索。

// ES5
function Parent(name,age){
    this.name = name;
    this.age = age;
}
Parent.prototype.say = function(){console.log('I am' + this.name)
}

function Child(name, age, sex){Parent.call(this,name,age);
    this.sex = sex;
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
// ES6
class Parent {constructor(name,age){
        this.name = name;
        this.age = age;
    }
}

class Child extends Parents{constructor(name,age,sex){super(name,age);
        this.sex = sex; // 必须先调用 super,才能使用 this
    }
}

实现 instanceof

首先要了解 instanceof 实现的功能,instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。其实考察的也是继承。

function myInstanceof(left,right){
    var proto = left.__proto__;
    var protoType = right.prototype;
    while(true){if(proto === null){return false}
        if(proto == protoType){return true}
        proto = proto.__proto__
    }
}

new 的过程

当我们 new 一个对象的时候,具体执行的是什么?MDN上给的说明如下:

  1. 创建一个空的简单 JavaScript 对象(即{});
  2. 链接该对象(即设置该对象的构造函数)到另一个对象;
  3. 将步骤 1 新创建的对象作为 this 的上下文;
  4. 如果该函数没有返回对象,则返回 this

var child = new Parent()为例:

function newParent(){var obj = {}; // 首先创建一个对象
    obj.__proto__ = Parent.prototype; // 然后将该对象的__proto__属性指向构造函数的 protoType
    var result = Parent.call(obj) // 执行构造函数的方法,将 obj 作为 this 传入
    return typeof(result) == 'object' ?  result : obj
}

lazyMan

原题如下:

实现一个 LazyMan,可以按照以下方式调用:
LazyMan("Hank")输出:
Hi! This is Hank!
 
LazyMan("Hank").sleep(10).eat("dinner")输出
Hi! This is Hank!
// 等待 10 秒..
Wake up after 10
Eat dinner~
 
LazyMan("Hank").eat("dinner").eat("supper")输出
Hi This is Hank!
Eat dinner~
Eat supper~
 
LazyMan("Hank").sleepFirst(5).eat("supper")输出
// 等待 5 秒
Wake up after 5
Hi This is Hank!
Eat supper
 
以此类推。

这道题主要考察的是链式调用、任务队列、流程控制等。关键是用手动调用 next 函数来进行下次事件的调用,类似 express 中间件和 vue-router 路由的执行过程。

  function _LazyMan(name){
    this.nama = name;
    this.queue = [];
    this.queue.push(() => {console.log("Hi! This is" + name + "!");
        this.next();})
    setTimeout(()=>{this.next()
    },0)
  }
  
  _LazyMan.prototype.eat = function(name){this.queue.push(() =>{console.log("Eat" + name + "~");
        this.next()})
    return this;
  }

  _LazyMan.prototype.next = function(){var fn = this.queue.shift();
    fn && fn();}

  _LazyMan.prototype.sleep = function(time){this.queue.push(() =>{setTimeout(() => {console.log("Wake up after" + time + "s!");
            this.next()},time * 1000)
    })
    return this;
  }

  _LazyMan.prototype.sleepFirst = function(time){this.queue.unshift(() =>{setTimeout(() => {console.log("Wake up after" + time + "s!");
            this.next()},time * 1000)
    })
    return this;
  }

  function LazyMan(name){return new _LazyMan(name)
  }

实现 jsonp

jsonp 的作用是跨域。原理是通过动态插入 script 标签来实现跨域,因为 script 脚本不受同源策略的限制。它由两部分组成:回调函数和数据。举例:

 function handleResponse(response){alert("You’re at IP address" + response.ip + ", which is in" +response.city + "," + response.region_name);    
    }
    var script = document.createElement("script");
    script.src = "http://freegeoip.net/json/?callback=handleResponse";
    document.body.insertBefore(script,document.body.firstChild);    
}

根据上面的例子,下面来实现一个通用的 JSONP 函数

function jsonp(obj) {const {url,data} = obj;
    if (!url) return
    return new Promise((resolve, reject) => {const cbFn = `jsonp_${Date.now()}` 
        data.callback = cbFn
        const head = document.querySelector('head')
        const script = document.createElement('script')
        const src = `${url}?${data2Url(data)}`
        console.log('scr',src)
        script.src = src
        head.appendChild(script)
        
        window[cbFn] = function(res) {res ? resolve(res) : reject('error')
            head.removeChild(script)
            window[cbFn] = null 
        }
    })
}

function data2Url(data) {return Object.keys(data).reduce((acc, cur) => {acc.push(`${cur}=${data[cur]}`)
        return acc
    }, []).join('&')
}
// jsonp({url:'www.xxx.com',data:{a:1,b:2}})

函数 currying

函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术,是高阶函数的一种用法。比如求和函数add(1,2,3), 经过柯里化后变成add(1)(2)(3)

function currying(fn,...args){if(fn.length <= args.length){return fn(...args)
    }
    return function(...args1){return currying(fn,...args,...args1)
    }
}
function add(a,b,c){return a + b + c}
add(1,2,3) // 6
var curryingAdd = currying(add);
curryingAdd(1)(2)(3) // 6

写在最后

有错误之处还请小伙伴们及时指出,以免误人子弟。想看往期内容,翻到页面最上面有链接~

正文完
 0