一、Promise场景题
01. 交通灯
function red() {
console.log('red')
}
function green() {
console.log('green')
}
function yellow() {
console.log('yellow')
}
const task = (timer, light) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
} else if (light === 'green') {
green()
} else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
}
const taskRunner = async () => {
await task(3000, 'red')
await task(1000, 'green')
await task(2000, 'yellow')
taskRunner()
}
taskRunner()
02. 封装异步的fetch,使用async await方式来使用
async function myFetchAsync(url, options) {
try {
//等 获取到数据
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return response;
} catch (error) {
console.error(error);
throw error;
}
}
03. repeat(console.log, 5, 1000)
const repeat = (cb, delay = 1000, times = 5) => {
/* 高阶函数 */
return (text) => {
/* 封装为 promise */
const asyncFn = () => {
return new Promise((resolve) => {
setTimeout(() => {
cb(text);
resolve();
}, delay);
})
}
/* 执行串:Promise.resolve().then(()=>a()).then(()=>b()) */
new Array(times).fill(asyncFn).reduce((pre, cur) => {
return pre.then(() => cur());
}, Promise.resolve())
}
}
const mockLog = repeat(console.log);
mockLog("Hello world!!")
04. 封装一个工具函数输入一个promiseA返回一个promiseB如果超过1s没返回则抛出异常如果正常则输出正确的值
function timeoutPromise(promise, timeout) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout} ms`));
}, timeout);
promise
.then((result) => {
clearTimeout(timer);
resolve(result);
})
.catch((error) => {
clearTimeout(timer);
reject(error);
});
});
}
05. 请求5s未完成就终止
// 方式一:
const funWait = (call, timeout = 5000) => {
let wait = new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时')
}, timeout)
})
return Promise.race([call(), wait])
}
const t = () => new Promise(resolve => setTimeout(resolve, 4000))
funWait(t).then(res => {
console.log("t1 resolve")
}).catch(err => {
console.log("t1 timeout")
})
// 方式二:
const abort = new AbortController();
let res = null;
fetch(url, {
signal: abort.signal
}).then(_res => {
res = _res;
})
setTimeout(() => {
if (!res) abort.abort();
}, 5000);
06. 每隔一秒打印1,2,3,4,5
// 1. let 块级作用域
for (let i = 1; i <= 5; i++){
setTimeout(()=>console.log(i),i*1000)
}
// 2. var+闭包 ---- setTimeout传参
for (var i = 1; i <= 5; i++){
setTimeout((i)=>console.log(i),i*1000,i)
}
// 3. var+闭包 ---- 闭包传参
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(() => {
console.log(j);
}, j * 1000);
})(i);
}
// 4. Promise
function delay(i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i);
resolve();
}, 1000);
});
}
async function generate() {
for (let i = 1; i <= 5; i++) {
await delay(i);
}
}
generate();
07. 使用Promise封装AJAX请求
function ajaxPromise(url, method, data) {
return new Promise((resolve, reject) => {
// 1.创建XMLHttpRequest对象
const xhr = new XMLHttpRequest();
// 2.与服务器建立连接
xhr.open(method, url, true);
// 3.给服务器发送数据
xhr.send(method === "POST" && data ? JSON.stringify(data) : null);
// 4.接收请求
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 300) {
resolve(JSON.parse(this.responseText));
} else {
reject(this.status);
}
}
});
});
}
08. 处理并发请求
/**
* 并发请求
* @param {string[]} urls 待请求的url数组
* @param {number} maxNum 最大并发数
*/
function concurRequest(urls, maxNum) {
return new Promise(reslove => {
if (urls.length === 0) {
reslove([]);
return;
}
let count = 0; // 记录完成的数量
let index = 0; // 下一个请求的下标
let res = []; // 存放结果
// 请求方法,每调用一次,就从urls从取出一个地址发送请求
async function request() {
if (index === urls.length) {
return;
}
const i = index; // 记录当前请求的下标,保证输出顺序
const url = urls[index++];
try {
let resp = await fetch(url);
res[i] = resp;
} catch (err) {
res[i] = err;
} finally {
if (++count === urls.length) {
reslove(res);
}
request();
}
}
const times = Math.min(maxNum, urls.length);
for (let i = 0; i < times; i++) {
request();
}
});
}
09. 实现有并行限制的 Promise 调度器
// Scheduler调度器:
class Scheduler {
constructor(max) {
// 最大可并发任务数
this.max = max;
// 当前并发任务数
this.count = 0;
// 阻塞的任务队列
this.queue = [];
}
async add(fn) {
if (this.count >= this.max) {
// 若当前正在执行的任务,达到最大容量max
// 阻塞在此处,等待前面的任务执行完毕后将resolve弹出并执行
await new Promise(resolve => this.queue.push(resolve));
}
// 当前并发任务数++
this.count++;
// 使用await执行此函数
const res = await fn();
// 执行完毕,当前并发任务数--
this.count--;
// 若队列中有值,将其resolve弹出,并执行
// 以便阻塞的任务,可以正常执行
this.queue.length && this.queue.shift()();
// 返回函数执行的结果
return res;
}
}
// 使用
// 延迟函数
const sleep = time => new Promise(resolve => setTimeout(resolve, time));
// 同时进行的任务最多2个
const scheduler = new Scheduler(2);
// 添加异步任务
// time: 任务执行的时间
// val: 参数
const addTask = (time, val) => {
scheduler.add(() => {
return sleep(time).then(() => console.log(val));
});
};
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// 2
// 3
// 1
// 4
10. Promise.retry 超时重新请求,并在重试一定次数依然失败时输出缓存内容
/**
* 超时重新请求,并在重试一定次数依然失败时输出缓存内容
* @param {*} promiseFactory 一个返回 Promise 的函数,表示要执行的异步操作。
* @param {*} maxRetries 一个整数,表示最大的重试次数。
* @param {*} timeout 一个整数,表示每次重试的间隔时间(单位为毫秒)。
* @param {*} cache 一个可选的参数,表示缓存的内容,如果重试多次依然失败,则会返回该缓存内容。
* @returns promise
*/
function retry(promiseFactory, maxRetries, timeout, cache=null) {
return new Promise((resolve, reject) => {
let retries = 0;
const executePromise = () => {
promiseFactory()
.then(resolve)
.catch((error) => {
retries++;
if (retries >= maxRetries) {
if (cache) {
resolve(cache);
} else {
reject(error);
}
} else {
setTimeout(executePromise, timeout);
}
});
};
executePromise();
});
}
二、实现 Promise 的一系列方法
01. Promise.all
Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组;如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
Promise._all = (iterObj) => {
if(!(typeof iterObj === "object" && iterObj !== null && typeof iterObj[Symbol.iterator] === "function")){
throw new TypeError(`${iterObj} is not iterable`);
}
iterObj = [...iterObj];
return new Promise((resolve, reject) => {
const len = iterObj.length;
let count = 0;
if(len === 0) return resolve([]);
const res = new Array(len);
iterObj.forEach(async (item, index) => {
const newItem = Promise.resolve(item);
try{
const result = await newItem;
res[index] = result;
if(++count === len){
resolve(res)
}
}catch(err){
reject(err);
}
})
})
}
02. Promise.prototype.catch
Promise 实例的 catch() 方法用于注册一个在 promise 被拒绝时调用的函数,它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 的方法。
此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。
/**
* 本质就是then,只是少传了一个onFulfilled
* 所以仅处理失败的场景
* @param {*} onRejected
*/
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
03. Promise.race
Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise,这个返回的 promise 会随着第一个 promise 的敲定而敲定。
Promise._race = function (promises) {
if(!(typeof promises === "object" && promises !== null && typeof promises[Symbol.iterator] === "function")){
throw new TypeError(`${promises} is not iterable`);
}
promises = [...promises];
return new Promise((resolve, reject) => {
for (let p of promises) {
Promise.resolve(p).then(resolve).catch(reject);
}
});
};
04. Promise.allSettled
Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。
Promise.prototype.mySettled = function (promises) {
return new Promise((resolve) => {
const data = [],
len = promises.length;
let cnt = 0;
for (let i = 0; i < len; i++) {
const promise = promises[i];
Promise.resolve(promise)
.then(
(res) => {
data[i] = { status: "fulfilled", value: res };
},
(error) => {
data[i] = { status: "rejected", reason: error };
}
)
.finally(() => {
if (cnt === len) resolve(data);
});
}
});
};
05. Promise.reject
Promise._reject = function(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
06. Promise.resolve
Promise.resolve() 静态方法将给定的值转换为一个 Promise,如果该值本身就是一个 Promise,那么该 Promise 将被返回;如果该值是一个 thenable 对象,Promise.resolve() 将调用其 then() 方法及其两个回调函数;否则,返回的 Promise 将会以该值兑现。
该函数将嵌套的类 Promise 对象(例如,一个将被兑现为另一个 Promise 对象的 Promise 对象)展平,转化为单个 Promise 对象,其兑现值为一个非 thenable 值。
Promise._resolve = function(value) {
// 如果 value 已经是 Promise 对象,则直接返回该 Promise 对象
if (value && value instanceof Promise) {
return value;
}
// 如果 value 是 thenable 对象,则包装成 Promise 对象并返回
if (value && typeof value.then === 'function') {
return new Promise(function(resolve, reject) {
value.then(resolve, reject);
});
}
// 将传入的值作为 Promise 的成功值,并返回 Promise 对象
return new Promise(function(resolve) {
resolve(value);
});
}
07. Promise.prototype.finally
Promise 实例的 finally() 方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 方法;这可以让你避免在 promise 的 then() 和 catch() 处理器中重复编写代码。
/**
* 无论成功还是失败都会执行回调
* @param {Function} onSettled
*/
Promise.prototype.finally = function (onSettled) {
return this.then(
(data) => {
onSettled(); // 实现了收不到参数了
return data;
},
(reason) => {
onSettled();
throw reason;
}
);
// finally函数 返回结果应该是无效的
}
三、数组原型上的方法
01. Array.prototype.push
let arr = [];
Array.prototype.push = function() {
for( let i = 0 ; i < arguments.length ; i++){
this[this.length] = arguments[i] ;
}
return this.length;
}
02. Array.prototype.map
Array.prototype._map = function (cb, thisBinding) {
// 排除回调非函数情况
if (typeof cb !== "function") {
throw new TypeError(`${cb} is not a function`);
}
// 排除this为非可迭代对象情况
if (this == null || typeof this[Symbol.iterator] !== "function") {
throw new TypeError(`${this} is not a iterable`);
}
// 将可迭代对象转换成数组
const array = [...this];
const result = [];
// 执行遍历并回调
for (let i = 0; i < array.length; i++) {
result.push(cb.call(thisBinding, array[i], i, this));
}
return result;
};
03. Array.prototype.forEach
Array.prototype._forEach = function (cb, thisBinding = globalThis) {
// 排除回调非函数情况
if (typeof cb !== "function") {
throw new TypeError(`${cb} is not a function`);
}
// 排除this为非可迭代对象情况
if (this == null || typeof this[Symbol.iterator] !== "function") {
throw new TypeError(`${this} is not a iterable`);
}
// 将可迭代对象转换成数组
const array = [...this];
// 执行遍历并回调
for (let i = 0; i < array.length; i++) {
cb.call(thisBinding, array[i], i, this);
}
};
04. Array.prototype.reduce
Array.prototype.myReduce = function (callback, ...args) {
let start = 0,
pre;
if (args.length) {
//有参数的话pre等于参数第0项
pre = args[0];
} else {
//没参数的话,默认从数组0项开始
pre = this[0];
start = 1;
}
for (let i = start; i < this.length; i++) {
pre = callback(pre, this[i], i, this);
}
return pre;
};
05. Array.prototype.unshift
Array.prototype.myUnshift = function (...items) {
this.reverse().push(...items.reverse())
this.reverse()
return this.length
}
06. Array.prototype.pop
Array.prototype.myPop = function() {
if(this == undefined){
throw new TypeError('this is null or not defined');
}
if(this.length == undefined){
this.length = 0;
return undefined;
}
const item = this[this.length-1];
this.length--;
if(!Array.isArray(this)){
delete this[this.length];
}
return item;
}
07. Array.prototype.shift
Array.prototype.myShift = function() {
if(this == undefined){
throw new TypeError('this is null or not defined');
}
if(this.length = undefined){
this.length = 0;
return undefined;
}
const item = this[0],
len = this.length;
for(let i = 0; i < len - 1; i++){
this[i] = this[i+1];
}
this.length--;
if(!Array.isArray(this)){
delete this[this.length];
}
return item;
}
四、JavaScript原理题
01. async await 如何实现
function asyncToGenerator(generatorFunc) {
//传入一个生成器函数
//返回一个新的函数
return function () {
//先调用generator函数生成<迭代器>
const gen = generatorFunc.apply(this, arguments);
//返回一个promise
return new Promise((resolve, reject) => {
//内部定义一个step函数来源 用来一步步跨过yield的阻碍
//key有next和throw两种取值,分别对应了gen的next和throw方法
//arg参数则是用来promise resolve得带的值交给下一个yield
function step(key, arg) {
let generatorResult;
try {
generatorResult = gen[key](arg);
} catch (err) {
return reject(err);
}
//gen.next()得到的结果是一个{value,done}的结构
const { value, done } = generatorResult;
if (done) {
//已经完成
return resolve(value);
} else {
return Promise.resolve(
//对value不是promise的情况包裹一层
value //这个value对应的是yield后面的promise
).then(
function onResolve(val) {
step("next", val);
},
function onReject(err) {
step("throw", err);
}
);
}
}
step("next"); //第一次调用next
});
};
}
function fn(nums) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(nums * 2);
}, 1000);
});
}
function* gen() {
const num1 = yield fn(1);
console.log(num1); // 2
const num2 = yield fn(num1);
console.log(num2); // 4
const num3 = yield fn(num2);
console.log(num3); // 8
return num3;
}
const testGAsync = asyncToGenerator(gen);
// 返回的是一个函数,函数调用返回一个promise
testGAsync().then(res => {
console.log(res);
});
//对应上面的gen()
async function asyncFn() {
const num1 = await fn(1);
console.log(num1); // 2
const num2 = await fn(num1);
console.log(num2); // 4
const num3 = await fn(num2);
console.log(num3); // 8
return num3;
}
asyncFn()
02. 手写vue2响应式
let data = {
name: "hdf",
age: 19,
friend: {
name: "zwl"
}
};
//变成响应式数据
observer(data);
function observer(target) {
if (!target || typeof target == "object") {
return target;
}
for (let key in target) {
defineReactive(target, key, target[key]);
}
}
function defineReactive(target, key, value) {
//深度观察
observer(value);
Object.defineProperty(target, key),
{
get() {
return value;
},
set(newValue) {
observer(newValue);
if (newValue !== value) {
value = newValue;
console.log("更新视图");
}
}
};
}
03. 手写v-model简易版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<input type="text" id="myInput">
<h1 id="myTitle"></h1>
</div>
</body>
<script>
let userinfo = {
username: '小明',
};
//开始监控
watcher();
function watcher() {
Object.defineProperty(userinfo, "username", {
set(value) {
updateDom(value);
},
get(val) {
return val;
},
});
}
//更新dom数据
function updateDom(value) {
document.querySelector('#myInput').value = value;
document.querySelector('#myTitle').innerHTML = value;
}
//给input绑定input事件,实时修改username的值
document.querySelector('#myInput').oninput = function (e) {
let value = e.target.value;
userinfo.username = value;
}
</script>
</html>
04. 手写call apply bind
Function.prototype.myCall = function(ctx, ...args) {
// 使用globalThis的原因,node环境没有window
ctx = (ctx === undefined || ctx === null) ? globalThis : Object(ctx)
// 生成唯一的key,防止污染对象中的属性
let key = Symbol('temp')
// this表示调用的函数 比如 fun.call(obj, 2) this就是fun
// 原理:在obj中添加一个属性(fun),然后通过obj.fun的形式调用,谁调用this就指向谁,使得fun里的this指向obj
Object.defineProperty(ctx, key, {
enumerable: fasle,
value: this
})
// 执行函数,拿到结果
let result = ctx[key](...args)
// 删除属性
delete ctx[key]
return result
}
Function.prototype.myApply = function(ctx, args) {
ctx = (ctx === undefined || ctx === null) ? globalThis : Object(ctx)
let key = Symbol('temp')
Object.defineProperty(ctx, key, {
enumerable: fasle,
value: this
})
let result = ctx[key](...args)
delete ctx[key]
return result
}
Function.prototype.myBind = function(context, ...args1) {
ctx = (ctx === undefined || ctx === null) ? globalThis : Object(ctx)
let _this = this
return function(...args2) {
let key = Symbol('temp')
Object.defineProperty(ctx, key, {
enumerable: fasle,
value: this
})
let result = ctx[key](...[...args1, ...args2])
delete ctx[key]
return result
}
}
05. 节流防抖
防抖:在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时
// 简易版
function debounce(func, wait) {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
// 立即执行版
// 有时希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this;
const args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
} else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
}
}
// 返回值版实现
function debounce(func, wait, immediate) {
let timeout, result;
return function () {
const context = this;
const args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
return result;
}
}
节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效
// 使用时间戳实现
function throttle(func, wait) {
let previous = 0;
return function () {
const context = this;
const args = arguments;
let now = +new Date();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
// 使用定时器实现
function throttle(func, wait) {
let timeout;
return function () {
const context = this;
const args = arguments;
if (!timeout) {
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
06. 函数柯里化
function add(a, b, c) {
return a + b + c
}
function curry(fn) {
let judge = (...args1) => {
if (args1.length == fn.length) return fn(...args1)
return (...args2) => judge(...args1, ...args2)
}
return judge
}
let addCurry = curry(add)
const res1 = addCurry(1, 2)(3)
const res2 = addCurry(1)(2)(3)
07. 实现深拷贝
- 浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的属性值,因此修改拷贝后的属性值是引用类型的,就会影响源对象
- 深拷贝就是对对象以及对象的所有子对象进行拷贝
// 方式一 MessageChannel
function deepClone(obj) {
return new Promise(reslove => {
const { port1, port2 } = new MessageChannel();
port1.postMessage(obj);
port2.onmessage = msg => {
reslove(msg.data);
};
});
}
// 方式二 递归
const _completeDeepClone = (target, map = new WeakMap()) => {
// 基本数据类型,直接返回
if (typeof target !== 'object' || target === null) return target
// 函数 正则 日期 ES6新对象,执行构造题,返回新的对象
const constructor = target.constructor
if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return new constructor(target)
// map标记每一个出现过的属性,避免循环引用
if (map.get(target)) return map.get(target)
let cloneTarget = Array.isArray(target) ? [] : {}
map.set(target, cloneTarget)
for (prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = _completeDeepClone(target[prop], map)
}
}
return cloneTarget
}
08. 模拟一个微任务
function asyncRun(func) {
if (typeof Promise !== 'undefined') {
Promise.resolve().then(func);
} else if (typeof MutationObserver !== 'undefined') {
const ob = new MutationObserver(func);
const textNode = document.createTextNode('0');
ob.observe(textNode, {
characterData: true,
});
textNode.data = '1';
} else {
setTimeout(func);
}
}
09. 实现发布订阅模式
class EventEmitter {
constructor() {
// key: 事件名
// value: callback [] 回调数组
this.events = {}
}
on(name, callback) {
if (this.events[name]) {
this.events[name].push(callback)
} else {
this.events[name] = [callback]
}
}
off(name, callback) {
if (!this.events[name]) return;
if (!callback) {
// 如果没有callback,就删掉整个事件
this.events[name] = undefined;
}
this.events[name] = this.events[name].filter((item) => item !== callback);
}
emit(name, ...args) {
if (!this.events[name]) return
this.events[name].forEach(cb => cb(...args))
}
}
10. 实现 instanceof 方法
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
11. 实现 new 操作符
function myNew(constructorFn, ...args) {
let newObj = {}
newObj.__proto__ = constructorFn.prototype;
// newObj = Object.create(constructor.prototype);
let result = constructorFn.apply(newObj, args)
return result instanceof Object ? result : newObj
}
12. 树形结构转成列表
const data = [
{
id: 1,
text: '节点1',
parentId: 0,
children: [
{
id: 2,
text: '节点1_1',
parentId: 1
}
]
}
]
function treeToList(data) {
let res = [];
const dfs = (tree) => {
tree.forEach((item) => {
if (item.children) {
dfs(item.children);
delete item.children;
}
res.push(item);
});
};
dfs(data);
return res;
}
13. 列表转成树形结构
let arr = [
{ id: 1, name: '部门1', pid: 0 },
{ id: 2, name: '部门2', pid: 1 },
{ id: 3, name: '部门3', pid: 1 },
{ id: 4, name: '部门4', pid: 3 },
{ id: 5, name: '部门5', pid: 4 },
{ id: 6, name: '部门6', pid: 0 },
]
function getTreeList(rootList, pid, list) {
for (const item of rootList) {
if (item.pid === pid) {
list.push(item);
}
}
for (const i of list) {
i.children = [];
getTreeList(rootList, i.id, i.children);
if (i.children.length === 0) {
delete i.children;
}
}
return list;
}
const res = getTreeList(arr, 0, []);
14. 扁平化数组 || Array.prototype._flat
(1)递归实现
const arr = [1, [2, [3, [4, [5, [6, [7, [8, [9], 10], 11], 12], 13], 14], 15], 16]]
Array.prototype.flat = function (deep = 1) {
let res = []
// 完全展开
if (deep === 'Infinity') {
this.forEach((item) => {
if (Array.isArray(item)) {
res = res.concat(item.flat());
} else {
res.push(item)
}
})
} else {
// 展开指定层数
deep--
this.forEach(item => {
if (Array.isArray(item) && deep >= 0) {
res = res.concat(item.flat(deep))
} else {
res.push(item)
}
})
return res
}
}
console.log('展开一层', arr.flat(1))
console.log('完全展开', arr.flat(Infinity))
(2)reduce 函数
const arr = [1, 2, [3, 4, [5, 6]]]
Array.prototype._flat = function (depth = Infinity) {
--depth
return this.reduce((prev, cur) => {
if(Array.isArray(cur) && depth >= 0) {
prev = prev.concat(cur._flat(depth))
} else {
prev.push(cur)
}
return prev
}, [])
}
console.log(arr._flat())
console.log(arr._flat(1))
(3)扩展运算符实现
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
(4)split 和 toString
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',');
}
16. 实现 lodash 的_.get
在 js 中经常会出现嵌套调用这种情况,如 a.b.c.d.e,但是这么写很容易抛出异常。你需要这么写 a && a.b && a.b.c && a.b.c.d && a.b.c.d.e,但是显得有些啰嗦与冗长了。特别是在 graphql 中,这种嵌套调用更是难以避免。 这时就需要一个 get 函数,使用 get(a, 'b.c.d.e') 简单清晰,并且容错性提高了很多。
function get(source, path, defaultValue = undefined) {
// a[3].b -> a.3.b -> [a,3,b]
// path 中也可能是数组的路径,全部转化成 . 运算符并组成数组
const paths = path.replace(/[(\d+)]/g, ".$1").split(".");
let result = source;
for (const p of paths) {
// 注意 null 与 undefined 取属性会报错,所以使用 Object 包装一下。
result = Object(result)[p];
if (result == undefined) {
return defaultValue;
}
}
return result;
}
五、场景题大全
01. 交换a,b的值,不能用临时变量
a = a + b
b = a - b
a = a - b
02. 实现数组的乱序输出
洗牌算法
- 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行交换。
- 第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行交换
- 按照上面的规律执行,直到遍历完成
function shuffleSelf(array, size) {
let index = -1,
length = array.length,
lastIndex = length - 1
size = size === undefined ? length : size
while (++index < size) {
let rand = index + Math.floor( Math.random() * (lastIndex - index + 1)) //含最大值,含最小值
value = array[rand]
array[rand] = array[index]
array[index] = value
}
array.length = size
return array
}
03. 实现数组元素求和
(1)for 循环
function sum(arr) {
let s = 0
for (let i = 0; i < arr.length; i++) {
s += arr[i]
}
return s
}
(2)forEach 遍历
function sum(arr) {
let s = 0
arr.forEach(function(val, idx, arr) {
s += val
}, 0)
return s
}
(3)eval 函数
function sum(arr) {
return eval(arr.join("+"));
}
(4)reduce 方法
let sum = arr.reduce( (total,i) => total += i,0)
(5)递归实现
function add(arr) {
if (arr.length == 1) return arr[0]
return arr[0] + add(arr.slice(1))
}
05. 实现数组去重
(1)Set
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
(2)filter() + indexOf
let arr = ['apple','apps','pear','apple','orange','apps']
let newArr = arr.filter(function(item,index){
return arr.indexOf(item) === index // 因为indexOf 只能查找到第一个
})
(3)indexOf
let arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22]
function noRepeat(arr) {
let newArr=[]
for(let i=0;i<arr.length;i++) {
if(newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i])
}
}
return newArr
}
(4)includes
let arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22]
function noRepeat(arr) {
let newArr = []
for(let i = 0; i<arr.length; i++){
if(!newArr.includes(arr[i])){
newArr.push(arr[i])
}
}
return newArr
}
(5)Map
let arr = ['blue', 'green', 'blue', 'yellow', 'black', 'yellow', 'blue', 'green']
let unique = (arr)=> {
let seen = new Map()
return arr.filter((item) => {
return !seen.has(item) && seen.set(item,1)
})
}
06. 实现字符串翻转
const reverse = (a) => {
return a.split("").reverse().join("");
}
07. 用proxy 实现 arr[-1] 的访问
let arr= [1,2,3,4]
let proxy = new Proxy(arr,{
get(target,key){
if(key<0){
return target[target.length+parseInt(key)]
}
return target[key]
}
})
08. 实现一个 sleep() 函数
// 1. Promise
const sleep = time => {
return new Promise(resolve => setTimeout(resolve,time))
}
sleep(1000).then(()=>{
console.log(1)
})
// 2. Generator
function* sleepGenerator(time) {
yield new Promise(function(resolve,reject){
setTimeout(resolve,time);
})
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)})
// 3. async
function sleep(time) {
return new Promise(resolve => setTimeout(resolve,time))
}
async function output() {
let out = await sleep(1000);
console.log(1);
return out;
}
output();
// 4. ES5
function sleep(callback,time) {
if(typeof callback === 'function')
setTimeout(callback,time)
}
function output(){
console.log(1);
}
sleep(output,1000);
09. 原始 list 转换成树形结构
// 原始 list 如下
let list =[
{id:1,name:'部门A',parentId:0},
{id:2,name:'部门B',parentId:0},
{id:3,name:'部门C',parentId:1},
{id:4,name:'部门D',parentId:1},
{id:5,name:'部门E',parentId:2},
{id:6,name:'部门F',parentId:3},
{id:7,name:'部门G',parentId:2},
{id:8,name:'部门H',parentId:4}
];
const result = convert(list, ...);
// 转换后的结果如下
let result = [
{
id: 1,
name: '部门A',
parentId: 0,
children: [
{
id: 3,
name: '部门C',
parentId: 1,
children: [
{
id: 6,
name: '部门F',
parentId: 3
}, {
id: 16,
name: '部门L',
parentId: 3
}
]
},
{
id: 4,
name: '部门D',
parentId: 1,
children: [
{
id: 8,
name: '部门H',
parentId: 4
}
]
}
]
},
···
];
const convert = (list) => {
const res = []
// 将list中的每一项放到一个新的对象当中
const map = list.reduce((res, v) => (res[v.id] = v, res), {})
for (const item of list) {
if (item.parentId === 0) {
res.push(item)
continue
}
if (item.parentId in map) {
const parent = map[item.parentId]
parent.children = parent.children || []
parent.children.push(item)
}
}
return res
}
10. 千分位转化
function regexHandleNum(num) {
return String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ','); // 3是千分位,4是万分位
}
function handleNum(num) {
return String(num)
.split('')
.reverse()
.reduce((prev, next, index) => {
return (index % 3 ? next : next + ',') + prev; // 3是千分位,4是万分位
});
}
11. 实现 (5).add(3).minus(2) 功能
Number.prototype.add = function (number) {
if (typeof number !== 'number') {
throw new Error('请输入数字~');
}
return this + number;
};
Number.prototype.minus = function (number) {
if (typeof number !== 'number') {
throw new Error('请输入数字~');
}
return this - number;
};
console.log((5).add(3).minus(2));
12. 按字母顺序合并两个数组
请把两个数组 [A1, A2, B1, B2, C1, C2, D1, D2] 和 [A, B, C, D],合并为 [A1, A2, A, B1, B2, B, C1, C2, C, D1, D2, D]。
const arr1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
const arr2 = ['A', 'B', 'C', 'D']
const ret = []
let tmp = arr2[0]
let j = 0
for (let i=0;i<arr1.length;i++) {
if (tmp === arr1[i].charAt(0)){
ret.push(arr1[i])
}else {
ret.push(tmp)
ret.push(arr1[i])
tmp=arr2[++j]
}
if(i===arr1.length-1){
ret.push(tmp)
}
}
console.log(ret)
13. 转驼峰命名法
let str = 'get-element-by-id'
const transFrom = (str) => {
let arr = str.split('-')
for (let i = 1; i < arr.length; i++) {
arr[i] = arr[i][0].toUpperCase() + arr[i].substring(1, arr[i].length)
}
return arr.join('')
}
14. URL参数解析为对象
function getURLParams(url) {
let params = {};
url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(match, key, value) {
key = decodeURIComponent(key);
value = decodeURIComponent(value);
if (obj[key]) {
if(Array.isArray(params[key])) {
params[key].push(value);
} else {
params[key] = [params[key], value];
}
} else {
params[key] = value;
}
});
return params;
}
let url = "http://example.com/?param1=abc¶m2=def";
console.log(getURLParams(url)); // 输出: { param1: 'abc', param2: 'def' }
15. 输出页面所有标签名称
获取当前页面中所有 HTML tag 的 名字,以数组形式输出, 重复的标签不重复输出(不考虑 iframe 和 shadowDOM)
1. 借助API
function getAllHTMLTags() {
let set = new Set()
const tags = [...window.document.querySelectorAll('*')]
tags.forEach((dom) => {
if (!set.has(dom.tagName)) {
set.add(dom.tagName)
}
})
return [...set]
}
2. 递归:这里利用了DOM结点的nodeType属性
/*
如果节点是一个元素节点,nodeType 属性返回 1
如果节点是属性节点, nodeType 属性返回 2
如果节点是一个文本节点,nodeType 属性返回 3
如果节点是一个注释节点,nodeType 属性返回 8
*/
let res = []
function getAllHTMLTags(node) {
if (node.nodeType === 1) {
let tagName = node.nodeName
res.push(tagName)
}
let children = node.childNodes;
for (let i = 0; i < children.length; i++) {
getAllHTMLTags(children[i])
}
}
getAllHTMLTags(document)
console.log([...new Set(res)])
16. 实现链式调用
链式调用的核心就在于调用完的方法将自身实例返回
function Class1() {
console.log('初始化')
}
Class1.prototype.method = function(param) {
console.log(param)
return this
}
let cl = new Class1()
cl.method('第一次调用').method('第二次链式调用').method('第三次链式调用')
17. 实现两个超过整数存储范围的大整数相加
// a,b为字符串,返回也为字符串
function sum(a, b) {
const len = Math.max(a.length, b.length)
a = a.padStart(len, '0')
b = b.padStart(len, '0')
let carry = 0
let result = ''
for (let i = len - 1;i >= 0; i--) {
const sum = +a[i] + +b[i] + carry
result = (sum % 10) + result
carry = Math.floor(sum / 10)
}
if (carry) {
result = carry + result
}
return result
}
18. 手写 dom 操作,翻转 li 标签,如何处理更优
// (1)拼接html字符串,然后一次性插入ul中
const oUl = document.getElementById('root');
const aLi = Array.from(oUl.getElementsByTagName('li'));
let str = '';
for (let index = aLi.length - 1; index >= 0; index--) {
str += `<li>${aLi[index].innerHTML}</li>`;
}
oUl.innerHTML = str;
// (2)使用文档片段
// ownerDocument:返回当前节点所在的顶层文档对象
// node.lastChild:返回最后一个子元素
function reverseChildNodes(node = document) {
const frag = node.ownerDocument.createDocumentFragment();
while(node.lastChild) {
// 每次取出最后一个子节点也会将该节点从源节点中移除,并且更新lastChild
frag.appendChild(node.lastChild);
}
// 将文档碎片直接插入到node节点下
node.appendChild(frag);
}
const oUl = document.getElementById('root');
reverseChildNodes(oUl);
19. 实现输出一个十六进制的随机颜色
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getColor() {
let result = new Array(6)
let i = 0
let hexMap = ['a', 'b', 'c', 'd', 'e', 'f']
while (i < 6) {
let data = getRandomInt(0, 16)
result[i] = data > 10 ? hexMap[data % 10] : data
i++
}
return `#${result.join('')}`
}
20. 设计一个函数,奇数次执行的时候打印 1,偶数次执行的时候打印 2
function countFn() {
let count = 0;
return function (...args) {
count++;
if (count & 1 === 1) return console.log(1);
console.log(2);
}
}
const testFn = countFn();
testFn(); // 1
testFn(); // 2
testFn(); // 1
testFn(); // 2