2019年幾道常見js面試題整理

花間酒發表於2019-11-11

最近出去面了幾家試試水,也在整理一些面試題。我已經總結在gitbook/github裡了,主要作用就是總結和分享一下自己的心得體會,現在每天還在持續更新中,歡迎大家star,有問題請隨時提issue

github地址

gitbook地址

1. 手寫new操作符

function newClass(obj, args) {
    let newObj = {};
    newObj.__proto__ = obj.prototype
    obj.call(newObj, args);
    return newObj
}

function a(text) {
    this.text = text;
}

let b = newClass(a, 'test');
console.log(b) // {text: "test"}
複製程式碼

2. 手寫防抖/節流

// 防抖
function debounceHandle(fn) {
    let timer = null;
    return function () {
        clearTimeout(timer);
        timer = setTimeout(function () {
            fn.call(this, arguments);
        }, 300)
    }
}
複製程式碼
// 節流
function throttle(fn, delay) {     
    var timer = null;     
    var lastTime = Date.now();     
    return function() {             
        var curTime = Date.now();
        var interval = delay - (curTime - lastTime);  // 計算間隔             
        var context = this;             
        var args = arguments;             
        clearTimeout(timer);              
        if (interval <= 0) {              
            fn.apply(context, args);                    
            startTime = Date.now();              
        } else {                    
            timer = setTimeout(fn, interval);              
        }
    }
}
複製程式碼

3. 手寫promise

首先明確三種狀態

  • pending - 進行中
  • fulfilled - 成功
  • rejected - 失敗
function NewPromise(executor) {
    let _this = this;
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFunc = [];//儲存成功回撥
    this.onRejectedFunc = [];//儲存失敗回撥

    executor(resolve, reject);

    function resolve(value) {
        if (_this.state === 'pending') {
            _this.value = value;
            //依次執行成功回撥
            _this.onFulfilledFunc.forEach(fn => fn(value));
            _this.state = 'fulfilled';
        }
    }

    function reject(reason) {
        if (_this.state === 'pending') {
            _this.reason = reason;
            //依次執行失敗回撥
            _this.onRejectedFunc.forEach(fn => fn(reason));
            _this.state = 'rejected';
        }
    }
}

NewPromise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;
    if (self.state === 'pending') {
        if (typeof onFulfilled === 'function') {
            return new NewPromise((resolve, reject) => {
                self.onFulfilledFunc.push(() => {
                    let x = onFulfilled(self.value);
                    if (x instanceof Promise) {
                        x.then(resolve, reject)
                    } else {
                        resolve(x)
                    }
                });
            })
        }
        if (typeof onRejected === 'function') {
            return new NewPromise((resolve, reject) => {
                self.onRejectedFunc.push(() => {
                    let x = onRejected(self.value);
                    if (x instanceof Promise) {
                        x.then(resolve, reject)
                    } else {
                        resolve(x)
                    }
                });
            })
        }
    }
    if (self.state === 'fulfilled') {
        if (typeof onFulfilled === 'function') {
            return new NewPromise((resolve, reject) => {
                let x = onFulfilled(self.value);
                if (x instanceof Promise) {
                    x.then(resolve, reject)
                } else {
                    resolve(x)
                }
            })
        }

    }
    if (self.state === 'rejected') {
        if (typeof onRejected === 'function') {
            return new NewPromise((resolve, reject) => {
                let x = onRejected(self.reason);
                if (x instanceof Promise) {
                    x.then(resolve, reject)
                } else {
                    resolve(x)
                }
            })
        }
    }
};

let p = new NewPromise((resolve, reject) => {
    console.log(1) // 輸出 1
    resolve(2);
});

p.then(x => {
    console.log(x); // 輸出 2
    return 3
}).then(x => {
    console.log(x); // 輸出3
    return 4;
}).then(x => {
    console.log(x) // 輸出4
    console.log('輸出完畢')
});
複製程式碼

執行結果

2019年幾道常見js面試題整理

4. 箭頭函式

ES6新增箭頭函式,總結起來有如下幾個注意點

this指向

let obj = {
    name: 'ronaldo',
    getName: function () {
        return this.name;
    },
    getName2: () => {
        return this.name;
    }
}

console.log(obj.getName())  // 輸出ronaldo
console.log(obj.getName2()) // 輸出空,此時this等於window
複製程式碼

無法當建構函式

var Person = (name, age) => {
    this.name = name;
    this.age = age;
}
var p = new Person('messi', 18); // Uncaught TypeError: Person is not a constructor
複製程式碼

arguments引數無法獲取當前傳入的引數

let func1 = function () {
    console.log(arguments);
}

let func2 = () => {
    console.log(arguments);
}
func1(1, 2, 3);  // Arguments(3) [1, 2, 3] 引數有1,2,3
func2(1, 2, 3);  //Arguments() [] 無引數
複製程式碼

但是可以通過剩餘引數來獲取箭頭函式傳入引數

let func1 = function () {
    console.log(arguments);
}

