反转字符串 ‘I love you’
将‘I love you’变为’you love I‘
关键点用join把数组转变为字符串(join可以指定字符串的连接符)
const str = 'i love you';
const temp = str.split(' ');
temp.reverse();
console.log(temp.join(' ')) // you love i
将[1,2,3,4,5,6,7,8,9,10]转变为二维数组[[1,2,3],[4,5,6],[7,8,9],[10]]
思路: 将一维数组每隔三项放一起,可以利用arr.slice(i, i+3),所以关键是slice的索引设置,这时候用forEach和map不合适因为不需要对每一项遍历,所以使用for循环
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
const result = [];
// 循环的i每次都+3
for (let i = 0; i <= arr.length - 1; i += 3) {
result.push(arr.slice(i, i + 3))
}
console.log(result) // [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ], [ 10, 11, 12 ], [ 13 ] ]
简短优雅地利用js实现 sleep 函数
最佳方案:利用Promise()结合setTimeout 实现sleep 然后在async await中使用
const log = console.log;
const sleep = (timeout) => {
return new Promise((resolve)=>{
setTimeout(()=>{
resolve();
}, timeout)
})
}
const main = async()=>{
await sleep(1000);
log(1);
await sleep(2000);
log(2);
await sleep(3000);
log(3);
}
字符串替换
str='aa{a.b}ccdd{a.c}aa123{a.d}',obj={b: 789,c:56}, 输出'aa789ccdd56aa123',即括号里的用obj里面的值替换?
function parseString(str, obj) {
Object.keys(obj).forEach(key => {
str = str.replace(new RegExp(`{a.${key}}`, 'g'), obj[key]);
});
return str;
}
字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在则返回 -1。
示例 1:
输入: s = "leetcode"
输出: 0
示例 2:
输入: s = "loveleetcode"
输出: 2
示例 3:
输入: s = "aabb"
输出: -1
// 遍历字符串的每一项 判断lastIndexOf 和 indexOf这两项是否相等, 如果相等就证明是第一个唯一的字符串
var firstUniqChar = function(s) {
for(let i=0;i<s.length;i++){
if(s.lastIndexOf(s[i])==s.indexOf(s[i])){
let a = s.indexOf(s[i])
return a;
}
}return -1;
};
console.log(firstUniqChar("loveleetcode"));
6.罗马数字转整数
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = "III"
输出: 3
示例 2:
输入: s = "IV"
输出: 4
示例 3:
输入: s = "IX"
输出: 9
示例 4:
输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
var romanToInt = function(s) {
var num1 = 0;
var num2 = 0;
var value = 0;
for(var i=s.length;i>=0;i--){
switch(s[i]){
case 'I':
num1 = 1;
break;
case 'V':
num1 = 5;
break;
case 'X':
num1 = 10;
break;
case 'L':
num1 = 50;
break;
case 'C':
num1 = 100;
break;
case 'D':
num1 = 500;
break;
case 'M':
num1 = 1000;
break;
}
if(num2 <= num1){
value += num1;
}else{
value -= num1;
}
num2 = num1;
}
return value;
};
1 compose
题目描述:实现一个 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(...fn) {
if (!fn.length) return (v) => v;
if (fn.length === 1) return fn[0];
// 相当于每一个函数的执行结果又作为下一个函数的参数
return fn.reduce(
(pre, cur) =>
(...args) =>
pre(cur(...args))
);
}
复制代码
2 settimeout 模拟实现 setinterval(带清除定时器的版本)
题目描述:setinterval 用来实现循环定时调用 可能会存在一定的问题 能用 settimeout 解决吗
实现代码如下:
function mySettimeout(fn, t) {
let timer = null;
function interval() {
fn();
timer = setTimeout(interval, t);
}
interval();
return {
cancel:()=>{
clearTimeout(timer)
}
}
}
// let a=mySettimeout(()=>{
// console.log(111);
// },1000)
// let b=mySettimeout(() => {
// console.log(222)
// }, 1000)
复制代码
扩展:我们能反过来使用 setinterval 模拟实现 settimeout 吗?
const mySetTimeout = (fn, time) => {
const timer = setInterval(() => {
clearInterval(timer);
fn();
}, time);
};
// mySetTimeout(()=>{
// console.log(1);
// },1000)
复制代码
扩展思考:为什么要用 settimeout 模拟实现 setinterval?setinterval 的缺陷是什么?
答案请自行百度哈 这个其实面试官问的也挺多的 小编这里就不展开了
3 发布订阅模式
题目描述:实现一个发布订阅模式拥有 on emit once off 方法
实现代码如下:
class EventEmitter {
constructor() {
this.events = {};
}
// 实现订阅
on(type, callBack) {
if (!this.events[type]) {
this.events[type] = [callBack];
} else {
this.events[type].push(callBack);
}
}
// 删除订阅
off(type, callBack) {
if (!this.events[type]) return;
this.events[type] = this.events[type].filter((item) => {
return item !== callBack;
});
}
// 只执行一次订阅事件
once(type, callBack) {
function fn() {
callBack();
this.off(type, fn);
}
this.on(type, fn);
}
// 触发事件
emit(type, ...rest) {
this.events[type] &&
this.events[type].forEach((fn) => fn.apply(this, rest));
}
}
// 使用如下
// const event = new EventEmitter();
// const handle = (...rest) => {
// console.log(rest);
// };
// event.on("click", handle);
// event.emit("click", 1, 2, 3, 4);
// event.off("click", handle);
// event.emit("click", 1, 2);
// event.once("dbClick", () => {
// console.log(123456);
// });
// event.emit("dbClick");
// event.emit("dbClick");
复制代码
4 数组去重
实现代码如下:
function uniqueArr(arr) {
return [...new Set(arr)];
}
复制代码
5 数组扁平化
题目描述:实现一个方法使多维数组变成一维数组
最常见的递归版本如下:
function flat(arr, depth = 1) {
if (depth > 0) {
// 以下代码还可以简化,不过为了可读性,还是....
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur);
}, []);
}
return arr.slice();
}
复制代码
扩展思考:能用迭代的思路去实现吗?
实现代码如下:
function flatter(arr) {
if (!arr.length) return;
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));
复制代码
6 寄生组合继承
题目描述:实现一个你认为不错的 js 继承方式
实现代码如下:
function Parent(name) {
this.name = name;
this.say = () => {
console.log(111);
};
}
Parent.prototype.play = () => {
console.log(222);
};
function Children(name) {
Parent.call(this);
this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
// let child = new Children("111");
// // console.log(child.name);
// // child.say();
// // child.play();
复制代码
7 实现有并行限制的 Promise 调度器
题目描述:JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个
addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的输出顺序是:2 3 1 4
整个的完整执行流程:
一开始1、2两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
复制代码
实现代码如下:
class Scheduler {
constructor(limit) {
this.queue = [];
this.maxCount = limit;
this.runCounts = 0;
}
add(time, order) {
const promiseCreator = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(order);
resolve();
}, time);
});
};
this.queue.push(promiseCreator);
}
taskStart() {
for (let i = 0; i < this.maxCount; i++) {
this.request();
}
}
request() {
if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
return;
}
this.runCounts++;
this.queue
.shift()()
.then(() => {
this.runCounts--;
this.request();
});
}
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();
复制代码
8 new 操作符
题目描述:手写 new 操作符实现
实现代码如下:
function myNew(fn, ...args) {
let obj = Object.create(fn.prototype);
let res = fn.call(obj, ...args);
if (res && (typeof res === "object" || typeof res === "function")) {
return res;
}
return obj;
}
用法如下:
// // function Person(name, age) {
// // this.name = name;
// // this.age = age;
// // }
// // Person.prototype.say = function() {
// // console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();
复制代码
9 call apply bind
题目描述:手写 call apply bind 实现
实现代码如下:
Function.prototype.myCall = function (context, ...args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this; //this指向调用call的函数
// 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了
return context[fn](...args);
};
// apply原理一致 只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this;
// 执行函数并返回结果
return context[fn](...args);
};
//bind实现要复杂一点 因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)
Function.prototype.myBind = (obj) => {
if (typeof this !== 'function') {
throw new Error()
}
const fn = this;
const args = [...arguments].slice(1)
return function Fn() {
return fn.apply(this instanceof Fn ? this : obj, args.concat(...arguments))
}
}
复制代码
10 深拷贝(考虑到复制 Symbol 类型)
题目描述:手写 new 操作符实现
实现代码如下:
function deepClone(obj, hash = new WeakMap()) {
if (hash.has(obj)) {
return hash.get(obj);
}
const target = Array.isArray(obj) ? [] : {};
hash.set(obj, target);
Object.keys(obj).forEach(item => {
if (typeof obj[item] === 'object') {
target[item] = deepClone(obj[item], hash);
} else {
target[item] = obj[item]
}
})
return target;
}
复制代码
11 instanceof
题目描述:手写 instanceof 操作符实现
实现代码如下:
function myInstanceof(left, right) {
while (true) {
if (left === null) {
return false;
}
if (left.__proto__ === right.prototype) {
return true;
}
left = left.__proto__;
}
}
复制代码
12 柯里化
题目描述:柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。
实现代码如下:
function currying(fn, ...args) {
const length = fn.length;
let allArgs = [...args];
const res = (...newArgs) => {
allArgs = [...allArgs, ...newArgs];
if (allArgs.length === length) {
return fn(...allArgs);
} else {
return res;
}
};
return res;
}
// 用法如下:
// const add = (a, b, c) => a + b + c;
// const a = currying(add, 1);
// console.log(a(2,3))
复制代码
13 冒泡排序--时间复杂度 n^2
题目描述:实现一个冒泡排序
实现代码如下:
function bubbleSort(arr) {
// 缓存数组长度
const len = arr.length;
// 外层循环用于控制从头到尾的比较+交换到底有多少轮
for (let i = 0; i < len; i++) {
// 内层循环用于完成每一轮遍历过程中的重复比较+交换
for (let j = 0; j < len - 1; j++) {
// 若相邻元素前面的数比后面的大
if (arr[j] > arr[j + 1]) {
// 交换两者
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
// 返回数组
return arr;
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));
复制代码
14 选择排序--时间复杂度 n^2
题目描述:实现一个选择排序
实现代码如下:
function selectSort(arr) {
// 缓存数组长度
const len = arr.length;
// 定义 minIndex,缓存当前区间最小值的索引,注意是索引
let minIndex;
// i 是当前排序区间的起点
for (let i = 0; i < len - 1; i++) {
// 初始化 minIndex 为当前区间第一个元素
minIndex = i;
// i、j分别定义当前区间的上下界,i是左边界,j是右边界
for (let j = i; j < len; j++) {
// 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 j
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 如果 minIndex 对应元素不是目前的头部元素,则交换两者
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
return arr;
}
// console.log(quickSort([3, 6, 2, 4, 1]));
复制代码
15 插入排序--时间复杂度 n^2
题目描述:实现一个插入排序
实现代码如下:
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
let j = i;
let target = arr[j];
while (j > 0 && arr[j - 1] > target) {
arr[j] = arr[j - 1];
j--;
}
arr[j] = target;
}
return arr;
}
// console.log(insertSort([3, 6, 2, 4, 1]));
复制代码
16 快排--时间复杂度 nlogn~ n^2 之间
题目描述:实现一个快排
实现思想: 任意选一个数字作为中间数,然后递归把所有小于中间数的放左边,大于的放右边,每个数字都重复这一步
实现代码如下:
function quickSort(arr) {
// 前面的if判断必须加 否则filter会报错 :Maximum call stack size exceeded
if (arr.length < 2) {
return arr;
}
const cur = arr[arr.length - 1];
const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);
const right = arr.filter((v) => v > cur);
return [...quickSort(left), cur, ...quickSort(right)];
}
// console.log(quickSort([3, 6, 2, 4, 1]));
复制代码
17 归并排序--时间复杂度 nlog(n)
题目描述:实现一个时间复杂度为 nlog(n)的排序算法
实现代码如下:
function merge(left, right) {
let res = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
res.push(left[i]);
i++;
} else {
res.push(right[j]);
j++;
}
}
if (i < left.length) {
res.push(...left.slice(i));
} else {
res.push(...right.slice(j));
}
return res;
}
function mergeSort(arr) {
if (arr.length < 2) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
// console.log(mergeSort([3, 6, 2, 4, 1]));
复制代码
18 二分查找--时间复杂度 log2(n)
题目描述:如何确定一个数在一个有序数组中的位置
实现代码如下:
function search(arr, target, start, end) {
let targetIndex = -1;
let mid = Math.floor((start + end) / 2);
if (arr[mid] === target) {
targetIndex = mid;
return targetIndex;
}
if (start >= end) {
return targetIndex;
}
if (arr[mid] < target) {
return search(arr, target, mid + 1, end);
} else {
return search(arr, target, start, mid - 1);
}
}
// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// const position = search(dataArr, 6, 0, dataArr.length - 1);
// if (position !== -1) {
// console.log(`目标元素在数组中的位置:${position}`);
// } else {
// console.log("目标元素不在数组中");
// }
复制代码
19 实现 LazyMan
题目描述:
实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan(“Hank”).eat(“supper”).sleepFirst(5)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
复制代码
实现代码如下:
class _LazyMan {
constructor(name) {
this.tasks = [];
const task = () => {
console.log(`Hi! This is ${name}`);
this.next();
};
this.tasks.push(task);
setTimeout(() => {
// 把 this.next() 放到调用栈清空之后执行
this.next();
}, 0);
}
next() {
const task = this.tasks.shift(); // 取第一个任务执行
task && task();
}
sleep(time) {
this._sleepWrapper(time, false);
return this; // 链式调用
}
sleepFirst(time) {
this._sleepWrapper(time, true);
return this;
}
_sleepWrapper(time, first) {
const task = () => {
setTimeout(() => {
console.log(`Wake up after ${time}`);
this.next();
}, time * 1000);
};
if (first) {
this.tasks.unshift(task); // 放到任务队列顶部
} else {
this.tasks.push(task); // 放到任务队列尾部
}
}
eat(name) {
const task = () => {
console.log(`Eat ${name}`);
this.next();
};
this.tasks.push(task);
return this;
}
}
function LazyMan(name) {
return new _LazyMan(name);
}
复制代码
20 防抖节流
题目描述:手写防抖节流
实现代码如下:
// 防抖
function debounce(fn, delay = 300) {
//默认300毫秒
let timer;
return function () {
const args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args); // 改变this指向为调用debounce所指的对象,且apply的第二个参数可以是数组也可以是类数组对象,所以没必要把arguments转为数组
}, delay);
};
}
window.addEventListener(
"scroll",
debounce(() => {
console.log(111);
}, 1000)
);
// 节流
// 设置一个标志
// throttle.js
// 使用时间戳
function throttle(func, wait) {
let preTime = 0;
return function () {
let nowTime = +new Date();
let context = this;
let args = arguments;
if (nowTime - preTime > wait) {
func.apply(context, args);
preTime = nowTime;
}
};
}
window.addEventListener(
"scroll",
throttle(() => {
console.log(111);
}, 1000)
);
复制代码
21 写版本号排序的方法
题目描述:有一组版本号如下['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);
复制代码
22 LRU 算法
题目描述:
实现代码如下:
// 一个Map对象在迭代时会根据对象中元素的插入顺序来进行
// 新添加的元素会被插入到map的末尾,整个栈倒序查看
class LRUCache {
constructor(capacity) {
this.secretKey = new Map();
this.capacity = capacity;
}
get(key) {
if (this.secretKey.has(key)) {
let tempValue = this.secretKey.get(key);
this.secretKey.delete(key);
this.secretKey.set(key, tempValue);
return tempValue;
} else return -1;
}
put(key, value) {
// key存在,仅修改值
if (this.secretKey.has(key)) {
this.secretKey.delete(key);
this.secretKey.set(key, value);
}
// key不存在,cache未满
else if (this.secretKey.size < this.capacity) {
this.secretKey.set(key, value);
}
// 添加新key,删除旧key
else {
this.secretKey.set(key, value);
// 删除map的第一个元素,即为最长未使用的
this.secretKey.delete(this.secretKey.keys().next().value);
}
}
}
// let cache = new LRUCache(2);
// cache.put(1, 1);
// cache.put(2, 2);
// console.log("cache.get(1)", cache.get(1))// 返回 1
// cache.put(3, 3);// 该操作会使得密钥 2 作废
// console.log("cache.get(2)", cache.get(2))// 返回 -1 (未找到)
// cache.put(4, 4);// 该操作会使得密钥 1 作废
// console.log("cache.get(1)", cache.get(1))// 返回 -1 (未找到)
// console.log("cache.get(3)", cache.get(3))// 返回 3
// console.log("cache.get(4)", cache.get(4))// 返回 4
复制代码
23 Promise 以及相关方法的实现
题目描述:手写 Promise 以及 Promise.all Promise.race 的实现
实现代码如下:
class Mypromise {
constructor(fn) {
// 表示状态
this.state = "pending";
// 表示then注册的成功函数
this.successFun = [];
// 表示then注册的失败函数
this.failFun = [];
let resolve = (val) => {
// 保持状态改变不可变(resolve和reject只准触发一种)
if (this.state !== "pending") return;
// 成功触发时机 改变状态 同时执行在then注册的回调事件
this.state = "success";
// 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里为模拟异步
setTimeout(() => {
// 执行当前事件里面所有的注册函数
this.successFun.forEach((item) => item.call(this, val));
});
};
let reject = (err) => {
if (this.state !== "pending") return;
// 失败触发时机 改变状态 同时执行在then注册的回调事件
this.state = "fail";
// 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里模拟异步
setTimeout(() => {
this.failFun.forEach((item) => item.call(this, err));
});
};
// 调用函数
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
// 实例方法 then
then(resolveCallback, rejectCallback) {
// 判断回调是否是函数
resolveCallback =
typeof resolveCallback !== "function" ? (v) => v : resolveCallback;
rejectCallback =
typeof rejectCallback !== "function"
? (err) => {
throw err;
}
: rejectCallback;
// 为了保持链式调用 继续返回promise
return new Mypromise((resolve, reject) => {
// 将回调注册到successFun事件集合里面去
this.successFun.push((val) => {
try {
// 执行回调函数
let x = resolveCallback(val);
//(最难的一点)
// 如果回调函数结果是普通值 那么就resolve出去给下一个then链式调用 如果是一个promise对象(代表又是一个异步) 那么调用x的then方法 将resolve和reject传进去 等到x内部的异步 执行完毕的时候(状态完成)就会自动执行传入的resolve 这样就控制了链式调用的顺序
x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
} catch (error) {
reject(error);
}
});
this.failFun.push((val) => {
try {
// 执行回调函数
let x = rejectCallback(val);
x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
} catch (error) {
reject(error);
}
});
});
}
//静态方法
static all(promiseArr) {
let result = [];
//声明一个计数器 每一个promise返回就加一
let count = 0;
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
//这里用 Promise.resolve包装一下 防止不是Promise类型传进来
Promise.resolve(promiseArr[i]).then(
(res) => {
//这里不能直接push数组 因为要控制顺序一一对应(感谢评论区指正)
result[i] = res;
count++;
//只有全部的promise执行成功之后才resolve出去
if (count === promiseArr.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
}
});
}
//静态方法
static race(promiseArr) {
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
Promise.resolve(promiseArr[i]).then(
(res) => {
//promise数组只要有任何一个promise 状态变更 就可以返回
resolve(res);
},
(err) => {
reject(err);
}
);
}
});
}
}
// 使用
// let promise1 = new Mypromise((resolve, reject) => {
// setTimeout(() => {
// resolve(123);
// }, 2000);
// });
// let promise2 = new Mypromise((resolve, reject) => {
// setTimeout(() => {
// resolve(1234);
// }, 1000);
// });
// Mypromise.all([promise1,promise2]).then(res=>{
// console.log(res);
// })
// Mypromise.race([promise1, promise2]).then(res => {
// console.log(res);
// });
// promise1
// .then(
// res => {
// console.log(res); //过两秒输出123
// return new Mypromise((resolve, reject) => {
// setTimeout(() => {
// resolve("success");
// }, 1000);
// });
// },
// err => {
// console.log(err);
// }
// )
// .then(
// res => {
// console.log(res); //再过一秒输出success
// },
// err => {
// console.log(err);
// }
// );
复制代码
实现如何取消 promise
Promise.race()方法可以用来竞争 Promise 可以借助这个特性 自己包装一个 空的 Promise 与要发起的 Promise 来实现
function wrap(pro) {
let obj = {};
// 构造一个新的promise用来竞争
let p1 = new Promise((resolve, reject) => {
obj.resolve = resolve;
obj.reject = reject;
});
obj.promise = Promise.race([p1, pro]);
return obj;
}
let testPro = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 1000);
});
let wrapPro = wrap(testPro);
wrapPro.promise.then((res) => {
console.log(res);
});
wrapPro.resolve("被拦截了");
复制代码
24 实现一个 add 方法
题目描述:实现一个 add 方法 使计算结果能够满足如下预期: add(1)(2)(3)()=6 add(1,2,3)(4)()=10
其实就是考函数柯里化
柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术
实现代码如下:
const curry = (fn, ...args) =>
// 函数的参数个数可以直接通过函数数的.length属性来访问
args.length >= fn.length // 这个判断很关键!!!
// 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
? fn(...args)
/**
* 传入的参数小于原始函数fn的参数个数时
* 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数
*/
: (..._args) => curry(fn, ...args, ..._args);
function add1(x, y, z) {
return x + y + z;
}
const add = curry(add1);
console.log(add(1, 2, 3));
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));
25 动态规划求解硬币找零问题
题目描述:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1
示例1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例2:
输入: coins = [2], amount = 3
输出: -1
复制代码
实现代码如下:
const coinChange = function (coins, amount) {
// 用于保存每个目标总额对应的最小硬币个数
const f = [];
// 提前定义已知情况
f[0] = 0;
// 遍历 [1, amount] 这个区间的硬币总额
for (let i = 1; i <= amount; i++) {
// 求的是最小值,因此我们预设为无穷大,确保它一定会被更小的数更新
f[i] = Infinity;
// 循环遍历每个可用硬币的面额
for (let j = 0; j < coins.length; j++) {
// 若硬币面额小于目标总额,则问题成立
if (i - coins[j] >= 0) {
// 状态转移方程
f[i] = Math.min(f[i], f[i - coins[j]] + 1);
}
}
}
// 若目标总额对应的解为无穷大,则意味着没有一个符合条件的硬币总数来更新它,本题无解,返回-1
if (f[amount] === Infinity) {
return -1;
}
// 若有解,直接返回解的内容
return f[amount];
};
复制代码
26 请实现 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;
}
复制代码
扩展思考:如果给定的不是一个 Dom 树结构 而是一段 html 字符串 该如何解析?
那么这个问题就类似 Vue 的模板编译原理 我们可以利用正则 匹配 html 字符串 遇到开始标签 结束标签和文本 解析完毕之后生成对应的 ast 并建立相应的父子关联 不断的 advance 截取剩余的字符串 直到 html 全部解析完毕 感兴趣的可以看这里
27 类数组转化为数组的方法
题目描述:类数组拥有 length 属性 可以使用下标来访问元素 但是不能使用数组的方法 如何把类数组转化为数组?
实现代码如下:
const arrayLike=document.querySelectorAll('div')
// 1.扩展运算符
[...arrayLike]
// 2.Array.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.prototype.concat.apply([], arrayLike)
复制代码
28 Object.is 实现
题目描述:
Object.is不会转换被比较的两个值的类型,这点和===更为相似,他们之间也存在一些区别。
1. NaN在===中是不相等的,而在Object.is中是相等的
2. +0和-0在===中是相等的,而在Object.is中是不相等的
复制代码
实现代码如下:
Object.is = function (x, y) {
if (x === y) {
// 当前情况下,只有一种情况是特殊的,即 +0 -0
// 如果 x !== 0,则返回true
// 如果 x === 0,则需要判断+0和-0,则可以直接使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断
return x !== 0 || 1 / x === 1 / y;
}
// x !== y 的情况下,只需要判断是否为NaN,如果x!==x,则说明x是NaN,同理y也一样
// x和y同时为NaN时,返回true
return x !== x && y !== y;
};
复制代码
29 AJAX
题目描述:利用 XMLHttpRequest 手写 AJAX 实现
实现代码如下:
const getJSON = function (url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
};
xhr.send();
});
};
复制代码
30 分片思想解决大数据量渲染问题
题目描述:渲染百万条结构简单的大数据时 怎么使用分片思想优化渲染
文档碎片 + requestAnimationFrame
文档碎片
的好处
- 1、之前都是每次创建一个
div
标签就appendChild
一次,但是有了文档碎片
可以先把1页的div
标签先放进文档碎片
中,然后一次性appendChild
到container
中,这样减少了appendChild
的次数,极大提高了性能 - 2、页面只会渲染
文档碎片
包裹着的元素,而不会渲染文档碎片
实现代码如下:
const renderList = async () => {
console.time('列表时间')
const list = await getList()
console.log(list)
const total = list.length
const page = 0
const limit = 200
const totalPage = Math.ceil(total / limit)
const render = (page) => {
if (page >= totalPage) return
requestAnimationFrame(() => {
// 创建一个文档碎片
const fragment = document.createDocumentFragment()
for (let i = page * limit; i < page * limit + limit; i++) {
const item = list[i]
const div = document.createElement('div')
div.className = 'sunshine'
div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
// 先塞进文档碎片
fragment.appendChild(div)
}
// 一次性appendChild
container.appendChild(fragment)
render(page + 1)
})
}
render(page)
console.timeEnd('列表时间')
}
复制代码
扩展思考:对于大数据量的简单 dom 结构渲染可以用分片思想解决 如果是复杂的 dom 结构渲染如何处理?
这时候就需要使用虚拟列表了 大家自行百度哈 虚拟列表和虚拟表格在日常项目使用还是很频繁的
31 将虚拟 Dom 转化为真实 Dom
题目描述:JSON 格式的虚拟 Dom 怎么转换成真实 Dom
{
tag: 'DIV',
attrs:{
id:'app'
},
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
把上诉虚拟Dom转化成下方真实Dom
<div id="app">
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
复制代码
实现代码如下:
// 真正的渲染函数
function _render(vnode) {
// 如果是数字类型转化为字符串
if (typeof vnode === "number") {
vnode = String(vnode);
}
// 字符串类型直接就是文本节点
if (typeof vnode === "string") {
return document.createTextNode(vnode);
}
// 普通DOM
const dom = document.createElement(vnode.tag);
if (vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach((key) => {
const value = vnode.attrs[key];
dom.setAttribute(key, value);
});
}
// 子数组进行递归操作
vnode.children.forEach((child) => dom.appendChild(_render(child)));
return dom;
}
复制代码
32 实现模板字符串解析功能
题目描述:
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
复制代码
实现代码如下:
function render(template, data) {
let computed = template.replace(/{{(\w+)}}/g, function (match, key) {
return data[key];
});
return computed;
}
复制代码
33 实现一个对象的 flatten 方法
题目描述:
const obj = {
a: {
b: 1,
c: 2,
d: {e: 5}
},
b: [1, 3, {a: 2, b: 3}],
c: 3
}
flatten(obj) 结果返回如下
// {
// 'a.b': 1,
// 'a.c': 2,
// 'a.d.e': 5,
// 'b[0]': 1,
// 'b[1]': 3,
// 'b[2].a': 2,
// 'b[2].b': 3
// c: 3
// }
复制代码
实现代码如下:
function isObject(val) {
return typeof val === "object" && val !== null;
}
function flatten(obj) {
if (!isObject(obj)) {
return;
}
let res = {};
const dfs = (cur, prefix) => {
if (isObject(cur)) {
if (Array.isArray(cur)) {
cur.forEach((item, index) => {
dfs(item, `${prefix}[${index}]`);
});
} else {
for (let k in cur) {
dfs(cur[k], `${prefix}${prefix ? "." : ""}${k}`);
}
}
} else {
res[prefix] = cur;
}
};
dfs(obj, "");
return res;
}
flatten();
复制代码
34 列表转成树形结构
题目描述:
[
{
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) {
// 定义一个中间的对象,key为id 值为data的每一项
let temp = {};
// treeData为最后的目标数据
let treeData = [];
// 遍历data构建temp对象,拿每一项
for (let i = 0; i < data.length; i++) {
temp[data[i].id] = data[i];
}
// 遍历temp对象,判断parendId是否为0,若为0表示是顶层节点,直接push到treeData中
// 若不为0,先进行一个小初始化,当这一项的父节点的children没有的话初始化为[]
// 然后temp的每一项的父节点的children push当前这一项temp[i]
for (let i in temp) {
if (+temp[i].parendId !== 0) {
if (!temp[temp[i].parendId].children) {
temp[temp[i].parendId].children = [];
}
temp[temp[i].parendId].children.push(temp[i]);
} else {
treeData.push(temp[i])
}
}
return treeData;
}
复制代码
35 树形结构转成列表
题目描述:
[
{
id: 1,
text: '节点1',
parentId: 0,
children: [
{
id:2,
text: '节点1_1',
parentId:1
}
]
}
]
转成
[
{
id: 1,
text: '节点1',
parentId: 0 //这里用0表示为顶级节点
},
{
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;
}
复制代码
36 大数相加
题目描述:实现一个add方法完成两个大数相加
let a = "9007199254740991";
let b = "1234567899999999999";
function add(a ,b){
//...
}
复制代码
实现代码如下:
function add(a ,b){
//取两个数字的最大长度
let maxLength = Math.max(a.length, b.length);
//用0去补齐长度
a = a.padStart(maxLength , 0);//"0009007199254740991"
b = b.padStart(maxLength , 0);//"1234567899999999999"
//定义加法过程中需要用到的变量
let t = 0;
let f = 0; //"进位"
let sum = "";
for(let i=maxLength-1 ; i>=0 ; i--){
t = parseInt(a[i]) + parseInt(b[i]) + f;
f = Math.floor(t/10);
sum = t%10 + sum;
}
if(f!==0){
sum = '' + f + sum;
}
return sum;
}
React实现点击按钮数字+1
import React, { useState } from 'react';
const Test = function () {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1)
}
return <div onClick={handleClick}>click me {count}</div>;
};
实现每间隔1s打印当前时间
import React, { useState, useRef, useEffect } from 'react';
const Test = () => {
const [time, setTime] = useState(new Date());
// ref解决闭包陷阱问题,这样能保证ref一直是一个引用值
let timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer.current);
}, [])
return (
<div>{time.toLocaleString()}</div>
)
}
ts实现两个数相加的函数,两个参数的类型必须同时为字符串或者数字
type Combinable = string | number;
function add<T extends Combinable>(a: T, b: T): Combinable {
if (typeof a === 'string') {
return a + b;
} else {
return (a as number) + (b as number);
}
}
let a: Combinable = 123;
let b: Combinable = 123;
add(a, b);
要求输出数组,数组里面的每一项是由随机数生成
const arr = [];
for(let i=0;i<100;i++){
const arrNum=parseInt(Math.random()*100)+1;
const flag=true;
for(let j=0;j<=arr.length;j++){
if(arrNum==arr[j]) {
flag = false;
break;
}
}
if(flag){
arr.push(arrNum);
}else{
i--;
}
}
一个数组里面有若干个对象,要求去除重复的对象
遍历数组然后使用JSON.stringify()将每一项转为字符串,然后对比字符串是否相等来取除