2019年几道常见js手写面试题总结

最近出去面了几家试试水,也在整理一些面试题。我已经总结在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('输出完毕')
});

执行结果

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);

执行结果

  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()

这个面试题综合了原型,工厂模式,异步队列,闭包知识。含金量很高呦

你可能感兴趣的:(javascript,面试)