# 手写常见的JS面试题 1-10
# 1、寄生组合式继承
function Parent(name) {
this.name = name;
this.say = () => {
console.log(111);
}
}
Parent.prototype.play = () => {
console.log(222);
}
function Children(name) {
Parent.call(this);
this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
# 2、compose
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function fn4(x) {
return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1));
实现代码如下
function compose() {
const arr = [...arguments];
return function (x) {
return arr.reduceRight((pre, cur) => {
return pre += cur(pre);
}, x)
}
}
# 3、setTimeout模拟实现setInterval(带定时清除);
setInterval的缺点:
- 使用 setInterval 时,某些间隔会被跳过;(函数操作耗时过长导致的不准确)
- 可能多个定时器会连续执行;
可以这么理解:每个 setTimeout 产生的任务会直接 push 到任务队列中; 而 setInterval 在每次把任务 push 到任务队列前, 都要进行一下判断(看上次的任务是否仍在队列中,如果有则不添加,没有则添加)。
function mySetTimeout(fn, delay) {
this.timer = null;
function interval() {
timer = setTimeout(fn, delay);
interval();
}
interval();
return {
calcel: () => clearTimeout(timer)
}
}
# 4、发布订阅模式
题目描述: 实现一个发布订阅模式拥有, 书写一下vue的bus管线
class EventEmitter {
constructor() {
// handlers是一个map,用于存储事件与回调之间的对应关系
this.handlers = {}
}
// on方法用于安装事件监听器,它接受目标事件名和回调函数作为参数
on(eventName, cb) {
// 先检查一下目标事件名有没有对应的监听函数队列
if (!this.handlers[eventName]) {
// 如果没有,那么首先初始化一个监听函数队列
this.handlers[eventName] = []
}
// 把回调函数推入目标事件的监听函数队列里去
this.handlers[eventName].push(cb);
}
// emit方法用于触发目标事件,它接受事件名和监听函数入参作为参数
emit(eventName, ...args) {
// 检查目标事件是否有监听函数队列
if (this.handlers[eventName]) {
// 这里需要对 this.handlers[eventName] 做一次浅拷贝,主要目的是为了避免通过 once 安装的监听器在移除的过程中出现顺序问题
const handlers = this.handlers[eventName].slice()
// 如果有,则逐个调用队列里的回调函数
handlers.forEach((callback) => {
callback(...args)
})
}
}
// 移除某个事件回调队列里的指定回调函数
off(eventName, cb) {
const callbacks = this.handlers[eventName]
const index = callbacks.indexOf(cb)
if (index !== -1) {
callbacks.splice(index, 1)
}
}
// 为事件注册单次监听器
once(eventName, cb) {
// 对回调函数进行包装,使其执行完毕自动被移除
const wrapper = (...args) => {
cb(...args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}
// 使用如下
// const event = new EventEmitter();
// const handle = (...rest) => {
// console.log(rest);
// };
// event.on("click", handle);
// event.emit("click", 1, 2, 3, 4);
// event.off("click", handle);
// event.emit("click", 1, 2);
// event.once("dbClick", () => {
// console.log(123456);
// });
// event.emit("dbClick");
// event.emit("dbClick");
# 5、数组去重
function uniqueArr(arr) {
return [...new Set(arr)]; // 简单类型
}
function uniqueFunc(arr, uniId) {
const res = new Map(); // 对象去重
return arr.filter((item) => !res.has(item[uniId]) && res.set(item[uniId], 1));
}
# 6、数组扁平化
题目描述:实现一个方法使多维数组变成一维数组
// 最常见的递归版本如下:
function flatter(arr) {
if (!arr.length) return;
return;
arr.reduce((pre, cur) => {
return Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur];
}, []);
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));
// 迭代的方法
function flatter(arr) {
if (!arr.length) return;
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));
# 7、实现并行限制的Promise调度器。
题目描述:JS实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个。
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
的输出顺序是:2 3 1 4
整个的完整执行流程:
一开始1、2两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
class Scheduler {
constructor(limit) {
this.queue = [];
this.limit = limit;
this.runCounts = 0;
}
add(delay, order) {
const fun = () => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(order);
resolve(order);
}, delay)
})
}
this.queue.push(fun);
}
start() {
for (let i = 0; i < this.limit; i++) {
this.request();
}
}
request() {
if (!this.queue.length || this.runCounts >= this.limit) return;
this.runCounts++;
this.queue
.shift()()
.then(() => {
this.runCounts--;
this.request();
}
);
}
}
const scheduler = new Scheduler(2);
const addTask = (delay, order) => {
scheduler.add(delay, order);
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
scheduler.start();
# 8、new操作符
function _new(fn, ...arg) {
const obj = Object.create(fn.prototype);
const result = fn.apply(obj, arg);
return result instanceof Object ? result : obj;
}
# 9、call、apply、bind
Function.prototype.call = function (context, ...arg) {
context = context || window;
// 创造唯一的key值 作为我们构造的context内部方法名
const fn = Symbol();
context[fn] = this; // this指向调用call的函数
// 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了
return context[fn](...arg);
}
// apply原理一致 只是第二个参数是传入的数组
Function.prototype.apply = function (context, arg) {
context = context || window;
const fn = Symbol();
context[fn] = this;
return context[fn](...arg)
}
Function.prototype.bind = function (context, ...arg) {
const self = this;
function Fn() {
return self.apply(this instanceof Fn ? this : context, [...arguments].concat(arg));
}
Fn.prototype = Object.create(this.prototype);
return Fn;
}
// 定义 Function.prototype.bind2 方法
Function.prototype.bind2 = function (context) {
// 检查当前对象是否为函数
if (typeof this !== "function") {
// 如果不是函数,则抛出错误
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
// 保存原始函数
var self = this;
// 收集除了第一个参数(context)之外的所有参数
var args = Array.prototype.slice.call(arguments, 1);
// 创建一个空函数,用于继承原始函数的原型属性
var fNOP = function () {};
// 创建绑定函数
var fBound = function () {
// 收集传递给 fBound 的所有参数
var bindArgs = Array.prototype.slice.call(arguments);
// 判断 fBound 是否作为构造函数被调用
// 如果是构造函数调用,则 this 为新创建的对象
// 如果不是构造函数调用,则 this 为 context
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
// 设置 fNOP 的原型为原始函数的原型
fNOP.prototype = this.prototype;
// 设置 fBound 的原型为 fNOP 的一个新实例
// 这样当 fBound 作为构造函数被调用时,创建的对象具有正确的原型链
fBound.prototype = new fNOP();
// 返回绑定函数
return fBound;
}
# 10、深拷贝(考虑到复制Symbol)
function isObject(val) {
return typeof val === "object" && val !== null;
}
function deepClone(obj, hash = new WeakMap()) {
if (!isObject(obj)) return obj;
if (hash.has(obj)) {
return hash.get(obj);
}
let target = Array.isArray(obj) ? [] : {};
hash.set(obj, target);
Reflect.ownKeys(obj).forEach((item) => {
if (isObject(obj[item])) {
target[item] = deepClone(obj[item], hash);
} else {
target[item] = obj[item];
}
});
return target;
}
// var obj1 = {
// a:1,
// b:{a:2}
// };
// var obj2 = deepClone(obj1);
// console.log(obj1);