daisy手写面试题汇总

163 阅读10分钟

一、javascript基础

1. lodash.get 参考 lodash中文文档

var object = { 'a': [{ 'b': { 'c': 3 } }] };

get(object, 'a[0].b.c'); // => 3

get(object, ['a', '0', 'b', 'c']); // => 3

get(object, 'a.b.c', 'default'); // => 'default'

解析:这里可以预先处理,先把所有输入的 path路径,统一转成 ['a', '0', 'b', 'c'] 形式的数组,然后再迭代取值,

get实现如下:

/**
* object: 对象
* path: 输入的路径
* defaultVal: 默认值
**/
 
function get(object, path, defaultVal='undefined') {
    // 先将path处理成统一格式
    let newPath = [];
    if (Array.isArray(path)) {
        newPath = path;
    } else {
        // 先将字符串中的'['、']'去除替换为'.',split分割成数组形式
        newPath = path.replace(/\[/g,'.').replace(/\]/g,'').split('.');
    }
 
    // 递归处理,返回最后结果
    return newPath.reduce((o, k) => {
        console.log(o, k); // 此处o初始值为下边传入的 object,后续值为每次取的内部值
        return (o || {})[k]
    }, object) || defaultVal;   
}

传送门

2. 实现一个对象的 flatten 方法

题目描述:

const obj = {
 a: {
        b: 1,
        c: 2,
        d: {e: 5}
    },
 b: [1, 3, {a: 2, b: 3}],
 c: 3
}

flatten(obj) 结果返回如下
// {
//  'a.b': 1,
//  'a.c': 2,
//  'a.d.e': 5,
//  'b[0]': 1,
//  'b[1]': 3,
//  'b[2].a': 2,
//  'b[2].b': 3
//   c: 3
// }

代码实现

function isObject(val) {
  return typeof val === "object" && val !== null;
}

function flatten(obj) {
  if (!isObject(obj)) {
    return;
  }
  let res = {};
  const dfs = (cur, prefix) => {
    if (isObject(cur)) {
      if (Array.isArray(cur)) {
        cur.forEach((item, index) => {
          dfs(item, `${prefix}[${index}]`);
        });
      } else {
        for (let k in cur) {
          dfs(cur[k], `${prefix}${prefix ? "." : ""}${k}`);
        }
      }
    } else {
      res[prefix] = cur;
    }
  };
  dfs(obj, "");

  return res;
}
flatten();

3. 手写数组的 flat

    const flat = function (arr, deep = 1) {
      // 声明一个新数组
      let result = []
      
      arr.forEach(item => {
        if (Array.isArray(item) && deep > 0) {
          // 层级递减
          // deep--  来自评论区的大佬指正:deep - 1
          // 使用concat链接数组  
          result = result.concat(flat(item, deep - 1))
        } else {
          result.push(item)
        }
      })
      return result
    }

4. 实现 reduce

Array.prototype.reduce = function(func, init = '') {
let result = init;
this.forEach((item, index) => {
result = func(result, item, this);
});
return result;
};

5. 闭包题

  • 函数的上级作用域在哪里创建创建的,上级作用域就是谁
var a = 10
function foo(){
    console.log(a)
}

function sum() {
    var a = 20
    foo()
}

