前端面試手寫題有備無患

前言

手寫其實在前端的面試過程當中必不可少,由於手寫是考驗你瞭解某一原理的最可觀的體現。下面我彙總了一些,我在面試複習過程當中遇到的手寫題。我將實現思路寫出來與你們共享,而實現只是一個參考,有興趣的能夠點擊參考答案,有問題請指正。前端

原理實現

實現一個new操做符

首先咱們須要瞭解new操做符幹了哪些事情面試

  • new操做符返回的是一個對象。
  • 對象的原型,指向的是構造函數的原型
  • 若是構造函數有return的話,須要對return的進行判斷,若是是對象,那麼用函數return的,若是不是對象,那麼直接返回新建立的對象
參考答案
function myNew(fn) {
    let obj = Object.create(fn.prototype);
    let res = fn.call(obj);
    return res instanceof Object ? res : obj;
}
複製代碼

實現一個instanceof操做符

首先咱們須要知道instanceof是經過原型鏈來進行判斷的ajax

參考答案
instanceof操做符是判斷原型鏈來生效的,因此只要你將左邊的_proto_和右邊的prototype作對比
複製代碼
function myInstance(left, right) {
    // 當left是基礎類型的時候直接返回false
    if(typeof left !== 'object' || left === null) return false;
    let proto = Object.getPrototypeOf(left);
    while(true) {
        if(proto === null) return false;
        if(proto === right.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
}
複製代碼

實現一個apply

實現apply主要是要注意如下幾個方面:數組

一、參數不能取第一個,由於第一個是上下文。promise

二、賦值到對象上的方法須要進行刪除,不能影響原對象。bash

三、對於上下文爲undefined的狀況須要把上下文指向window對象app

參考答案
Function.prototype.myApply = function(context, [...args]) {
    // 先判斷context是否爲空,若是爲空則指向window對象
    context = context || window;
    context.fn = this;
    context.fn(...args);
    delete context.fn;
}
複製代碼

實現一個call

call的實現和apply的實現相似,可是參數處理層面會略有不一樣。異步

參考答案
Function.prototype.myCall = function(context) {
    // 先判斷context是否爲空,若是爲空則指向window對象
    context = context || window;
    let args = [...arguments].slice(1);
    context.fn = this;
    context.fn(args);
    delete context.fn;
}
複製代碼

實現一個bind

bind的實現須要注意的是函數柯里化的狀況。async

參考答案
Function.prototype.myBind = function(context) {
    const self = this;
    let args = [...arguments].slice(1);
    return function() {
        // 考慮函數柯里化的狀況
        let newArgs = [...arguments];
        this.apply(context, newArgs.concat(args))
    }
}
複製代碼

實現一個promise

promise的實現有必要着重看一下,我本身劃分幾個步驟來看:函數

一、先處理同步的邏輯,好比狀態值變化以後,將不在發生改變,以及有哪些屬性值等。

二、再處理異步邏輯,用回調的形式,以及存放的列表。

三、再處理鏈式調用的邏輯,鏈式調用比較複雜,多注意thenable的對象。

四、在處理promise的其餘方法。

最後我在這裏給你們推薦一篇文章,寫的很不錯!!promise文章

參考答案
class Promise(exector) {
    constructor() {
        this.value = undefined;
        this.reason = '';
        this.state = 'pending';
        this.onResolveList = [];
        this.onRejectList = [];
        const resolve = (value) => {
            if(this.state === 'fulfilled') {
                this.value = value
                this.state = 'fulfilled';
                this.onResolveList.forEach((fn) => {
                    fn();
                })
            }
        };
        const reject = (reason) => {
            if(this.state === 'rejected') {
                this.reason = reason
                this.state = 'rejected';
                this.onRejectList.forEach((fn) => {
                    fn();
                })
            }
        }
        
        try {
            exector(resolve, reject);
        } catch(err) {
            reject(err)
        }
    }
    then(onFulfilled, onRejected) {
        const promise2 = new Promise((reslove, reject) => {
            if(this.state === 'fulfilled') {
                let x = onFulfilled(this.value);
                resolvePromise(promise2, x, reslove, reject);
            }
            if(this.state === 'rejected') {
                onRejected(this.reason);
                resolvePromise(promise2, x, reslove, reject);
            }
            if(this.state === 'pending') {
                onResolveList.push(() => {
                    let x = onFulfilled(this.value);
                    resolvePromise(promise2, x, reslove, reject);
                });
                onRejectList.push(() => {
                    let x = onRejected(this.reason);
                    resolvePromise(promise2, x, reslove, reject);
                });
            }
        });
        return  promise2;
    }
    race(promises) {
        return new Promise((resolve, reject) => {
            for(let i = 0; i < promises.length; i++) {
                promises[i].then(resolve, reject);
            }
        })
    }
    all(promises) {
        let arr = [];
        let i = 0;
        function processData(index, data) {
            arr[index] = data;
            i++;
            if(i === promises.length) {
                resolve(data);
            }
        }
        return new Promise((resolve, reject) => {
            for(let i = 0; i < promises.length; i++) {
                promises[i].then((val) => {
                    processData(i, val);
                }, reject)
            }
        })
    }
    resolve(val) {
        return new Promise((resovle, reject) => {
            resovle(val);
        })
    }
    reject(val) {
        return new Promise((resovle, reject) => {
            reject(val);
        })
    }
}
// 這邊要處理的狀況有如下幾種:一、循環引用, 二、thenable對象,三、promise對象
resolvePromise(promise2, x, reslove, reject) {
    if(promise2 === x) {
        reject('循環引用');
        return;
    }
    // 防止屢次調用
    let called;
    // 檢測x的值的類型,若是不是對象或者函數,直接返回resolve
    if(x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 規範中then邏輯報錯也會進入catch
        try {
            if(called) return;
            let then = x.then; 
            if(typeof then === 'function') {
                then.call(x, (y) => {
                    if(called) return;
                    called = true;
                    resolvePromise(promise2, y ,reslove, reject)
                }, (err) => {
                    if(called) return;
                    reject(err);
                    called = true;
                })
            } else {
                resolve(x);
            }
        } catch(err) {
            if(called) return;
            reject(err);
            called = true;
        }
    } else {
        resolve(x);
    }
}
複製代碼

實現一個寄生組合繼承

寄生組合繼承其實須要注意的是子構造函數constructor的指向問題。以及繼承的弊病:超集會調用兩次。

參考答案
function Super() {}
function Sub() {
    Super.call(this)
}
Sub.prototype = new Super();
Sub.constructor = Sub;
複製代碼

業務題實現

如何實現一個防抖函數

對於防抖的理解,最好結合業務場景記憶:防抖通常用於輸入框場景。因此在實現層面會有如下兩個方面:

一、當必定時間內事件再次觸發時,定時器應該重置。

二、執行完畢後定時器重置。

參考答案
function debounce(cb, delay, ...args) {
    let time = null;
    return function() {
        if(time) {
            clearTimeout(time);
        }
        time = setTimeout(() => {
            cb.apply(this, ...args);
            clearTimeout(time);
        }, delay);
    }
}
複製代碼

如何實現一個節流函數

對於節流函數,也須要結合場景來記憶。通常用於滾動事件中,必定時間內只會觸發一次。實現層面也會有兩個須要注意的點:

一、用一個鎖變量來保證必定時間內只會觸發一次。

二、執行完畢以後,解開鎖便可

參考答案
function tr(fn, time, ...args) {
    let lock = false;
    return  function() {
        if(lock) return;
        lock = true;
        setTimeout(() => {
            fn.apply(this, ...args);
            lock = false;
        }, time)
    }
}
複製代碼

實現一箇中劃線與駝峯的互相轉換

這個其實主要考的是正則和replace方法。

參考答案
function camelize(str) {
    return (str + '').replace(/-\D/g, function(match) {
        return match.charAt(1).toUpperCase()
    })
}
複製代碼
function hyphenate(str) {
    return (str + '').replace(/[A-Z]/g, function(match) {
        return '-' + match.toLowerCase();
    })
}
複製代碼

實現一個sleep函數

sleep函數實現的途徑有不少,promise,async/await等等。我在這裏就將一些最普通的。

參考答案
function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(true);
        }, time)
    })
}
複製代碼

