实现数组扁平化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
中,又新增了Map
和Set
。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以ES6
在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator
)。
ES6
约定,任何数据结构只有具备Symbol.iterator
属性(这个属性就是Iterator
的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被for...of...
循环和迭代器的next办法遍历。 事实上,for...of...
的背地正是对next
办法的重复调用。
在ES6中,针对Array
、Map
、Set
、String
、TypedArray
、函数的 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;}