sum()
/* 输出
    10
/

6. 深拷贝

const isObj = (val) => typeof val === "object" && val !== null;

// 写法1
function deepClone(obj) {
    // 通过 instanceof 去判断你要拷贝的变量它是否是数组(如果不是数组则对象)。

    // 1. 准备你想返回的变量(新地址)。
    const newObj = obj instanceof Array ? [] : {}; // 核心代码。

    // 2. 做拷贝;简单数据类型只需要赋值,如果遇到复杂数据类型就再次进入进行深拷贝,直到所找到的数据为简单数据类型为止。
    for (const key in obj) {
        const item = obj[key];
        newObj[key] = isObj(item) ? deepClone(item) : item;
    }

    // 3. 返回拷贝的变量。
    return newObj;
}




// 写法2 利用es6新特性 WeakMap弱引用 性能更好 并且支持 Symbol
function deepClone2(obj, wMap = new WeakMap()) {
  if (isObj(obj)) {
    // 判断是对象还是数组
    let target = Array.isArray(obj) ? [] : {};

    // 如果存在这个就直接返回
    if (wMap.has(obj)) {
      return wMap.get(obj);
    }

    wMap.set(obj, target);

    // 遍历对象
    Reflect.ownKeys(obj).forEach((item) => {
      // 拿到数据后判断是复杂数据还是简单数据 如果是复杂数据类型就继续递归调用
      target[item] = isObj(obj[item]) ? deepClone2(obj[item], wMap) : obj[item];
    });

    return target;
  } else {
    return obj;
  }
}

7. Promise相关方法

Promise.all、Promise.race

8. 实现有并行限制的 Promise 调度器

题目描述:JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个

 addTask(1000,"1");
 addTask(500,"2");
 addTask(300,"3");
 addTask(400,"4");
 的输出顺序是:2 3 1 4

 整个的完整执行流程:

一开始12两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4


代码实现

class Scheduler {
  constructor(limit) {
    this.queue = [];
    this.maxCount = limit;
    this.runCounts = 0;
  }
  add(time, order) {
    const promiseCreator = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(order);
          resolve();
        }, time);
      });
    };
    this.queue.push(promiseCreator);
  }
  taskStart() {
    for (let i = 0; i < this.maxCount; i++) {
      this.request();
    }
  }
  request() {
    if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
      return;
    }
    this.runCounts++;
    this.queue
      .shift()()
      .then(() => {
        this.runCounts--;
        this.request();
      });
  }
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
  scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();

解法二·

// 实现如下
class Scheduler {
  constructor () {
    this.tasks = [] // 任务缓冲队列
    this.runningTask = [] // 任务队列
  }

  // promiseCreator 是一个异步函数,return Promise
  add (promiseCreator) {
    return new Promise((resolve, reject) => {
      promiseCreator.resolve = resolve
      if (this.runningTask.length < 2) {
        this.run(promiseCreator)
      } else {
        this.tasks.push(promiseCreator)
      }
    })
  }

  run (promiseCreator) {
    this.runningTask.push(promiseCreator)
    promiseCreator().then(() => {
      promiseCreator.resolve()
      // 删除运行完的任务
      this.runningTask.splice(this.runningTask.findIndex(promiseCreator), 1)
      if (this.tasks.length > 0) {
        this.run(this.tasks.shift())
      }
    })
  }
}

const promiseAll = (promises: (() => Promise<void>)[], limit = 100): Promise<Awaited<any>[]> => new Promise((resolve, reject) => {
    let total = promises.length;
    if (total === 0) {
        resolve([]);
        return;
    }
    let done = 0;
    let cur = 0;
    let results: any = [];
    const run = (index: number) => {
        const nextPromise = promises[cur++];
        if (nextPromise != null) {
            nextPromise().then((res: any) => {
                results[index] = res;
                if (++done >= total) {
                    resolve(results);
                    return;
                }
                run(cur);
            }).catch((err) => {
                reject(err);
            });
        }
    };
    for (let i = 0; i < limit; i++) {
        run(i);
    }
});

11. proxy

1.  var obj = new Proxy({}, {`
1.  `get: function (target, propKey, receiver) {`
1.  ``console.log(`getting ${propKey}!`);``
1.  `return Reflect.get(target, propKey, receiver);`
1.  `},`
1.  `set: function (target, propKey, value, receiver) {`
1.  ``console.log(`setting ${propKey}!`);``
1.  `return Reflect.set(target, propKey, value, receiver);`
1.  `}`
1.  `});`

12. 实现数据双向绑定

let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
  configurable: true,
  enumerable: true,
  get() {
    console.log('获取数据了')
  },
  set(newVal) {
    console.log('数据更新了')
    input.value = newVal
    span.innerHTML = newVal
  }
})
// 输入监听
input.addEventListener('keyup', function(e) {
  obj.text = e.target.value
})

14. compose

题目描述:实现一个 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)); // 1+4+3+2+1=11

代码实现

function compose(...fn) {
  if (!fn.length) return (v) => v;
  if (fn.length === 1) return fn[0];
  return fn.reduce(
    (pre, cur) =>
      (...args) =>
        pre(cur(...args))
  );
}



function compose(...funcs) {
    return function (input) {
        return funcs.reverse().reduce((result, next) => next.call(this, result), input)
    }
}



手写中间件 compose,并支持异步函数

// 文件:app.js
app.compose = function(...fn) {
    return Promise.resolve(
        fn.reduce((a, b) => arg =>
            Promise.resolve(a(() => b(arg)))
        )(() => Promise.resolve())
    );
};

// 2
function middleware(...funcs) {
    return funcs.reverse().reduce((result, next) => (arg) => Promise.resolve(next.call(this, arg, result)), () => Promise.resolve())
}


\

中间件compose

function middleware(...funcs) {
    return funcs.reverse().reduce((result, next) => (arg) => next.call(this, arg, result), () => { })
}

const fn1 = (data, next) => {
    console.log('enter 1')
    data.step1 = 'step1'
    next(data)
    console.log(data)
    console.log('exit 1')
}
const fn2 = (data,next) => {
    console.log('enter 2')
    data.step2 = 'step2'
    next(data)
    console.log('exit 2')
}

const fn3 = (data,next) => {
    console.log('enter 3')
    data.step3 = 'step3'
    next()
    console.log('exit 3')
}

middleware(fn1,fn2,fn3)
// koa有个特点,调用next参数表示调用下一个函数
function fn1(next) {
    console.log(1);
    next();
}

function fn2(next) {
    console.log(2);
    next();
}

function fn3(next) {
    console.log(3);
    next();
}

middleware = [fn1, fn2, fn3]

function compose(middleware){
   function dispatch (index){
        if(index == middleware.length) return ;
        var curr;
        curr = middleware[index];
       // 这里使用箭头函数,让函数延迟执行
        return curr(() => dispatch(++index))
  }
  dispatch(0)
};

compose(middleware);

16.事件处理

(1)eventEmitter,事件监听处理器,包括on()、off()、once()、emit()方法

events 是一个对象,用来存储事件名以及对应的回调函数;return this 返回该实例,可以链式调用。

class EventEmitter{
    constructor(){
        this._events={}
    }
    on(event,callback){
        let callbacks = this._events[event] || []
        
        this._events[event] = callbacks.push(callback);
        return this
    }
    off(event,callback){
        let callbacks = this._events[event]
        this._events[event] =  callbacks && callbacks.filter(function(fn){
               return fn !== callback;
        })
        return this
    }
    emit(eventName,...args) {
         const callbacks = this._events[eventName]
         callbacks.map(cb => {
              cb(...args)
         })
         return this;
    }
    once(event,callback){
        let wrap = (...args) => {
            callback.apply(this, args)
            this.off(event, wrap)
        }
        this.on(event, wrap )
        return this
    }
}

(2)EventBus

class EventBus {
  constructor() {
    this._events = {};
  }

  on(event, fn) {
    if (Array.isArray(event)) {
      event.map((item) => this.on(item, fn));
    } else {
      this._events[event] = this._events[event] || [];
      this._events[event].push(fn);
    }
    return this;
  }
  off(event, fn) {
    if (arguments.length == 0) {
      this._events = {};
      return;
    }

    if (Array.isArray(event)) {
      event.map((item) => this.off(item, fn));
    } else {
      const cbs = this._events[event];
      if (!cbs) return this;
      if (!fn) {
        this._events[event] = null;
        return this;
      }

      let cb,
        i = cbs.length;

      while (i--) {
        cb = cbs[i];
        if (cb === fn || cb.fn === fn) {
          cbs.splice(i, 1);
          break;
        }
      }
      return this;
    }
  }

  once(event, fn) {
    let on = () => {
      this.off(event, on);
      fn.apply(this, arguments);
    };
    on.fn = fn;
    this.on(event, on);
    return this;
  }

  emit(event) {
    let cbs = this._events[event];
    if (cbs) {
      const args = [].slice.call(arguments, 1);
      cbs.map((item) => {
        args ? item.apply(this, args) : item.call(this);
      });
    }
    return this;
  }
}

(3)订阅发布模式

// 订阅发布模式
class Event {
	constructor(){
  	this.events = {}
  }
  on(name, event){
  	let list = this.events[name]||[]
    list.push(event)
    this.events[name] = list
  }
  emit(name, ...arg){
  	const list = this.events[name]||[]
    list.forEach(item=>{
    	item(...arg)
    })
  }
  off(name, event){
    const list = this.events[name]
    if(list && list.length>0){
   		this.events[name] = list.filter(item=>item!=event)
    }
  }
 	once(name, event){
  	const fn = (...arg)=>{
    	event(...arg)
      this.off(name, fn)
    }
    this.on(name, fn)
  }
}

const A = new Event()
const B = {
	update(data){
  	console.log('收到A的消息啦', data)
  }
}

A.on('update', B.update)
A.emit('update', 'send-info')


// 观察者模式

class Dep {
  constructor(){
  	this.list = []
  }
	on(watcher){
    this.list.push(watcher)
  }
  emit(data){
  	this.list.forEach(item=>{
    	item.update(data)
    })
  }
  off(watcher){
  	this.list = this.list.filter(item=>item!=watcher)
  }
}

class Watcher{
	update(){}
  add(dep){
  	dep.on(this)
  }
}

const A = new Dep();
const B = new Watcher();
B.add(A);
B.update = function(data){
	console.log('收到A的消息啦', data)
}
A.emit('广播啦')

17. nextTick

juejin.cn/post/706970…

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => { // 将拿到的回调函数存放到数组中
    if (cb) {
      try { // 错误捕获
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) { // 如果当前没有在pending的时候,就会执行timeFunc
    pending = true
    timerFunc() // 多次执行nextTick只会执行一次,timerFunc就是一个异步方法
  }
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
// 

数据处理

1. js对象树形结构

(1)对象树形结构根据id返回路径

有一个对象表示的树形结构,大致如下,实际深度不止 2 层 // 实现一个 getPathByNodeId 的方法,传入节点的 id 值,返回节点的路径

const root = {
id: "root",
nodes: [
{
id: "node-123", // path 1 / [1]
nodes: [
{
id: "node-234" // path 1-1 / [1, 1]
} ]
},
{
id: "node-345", // path 2 ...
nodes: [
{
id: "node-456", // path 2-1
} ] } ] }
// getPathByNodeId(root, 'node-456’ ) => 2-1 或 [2, 1]

代码实现

let path = [];
const getPathByNodeId = (root, id) => {
    path.push(root.id);
    if (root.id === id) {
    return path;
    }
    let result = null;
    (root.nodes || []).forEach((node) => {
    const nodePathResult = getPathByNodeId(node, id);
    if (nodePathResult) {
    result = nodePathResult;
    }
    });
    if (!result) {
        path = [];
    }
    return result;
};

(2)将js对象转化为树形结构

// 转换前:
source = [{
            id: 1,
            pid: 0,
            name: 'body'
          }, {
            id: 2,
            pid: 1,
            name: 'title'
          }, {
            id: 3,
            pid: 2,
            name: 'div'
          }]
// 转换为: 
tree = [{
          id: 1,
          pid: 0,
          name: 'body',
          children: [{
            id: 2,
            pid: 1,
            name: 'title',
            children: [{
              id: 3,
              pid: 1,
              name: 'div'
            }]
          }
        }]

代码实现

function jsonToTree(data) {
  // 初始化结果数组,并判断输入数据的格式
  let result = []
  if(!Array.isArray(data)) {
    return result
  }
  // 使用map,将当前对象的id与当前对象对应存储起来
  let map = {};
  data.forEach(item => {
    map[item.id] = item;
  });
  // 
  data.forEach(item => {
    let parent = map[item.pid];
    if(parent) {
      (parent.children || (parent.children = [])).push(item);
    } else {
      result.push(item);
    }
  });
  return result;
}

(3)获取数组(树)的最大深度

var deepArr = [];				//定义存放每条路径深度的数组
  getDeep(treeArr,0,deepArr);	//调用函数,三个参数分别为,结点,计数器,以及存放深度的数组

  function getDeep(data,i,deepArr){
  	//获取当前结点的子数组,并且打印当前结点的值
  	console.log(data.name)
    var treeRoot = data.child
    //如果当前结点没有子数组了(没有子结点)就跳出当前递归,并且使计数器+1,并把计数器i的值存入深度数组中
    if(!treeRoot){
      i++
      deepArr.push(i);
      return
    }
    //如果当前结点有子数组,就要使计数器+1
    i++
    //通过for循环来找出每一条路径,对遍历到的结点使用递归
    for(let j=0;j<treeRoot.length;j++){
      getDeep(treeRoot[j],i,deepArr)		//递归时传入的就是当前结点的第j个子结点,当这第j个子结点中的所有子孙结点全部遍历完成之后,再去遍历第j+1个结点的所有子孙结点
    }
  }
  //最后的到的这个深度数组,就可以通过对每一项进行比较从而得出最大值即最大深度
  console.log(deepArr)

2. 数组转链表

3. 数字转汉字

4. 找出连续整数缺少的数字

给定一个数组,给出上边界和下边界数据,里面的数是连续的,但是缺失了一个,要求找出这个缺失的数 例如:arr=[2,1,3,5,4,8,9,6] 一共9个数,已知上边界为1,下边界为9,要找到缺失的7 思路: 首先遍历数组获取目前数组中个数想加的结果 然后根据高斯求和,求出理论上的和 最后相减即得到了缺失的数

const arr = [2,1,3,5,4,8,9,6];
const upperBound = 1;
const lowerBound = 9;
let prevSum = 0;
MisssionNumber(arr);
function MisssionNumber(arr) {
	for(let i=0; i<arr.length; i++){
		prevSum += arr[i];
	}
	const gaosiSum = ((upperBound+lowerBound)*9)/2;
	const missionNum = gaosiSum-prevSum;
	console.log(missionNum);
}

10. 丢失的数字

5. 判断完全平方数

就是判断一个数字能不能被开平方, 比如9的开平方是3 是对的。 5没法开平方就是错的。

var fn = function (num) {
  return num ** 0.5 % 1 == 0
};

6. 二进制加法

实现一个二进制加法,输入输出均为二进制字符串

function addBinary(a,b) {
    let ans = '', carry = 0
    let pa = a.length-1;
    let pb = b.length-1;
    while (pa >=0 || pb >= 0) {
        const sum = Number(a[pa] || 0) + Number(b[pb] || 0) + carry
        carry = Math.floor(sum / 2);
        ans = sum % 2 + ans
        pa--;
        pb--;
    }
    if(carry !== 0) ans = '1' + ans
    return  ans
}

7. 随机乱序数组

function shuffle(arr) { // 随机打乱数组
  let _arr = arr.slice() // 调用数组副本,不改变原数组
  for (let i = 0; i < _arr.length; i++) {
    let j = getRandomInt(0, i)
    let t = _arr[i]
    _arr[i] = _arr[j]
    _arr[j] = t
  }
  return _arr
}
function getRandomInt(min, max) { // 获取min到max的一个随机数,包含min和max本身
  return Math.floor(Math.random() * (max - min + 1) + min)
}

function shuffle(arr) {
    let i = arr.length;
    while (i) {
        let j = Math.floor(Math.random() * i--);
        [arr[j], arr[i]] = [arr[i], arr[j]];
    }
}
let arr = [1,2,3,4,5,6,7]
shuffle(arr)
console.log(arr)
function randomSort(a,b) { 
    return .5 - Math.random(); 
}

let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
arr.sort(randomSort);

  1. 两个数组中完全独立的数据

三、场景应用

1. 实现一个字符串计数方法,规则如下:

// 连续的大小写字母/数字记为 1(可以认为数字、大写字母就是小写字母)

// 每一个中文(可以视为 \u4e00-\u9fa5)记为 1

// 所有符号(如果枚举不完,可以假设符号只有 @ 符号一种)视为空格,空格和符号不计数

// Person123 => 1 你好世界 => 4 Hello world => 2 hello@world => 2 hello 你 world => 3

const count = (str) => {
let count = 0;

// 正则方式
// count += str.match(/([a-zA-Z]+)/g).length - 1;
// count += str.match(/\d+/g).length - 1;
// count += str.match(/[\u4e00-\u9fa5]/g).length - 1;
// return count;

// 字符串遍历方式
const getType = (char) => {
if ((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')) {
return 'character';
}
if (char >= 0 && char <= 9) {
return 'number';
}
if (char >= '\u4e00' && char <= '\u9fa5') {
return 'cn';
}
return '';
};
let currentType = '';
let currentLength = 0;
for (let i = 0; i < str.length; i++) {
const type = getType(str[i]);
if (currentType && currentType === type) {
currentLength++;
} else if (!currentType) {
currentType = type;
currentLength++;
} else if (type === 'cn') {
count++;
currentType = '';
currentLength = 0;
} else {
count++;currentType = type;
currentLength = 1;
} }
return count;
};

17. 实现一个具有优先级的任务队列,如果优先级相同按照先进先出的顺序\

class queue{
	constructor(){
  	this.list = {};
    this.count = 0;
   	this.high = new Set();
  }
  push(item, order){
    if(this.list[order]){
    	this.list[order].push(item)
    }else{
    	this.list[order] = [item]
    }
		this.high.add(order);
    this.count++
  }
  shift(){
  	const highOrder = Math.max(...this.high);
    const itemList = this.list[highOrder];
    if(itemList&&itemList.length>0){
      this.count--
      
    	const item =  itemList.shift();
      if(itemList.length===0){
      	this.high.delete(highOrder)
        delete this.list[highOrder]
      }
      return item;
    }
    return 
  }
  get size(){
  	return this.count;
  }
}
const list = new queue();
list.push('a', 1)
list.push('b', 2)
list.push('c', 1)
console.log(list.size)
console.log(list.shift())
console.log(list.shift())
console.log(list.shift())

18. 最多耗时多少能完成全部任务

// 有多个任务,每个任务耗时天数不同,有些任务依赖有前置任务(即必须等前置任务完成才能进行当前任务)

// 写个方法计算最多耗时多少能完成全部任务

var findOrder = function(task, prerequisites) {
  let numTask = task.length;
  let inDegree = new Array(numTask).fill(0);
  let map = {};
  prerequisites.map(item => {
      inDegree[item[0]]++;
      map[item[1]] = (map[item[1]] || []).concat(item[0]);
  })

  let quene = [], order = [];

  inDegree.map((item, index) => {
      item == 0 && quene.push(index);
  })

  while(quene.length) {
      let selected = quene.shift();
      order.push(selected);
      let toQuene = map[selected];
      if (toQuene && toQuene.length) {
          toQuene.map(to => {
              inDegree[to]--;
              if(inDegree[to] == 0) {
                  quene.push(to)
              }
          })
      }
  }
  if (order.length != numTask) return false;
  return  task.reduce((val,item) => {
    return val + item;
  },0)
};

let res = findOrder([1,2], [[1,0]]);
console.log(res);

19. 实现一个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
//  
// 以此类推。
class LazyLife {
  constructor(name){
      this.name = name;
      this.timer = null;
      this.tasks = []
      this.sayHi(name)
      setTimeout(() => {
          this.next()
      }, 0)
  }
  next() {
      const task = this.tasks.shift();
      task && task();
  }   
  sayHi() {
      console.log('sayHi')
      this.tasks.push(() => {
          console.log(`Hi This is ${this.name}!`)
          this.next()
      })
      return this
  }
  eat(something) {
      console.log('eat')
      this.tasks.push(() => {
    zz      console.log(`Eat ${something}!`)
          this.next()
      })
      return this
  }
  sleep(time) {
      console.log('sleep')
      this.tasks.push(() => {
          setTimeout(() => {
              console.log(`Wake up after ${time}`)
              this.next()
          }, time * 1000)
      })
      return this
  }
  sleepFirst(time) {
      console.log('sleepFirst')
      this.tasks.unshift(() => {
          setTimeout(() => {
              console.log(`Wake up after ${time}`)
              this.next()
          }, time * 1000)
      })
      return this
  }
}

20. 图片懒加载

// directive/imgLazy.js

// 引入默认图片
import baseImg from "@/assets/logo.png";
let timer = null;
// 创建一个监听器
let observer = new IntersectionObserver((entries) => {
  // entries是所有被监听对象的集合
  entries.forEach((entry) => {
    if (entry.isIntersecting || entry.intersectionRatio > 0) {
      // 当被监听元素到临界值且未加载图片时触发。
      !entry.target.isLoaded && showImage(entry.target, entry.target.data_src);
    }
  });
});

function showImage(el, imgSrc) {
  const img = new Image();
  img.src = imgSrc;
  img.onload = () => {
    el.src = imgSrc;
    el.isLoaded = true;
  };
}
export default {
  // 这里用inserted和bind都行,因为IntersectionObserver时异步的,以防意外还是用inserted好一点
  // inserted和bind的区别在于inserted时元素已经插入页面,能够直接获取到dom元素的位置信息。
  inserted(el, binding, vnode) {
    clearTimeout(timer); // 初始化时展示默认图片
    el.src = baseImg; // 将需要加载的图片地址绑定在dom上
    el.data_src = binding.value;
    observer.observe(el); // 防抖,这里在组件卸载的时候停止监听
    const vm = vnode.context;
    timer = setTimeout(() => {
      vm.$on("hook:beforeDestroy", () => {
        observer.disconnect();
      });
    }, 20);
  }, // 图片更新触发
  update(el, binding) {
    el.isLoaded = false;
    el.data_src = binding.value;
  }, 
  // unbind不太好,会执行多次,改进一下用组件的beforeDestroy卸载
  // unbind(){  //   // 停止监听  //   observer.disconnect();  // }
};

vue组件内使用

// main.js
import imgLazy from '@/directive/imgLazy.js'
Vue.directive('imgLazy', imgLazy)


// 在组件中定义directives使用,给当前组件注册指令

import imgLazy from '@/directive/imgLazy.js'
export default {
  // ...
  directives: {
    imgLazy: imgLazy,
  },
}


// 在组件中使用

// <template> 
//    <div class='container'> 
//      <div v-for="(item,index) in imgSrc" :key="index" >  
//       <img v-imgLazy="item" > 
//      </div>  
//   </div>
// </template>

21. sleep

const sleep = (time) => {
  return new Promise((resovle, rej) => {
    setTimeout(() => {
      resovle();
    }, time * 1000);
  });
};

22. repeat

const repeat = (fn, count, time) => {
  if (count == 0) return;
  new Promise((resolve, rej) => {
    fn();
    setTimeout(() => {
      resolve();
    }, time * 1000);
  })
  .then(() => {
    repeat(fn, count - 1, time);
  })
}

23. 数字转汉字 101 一百零一

方法一:支持7位,也就是最大1234567   

案例:this.toChinesNum(10101010)  得到 "一千零一十万一千零一十"

     /**
     * 数字转成汉字
     * @params num === 要转换的数字
     * @return 汉字
     * */
    toChinesNum(num) {
      let changeNum = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
      let unit = ['', '十', '百', '千', '万']
      num = parseInt(num)
      let getWan = (temp) => {
        let strArr = temp.toString().split('').reverse()
        let newNum = ''
        let newArr = []
        strArr.forEach((item, index) => {
          newArr.unshift(item === '0' ? changeNum[item] : changeNum[item] + unit[index])
        })
        let numArr = []
        newArr.forEach((m, n) => {
          if (m !== '零') numArr.push(n)
        })
        if (newArr.length > 1) {
          newArr.forEach((m, n) => {
            if (newArr[newArr.length - 1] === '零') {
              if (n <= numArr[numArr.length - 1]) {
                newNum += m
              }
            } else {
              newNum += m
            }
          })
        } else {
          newNum = newArr[0]
        }
 
        return newNum
      }
      let overWan = Math.floor(num / 10000)
      let noWan = num % 10000
      if (noWan.toString().length < 4) {
        noWan = '0' + noWan
      }
      return overWan ? getWan(overWan) + '万' + getWan(noWan) : getWan(num)
    }

