实现数组扁平化flat办法

题目形容: 实现一个办法使多维数组变成一维数组

let ary = [1, [2, [3, [4, 5]]], 6];let str = JSON.stringify(ary);

第0种解决:间接的调用

arr_flat = arr.flat(Infinity);

第一种解决

ary = str.replace(/(\[|\])/g, '').split(',');

第二种解决

str = str.replace(/(\[\]))/g, '');str = '[' + str + ']';ary = JSON.parse(str);

第三种解决:递归解决

let result = [];let fn = function(ary) {  for(let i = 0; i < ary.length; i++) }{    let item = ary[i];    if (Array.isArray(ary[i])){      fn(item);    } else {      result.push(item);    }  }}

第四种解决:用 reduce 实现数组的 flat 办法

function flatten(ary) {    return ary.reduce((pre, cur) => {        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);    }, []);}let ary = [1, 2, [3, 4], [5, [6, 7]]]console.log(flatten(ary))

第五种解决:能用迭代的思路去实现

function flatten(arr) {  if (!arr.length) return;  while (arr.some((item) => Array.isArray(item))) {    arr = [].concat(...arr);  }  return arr;}// console.log(flatten([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

第六种解决:扩大运算符

while (ary.some(Array.isArray)) {  ary = [].concat(...ary);}

类数组转化为数组

类数组是具备length属性,但不具备数组原型上的办法。常见的类数组有arguments、DOM操作方法返回的后果。

办法一:Array.from
Array.from(document.querySelectorAll('div'))
办法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
办法三:扩大运算符
[...document.querySelectorAll('div')]
办法四:利用concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));

手写 Promise

const PENDING = "pending";const RESOLVED = "resolved";const REJECTED = "rejected";function MyPromise(fn) {  // 保留初始化状态  var self = this;  // 初始化状态  this.state = PENDING;  // 用于保留 resolve 或者 rejected 传入的值  this.value = null;  // 用于保留 resolve 的回调函数  this.resolvedCallbacks = [];  // 用于保留 reject 的回调函数  this.rejectedCallbacks = [];  // 状态转变为 resolved 办法  function resolve(value) {    // 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转    if (value instanceof MyPromise) {      return value.then(resolve, reject);    }    // 保障代码的执行程序为本轮事件循环的开端    setTimeout(() => {      // 只有状态为 pending 时能力转变,      if (self.state === PENDING) {        // 批改状态        self.state = RESOLVED;        // 设置传入的值        self.value = value;        // 执行回调函数        self.resolvedCallbacks.forEach(callback => {          callback(value);        });      }    }, 0);  }  // 状态转变为 rejected 办法  function reject(value) {    // 保障代码的执行程序为本轮事件循环的开端    setTimeout(() => {      // 只有状态为 pending 时能力转变      if (self.state === PENDING) {        // 批改状态        self.state = REJECTED;        // 设置传入的值        self.value = value;        // 执行回调函数        self.rejectedCallbacks.forEach(callback => {          callback(value);        });      }    }, 0);  }  // 将两个办法传入函数执行  try {    fn(resolve, reject);  } catch (e) {    // 遇到谬误时,捕捉谬误,执行 reject 函数    reject(e);  }}MyPromise.prototype.then = function(onResolved, onRejected) {  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数  onResolved =    typeof onResolved === "function"      ? onResolved      : function(value) {          return value;        };  onRejected =    typeof onRejected === "function"      ? onRejected      : function(error) {          throw error;        };  // 如果是期待状态,则将函数退出对应列表中  if (this.state === PENDING) {    this.resolvedCallbacks.push(onResolved);    this.rejectedCallbacks.push(onRejected);  }  // 如果状态曾经凝固,则间接执行对应状态的函数  if (this.state === RESOLVED) {    onResolved(this.value);  }  if (this.state === REJECTED) {    onRejected(this.value);  }};

实现防抖函数(debounce)

防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则从新计时。

那么与节流函数的区别间接看这个动画实现即可。

手写简化版:

// 防抖函数const debounce = (fn, delay) => {  let timer = null;  return (...args) => {    clearTimeout(timer);    timer = setTimeout(() => {      fn.apply(this, args);    }, delay);  };};

实用场景:

  • 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
  • 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似

生存环境请用lodash.debounce

参考:前端手写面试题具体解答

数组扁平化

数组扁平化是指将一个多维数组变为一个一维数组

const arr = [1, [2, [3, [4, 5]]], 6];// => [1, 2, 3, 4, 5, 6]
办法一:应用flat()
const res1 = arr.flat(Infinity);
办法二:利用正则
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');

但数据类型都会变为字符串

办法三:正则改进版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
办法四:应用reduce
const flatten = arr => {  return arr.reduce((pre, cur) => {    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);  }, [])}const res4 = flatten(arr);
办法五:函数递归
const res5 = [];const fn = arr => {  for (let i = 0; i < arr.length; i++) {    if (Array.isArray(arr[i])) {      fn(arr[i]);    } else {      res5.push(arr[i]);    }  }}fn(arr);

