「寒冬」三年經驗前端面試總結(含頭條、百度、餓了麼、滴滴等)之手寫題(二)

前言

不管是寒冬仍是暖冬,找工做以前都須要作好充足的準備,面試的時候才能作到遊刃有餘。此文是把我最近找工做準備的以及筆試面試中涉及到的手寫題作一個總結。給本身,也給須要的同窗。前端

手寫題是比較好準備的一個環節,大部分公司考察的題也就那麼多,大都不會超出範圍。vue

本文是手寫題系列的第二篇文章。node


往期:

  1. "寒冬"三年經驗前端面試總結(含頭條、百度、餓了麼、滴滴等)
  2. "寒冬"三年經驗前端面試總結(含頭條、百度、餓了麼、滴滴等)之手寫題(一)
  3. "寒冬"三年經驗前端面試總結(含頭條、百度、餓了麼、滴滴等)之手寫題(promise篇)

實現eventEmitter

觀察者模式是咱們工做中常常能接觸到的一種設計模式。用過 jquery 的應該對這種設計模式都不陌生。eventEmitternode 中的核心,主要方法包括on、emit、off、oncejquery

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 屬性是否出如今某個實例對象的原型鏈上。其實考察的也是繼承。vue-router

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上給的說明以下:express

  1. 建立一個空的簡單 JavaScript 對象(即{});
  2. 連接該對象(即設置該對象的構造函數)到另外一個對象 ;
  3. 將步驟1新建立的對象做爲 this 的上下文 ;
  4. 若是該函數沒有返回對象,則返回 this

var child = new Parent()爲例:json

function newParent(){
    var obj = {}; // 首先建立一個對象
    obj.__proto__ = Parent.prototype; // 而後將該對象的__proto__屬性指向構造函數的protoType
    var result = Parent.call(obj) // 執行構造函數的方法,將obj做爲this傳入
    return typeof(result) == 'object' ?  result : obj
}

lazyMan

原題以下:segmentfault

實現一個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

寫在最後

有錯誤之處還請小夥伴們及時指出,以避免誤人子弟。想看往期內容,翻到頁面最上面有連接~

相關文章
相關標籤/搜索