方法二:支持9位以上也就是亿级别的,如果需要钱的那种单位,把注释放开就行

toChineseBig(num) {
      // 将接收到的num转换为字符串
      var strNum = String(num)
      // 定义单位
      // var unit = ['拾', '佰', '仟', '万', '拾', '佰', '仟', '亿', '拾', '佰', '仟']
      var unit = ['十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千']
      // 结果中放一个符号,用来解决最后的零去不掉的问题
      var result = ['@']
      // 单位下标
      var unitNo = 0
      // 从后往前遍历接收到的数据,省略结束条件
      for (let i = strNum.length - 1;; i--) {
        // 调用转大写函数,将每一个数字转换成中文大写,一次放入一个到结果数组中
        result.unshift(numToChinese(strNum[i]))
        // 如果不大于0
        if (i <= 0) {
          // 结束循环
          break
        }
        // 放入一个数字,放入一个单位
        result.unshift(unit[unitNo])
        // 单位下标加1
        unitNo++
      }
      // 将结果数组转换成字符串,并使用正则替换一些关键位置,让结果符合语法
      // return result.join('').replace(/(零[仟佰拾]){1,3}/g, '零').replace(/零{2,}/g, '零').replace(/零([万亿])/g, '$1').replace(/亿万/g, '亿').replace(/零*@/g, '')
      return result.join('').replace(/(零[千百十]){1,3}/g, '零').replace(/零{2,}/g, '零').replace(/零([万亿])/g, '$1').replace(/亿万/g, '亿').replace(/零*@/g, '')
 
      function numToChinese(n) {
        // var chineseBigNum = '零壹贰叁肆伍陆柒捌玖'
        var chineseBigNum = '零一二三四五六七八九'
        return chineseBigNum[n]
      }
    }
  }

