前端手撕、底层源码
排序升序
// js底层sort是插入和快排结合,arr.length<10插入,否则快排
// 冒泡排序 稳定
function maopao(arr){
let n=arr.length
//外层循环趟数
for (let i = 0; i < n - 1; i++){
// 内层循环每个元素需要的次数
for (let j = 0; j < n - 1 - i; j++){
//相邻数字比较
if (arr[j] > arr[j + 1])[arr[j],arr[j+1]]=[arr[j+1],arr[j]]
}
}
return arr
}
// 插入排序 稳定
function cha(arr){
let n=arr.length
for (let i = 1; i < n; i++){
let temp=arr[i]
for(var j=i;j>0&&temp<arr[j-1];j--){
arr[j]=arr[j-1]
}
arr[j]=temp
}
return arr
}
// 快排 不稳定 O(nlogn)
function quick(arr){
let n=arr.length
if(n<2)return arr
let a1=[]
let a2=[]
for (let i = 1; i < n; i++){
if(arr[i]<arr[0]){
a1.push(arr[i])
}else{
a2.push(arr[i])
}
}
a1=quick(a1)
a2=quick(a2)
return [...a1,arr[0],...a2]
}
// 选择排序 不稳定
查找
// 数组查找 O(1)
// 顺序查找 O(n)
// 二分 O(log2n)
function find(arr,target){
let left=0
let right=arr.length-1
while(left<=right){
let mid=Math.floor((left+right)/2)
if(target===arr[mid])return mid
if(target<arr[mid])right=mid-1
if(target>arr[mid])left=mid+1
}
return -1
}
URL参数解析
String.prototype.myQueryUrlParameter=function(){
let obj={}
let arr=this.split('?')[1].split('#')[0].split('&')
arr.map(item=>{
if(item.includes('=')){
let [key,val]=item.split('=');
//判断是否数字
val = /^[0-9]+$/.test(val)? parseInt(val) : val;
obj[key]=obj.hasOwnProperty(key)?obj[key]=[].concat(obj[key],val):val;
}else{
obj[item]=true;
}
})
obj['HASH']=this.split('#')[1]
return obj
}
let url=`http://www.baidu.com?ie=utf-8&lang=en&lang=cn&name=tom&id=123&id=456&enabled#teacher`;
let result=url.myQueryUrlParameter();
console.log(result)