let func2 = (...args) => {
    console.log(args);
}
func1(1, 2, 3);  // Arguments(3) [1, 2, 3] 引數有1,2,3
func2(1, 2, 3);  // [1,2,3] 注:純陣列,不再是Arguments物件
複製程式碼

5. 解構

個人理解,解構就是ES6新增對陣列和物件實現分離內部元素/屬性對快速操作

解構還是很好理解的,下面一段程式碼理解了就足夠了

let obj = { d: 'aaaa', e: { f: 'bbbb' }, g: 100 };
let { d, ...a } = obj;
console.log(d);
console.log(a);
a.e.f = 'cccc';
console.log(a);
console.log(obj);
複製程式碼

執行結果

2019年幾道常見js面試題整理

  1. 用...的時候是解構出來的是剩下的所有屬性
  2. 解構是淺拷貝!!!!!

6. 手寫EventBus

function EventBusClass() {
    this.msgQueues = {}
}

EventBusClass.prototype = {
    // 將訊息儲存到當前的訊息佇列中
    on: function (msgName, func) {
        if (this.msgQueues.hasOwnProperty(msgName)) {
            if (typeof this.msgQueues[msgName] === 'function') {
                this.msgQueues[msgName] = [this.msgQueues[msgName], func]
            } else {
                this.msgQueues[msgName] = [...this.msgQueues[msgName], func]
            }
        } else {
            this.msgQueues[msgName] = func;
        }
    },
    // 訊息佇列中僅儲存一個訊息
    one: function (msgName, func) {
        // 無需檢查msgName是否存在
        this.msgQueues[msgName] = func;
    },
    // 傳送訊息
    emit: function (msgName, msg) {
        if (!this.msgQueues.hasOwnProperty(msgName)) {
            return
        }
        if (typeof this.msgQueues[msgName] === 'function') {
            this.msgQueues[msgName](msg)
        } else {
            this.msgQueues[msgName].map((fn) => {
                fn(msg)
            })
        }
    },
    // 移除訊息
    off: function (msgName) {
        if (!this.msgQueues.hasOwnProperty(msgName)) {
            return
        }
        delete this.msgQueues[msgName]
    }
}

// 將EventBus放到window物件中
const EventBus = new EventBusClass()
EventBus.on('first-event', function (msg) {
    console.log(`訂閱的訊息是:${msg}`);
});
EventBus.emit('first-event', 123213)

// 輸出結果
// 訂閱的訊息是:123213

複製程式碼

7. 手寫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
 
以此類推。
複製程式碼

function _lazyman(name) {
    this.tasks = [];
    var that = this;
    var fn = (function (name) {
        return function () {
            console.log("Hello I'm " + name);
            that.next();
        }
    })(name);

    this.tasks.push(fn);

    setTimeout(function () { that.next() }, 0) // setTimeout延遲0ms也未必是立刻執行哦
}

_lazyman.prototype = {
    constructor: _lazyman,

    //next是實現函式在佇列中順序執行功能的函式

    next: function () {
        var fn = this.tasks.shift();
        fn && fn();
    },

    sleep: function (time) {
        var that = this;
        var fn = (function (time) {
            return function () {
                console.log("sleep......." + time);
                setTimeout(function () {
                    that.next();
                }, time)
            }
        })(time);
        this.tasks.push(fn);

        return this; //return this是為了實現鏈式呼叫
    },

    sleepFirst: function (time) {
        var that = this;
        var fn = (function (time) {
            return function () {
                console.log("sleep......." + time);
                setTimeout(function () {
                    that.next();
                }, time)
            }
        })(time);
        this.tasks.unshift(fn);
        return this;
    },

    eat: function (something) {
        var that = this;
        var fn = (function (something) {
            return function () {
                console.log("Eat " + something);
                that.next();
            }
        })(something)
        this.tasks.push(fn);
        return this;
    }
}
function LazyMan(name) {
    return new _lazyman(name);
}
LazyMan("Joe").sleepFirst(3000).eat("breakfast").sleep(1000).eat("dinner");
// LazyMan('Hank').sleepFirst(5).eat('supper')
// sleep.......3000
// Hello I'm Joe
// Eat breakfast
// sleep.......1000
// Eat dinner
複製程式碼

實現思路

  1. LazyMan()不是new出來的,需要在其內部封裝一下return new _lazyman,_lazyman等同於建構函式,這樣我執行一次LazyMan(),就會建立一個物件,是不是有點工廠模式的感覺
  2. 內部用tasks陣列儲存所有任務
  3. next()用於執行tasks陣列中第一任務,並將其從tasks陣列中刪除
  4. sleepFirst()方法,內部將建立的閉包函式,將建立的sleepFirst任務加入tasks陣列第一個
  5. eat,sleep,sleepFirst內部是用閉包執行,這樣就能保留傳入的引數,待後續tasks取出任務執行
  6. 重點:_lazyman()中的 setTimeout(function () { that.next() }, 0)最後執行時,tasks裡按執行順序存放所有任務,是不是很巧妙,並且每個任務都會執行that.next()

這個面試題綜合了原型,工廠模式,非同步佇列,閉包知識。含金量很高呦

相關文章