24. 麦乐鸡块问题

问题:在一个平行宇宙中,麦当劳的麦乐鸡块分为 7 块装、13 块装和 29 块装。有一天,你的老板让你出去购买正好为 n 块(0 < n <= 10000)的麦乐鸡块回来,请提供一个算法判断是否可行。

const checkNuggets = (nuggets) => {
    let temp = []
    temp[7] = true
    temp[13]-= true
    temp[29] = true
    for (let i = 7; i < nuggets; i+= 1) {
        if (temp[i]) {
            temp[i + 7] = true
            temp[i + 13] = true
            temp[i + 29] = true
        } else continue
    }
    return !!temp[nuggets]
}

console.log(checkNuggets(25)) // false
console.log(checkNuggets(26)) // true
console.log(checkNuggets(27)) // true
console.log(checkNuggets(28)) // true
console.log(checkNuggets(29)) // true
console.log(checkNuggets(30)) // false

25. 红绿灯

const task = function (timer, light) {
    setTimeout(() => {
        console.log(light);
    }, timer)
};
const taskRunner =  async () => {
    await task(3000, 'red')
    await task(2000, 'green')
    await task(2100, 'yellow')
    taskRunner()
}
taskRunner()

26. 每日一题,已知数据格式,实现一个函数 fn 找出链条中所有的父级 id

