-1. JS排序算法
// 一、冒泡排序 --- 逐一比较(在比较交换的时候,就是计算机中最经典的交换策略,用临时变量temp保存值)
// 这里有两点需要注意:
// (1)外层循环,从最大值开始递减,因为内层是两两比较,因此最外层当>=2时即可停止;
// (2)内层是两两比较,从0开始,比较y与y+1,因此,临界条件是y<i -1
function bubleSort(arr) {
var len = arr.length;
for (let i = len; i >= 2; i--) {
for (let y = 0; y <= i - 1; y++) {
if (arr[y] > arr[y + 1]) {
let temp = arr[y];
arr[y] = arr[y + 1];
arr[y + 1] = temp;
}
}
}
return arr;
}
// 其中,ES6的方法就是 -- 解构赋值 (冒泡排序优化)
arr2 = [1, 2, 3, 4];
[arr2[0], arr2[1]] = [arr2[1], arr2[0]]; //ES6解构赋值
function bubleSort(arr) {
var len = arr.length;
for (let i = len; i >= 2; i--) {
for (let y = 0; y <= i - 1; y++) {
if (arr[y] > arr[y + 1]) {
[arr[y], arr[y + 1]] = [arr[y + 1], arr[y]];
}
}
}
return arr;
}
// 二、选择排序 --- 选择排序是从数组的开头开始,将第一个元素和其他元素作比较,检查完所有的元素后,
// 最小的放在第一个位置,接下来再开始从第二个元素开始,重复以上一直到最后。
function selectSort(arr) {
var len = arr.length;
for (let i = 0; i < len - 1; i++) {
for (let j = i; j < len; j++) {
if (arr[j] < arr[i]) {
[arr[i], arr[j]] = [arr[j], arr[i]]; //交换位置
}
}
}
return arr;
}
// 外层循环的i表示第几轮,arr[i]就表示当前轮次最靠前(小)的位置;
// 内层从i开始,依次往后数,找到比开头小的,互换位置即可
// 三、高级排序算法 ============================================================================= 必考
// (1)快速排序
function quickSort(arr) {
if (arr.length <= 1) {
return arr; //递归出口
}
var left = [],
right = [],
current = arr.splice(0, 1); //注意splice后,数组长度少了一个
for (let i = 0; i < arr.length; i++) {
if (arr[i] < current) {
left.push(arr[i]); //放在左边
} else {
right.push(arr[i]); //放在右边
}
}
return quickSort(left).concat(current, quickSort(right)); //递归
}
// 升级版--三路快排
function qSort3(arr) {
//三路快排
if (arr.length == 0) {
return [];
}
var left = [];
var center = [];
var right = [];
var pivot = arr[0]; //偷懒,直接取第一个,实际取值情况 参考[快速排序算法的优化思路总结]
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else if (arr[i] == pivot) {
center.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...qSort3(left), ...center, ...qSort3(right)];
}
0、防抖、截流
防抖是将多次执行变为最后一次执行
// 防抖 防抖重在清零「clearTimeout(timer)」
// 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
// 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
function debounce (fn, delay) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
fn(...args)
}, delay)
}
}
节流是将多次执行变成每隔一段时间执行
// 所以节流就像是一个看门大爷,每一段时间它只会放一个人进去
// 节流 节流重在加锁「flag = false」
// scroll 事件,每隔一秒计算一次位置信息等
// 浏览器播放事件,每个一秒计算一次进度信息等
function throttle (fn, delay) {
let timer
return (...args) => {
if (timer) return
timer = setTimeout(() => {
fn(...args)
timer = null
}, delay)
}
}
一、 数组扁平化
while循环 、for循环 、reduce
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]],
];
function flatten(arr) {
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
flatten(arr); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
function flatten(data) {
let arr = [];
for (let i = 0; i < data.length; i++) {
const element = data[i];
if (Array.isArray(element)) {
arr = arr.concat(flatten(element));
} else {
arr.push(element);
}
}
return arr;
}
const flatten = (arr) => {
return arr.reduce((a, b) => {
if (b instanceof Array) {
return a.concat(flatten(b));
}
return a.concat(b);
}, []);
};
console.log(flatten(arr1)); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
二、深拷贝
function deepcopy(data) {
let obj = data.constructor === Array ? [] : {};
if (typeof data === "object") {
for (const i in data) {
const element = data[i];
obj[i] = typeof element === "object" ? deepcopy(element) : element;
}
} else {
obj = data;
}
return obj;
}
-
深拷贝的终极解决方案 通过遍历的方式深拷贝(无视递归可能带来的爆栈问题)
function cloneLoop(obj) {
const root = {};
const stack = [
{
parent: root,
key: undefined,
data: obj,
},
];
// 深度优先
while (stack.length) {
const node = stack.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== "undefined") {
res = parent[key] = {};
}
for (let k in data) {
if (Object.hasOwnProperty.call(data, k)) {
if (typeof data[k] === "object") {
// 下一次循环
stack.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
三、深度优先遍历、广度优先遍历
const tree = {
name: "root",
children: [
{
name: "c1",
children: [
{
name: "c11",
children: [],
},
{
name: "c12",
children: [],
},
],
},
{
name: "c2",
children: [
{
name: "c21",
children: [],
},
{
name: "c22",
children: [],
},
],
},
],
};
// 深度优先遍历
// ======== 循环 栈! 版本 ========
// arr = [] stack = [parent]
// arr = [parent] stack = [child3,child2,child1]
// arr = [parent, child1] stack = [child3,child2,child1-2,child1-1]
// arr = [parent, child1-1] stack = [child3,child2,child1-2]
function deeptraversal(root) {
let arr = [];
let stack = []; //
if (!root) return [];
stack.push(root);
while (stack.length) {
let node = stack.pop();
if (node === null) continue;
arr.push(node);
for (let i = node.children.length - 1; i >= 0; i--) {
// 这里就是面试的重点,应该从后面的节点压入栈中
stack.push(node.children[i]);
}
}
return arr;
}
// 递归版本
let deepTraversal1 = (root, nodeList = []) => {
if (!root) return [];
nodeList.push(root);
for (let i = 0; i < root.children.length; i++) {
deepTraversal1(root.children[i], nodeList);
}
return nodeList;
};
// 广度优先遍历
let widthTraversal2 = (root) => {
let arr = [];
let stack = [];
if (root) {
stack.push(root);
while (stack.length) {
let item = stack.shift();
let children = item.children;
arr.push(item);
// 队列,先进先出
// arr = [] stack = [parent]
// arr = [parent] stack = [child1,child2,child3]
// arr = [parent, child1] stack = [child2,child3,child1-1,child1-2]
// arr = [parent,child1,child2]
for (let i = 0; i < children.length; i++) {
stack.push(children[i]);
}
}
}
return arr;
};
四、new
function myNew(func, ...args) {
// 1. 判断方法体
if (typeof func !== 'function') {
throw '第一个参数必须是方法体'
}
// 2. 创建新对象
const obj = {}
// 3. 这个对象的 __proto__ 指向 func 这个类的原型对象
// 即实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性
obj.__proto__ = Object.create(func.prototype)
// 为了兼容 IE 可以让步骤 2 和 步骤 3 合并
// const obj = Object.create(func.prototype);
// 4. 通过 apply 绑定 this 执行并且获取运行后的结果
let result = func.apply(obj, args)
// 5. 如果构造函数返回的结果是引用数据类型,则返回运行后的结果
// 否则返回新创建的 obj
const isObject = typeof result === 'object' && result !== null
const isFunction = typeof result === 'function'
return isObject || isFunction ? result : obj
}
Object.create
//实现Object.create方法
function create(proto) {
function Fn() {};
Fn.prototype = proto;
Fn.prototype.constructor = Fn;
return new Fn();
}
let demo = {
c : '123'
}
let cc = Object.create(demo)
五、call、apply、bind
// call
Function.prototype.call = function (context, ...args) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
context[fnSymbol](...args);
delete context[fnSymbol];
};
// apply
Function.prototype.apply = function (context, argsArr) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
context[fnSymbol](...argsArr);
delete context[fnSymbol];
};
// bind
Function.prototype.bind = function (context, ...args) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
return function (..._args) {
args = args.concat(_args);
context[fnSymbol](...args);
delete context[fnSymbol];
};
};
六、实现柯里化
function createCurry(func, args) {
var argity = func.length;
var args = args || [];
return function () {
var _args = [].slice.apply(arguments);
args.push(..._args);
if (args.length < argity) {
return createCurry.call(this, func, args);
}
return func.apply(this, args);
};
}
七、数组去重
// 最高性能数组去重方法 10万数量级:3毫秒,100万数量级:6毫秒,1000万数量级36毫秒
// 利用对象的属性不会重复这一特性,校验数组元素是否重复
let arr = [2, 45, 6, 6, 7, 8, 9, 0];
function myset(data) {
let arr = [];
let obj = {};
for (const item of data) {
if (!obj[item]) {
obj[item] = 1;
arr.push(item);
}
}
return arr;
}
// ES6 SET Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。
function distinct5(a, b) {
// let arr = a.concat(b);
return Array.from(new Set([...a, ...b]));
}
八、对象数组去重
const responseList = [
{ id: 1, a: 1 },
{ id: 2, a: 2 },
{ id: 3, a: 3 },
{ id: 1, a: 4 },
];
const result = responseList.reduce((acc, cur) => {
const ids = acc.map(item => item.id);
return ids.includes(cur.id) ? acc : [...acc, cur];
}, []);
console.log(result); // -> [ { id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 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");
十、在限定并发数下用 Promise 并发完成请求
class PromisePool {
constructor(max, fn) {
this.max = max; // 最大并发数
this.fn = fn; // 自定义的请求函数
this.pool = []; // 并发池
this.urls = []; // 剩余的请求地址
}
start(urls) {
this.urls = urls;
// 先循环把并发池塞满
while (this.pool.length < this.max) {
let url = this.urls.shift();
this.setTask(url);
}
// 利用Promise.race 方法来获得并发池中某任务完成的信号
let race = Promise.race(this.pool);
return this.run(race);
}
run(race) {
race
.then(res => {
// 每当并发池跑完一个任务,就再塞入一个任务
let url = this.urls.shift();
this.setTask(url);
return this.run(Promise.race(this.pool));
});
}
setTask(url) {
if (!url) return;
let task = this.fn(url);
this.pool.push(task); // 将该任务推入pool并发池中
console.log(`\x1B[43m ${url} 开始,当前并发数:${this.pool.length}`);
task.then(res => {
// 请求结束后将该Promise任务从并发池中移除
this.pool.splice(this.pool.indexOf(task), 1);
console.log(`\x1B[43m ${url} 结束,当前并发数:${this.pool.length}`);
});
}
}
// test
const URLS = [
'bytedance.com',
'tencent.com',
'alibaba.com',
'microsoft.com',
'apple.com',
'hulu.com',
'amazon.com'
];
// 自定义请求函数
var requestFn = url => {
return new Promise(resolve => {
setTimeout(_ => {
resolve(`任务 ${url} 完成`);
}, 1000*dur++)
}).then(res => {
console.log('外部逻辑 ', res);
})
}
const pool = new PromisePool(3, requestFn); // 并发数为3
pool.start(URLs);
十一、对对象属性访问的解析方法
eg:访问 a.b.c.d
函数柯里化 + 闭包 + 递归
function createGetValueByPath(path) {
let paths = path.split("."); // [ xxx, yyy, zzz ]
return function getValueByPath(obj) {
let res = obj;
let prop;
while ((prop = paths.shift())) {
res = res[prop];
}
return res;
};
}
let getValueByPath = createGetValueByPath("a.b.c.d");
var o = {
a: {
b: {
c: {
d: {
e: "正确了",
},
},
},
},
};
var res = getValueByPath(o);
console.log(res);
十二、vue处理响应式 defineReactive 实现+reactify
// 简化后的版本
function defineReactive(target, key, value, enumerable) {
// 折中处理后, this 就是 Vue 实例
let that = this;
// 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
if (typeof value === "object" && value != null && !Array.isArray(value)) {
// 是非数组的引用类型
reactify(value); // 递归
}
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get() {
console.log(`读取 ${key} 属性`); // 额外
return value;
},
set(newVal) {
console.log(`设置 ${key} 属性为: ${newVal}`); // 额外
value = reactify(newVal);
},
});
}
// 将对象 o 响应式化
function reactify(o, vm) {
let keys = Object.keys(o);
for (let i = 0; i < keys.length; i++) {
let key = keys[i]; // 属性名
let value = o[key];
if (Array.isArray(value)) {
// 数组
value.__proto__ = array_methods; // 数组就响应式了
for (let j = 0; j < value.length; j++) {
reactify(value[j], vm); // 递归
}
} else {
// 对象或值类型
defineReactive.call(vm, o, key, value, true);
}
}
}
十三、终极 Promise
class MyPromise {
constructor(exec) {
this.status = "pending";
this.value = undefined;
this.reason = undefined;
this.onResolved = [];
this.onRejected = [];
this.onFinally = [];
const resolve = (value) => {
if (this.status !== "pending") return;
this.status = "resolved";
this.value = value;
this.onResolved.forEach((fn) => fn());
};
const reject = (reason) => {
if (this.status !== "pending") return;
this.status = "rejected";
this.reason = reason;
this.onRejected.forEach((fn) => fn());
};
try {
exec(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onResolved, onRejected) {
return new MyPromise((resolve, reject) => {
const resolved = () => {
try {
const res = typeof onResolved === "function" ? onResolved(this.value) : this.value;
return res instanceof MyPromise ? res.then(resolve, reject) : resolve(res);
} catch (error) {
reject(error);
}
};
const rejected = () => {
try {
const res = typeof onRejected === "function" ? onRejected(this.reason) : this.reason;
return res instanceof MyPromise ? res.then(resolve, reject) : reject(res);
} catch (error) {
reject(error);
}
};
switch (this.status) {
case "pending":
this.onResolved.push(resolved);
this.onRejected.push(rejected);
break;
case "resolved":
resolved();
break;
case "rejected":
rejected();
break;
}
});
}
catch(onRejected) {
// catch是then(undefined, onRejected)的语法糖
return this.then(undefined, onRejected);
}
finally(onFinally) {
return this.then(
(value) => MyPromise.resolve(onFinally()).then(() => value),
(reason) => MyPromise.resolve(onFinally()).then(() =>
MyPromise.reject(reason))
);
}
static resolve(value) {
// 静态方法,用于快速返回一个已解析的Promise
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
// 静态方法,用于快速返回一个已拒绝的Promise
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then((value) => {
results[index] = value;
count++;
if (count === promises.length) {
resolve(results);
}
}, reject);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
}
}