共计 14535 个字符,预计需要花费 37 分钟才能阅读完成。
对象数组列表转成树形构造(解决菜单)
[
{
id: 1,
text: '节点 1',
parentId: 0 // 这里用 0 示意为顶级节点
},
{
id: 2,
text: '节点 1_1',
parentId: 1 // 通过这个字段来确定子父级
}
...
]
转成
[
{
id: 1,
text: '节点 1',
parentId: 0,
children: [
{
id:2,
text: '节点 1_1',
parentId:1
}
]
}
]
实现代码如下:
function listToTree(data) {let temp = {};
let treeData = [];
for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];
}
for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}
temp[temp[i].parentId].children.push(temp[i]);
} else {treeData.push(temp[i]);
}
}
return treeData;
}
前端手写面试题具体解答
实现一个 padStart()或 padEnd()的 polyfil
String.prototype.padStart
和 String.prototype.padEnd
是 ES8
中新增的办法,容许将空字符串或其余字符串增加到原始字符串的结尾或结尾。咱们先看下应用语法:
String.padStart(targetLength,[padString])
用法:
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
// 1. 若是输出的指标长度小于字符串本来的长度则返回字符串自身
'xxx'.padStart(2, 's') // 'xxx'
// 2. 第二个参数的默认值为 " ",长度是为 1 的
// 3. 而此参数可能是个不确定长度的字符串,若是要填充的内容达到了指标长度,则将不要的局部截取
'xxx'.padStart(5, 'sss') // ssxxx
// 4. 可用来解决日期、金额格式化问题
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
polyfill 实现:
String.prototype.myPadStart = function (targetLen, padString = " ") {if (!targetLen) {throw new Error('请输出须要填充到的长度');
}
let originStr = String(this); // 获取到调用的字符串, 因为 this 本来是 String{},所以须要用 String 转为字符串
let originLen = originStr.length; // 调用的字符串本来的长度
if (originLen >= targetLen) return originStr; // 若是 本来 > 指标 则返回本来字符串
let diffNum = targetLen - originLen; // 10 - 6 // 差值
for (let i = 0; i < diffNum; i++) { // 要增加几个成员
for (let j = 0; j < padString.length; j++) { // 输出的 padString 的长度可能不为 1
if (originStr.length === targetLen) break; // 判断每一次增加之后是否到了指标长度
originStr = `${padString[j]}${originStr}`;
}
if (originStr.length === targetLen) break;
}
return originStr;
}
console.log('xxx'.myPadStart(16))
console.log('xxx'.padStart(16))
还是比较简单的,而 padEnd
的实现和它一样,只须要把第二层 for
循环里的 ${padString[j]}${orignStr}
换下地位就能够了。
实现字符串翻转
在字符串的原型链上增加一个办法,实现字符串翻转:
String.prototype._reverse = function(a){return a.split("").reverse().join("");
}
var obj = new String();
var res = obj._reverse ('hello');
console.log(res); // olleh
须要留神的是,必须通过实例化对象之后再去调用定义的办法,不然找不到该办法。
实现 instanceOf
// 模仿 instanceof
function instance_of(L, R) {
//L 示意左表达式,R 示意右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {if (L === null) return false;
if (O === L)
// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
实现观察者模式
观察者模式(基于公布订阅模式)有观察者,也有被观察者
观察者须要放到被观察者中,被观察者的状态变动须要告诉观察者 我变动了 外部也是基于公布订阅模式,收集观察者,状态变动后要被动告诉观察者
class Subject { // 被观察者 学生
constructor(name) {
this.state = 'happy'
this.observers = []; // 存储所有的观察者}
// 收集所有的观察者
attach(o){ // Subject. prototype. attch
this.observers.push(o)
}
// 更新被观察者 状态的办法
setState(newState) {
this.state = newState; // 更新状态
// this 指被观察者 学生
this.observers.forEach(o => o.update(this)) // 告诉观察者 更新它们的状态
}
}
class Observer{ // 观察者 父母和老师
constructor(name) {this.name = name}
update(student) {console.log('以后' + this.name + '被告诉了', '以后学生的状态是' + student.state)
}
}
let student = new Subject('学生');
let parent = new Observer('父母');
let teacher = new Observer('老师');
// 被观察者存储观察者的前提,须要先接收观察者
student. attach(parent);
student. attach(teacher);
student. setState('被欺侮了');
实现 some 办法
Array.prototype.mySome=function(callback, context = window){
var len = this.length,
flag=false,
i = 0;
for(;i < len; i++){if(callback.apply(context, [this[i], i , this])){
flag=true;
break;
}
}
return flag;
}
// var flag=arr.mySome((v,index,arr)=>v.num>=10,obj)
// console.log(flag);
实现一下 hash 路由
根底的 html
代码:
<html>
<style>
html, body {
margin: 0;
height: 100%;
}
ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
}
.box {
width: 100%;
height: 100%;
background-color: red;
}
</style>
<body>
<ul>
<li>
<a href="#red"> 红色 </a>
</li>
<li>
<a href="#green"> 绿色 </a>
</li>
<li>
<a href="#purple"> 紫色 </a>
</li>
</ul>
</body>
</html>
简略实现:
<script>
const box = document.getElementsByClassName('box')[0];
const hash = location.hash
window.onhashchange = function (e) {const color = hash.slice(1)
box.style.background = color
}
</script>
封装成一个 class:
<script>
const box = document.getElementsByClassName('box')[0];
const hash = location.hash
class HashRouter {constructor (hashStr, cb) {
this.hashStr = hashStr
this.cb = cb
this.watchHash()
this.watch = this.watchHash.bind(this)
window.addEventListener('hashchange', this.watch)
}
watchHash () {let hash = window.location.hash.slice(1)
this.hashStr = hash
this.cb(hash)
}
}
new HashRouter('red', (color) => {box.style.background = color})
</script>
版本号排序的办法
题目形容: 有一组版本号如下 ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']
。当初须要对其进行排序,排序的后果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']
arr.sort((a, b) => {
let i = 0;
const arr1 = a.split(".");
const arr2 = b.split(".");
while (true) {const s1 = arr1[i];
const s2 = arr2[i];
i++;
if (s1 === undefined || s2 === undefined) {return arr2.length - arr1.length;}
if (s1 === s2) continue;
return s2 - s1;
}
});
console.log(arr);
手写深度比拟 isEqual
思路:深度比拟两个对象,就是要深度比拟对象的每一个元素。=> 递归
-
递归退出条件:
- 被比拟的是两个值类型变量,间接用“===”判断
- 被比拟的两个变量之一为
null
,直接判断另一个元素是否也为null
-
提前结束递推:
- 两个变量
keys
数量不同 - 传入的两个参数是同一个变量
- 两个变量
- 递推工作:深度比拟每一个
key
function isEqual(obj1, obj2){
// 其中一个为值类型或 null
if(!isObject(obj1) || !isObject(obj2)){return obj1 === obj2;}
// 判断是否两个参数是同一个变量
if(obj1 === obj2){return true;}
// 判断 keys 数是否相等
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if(obj1Keys.length !== obj2Keys.length){return false;}
// 深度比拟每一个 key
for(let key in obj1){if(!isEqual(obj1[key], obj2[key])){return false;}
}
return true;
}
实现 Array.isArray 办法
Array.myIsArray = function(o) {return Object.prototype.toString.call(Object(o)) === '[object Array]';
};
console.log(Array.myIsArray([])); // true
手写防抖函数
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。
// 函数防抖的实现
function debounce(fn, wait) {
let timer = null;
return function() {
let context = this,
args = arguments;
// 如果此时存在定时器的话,则勾销之前的定时器从新记时
if (timer) {clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {fn.apply(context, args);
}, wait);
};
}
设计一个办法提取对象中所有 value 大于 2 的键值对并返回最新的对象
实现:
var obj = {a: 1, b: 3, c: 4}
foo(obj) // {b: 3, c: 4}
办法有很多种,这里提供一种比拟简洁的写法,用到了 ES10
的Object.fromEntries()
:
var obj = {a: 1, b: 3, c: 4}
function foo (obj) {
return Object.fromEntries(Object.entries(obj).filter(([key, value]) => value > 2)
)
}
var obj2 = foo(obj) // {b: 3, c: 4}
console.log(obj2)
// ES8 中 Object.entries()的作用:var obj = {a: 1, b: 2}
var entries = Object.entries(obj); // [['a', 1], ['b', 2]]
// ES10 中 Object.fromEntries()的作用:Object.fromEntries(entries); // {a: 1, b: 2}
实现 call 办法
call 做了什么:
- 将函数设为对象的属性
- 执行和删除这个函数
- 指定
this
到函数并传入给定参数执行函数 - 如果不传入参数,默认指向为
window
// 模仿 call bar.mycall(null);
// 实现一个 call 办法:// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()
Function.prototype.myCall = function(context = window, ...args) {if (typeof this !== "function") {throw new Error('type error')
}
// this-->func context--> obj args--> 传递过去的参数
// 在 context 上加一个惟一值不影响 context 上的属性
let key = Symbol('key')
context[key] = this; // context 为调用的上下文,this 此处为函数,将这个函数作为 context 的办法
// let args = [...arguments].slice(1) // 第一个参数为 obj 所以删除, 伪数组转为数组
// 绑定参数 并执行函数
let result = context[key](...args);
// 革除定义的 this 不删除会导致 context 属性越来越多
delete context[key];
// 返回后果
return result;
};
// 用法:f.call(obj,arg1)
function f(a,b){console.log(a+b)
console.log(this.name)
}
let obj={name:1}
f.myCall(obj,1,2) // 否则 this 指向 window
查找文章中呈现频率最高的单词
function findMostWord(article) {
// 合法性判断
if (!article) return;
// 参数解决
article = article.trim().toLowerCase();
let wordList = article.match(/[a-z]+/g),
visited = [],
maxNum = 0,
maxWord = "";
article = "" + wordList.join(" ") +" ";
// 遍历判断单词呈现次数
wordList.forEach(function(item) {if (visited.indexOf(item) < 0) {
// 退出 visited
visited.push(item);
let word = new RegExp("" + item +" ","g"),
num = article.match(word).length;
if (num > maxNum) {
maxNum = num;
maxWord = item;
}
}
});
return maxWord + " " + maxNum;
}
原生实现
function ajax() {let xhr = new XMLHttpRequest() // 实例化,以调用办法
xhr.open('get', 'https://www.google.com') // 参数 2,url。参数三:异步
xhr.onreadystatechange = () => { // 每当 readyState 属性扭转时,就会调用该函数。if (xhr.readyState === 4) { //XMLHttpRequest 代理以后所处状态。if (xhr.status >= 200 && xhr.status < 300) { //200-300 申请胜利
let string = request.responseText
//JSON.parse() 办法用来解析 JSON 字符串,结构由字符串形容的 JavaScript 值或对象
let object = JSON.parse(string)
}
}
}
request.send() // 用于理论收回 HTTP 申请。不带参数为 GET 申请}
字符串查找
请应用最根本的遍从来实现判断字符串 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;
}
实现日期格式化函数
输出:
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
}
实现 Promise
var PromisePolyfill = (function () {
// 和 reject 不同的是 resolve 须要尝试开展 thenable 对象
function tryToResolve (value) {if (this === value) {
// 次要是避免上面这种状况
// let y = new Promise(res => setTimeout(res(y)))
throw TypeError('Chaining cycle detected for promise!')
}
// 依据标准 2.32 以及 2.33 对对象或者函数尝试开展
// 保障 S6 之前的 polyfill 也能和 ES6 的原生 promise 混用
if (value !== null &&
(typeof value === 'object' || typeof value === 'function')) {
try {
// 这里记录这次 then 的值同时要被 try 包裹
// 次要起因是 then 可能是一个 getter, 也也就是说
// 1. value.then 可能报错
// 2. value.then 可能产生副作用(例如屡次执行可能后果不同)
var then = value.then
// 另一方面, 因为无奈保障 then 的确会像预期的那样只调用一个 onFullfilled / onRejected
// 所以减少了一个 flag 来避免 resolveOrReject 被屡次调用
var thenAlreadyCalledOrThrow = false
if (typeof then === 'function') {
// 是 thenable 那么尝试开展
// 并且在该 thenable 状态扭转之前 this 对象的状态不变
then.bind(value)(
// onFullfilled
function (value2) {if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
tryToResolve.bind(this, value2)()}.bind(this),
// onRejected
function (reason2) {if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
resolveOrReject.bind(this, 'rejected', reason2)()}.bind(this)
)
} else {
// 领有 then 然而 then 不是一个函数 所以也不是 thenable
resolveOrReject.bind(this, 'resolved', value)()}
} catch (e) {if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
resolveOrReject.bind(this, 'rejected', e)()}
} else {
// 根本类型 间接返回
resolveOrReject.bind(this, 'resolved', value)()}
}
function resolveOrReject (status, data) {if (this.status !== 'pending') return
this.status = status
this.data = data
if (status === 'resolved') {for (var i = 0; i < this.resolveList.length; ++i) {this.resolveList[i]()}
} else {for (i = 0; i < this.rejectList.length; ++i) {this.rejectList[i]()}
}
}
function Promise (executor) {if (!(this instanceof Promise)) {throw Error('Promise can not be called without new !')
}
if (typeof executor !== 'function') {
// 非标准 但与 Chrome 谷歌保持一致
throw TypeError('Promise resolver' + executor + 'is not a function')
}
this.status = 'pending'
this.resolveList = []
this.rejectList = []
try {executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))
} catch (e) {resolveOrReject.bind(this, 'rejected', e)()}
}
Promise.prototype.then = function (onFullfilled, onRejected) {
// 返回值穿透以及谬误穿透, 留神谬误穿透用的是 throw 而不是 return,否则的话
// 这个 then 返回的 promise 状态将变成 resolved 即接下来的 then 中的 onFullfilled
// 会被调用, 然而咱们想要调用的是 onRejected
if (typeof onFullfilled !== 'function') {onFullfilled = function (data) {return data}
}
if (typeof onRejected !== 'function') {onRejected = function (reason) {throw reason}
}
var executor = function (resolve, reject) {setTimeout(function () {
try {
// 拿到对应的 handle 函数解决 this.data
// 并以此为根据解析这个新的 Promise
var value = this.status === 'resolved'
? onFullfilled(this.data)
: onRejected(this.data)
resolve(value)
} catch (e) {reject(e)
}
}.bind(this))
}
// then 承受两个函数返回一个新的 Promise
// then 本身的执行永远异步与 onFullfilled/onRejected 的执行
if (this.status !== 'pending') {return new Promise(executor.bind(this))
} else {
// pending
return new Promise(function (resolve, reject) {this.resolveList.push(executor.bind(this, resolve, reject))
this.rejectList.push(executor.bind(this, resolve, reject))
}.bind(this))
}
}
// for prmise A+ test
Promise.deferred = Promise.defer = function () {var dfd = {}
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
// for prmise A+ test
if (typeof module !== 'undefined') {module.exports = Promise}
return Promise
})()
PromisePolyfill.all = function (promises) {return new Promise((resolve, reject) => {const result = []
let cnt = 0
for (let i = 0; i < promises.length; ++i) {promises[i].then(value => {
cnt++
result[i] = value
if (cnt === promises.length) resolve(result)
}, reject)
}
})
}
PromisePolyfill.race = function (promises) {return new Promise((resolve, reject) => {for (let i = 0; i < promises.length; ++i) {promises[i].then(resolve, reject)
}
})
}
图片懒加载
能够给 img 标签对立自定义属性 data-src='default.png'
,当检测到图片呈现在窗口之后再补充src 属性,此时才会进行图片资源加载。
function lazyload() {const imgs = document.getElementsByTagName('img');
const len = imgs.length;
// 视口的高度
const viewHeight = document.documentElement.clientHeight;
// 滚动条高度
const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
for (let i = 0; i < len; i++) {const offsetHeight = imgs[i].offsetTop;
if (offsetHeight < viewHeight + scrollHeight) {const src = imgs[i].dataset.src;
imgs[i].src = src;
}
}
}
// 能够应用节流优化一下
window.addEventListener('scroll', lazyload);
基于 Promise.all 实现 Ajax 的串行和并行
基于 Promise.all 实现 Ajax 的串行和并行
- 串行:申请是异步的,须要期待上一个申请胜利,能力执行下一个申请
- 并行:同时发送多个申请「
HTTP
申请能够同时进行,然而 JS 的操作都是一步步的来的,因为 JS 是单线程」, 期待所有申请都胜利,咱们再去做什么事件?
Promise.all([axios.get('/user/list'),
axios.get('/user/list'),
axios.get('/user/list')
]).then(results => {console.log(results);
}).catch(reason => {});
Promise.all 并发限度及 async-pool 的利用
并发限度指的是,每个时刻并发执行的 promise 数量是固定的,最终的执行后果还是放弃与原来的
const delay = function delay(interval) {return new Promise((resolve, reject) => {setTimeout(() => {// if (interval === 1003) reject('xxx');
resolve(interval);
}, interval);
});
};
let tasks = [() => {return delay(1000);
}, () => {return delay(1003);
}, () => {return delay(1005);
}, () => {return delay(1002);
}, () => {return delay(1004);
}, () => {return delay(1006);
}];
/* Promise.all(tasks.map(task => task())).then(results => {console.log(results);
}); */
let results = [];
asyncPool(2, tasks, (task, next) => {task().then(result => {results.push(result);
next();});
}, () => {console.log(results);
});
const delay = function delay(interval) {return new Promise((resolve, reject) => {setTimeout(() => {resolve(interval);
}, interval);
});
};
let tasks = [() => {return delay(1000);
}, () => {return delay(1003);
}, () => {return delay(1005);
}, () => {return delay(1002);
}, () => {return delay(1004);
}, () => {return delay(1006);
}];
JS 实现 Ajax 并发申请管制的两大解决方案
tasks
:数组,数组蕴含很多办法,每一个办法执行就是发送一个申请「基于Promise
治理」
function createRequest(tasks, pool) {
pool = pool || 5;
let results = [],
together = new Array(pool).fill(null),
index = 0;
together = together.map(() => {return new Promise((resolve, reject) => {const run = function run() {if (index >= tasks.length) {resolve();
return;
};
let old_index = index,
task = tasks[index++];
task().then(result => {results[old_index] = result;
run();}).catch(reason => {reject(reason);
});
};
run();});
});
return Promise.all(together).then(() => results);
}
/* createRequest(tasks, 2).then(results => {
// 都胜利,整体才是胜利,按顺序存储后果
console.log('胜利 -->', results);
}).catch(reason => {
// 只有有也给失败,整体就是失败
console.log('失败 -->', reason);
}); */
function createRequest(tasks, pool, callback) {if (typeof pool === "function") {
callback = pool;
pool = 5;
}
if (typeof pool !== "number") pool = 5;
if (typeof callback !== "function") callback = function () {};
//------
class TaskQueue {
running = 0;
queue = [];
results = [];
pushTask(task) {
let self = this;
self.queue.push(task);
self.next();}
next() {
let self = this;
while (self.running < pool && self.queue.length) {
self.running++;
let task = self.queue.shift();
task().then(result => {self.results.push(result);
}).finally(() => {
self.running--;
self.next();});
}
if (self.running === 0) callback(self.results);
}
}
let TQ = new TaskQueue;
tasks.forEach(task => TQ.pushTask(task));
}
createRequest(tasks, 2, results => {console.log(results);
});