/*
* 已知数据格式,实现一个函数 fn 找出链条中所有的父级 id
* 实现: 通过es6的class实现,思路:递归调用,下传当前的父辈的id
*/
const fn = (data, value) => {
  let res = []
  const dfs = (arr, temp = []) => {
    for (const node of arr) {
      if (node.children) {
        dfs(node.children, temp.concat(node.id))
      } else {
        if (node.id === value) {
          res = temp
          return
        }
      }
    }
  }
  dfs(data)
  return res
}

27 解析出网站地址

var str = ‘sdfaswww.dasdfwww.xiaohongshu.comdsjks.comdjsdj’ 期望能够解析出网站的地址。

function getUrl(str){
	let reg = /\+.(www.)(\w)(.com)\+./
  let url = ''
  if(reg.test(str)){
    url = 'wwww.'+reg.exec(str)[1]+'.com'
		
  }
  return url
}
getUrl(str)

// 使用变量记录
function fn(str){
	let stack = [];
  let len = str.length;
  let i = 0;
  let url = [];
  while(i<len-4){
    let newS= str.substr(i, 4);
  	if(newS==='www.'){
      stack = [i+4]
    	i+=4
    }else if(newS === '.com'){
    	const start = stack.pop()
      if(start>0){
      	let s = str.substring(start, i)
        url.push(`www.${s}.com`)
      }
      
      i+=4
    }else{
     i++
    } 
  }
  return url
}
fn(str)


  1. 实现一个具有优先级的任务队列,如果优先级相同按照先进先出的顺序