實現一個柯里化函數

實現柯里化其實就是把多個參數長度很分開來調用的意思,好處在於能夠觀測你參數調用的一箇中間的過程,或者中間的變量。面試中常考的add(1, 2, 3)和add(1)(2)(3)就是這個問題

參考答案
function curry(fn) {
    const finalLen = fn.length
    let args = [].slice.call(this,1)
    return function currying () {
    args = args.concat(Array.from(arguments))
    const len = args.length
    return len >= fn.length ? fn.apply(this, args) : currying
    }
}

複製代碼

實現一個ajax

實現一個ajax其實主要是一個XMLHttpRequest對象以及其API方法的一個使用的問題。而在這裏我建議儘可能封裝成promise的形式,方便使用。

參考答案
function ajax({url, methods, body, headers}) {
    return new Promise((resolve, reject) => {
        let request = new XMLHttpRequest();
        request.open(url, methods);
        for(let key in headers) {
            let value = headers[key]
            request.setRequestHeader(key, value);
        }
        request.onreadystatechange = () => {
            if(request.readyState === 4) {
                if(request.status >= '200' && request.status < 300) {
                    resolve(request.responeText);
                } else {
                    reject(request)
                }
            }
        }
        request.send(body)
    })
}
複製代碼

實現一個深拷貝

深拷貝也是面試中的一個高頻考點,通常的方法,JSON的序列化和反序列化,可是這種方法的弊病有兩個:

一、undefined、null和symbol類型的值會被刪除

二、遇見循環引用的時候會報錯。

那麼咱們在實現深拷貝的時候,也要時刻關注循環引用這個問題。

下列方法中,我主要是經過數組的形式去解決循環引用的問題。那爲何要有兩個數組呢?

主要是一個數組維護的是原來對象的引用,一個數組維護的是新對象的引用。

參考答案
function deepClone(obj) {
    const parents = [];
    const children = [];
    function helper(obj) {
        if(obj === null) return null;
        if(typeof obj !== 'object') return obj;
        let child, proto;
        if(Array.isArray(obj)) {
            child = [];
        } else {
            proto = Object.getPrototypeOf(obj);
            child = Object.create(proto);
        }
        // 處理循環引用
        let index = parents.indexOf(obj)
        if(index === -1) {
            parents.push(obj);
            children.push(child)
        } else {
            return children[index];
        }
        // for in迭代
        for(let i in obj) {
            child[i] = helper(obj[i]);
        }
    }
}
複製代碼
相關文章
相關標籤/搜索