共计 11230 个字符,预计需要花费 29 分钟才能阅读完成。
实现数组扁平化 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/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(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'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(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 // true
NaN === 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.length
for(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.create
function 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));
}
}
// 模仿实现 removeListener
EventEmitter.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;
}