括号匹配
function kuohao(str){
let left=['(','[','{'];
let right=[')',']','}'];
let sc=[];//定义一个栈
for(c of str){
if(left.includes(c))sc.push(c);
if(right.includes(c)){
if(sc.length==0)return false;
let index=right.indexOf(c);
if(sc.pop()!=left[index])return false;
}
}
if(sc.length!=0)return false;
else return true;
}
合并有序数组
let a=[2,3,4,5]
let b=[1,2,6,6]
let i=0
let j=0
let res=[]
while(i<a.length&&j<b.length){
if(a[i]<=b[j]){
res.push(a[i++])
}else res.push(b[j++])
}
while(i<a.length)res.push(a[i++])
while(j<b.length)res.push(b[j++])
console.log(res)//1,2,2,3,4,5,6,6
数组转树树转数组
const arr = [
{ id: '00', name: '测试', pid: ''},
{ id: '01', name: '张大', pid: ''},
{ id: '02', name: '小亮', pid: '01'},
{ id: '03', name: '小美', pid: '01'},
{ id: '04', name: '小丽', pid: '02'},
{ id: '05', name: '小高', pid: '03'},
]
function transList2TreeData(list, rootValue) {//转树
const deepList = JSON.parse(JSON.stringify(list)) // 深拷贝
const arr = []
deepList.forEach(item => {
if (item.pid === rootValue) {
const children = transList2TreeData(deepList, item.id)
children.length ? (item.children = children) : false
arr.push(item)
}
})
return arr
}
const result = transList2TreeData(arr, '')
console.log(result)
let treeRes = [];
function tree2arr(res) { // 转数组
res.forEach(item => {
if (item.children) {
let tem=JSON.parse(JSON.stringify(item.children))
delete item.children
treeRes.push(item)
tree2arr(tem)
} else {
treeRes.push(item)
}
})
}
tree2arr(result)
console.log(treeRes);
随即洗牌
function shuffle(array){
let res = [], random;
while(array.length>0){
random = Math.floor(Math.random()*array.length);
res.push(array[random]);
array.splice(random, 1);
}
return res;
}
console.log(shuffle([1,2,3,4,5]));
数组L输出
var arr = [
[1,2,3],
[4,5,6],
[7,8,9]
];
var newArr = []; // L型数组
var length = arr.length; // 后面将替换成 arr.length
var number = 1; // 少掉的下标数
for(var i= length; i > 0; i--){
for(var j = 0; j < i; j++){
var len = length - i;
var value = arr[j][len];
newArr.push(value);
}
var arrSlice = arr[length - number].slice(number);
newArr.push(...arrSlice) // 用展开符把横数组添加到L型数组里
++number;
}
console.log(newArr)//1,4,7,8,9,2,5,6,3
统计字符串字符个数
function numstr(str){
let map=new Map()
for(c of str){
map.set(c,map.has(c)?map.get(c)+1:1)
}
map.forEach((val,key)=>console.log(key,val))// 遍历输出key val
}
后续可以判断字符个数相等与否,出现最多的字符
大数加法
function solve( s , t ) {
let maxLength = Math.max(s.length,t.length)
s = s.padStart(maxLength , 0);
t = t.padStart(maxLength , 0);
let sum = ""
let f = 0
for(let i = maxLength-1;i>=0;i--){
let a = parseInt(s[i])+parseInt(t[i]) + f
f = Math.floor(a/10)
sum =a%10 + sum
}
if(f==1){
sum = "1"+sum
}
return sum
}
dom操作
// 获取元素
var x = document.getElementById("intro");// 获取id名为intro的元素
var x = document.getElementsByTagName("p");// 获取p标签元素,list
var x = document.getElementsByClassName("intro");// 获取class名为intro的元素,list
var x = document.querySelectorAll("p.intro");// 获取p元素下的所有intro,list
var x = document.querySelector("p");// 获取第一个p元素
// 增加元素
var son = document.createElement("p");
var node = document.createTextNode("这是新文本。");
son.appendChild(node);
var parent = document.getElementById("div1");
parent.appendChild(son);
// 删除元素
const elmnt = document.getElementById("p1");
elmnt.remove();
// 修改元素
var para = document.createElement("p");
var node = document.createTextNode("这是新文本。");
para.appendChild(node);
var parent = document.getElementById("div1");
var child = document.getElementById("p1");
parent.replaceChild(para, child);
发布订阅
class PublishSubscribe{
subscribes={
a:[],//a事件和回调函数数组
b:[]
}
// 订阅
on(subscribe,callback){
//判断subscribe事件有没有数组[],没创建过就创建[],创建过了就添加
const subscribeCallbacks=this.subscribes[subscribe]||[];
this.subscribes[subscribe]=[...subscribeCallbacks,callback];//相当于把回调函数放在了订阅事件的数组里面
}
//发布
emit(subscribe,...args){
//拿到该事件所有回调函数
const subscribeCallbacks=this.subscribes[subscribe]||[];
//依次执行
subscribeCallbacks.forEach(callback=>callback(...args))
}
}
观察者模式
区别: 观察者模式和发布订阅模式都是为了解耦,减少代码的冗余,不同的是,观察者模式中观察者必须知道被观察者,而发布订阅模式解耦更彻底,订阅者与发布者不需要相互知道,只需要向事件大厅订阅和发布即可。
class Observerd {
constructor(name) {
this.name = name;
this.state = '走路';
this.observers = [];
}
setObserver(observer) {
this.observers.push(observer);
}
setState(state) {
this.state = state;
this.observers.forEach(observer => observer.update(this));
}
}
class Observer {
update(observerd) {
console.log(observerd.name + '正在' + observerd.state);
}
}
响应式原理
可以在控制台在修改任意person的属性,都会调用render渲染函数重新渲染页面
for (let key in person) {
let value = person[key]
Object.defineProperty(person, key, {
get() { //必须要有否则值全部变成undefined
return value
},
set(newVal) {
value = newVal
render(ul)
}
})
}
reduce
Array.prototype._reduce=function(fu){
if(typeof fu!="function")return;
let prev;
for(let i=0;i<this.length;i++){
if(prev===undefined){
prev=fu(this[i],this[i+1],i+1,this);
++i;
}else{
prev=fu(prev,this[i],i,this);
}
}
return prev;
}
深拷贝
const _completeDeepClone = (target) => {
// 1. 基本数据类型直接返回
if (typeof target !== 'object' || target === null) return target
// 2. 函数,正则,日期,ES6新对象,执行构造器,返回新的对象
const constructor = target.constructor
if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name))
return new constructor(target)
// 3.针对数组和对象区分
const cloneTarget = Array.isArray(target) ? [] : {}
Object.keys(target).forEach(key => {
cloneTarget[key] = _completeDeepClone(target[key])
})
return cloneTarget
}
防抖
只执行最后一次
function debounce(f) {
let timeout;
return function () {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(function() {f()}, 1000);
}
}
节流
单位时间内触发一次
function throttle(f) {
let timeout;
return function () {
if (!timeout) {
timeout = setTimeout(function () {
timeout = null;
f();
}, 1000)
}
}
}
扁平
let res=[]//数组
const flatten = (arr) => {
for(n of arr){
if(typeof n !='object')res.push(n)
else flatten(n)
}
}
flatten([1,2,3,[4,5,6,[7,8,9]]])
console.log(res)
let res={}//对象
const flatten = (obj) => {
for(key in obj){
if(typeof obj[key] !='object' ||obj[key] instanceof Array)res[key]=obj[key]
else flatten(obj[key])
}
}
flatten({'a':1,'b':{'c':3,'d':{'d1':55,'d2':{'d22':[1,2,3]},'d3':99},'f':8},e:5})
console.log(res)
setTimeout实现setInterval
setTimeout(function f() {
setTimeout(f, interval);
}, interval)
手写ajax(使用promise封装)
function getJSON(url) {
let promise = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open("GET", url, true)
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.onerror = function () {
reject(new Error(this.statusText))
}
xhr.responseType = "json"
xhr.setRequestHeader("Accept", "application/json")
xhr.send(null)
})
return promise
}
for of
// 创建一个函数,该函数会返回一个迭代器,用于遍历对象的所有属性
function* objectIterator(obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) { // 只遍历对象自身的属性,不遍历原型链上的属性
yield [key, obj[key]];
}
}
}
// 将这个函数添加到所有对象的原型上
Object.prototype[Symbol.iterator] = function() {
return objectIterator(this);
};
// 创建一个测试对象
let testObj = {
a: 1,
b: 2,
c: 3
};
// 使用for...of遍历测试对象
for (let [key, value] of testObj) {
console.log(`Key: ${key}, Value: ${value}`);
}
promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject("参数必须为数组");// promises必须是数组
}
let successNum = 0; // 数组中promise对象调用成功的个数
let totalNum = promises.length; // 数组长度
let resultArray = []; // 保存每一个promise对象调用成功后的信息
for (let i = 0; i < totalNum; i++) {
promises[i].then(value => {
successNum++;
resultArray[i] = value;
if (successNum === totalNum) {
return resolve(resultArray); // 都成功,返回信息
}
}, err => {
return reject('数组内文件没有全部读取成功'); // 一旦失败返回失败的回调信息
})
}
})
}
全排列(递归)
function permute( num ) {
let data = []
if(num.length == 1) return [num]
for(let i = 0; i < num.length;i++) {
let arr = [...num]
arr.splice(i,1) //const temp = str.slice(0, i) + str.slice(i + 1)
let info = this.permute(arr)
info.forEach(item => {
data.push([num[i],...item])
})
}
return data
}
二叉树遍历
// 中序 obj={val:2,left:{val:5},right:{val:3}}
let arr=[]
function inorderTraversal( root ) {
// write code here
if(root==null) return [];
if(root){
inorderTraversal(root.left);
arr.push(root.val);
inorderTraversal(root.right);
}
return arr;
}
// 层序
function levelOrder(root) {
// write code here
let res = [];
let arr = [];
if (root) arr.push(root);
while (arr.length > 0) {
let newArr = [];
let row = [];
arr.forEach((v) => {
row.push(v.val);
v.left && newArr.push(v.left);
v.right && newArr.push(v.right);
});
arr = newArr;
res.push(row);
}
return res;
}
链式调用、休眠、类
human.say('hello').sleep(2).say('world').done();
class Human {
constructor() {
this.queue = Promise.resolve();
}
say(word) {
this.queue = this.queue.then(() => {
console.log(word);
return Promise.resolve();
});
console.log(this)
return this;
}
sleep(time) {
this.queue = this.queue.then(() => {
return new Promise(resolve => setTimeout(resolve, time * 1000));
});
return this;
}
done() {
return this.queue;
}
}
链表
反转链表
function ReverseList(pHead)
{
let p1 = null
let p2 = pHead
while(p2){
let tmp = p2.next
p2.next = p1
p1 = p2
p2 = tmp
}
return p1
}
链表是否有环
function hasCycle( head ) {
while(head!=null){
if(head.flag)return true;
head.flag=true;
head=head.next;
}
return false;
}
删除链表重复项
// 1,1,2,3,3,4=>1,2,3,4
function deleteDuplicates(head) {
let cur = head;
while (cur) {
if (!cur.next) break;
cur.val == cur.next.val ? cur.next = cur.next.next : cur = cur.next;
}
return head;
}
// 1,1,2,3,3,4=>2,4
function deleteDuplicates2(head) {
let cur = head;
let map = new Map();
while (cur) {
map.set(cur.val, map.has(cur.val) ? map.get(cur.val) + 1 : 1);
cur = cur.next;
}
let p = head;
while (p && p.next) {
if (map.get(p.next.val) > 1) {
p.next = p.next.next;
} else {
p = p.next;
}
}
// 处理头节点
if (head && map.get(head.val) > 1) {
head = head.next;
}
return head;
}
链表排序
function sortInList( head ) {
let arr=[]
let p=head
while(p){
arr.push(p.val)
p=p.next
}
arr.sort((a,b)=>a-b)
let t=new ListNode(arr[0])
let res=t
for(let i=1;i<arr.length;i++){
t.next=new ListNode(arr[i])
t=t.next
}
return res
}
无重复字符的最长子串(双指针)
function maxLength(arr) {
const map = new Map();
let left = 0;
let right = 0;
let res = 0;
while (right < arr.length) {
if (map.has(arr[right])) {
map.set(arr[right], map.get(arr[right]) + 1);
} else {
map.set(arr[right], 1);
}
while (map.get(arr[right]) > 1) {
map.set(arr[left], map.get(arr[left]) - 1);
left++;
}
right++;
res = Math.max(res, right - left);
}
return res;
}