共计 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);
执行结果
- 用 … 的时候是解构出来的是剩下的所有属性
- 解构是浅拷贝!!!!!
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
实现思路
- LazyMan()不是 new 出来的,需要在其内部封装一下 return new _lazyman,_lazyman 等同于构造函数,这样我执行一次 LazyMan(),就会创建一个对象,是不是有点工厂模式的感觉
- 内部用 tasks 数组存储所有任务
- next()用于执行 tasks 数组中第一任务,并将其从 tasks 数组中删除
- sleepFirst()方法,内部将创建的闭包函数,将创建的 sleepFirst 任务加入 tasks 数组第一个
- eat,sleep,sleepFirst 内部是用闭包执行,这样就能保留传入的参数,待后续 tasks 取出任务执行
- 重点:
_lazyman()
中的setTimeout(function () {that.next() }, 0)
最后执行时,tasks 里按执行顺序存放所有任务,是不是很巧妙,并且每个任务都会执行that.next()
这个面试题综合了原型,工厂模式,异步队列,闭包知识。含金量很高呦
正文完
发表至: javascript
2019-11-13