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

35次阅读

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

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

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

正文完
 0