class queue{
	constructor(){
  	this.list = {};
    this.count = 0;
   	this.high = new Set();
  }
  push(item, order){
    if(this.list[order]){
    	this.list[order].push(item)
    }else{
    	this.list[order] = [item]
    }
		this.high.add(order);
    this.count++
  }
  shift(){
  	const highOrder = Math.max(...this.high);
    const itemList = this.list[highOrder];
    if(itemList&&itemList.length>0){
      this.count--
      
    	const item =  itemList.shift();
      if(itemList.length===0){
      	this.high.delete(highOrder)
        delete this.list[highOrder]
      }
      return item;
    }
    return 
  }
  get size(){
  	return this.count;
  }
}
const list = new queue();
list.push('a', 1)
list.push('b', 2)
list.push('c', 1)
console.log(list.size)
console.log(list.shift())
console.log(list.shift())
console.log(list.shift())

实现一个方法,截断字符串,如果截断处处于一个链接中间,则从链接结尾处开始截断。示例:

示例1

substrRetainLink('1,2,3,4 [http://www.meituan.com](http://www.meituan.com/)   end', 4);

返回:1,2,

示例2

substrRetainLink('1,2,3,4 [http://www.meituan.com](http://www.meituan.com/) end', 10);

返回:1,2,3,4 [http://www.meituan.com](http://www.meituan.com/)

示例3

substrRetainLink('1,2,3,4 [美团网|[http://www.meituan.com](http://www.meituan.com/)] end', 10);/

返回:1,2,3,4 [美团网|[http://www.meituan.com](http://www.meituan.com/)]


示例4

substrRetainLink('1,2,3,4 [http://meituan.com](http://meituan.com/) [http://www.meituan.com](http://www.meituan.com/) end', 35);// 

返回:1,2,3,4 [http://meituan.com](http://meituan.com/) [http://www.meituan.com](http://www.meituan.com/)



/*

* @param {string} input 待处理的文本

* @param {number index 截断位置

*/

function substrRetainLink(input, length) {


}
/*
* @param {string} input 待处理的文本
* @param {number index 截断位置
*/

function getUrl(str) {
    let reg = /(http|https):\/\/((\w)+\.)*(\w)+(.com)/g;
    let reg2 = /\[(.+)\]/g;
    let url = '';
    if (reg2.test(str)) {
        url = str.match(reg2);
    } else {
        url = str.match(reg);
    }

    return url;
}
function substrRetainLink(input, length) {
    let url = '';
    let index = null;
    let urlLastIndex = null;
    if (getUrl(input)) {
        url = getUrl(input) && getUrl(input)[0];
        index = input.indexOf(url) + 1;
        urlLastIndex = index + url.length;
    }
    let len = input.length;
    let res = '';
    
    if (!url || length < index) {
        return input.slice(0, length);
    } else {

        res = input.slice(0, urlLastIndex)
                + (length - urlLastIndex > 0 ? substrRetainLink(input.slice(urlLastIndex), length - urlLastIndex) : '');
    }
    return res;
}

// var a = substrRetainLink('1,2,3,4 http://www.meituan.com   end', 4);
var a = substrRetainLink('1,2,3,4 http://www.meituan.com end', 10);
// var a = substrRetainLink('1,2,3,4 http://www.meituan.com http://www.meituan.com end', 35);
// var a = substrRetainLink('1,2,3,4 http://www.meituan.com http://meituan.com end', 35);
// var a = substrRetainLink('1,2,3,4 [美团网|http://www.meituan.com] end', 10);
console.log(a)

28. 千位分隔

给10000 得到10,000

function fn(num){
  let tmp = (num+'').split('.');
  let newS = tmp[0];
  let len = newS.length;
  let i = len%3;
  let s = newS.substr(0, i);
  while(i<len){
    const a = newS.substr(i, 3)
    if(s.length>0){
    	s +=','+a
    }else{
    	s = a
    }
  	 i+=3
  }
  if(tmp.length>1){
  	s+='.'+tmp[1]
  }
  return s
}
fn(10000)
fn(10000000)
fn(10000000.88)

29. Count and Say

var countAndSay = function(n) {
    let str = "1";
    for (let i = 2; i <= n; ++i) {
        const sb = [];
        let start = 0;
        let pos = 0;

        while (pos < str.length) {
            while (pos < str.length && str[pos] === str[start]) {
                pos++;
            }
            sb.push('' + (pos - start) + str[start]);
            start = pos;
        }
        str = sb.join('');
    }
    
    return str;
};

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/count-and-say/solution/wai-guan-shu-lie-by-leetcode-solution-9rt8/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
var countAndSay = function(n) {
    if(n==1){
        return n.toString()
    }
    var tempArr = countAndSay(n-1).match(/(\d)\1*/g)  // 该正则进行相同分组,调用match方法得出接下来用的数组
    var result = ""
    tempArr.forEach((item)=>{              // 循环上面得到的数组,然后取每个的长度(题里说的几个),还有第一个数字(题里说的哪个数)
        var lth = item.length.toString()
        var num = item.substring(0,1)
        result = result+lth+num
    })
    return result   //最后返回结果
    
};

作者:wo-shi-gao-xiao-de
链接:https://leetcode.cn/problems/count-and-say/solution/jsdi-gui-jie-jue-by-wo-shi-gao-xiao-de/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

30. prettyBytes

算法:
/ prettyBytes(1337); //'1.34 KB'
// 1000000 bytes
// B KB MB GB TB
// 文件; 单位进度; 保留几位小数
function prettyBytes(bytes, base = 1000, maximumFractionDigits = 2) {
const map = {
kb: base,
MB: base * base,
GB: base * base * base,
} le
t res = null;
Object.keys(map).some(key => {
if (bytes >= map[key] && bytes < map[key] * base) {
res = `${(bytes / map[key]).toFixed(maximumFractionDigits)}${key}`
} 
return res;
})
return res;
} //
console.log(prettyBytes(1000));

31. html片段标签闭合检验

// 给定一个 html 片断, 判断其中元素是否完整闭合。 为简化过程, 我们可以假设所有结点
都是空标签, 且没有自闭合的结点。
// 例子 1. "<div><a></a></div>", 返回 true, 是一个完整闭合的 html 片断,
// 例子 2. "<div><span></div></span>"返回 false
// 例子 3. "<div><div><em></em></div><p></p ></div>", true

function isValidHtml(html) {
const htmlStr = html.replace(/></g, ',').replace(/(<|>)/g, '').split(',');
console.log(htmlStr);
const line = [];
htmlStr.forEach(item => {
const lastItem = [...line].pop();
console.log(lastItem, `${item}`);
if (item === `/${lastItem}`) {
line.pop();
} else {
line.push(item);
}}
);
console.log(line);
return line.length === 0;
}
console.log(isValidHtml('<div><a></a ></div>'));

32. 字符数组转对象

//input ["a","b","c","d","e","f","g"]

//output {"a":{"b":{"c":{"d":{"e":{"f":"g"}}}}}}
//一开始很懵, 后面思路起来想到应该从后往前推就做出来了
function handler(arr){
    const len = arr.length;
    let prev = {
        [arr[len-2]]:arr[len-1]
    }
    for(let i=len-3;i>=0;i--){
        prev = {
            [arr[i]]:prev
        }
    }
    return prev
}

33. 如何获取页面中出现的所有dom元素的标签类型

//其实只要知道如何获取页面中所有元素就能做出来, 通过document.querySelectorAll("*")获取
const all = Array.from(document.querySelectorAll("*"));
const hash = {};
const res = [];
all.forEach(it=>{
    if(!hash[it.tagName]){
        res.push(it.tagName);
        hash[it.tagName] = true;
    }
})
console.log(res)

鲨鱼哥 最全的手写JS面试题

「2021」高频前端面试题汇总之手写代码篇