字符串解析问题

var a = {    b: 123,    c: '456',    e: '789',}var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;// => 'a123aa456aa {a.d}aaaa'

实现函数使得将str字符串中的{}内的变量替换,如果属性不存在放弃原样(比方{a.d}

相似于模版字符串,但有一点出入,实际上原理大差不差

const fn1 = (str, obj) => {    let res = '';    // 标记位,标记后面是否有{    let flag = false;    let start;    for (let i = 0; i < str.length; i++) {        if (str[i] === '{') {            flag = true;            start = i + 1;            continue;        }        if (!flag) res += str[i];        else {            if (str[i] === '}') {                flag = false;                res += match(str.slice(start, i), obj);            }        }    }    return res;}// 对象匹配操作const match = (str, obj) => {    const keys = str.split('.').slice(1);    let index = 0;    let o = obj;    while (index < keys.length) {        const key = keys[index];        if (!o[key]) {            return `{${str}}`;        } else {            o = o[key];        }        index++;    }    return o;}

实现日期格式化函数

输出:

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{    var day = dateInput.getDate()     var month = dateInput.getMonth() + 1      var year = dateInput.getFullYear()       format = format.replace(/yyyy/, year)    format = format.replace(/MM/,month)    format = format.replace(/dd/,day)    return format}

实现字符串的repeat办法

输出字符串s,以及其反复的次数,输入反复的后果,例如输出abc,2,输入abcabc。

function repeat(s, n) {    return (new Array(n + 1)).join(s);}

递归:

function repeat(s, n) {    return (n > 0) ? s.concat(repeat(s, --n)) : "";}

字符串查找

请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。

a='34';b='1234567'; // 返回 2a='35';b='1234567'; // 返回 -1a='355';b='12354355'; // 返回 5isContain(a,b);
function isContain(a, b) {  for (let i in b) {    if (a[0] === b[i]) {      let tmp = true;      for (let j in a) {        if (a[j] !== b[~~i + ~~j]) {          tmp = false;        }      }      if (tmp) {        return i;      }    }  }  return -1;}

实现公布-订阅模式

class EventCenter{  // 1. 定义事件容器,用来装事件数组    let handlers = {}  // 2. 增加事件办法,参数:事件名 事件办法  addEventListener(type, handler) {    // 创立新数组容器    if (!this.handlers[type]) {      this.handlers[type] = []    }    // 存入事件    this.handlers[type].push(handler)  }  // 3. 触发事件,参数:事件名 事件参数  dispatchEvent(type, params) {    // 若没有注册该事件则抛出谬误    if (!this.handlers[type]) {      return new Error('该事件未注册')    }    // 触发事件    this.handlers[type].forEach(handler => {      handler(...params)    })  }  // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和公布  removeEventListener(type, handler) {    if (!this.handlers[type]) {      return new Error('事件有效')    }    if (!handler) {      // 移除事件      delete this.handlers[type]    } else {      const index = this.handlers[type].findIndex(el => el === handler)      if (index === -1) {        return new Error('无该绑定事件')      }      // 移除事件      this.handlers[type].splice(index, 1)      if (this.handlers[type].length === 0) {        delete this.handlers[type]      }    }  }}

Promise.race

Promise.race = function(promiseArr) {  return new Promise((resolve, reject) => {    promiseArr.forEach(p => {      // 如果不是Promise实例须要转化为Promise实例      Promise.resolve(p).then(        val => resolve(val),        err => reject(err),      )    })  })}

实现一个队列

基于链表构造实现队列
const LinkedList = require('./实现一个链表构造')// 用链表默认应用数组来模仿队列,性能更佳class Queue {  constructor() {    this.ll = new LinkedList()  }  // 向队列中增加  offer(elem) {    this.ll.add(elem)  }  // 查看第一个  peek() {    return this.ll.get(0)  }  // 队列只能从头部删除  remove() {    return this.ll.remove(0)  }}var queue = new Queue()queue.offer(1)queue.offer(2)queue.offer(3)var removeVal = queue.remove(3)console.log(queue.ll,'queue.ll')console.log(removeVal,'queue.remove')console.log(queue.peek(),'queue.peek')

Object.is

Object.is解决的次要是这两个问题:

+0 === -0  // trueNaN === NaN // false
const is= (x, y) => {  if (x === y) {    // +0和-0应该不相等    return x !== 0 || y !== 0 || 1/x === 1/y;  } else {    return x !== x && y !== y;  }}

实现一个迭代器生成函数

ES6对迭代器的实现

JS原生的汇合类型数据结构,只有Array(数组)和Object(对象);而ES6中,又新增了MapSet。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以ES6在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator)。

ES6约定,任何数据结构只有具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被for...of...循环和迭代器的next办法遍历。 事实上,for...of...的背地正是对next办法的重复调用。

在ES6中,针对ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都能够通过for...of...进行遍历。原理都是一样的,此处咱们拿最简略的数组进行举例,当咱们用for...of...遍历数组时:

const arr = [1, 2, 3]const len = arr.lengthfor(item of arr) {   console.log(`以后元素是${item}`)}
之所以可能按程序一次一次地拿到数组里的每一个成员,是因为咱们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过重复调用迭代器对象的next办法拜访了数组成员,像这样:
const arr = [1, 2, 3]// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 对迭代器对象执行next,就能一一拜访汇合的成员iterator.next()iterator.next()iterator.next()

丢进控制台,咱们能够看到next每次会按程序帮咱们拜访一个汇合成员:

for...of...做的事件,根本等价于上面这通操作:
// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 初始化一个迭代后果let now = { done: false }// 循环往外迭代成员while(!now.done) {    now = iterator.next()    if(!now.done) {        console.log(`当初遍历到了${now.value}`)    }}
能够看出,for...of...其实就是iterator循环调用换了种写法。在ES6中咱们之所以可能开心地用for...of...遍历各种各种的汇合,全靠迭代器模式在背地给力。

ps:此处举荐浏览迭代协定 (opens new window),置信大家读过后会对迭代器在ES6中的实现有更深的了解。

实现Object.create

Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的 __proto__
// 模仿 Object.createfunction create(proto) {  function F() {}  F.prototype = proto;  return new F();}

验证是否是身份证

function isCardNo(number) {    var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;    return regx.test(number);}

实现一个compose函数

组合多个函数,从右到左,比方:compose(f, g, h) 最终失去这个后果 (...args) => f(g(h(...args))).

题目形容:实现一个 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(...funcs) {  if (!funcs.length) return (v) => v;  if (funcs.length === 1) {    return funcs[0]  }  return funcs.reduce((a, b) => {    return (...args) => a(b(...args)))  }}
compose创立了一个从右向左执行的数据流。如果要实现从左到右的数据流,能够间接更改compose的局部代码即可实现
  • 更换Api接口:把reduce改为reduceRight
  • 交互包裹地位:把a(b(...args))改为b(a(...args))

event模块

实现node中回调函数的机制,node中回调函数其实是外部应用了观察者模式

观察者模式:定义了对象间一种一对多的依赖关系,当指标对象Subject产生扭转时,所有依赖它的对象Observer都会失去告诉。
function EventEmitter() {  this.events = new Map();}// 须要实现的一些办法:// addListener、removeListener、once、removeAllListeners、emit// 模仿实现addlistener办法const wrapCallback = (fn, once = false) => ({ callback: fn, once });EventEmitter.prototype.addListener = function(type, fn, once = false) {  const hanlder = this.events.get(type);  if (!hanlder) {    // 没有type绑定事件    this.events.set(type, wrapCallback(fn, once));  } else if (hanlder && typeof hanlder.callback === 'function') {    // 目前type事件只有一个回调    this.events.set(type, [hanlder, wrapCallback(fn, once)]);  } else {    // 目前type事件数>=2    hanlder.push(wrapCallback(fn, once));  }}// 模仿实现removeListenerEventEmitter.prototype.removeListener = function(type, listener) {  const hanlder = this.events.get(type);  if (!hanlder) return;  if (!Array.isArray(this.events)) {    if (hanlder.callback === listener.callback) this.events.delete(type);    else return;  }  for (let i = 0; i < hanlder.length; i++) {    const item = hanlder[i];    if (item.callback === listener.callback) {      hanlder.splice(i, 1);      i--;      if (hanlder.length === 1) {        this.events.set(type, hanlder[0]);      }    }  }}// 模仿实现once办法EventEmitter.prototype.once = function(type, listener) {  this.addListener(type, listener, true);}// 模仿实现emit办法EventEmitter.prototype.emit = function(type, ...args) {  const hanlder = this.events.get(type);  if (!hanlder) return;  if (Array.isArray(hanlder)) {    hanlder.forEach(item => {      item.callback.apply(this, args);      if (item.once) {        this.removeListener(type, item);      }    })  } else {    hanlder.callback.apply(this, args);    if (hanlder.once) {      this.events.delete(type);    }  }  return true;}EventEmitter.prototype.removeAllListeners = function(type) {  const hanlder = this.events.get(type);  if (!hanlder) return;  this.events.delete(type);}

分片思维解决大数据量渲染问题

题目形容: 渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染

let ul = document.getElementById("container");// 插入十万条数据let total = 100000;// 一次插入 20 条let once = 20;//总页数let page = total / once;//每条记录的索引let index = 0;//循环加载数据function loop(curTotal, curIndex) {  if (curTotal <= 0) {    return false;  }  //每页多少条  let pageCount = Math.min(curTotal, once);  window.requestAnimationFrame(function () {    for (let i = 0; i < pageCount; i++) {      let li = document.createElement("li");      li.innerText = curIndex + i + " : " + ~~(Math.random() * total);      ul.appendChild(li);    }    loop(curTotal - pageCount, curIndex + pageCount);  });}loop(total, index);

扩大思考 :对于大数据量的简略 dom 构造渲染能够用分片思维解决 如果是简单的 dom 构造渲染如何解决?

这时候就须要应用虚构列表了,虚构列表和虚构表格在日常我的项目应用还是很多的

请实现 DOM2JSON 一个函数,能够把一个 DOM 节点输入 JSON 的格局

<div>  <span>    <a></a>  </span>  <span>    <a></a>    <a></a>  </span></div>把下面dom构造转成上面的JSON格局{  tag: 'DIV',  children: [    {      tag: 'SPAN',      children: [        { tag: 'A', children: [] }      ]    },    {      tag: 'SPAN',      children: [        { tag: 'A', children: [] },        { tag: 'A', children: [] }      ]    }  ]}

实现代码如下:

function dom2Json(domtree) {  let obj = {};  obj.name = domtree.tagName;  obj.children = [];  domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));  return obj;}