网站安全
xss 跨站脚本攻击
js脚本:注入脚本执行,可以获取你本地的cookie等。
防止:输入过滤、输出编码、cookie防盗(编码、与ip绑定)
csrf 跨站请求伪造
伪造成你的身份执行操作。
人机识别,验证码。
网络请求
一次限制n个fetch且保证最快速度
function limitPromise(callbacks, n){
const len = callbacks.length;
let count = 0;
let result = [];
return new Promise((resolve,reject)=>{
try{
while(count<n){
fetch();
}
function fetch(){
if(count>=len && result.length===n) return resolve(result);//全部执行完毕
if(count<n){//执行fetch
count++;//记录已执行fetch次数
Promise(callbacks[count]()).then(res=>{ result.push(res); fetch(); return res; })
}
}
} catch(err){
reject(err);
}
})
}
复制代码
前端算法
树
案例一:输出一个树的镜像
queue存储 left/right互换
queue=[root];
while(queue.length){
r=queue.shift();
r.left=r.right;
r.right=r.left;
queue.push(r.right);
queue.push(r.left);
}
root
复制代码
案例二:判断一个树是否是对称树
递归
root===null return true;
compare(left,right){
!left && !right true;
!left || !right false;
left.val!==right.val false;
return compare(left.left,right.right) && compare(left.right,right.left)
}
compare(root.left,root.right);
复制代码
案例三:t1b t2是否节点重合
双指针,分别走完自己的路并回到对方的起点,终会在我们第一次相交的地方重逢
function getpose(t1, t2){
const node1=t1;
const node2=t2;
while(node1 !== node2){
if(node1===null || node2===null){return null;}
node1=node1?node.next:t1;
node2=node2?node2.next:t2;
}
return node1;
}
复制代码
bfs
案例
<div id="roottag"> <div> <span>1</span> <span>2</span> <div> <span>3</span> <span>4</span> </div> </div> <div> <span>5</span> <span>6</span> </div> </div>
function bfs(root){
const nodelist=[];
let queue=[root];
while(queue.length>0){
const parent=queue.splice(0,1)[0];
nodelist.push(parent);
for(var i=0;i<parent.children.length;i++){
queue.push(parent.children[i]);
}
//if(parent.children) queue=queue.concat(parent.children) children需要转成数组
}
return nodelist;
}
复制代码
背包问题
动态规划
要点:
1.确认最后一个问题
2.确认子问题
3.找到状态转移函数
4.确认计算顺序
计算最大递增子序列的长度 eg: 输入:array[2,3,1,5,6] 输出:4
function maxLengthList(array){
let dp=[0];
for(let i=1;i<array.length;i++){
dp[i]=1;
for(let j=0;j<i;j++){
array[i]>array[j] && (dp[i]=Math.max(dp[i] + dp[j]+1));
}
}
return Math.max(...dp);
}
复制代码
回溯
要点:1.路径path 2.终止条件return出递归 3.可选择数组selection
1.从 i 开始尝试 path 路径,直到结尾并记录该路径(res.push([...path]))。
2.回到 i 继续开始其他路径
案例一:输入一个数组,返回其元素组成的所有可能的等长数组的集合
//返回输入array的元素可以组成的所有数组的集合
function backtrack(array){
let res=[]; //最终返回的集合
let temppath = []; //存放此次的路径
function dfs(path){
if(temppath.length===array.length){ return res.push([...temppath]); }
//本次路径终止条件
for(var i=0;i<array.length;i++){
temppath.push(array[i]);//路径入队一个节点
dfs(temppath);//从这个节点开始继续走
temppath.pop();//路径清空
}
}
dfs([]);
return res;
}
复制代码
案例二:输入一个数组,返回其子集
function sonarray(array) {
const res = [];
const path = [];
function dfs(path, start) {
if(path.length===array.length) return;//子集小于父集
res.push([...path]);//走过的节点都加上
for (var i = start; i < array.length; i++) {
//从当前节点下一个开始
if(path.indexOf(array[i])!==-1) continue;
path.push(array[i]);
dfs(path, i);
path.pop();
}
}
dfs(path,0);
return res;
}
复制代码
案例三:输入一个数组,找到数组中元素和为target的组合
function cacula(array, target) {
const res = [];
const path = [];//路径保存
if(target>sum(array)) return 'empty';
function sum(array) {
if (array.length === 0) return 0;
return array.reduce((cur, next) => { return cur + next })
} //计算总和
function deps(path, start) {
//终止条件:找到target返回 大于target返回
if (sum(path) === target) { res.push([...path]); return; }
if (sum(path) > target) return pathTotal = 0;
for (var i = start; i < array.length; i++) {
path.push(array[i]);
console.log(1);
deps(path, i + 1);
path.pop();
}
}
deps([], 0);
return res;
}
复制代码
反转链表
案例一:将一个链表反转
双指针法
pre cur {next=cur.next cur.next=pre pre=cur cur=cur.next}
cur
复制代码
案例二:截取倒数第K位链表到表尾
双指针法
slow; fast; for(i++<k) {fast=fast.next} while(fase) {slow=slow.next;fase=fase.next;}
slow
复制代码
案例三:合并两个递增链表,合并后不完全递增
head = new nodelist; h=head;
while(l1 && l2){ l1.val<l2.val head.next=l1 l1=l1.next head=head.next};
if(l1/l2) head.next=l1/l2;
h
复制代码
单调栈
1.从栈顶到栈底单调递增或递减
2.将不符合单调性得出栈
//直接将任意数组进栈
//实现递增单调栈
function monstack(array){
const stack = [];
const len = array.length;
for(var i=0;i<len;i++){
//while(stack.length && array[i] > stack[stack.length-1]){ //递增栈
while(stack.length && array[i] < stack[stack.length-1]){//递减栈
stack.pop();
continue;
}
stack.push(array[i]);
}
return stack;
}
复制代码
冒泡排序
1、i和i+1....len-1依次做对比,放到指定顺序的位置然后继续下两个
2、直到没有需要交换位置的时候停止
function bublesort(array){
const len = array.length;
for(var i=0;i<len;i++){
for(var j=i+1;j<len;j++){
let cache = 0;
if(array[i]>array[j]){
cache = array[i];
array[i] = array[j];
array[j] = cache;
}
}
}
return array;
}
复制代码
快速排序
1.选择一个中间值,从数组中取出该中间值key
2.遍历剩下的数组,将比其大的放到right,比其小的放到right
3.再依次对左边执行快排和右边执行快排。return left.concat([key],right)
function quiksort(array){
if(array.length<=1) return array;//终止条件
const index = Math.floor((array.length-1)/2);//基准值index
const key = array.splice(index,1)[0];//原数组中删除基准值
let left=[],right = [];
for(var i=0;i<array.length;i++){
const item = array[i];
if(item<=key){left.push(item);}if(item>key){right.push(item);}
}
return quiksort(left).concat([key],quiksort(right));
}
复制代码
插入排序
1.将current位与左侧(current-1...0)排序好的做比较,将current放入比他小的元素后面
2.current从1开始
3.如果没有比它小的则插入头部
function insertsort(array){
const len = array.length;
for(var i=1;i<len;i++){//从1开始
const temp = array[i];
array.splice(i,1);
for(var j=i-1;j>=0;j--){
if(array[j]<temp){ array.splice(j+1,0,temp);break;}
if(j===0){array.splice(0,0,temp);}//找不到比他小的直接插入第一位
}
}
return array;
}
复制代码
二分查找
1.使用以上排序为数组排序。建议quiksort
2.search与array[middle]比较如果search>array[middle]则比较search与middle右侧
3.直到查到或者遍历完成
//key为查找的值
//array为已排序的数组或 array = quiksort(array)
//使用双指针的方法
function binarysearch(key,array){
const len = array.length;
let left = 0,right = len-1;
while(left<=right){
const middle=Math.floor((left+right)/2);
if(key===array[middle]){return middle;}
if(key<array[middle]){right=middle-1;}
if(key>array[middle]){left=middle+1;}
}
return false;
}
复制代码
类型判断
whatType = function(arg){
//null undefined直接返回对应的string
if(arg === null || arg === undefined){
return String(arg);
}
//object需要分类 array object
if(typeof arg === 'object'){
const name = Object.prototype.toString.call(arg).slice(7); //'[object Array]'
if(name.includes('Array')){ return 'array'; }
}
//其他类型直接使用typeof返回
return typeof arg;
}
复制代码
深拷贝
要点:
1.如果为基础类型直接返回
2.应用类型分为:array、object需要兼容
const cloneLoop = function(source) {
let res;
if(typeof source !== 'object'){ return source; }
if(typeof source === 'object' && source !== null){
res = Array.isArray(source) ? [] : {}; //区分当数组 对象
for(var k in source){//iterator遍历
if(source.hasOwnProperty(k)){ //排除原型链上的属性
//子元素为引用类型时,子元素深拷贝
if(typeof source[k] === 'object'){ res[k] = cloneLoop(source[k]); }
else{ res[k] = source[k]; } //终止条件,直接赋值
}
}
}
return res; //返回本轮深拷贝结果
}
复制代码
防抖节流
要点:
一段时间内只执行一次;每隔一段时间执行一次;都需要存储上一次执行的状态
//防抖 一段时间内只执行一次
const tt = function(fn, delay){
let timer=null;
return function(){
clearTimeout(timer);
timer=setTimeout(fn,delay);
}
}
//节流 每隔一段时间执行一次
const ll = function(fn, delay){
let start=new Date().getTime();
return function(){
const end = new Date().getTime();
if(end-start>delay){
setTimeout(fn,delay);
start=end;
}
}
}
复制代码
js数据结构
js数据结构之堆栈
1.先入后出
2.操作都在栈顶
3.出栈后的元素依然在栈中:找回删除后的数据
4.可以入栈、出栈、获取栈长度、查看栈顶元素、清空栈
class stack{
top = 0; //栈顶
state = [];//使用js的数组模拟栈
//出栈-栈顶-1
pop = function(){
return this.top ? this.state[--this.top] : 'empty';//出栈后栈顶-1
}
//入栈-栈顶+1 且栈顶+1存储新值
push = function(arg){
const top = this.top;
this.state[top] = arg;
return ++this.top;
}
//返回栈顶的元素
peek = function(){
return this.state[this.top-1]; //栈顶在数组最后一位
}
//获取当前栈的长度
getLength = function(){return this.top;}
//清空栈-当前栈顶为0时
clear = function(){this.state=[]; this.top=0; return true;}
}
//使用
const stacky = new stack();
stacky.push(1);//....
复制代码
案例1:
判断一个字符串是不是回文
const isRe = function(string){
if(typeof string !== 'string') return '请输入字符串';
const pre = new stack();
for(var i=0 in string){ //假设为12321
const len = Math.floor(string.length/2); //两段长度为2的字符串
const middle = string.length%2 === 1 ? len : 0; //中间数index=2
if(i<len){
pre.push(string[i]);
}else{
if(middle===0 || Number(i)!==middle){//不是中间数/没有中间数
if(pre.pop()!==string[i]) return false;
}
}
}
return true;
}
//注意:for in 返回为string
//使用
isRe('')//true
isRe('1')//false
isRe('121');//true
复制代码
案例2:实现数制间的相互转换
要点:
num%base 为当前位数值;Math.floor(num/base)为下一位总数:num=Math.floor(num/base)
const transform = function(num, base){
const stacknum = new stack();//使用堆栈
let data=''; //保存转换后的位数
while(num!==0){//终止条件
stacknum.push(num%base);
num=Math.floor(num/base);
}
const len = stacknum.getLength();//位数长度
for(let i=0;i<len;i++){
data+=String(stacknum.pop());//从最高位抛出
}
return Number(data); //返回值转换成数值
}
//使用
ttransform(32,2) //100000
复制代码
js数据结构之列表
1、包含一系列属性和方法
2、基本操作类似数组可直接使用数组封装。笔者不做过多介绍了...
const list = ()=>{
this.listSize = 0; //初始化元素个数为0
this.pos = 0; //初始化位置为0
this.dataStore = []; //初始化空数组来保存列表元素
this.clear = clear;
this.find = find; //寻找元素
this.toString = toString; //显示列表中的元素
this.insert = insert;
this.append = append;
this.remove = remove;
this.front = front;
this.end = end;
this.prev = prev;
this.next = next;
this.length = length; //列表中的元素总数
this.currPos = currPos;
this.moveTo = moveTo;
this.getElement = getElement;
this.contains = contains; //判断给定值是否在列表中
}
复制代码
js数据结构之链表
js链表与数组的区别:数组是需要一块连续的内存空间来存储,对内存的要求比较高。 而链表却相反,它并不需要一块连续的内存空间。链表是通过指针将一组零散的内存块串联在一起。
1.链表的最小单位是元素,一个元素保存其本身以及其下一个元素or其上一个元素
2.只保存下一个元素的链表是单向链表。同时存在上一个和下一个元素的链表为双向链表
3.链表初始化时: 头节点为null, this.head=null; this.count=0; 可以增删改查.
class Node {
constructor(element){
this.element = element;
this.next = undefined;
}
}
class LinkedList {
constructor(){
this.head = null;
this.count = 0;
}
//往链表尾部添加元素
push(element){
const nodes = new Node(element);
let current=this.head;
if(this.head===null){
this.head = nodes;
}else{
while(current.next){
current = this.head.next;
}
current.next = nodes;
}
this.count++;
}
removeAt(position){
//[position-1].next=[position].next
let current = this.head; //寻找position-1记为current
if(position>this.count-1){return false;}
if(position===0){this.head = this.head.next || null; return true; }
for(let i=0;i<position-1;i++){
//position前为空即position位不存在
current = current.next;
}
current.next = current.next.next; //将position-1的next指向position的next
return true;
}
remove(element){
console.log('12');
if(this.head.element === element) {
this.head = this.head.next;
return true;
}
let current = this.head;
while(current.next){
if(current.next.element===element){
current.next=current.next.next; //将element的前一个的next指向element的next
return true;
}
current=current.next;
}
return false;
}
insert(position, element){
//0时直接将head替代,之前的head做head.next
//将[position-1].next = element
//且element.next = [position-1].next(记录position的next)
element = new Node(element);
let next;
if(position===0){
next = this.head;
this.head = element;
this.head.next = next;
return true;
}
let current = this.head;
for(let i=0; i < position-1; i++){
if(current === undefined && i<position-1){ return false;}
current=current.next;
}
next = current.next;
current.next = element;
element.next = next;
this.count++;
return true;
}
}
复制代码
js数据结构之队列
1. 先进先出
2. 入队在队尾,出队在队头
class Queue {
#data=[]; //私有数组变量存储队列内容
enqueue(item){ //队尾插入
this.#data.push(item);
return true;
}
dequeue(){ //队首出队
if(!this.#getLength()) return 'empty';
this.#data.reverse().pop(); //shifit为O(n)避免
this.#data.reverse(); //b反转
return true;
}
//查看队首元素
front(){ if(this.isEmpty()) return 'empty'; return this.#data[0]; }
//查看队尾元素
back(){ if(this.isEmpty()) return 'empty'; return this.#data[this.#getLength()-1]; }
//清空队列
clear(){ this.#data = []; }
//查看队列内容
toString(){ return this.#data.join()}
//判断是否为空
isEmpty(){ return !this.#data.length; }
//私有变量内部使用函数,返回队列的长度
#getLength(){
return this.#data.length;
}
}
复制代码
js数据结构之树
二叉树 满二叉树 完全二叉树
1.每个根节点只有两个子节点:root left right
2.节点之间有顺序:left<root<right
二叉树存储方式:
//链式存储
class Node {
current: '',
left: null,
right: null
}
//顺序存储 使用数组 从i=1 123 完全二叉树使用此最省存储空间
root=i;
left=2*i;
right=2*i + 1;
复制代码
遍历:前 中 后
前序
1、root出发
2、root.left.left不存在时 再root.right
//前 root left right
const preOrder=(root)=>{
let stack = [];
r(root);
return order;
function r(root){
if(root) return;
stack.push(root.val);
root.left && r(root.left); //遍历左子树
root.right && r(root.right); //遍历右子树
}
}
//中 left root right
const centerOrder=(root)=>{
const stack=[];
r(root);
return stack;
function r(root){
if(root) return;
root.left && r(root.left);
stack.push(root.val);
root.left && r(root.right);
}
}
//后 left right root
const nextOrder=(root)=>{
const stack=[];
r(root);
return stack;
function r(root){
if(!root) return;
root.left && r(root.left);
root.right && r(root.right);
stack.push(root);
}
}
//层次遍历 类似广度优先
function levelOrder() {
if (this.root == null) return '';
let a = [],
left, right;
a.push(this.root);
// 节点入队,指针指向头部元素,如果它有left/right,入队。
// 指针后移,继续同样步骤。。。直至指针跑到队列尾部后面去。。。
for(let i = 0; i < a.length; i++) { // 需要注意的是,数组 a 的长度是动态变化的(不停变长)
left = a[i].left;
if (left) a.push(left);
right = a[i].right;
if (right) a.push(right);
}
return a.map(item => item.val).join(',');
}
复制代码
dfs bfs
深度遍历: 广度遍历:
//BFS bread-first-search
function bfs(root){
const stack=[]; //打印出树的所有节点信息
const queue = [];
queue=[root];
while(queue.length>0)
stack.push(queue.splice(0,1)[0]);
root.children && queue=queue.concat(root.children);
}
}
//DFS depth-first-search
//递归
function dfs(root){
const nodelist=[];
function r(root){
if(!root) return;
nodelist.push(root);
for(leti=0;i<root.children.length;i++){
r(root.children[i]);
}
}
return nodeList;
}
//非递归
//栈--shifi成本太高了(O(n))
function dfs(root){
const nodelist=[];
const queue=[];
queue=[root];
while(queue.length>0){
rootitem=queue.pop();//后出
nodelist.push(rootitem);
for(let i=rootitem.children.length-1;i>0;i--){//右侧-先入 保证左侧先执行
queue.push(rootitem.children[i]);
}
}
}
复制代码
设计模式
观察者模式 and 发布订阅模式
要点:
1.观察者模式多个观察者对应一个被观察者,两者直接关联。被观察者提供绑定和触发功能,观察者使用其绑定功能
2.发布订阅中可以多对多,且引入一个中间数据管理者,两个对象只对数据管理中心做发布与订阅操作
观察者模式图:(来自知乎)
发布订阅模式图:(来自知乎)
//观察者模式
//多个观察者和一个被观察者
class Subject{
msg=[];
subscribe(callback){
if(this.msg.includes(callback)) return false;
this.msg.push(callback);
}
publish(args){
if(this.msg.length===0) return;
this.msg.map(handle => handle.apply(null, args))
}
}
const subject = new Subject(); //被观察者
//两个观察者
const observer1 = subject.subscribe((...args)=>{console.log('观察者1号观察到变化:'+args);});
const observer2 = subject.subscribe((...args)=>{console.log('观察者2号观察到变化:'+args)});
subject.publish('hahaha'); //被观察者发布变化
//发布订阅模式
//实现一个观察者中心
//订阅者通过中心订阅事件
//数据源通过中心发布改变
//中心触发订阅者更新
class EventCenter{
msg = {}; //储存所有观察信息
emit(event,...args){
//事件触发
if(!this.msg[event]) return false;
this.msg[event].forEach(item=>{ item.apply(null, args) });
}
on(event, callback){
//事件注册
if(!this.msg[event]){ this.msg[event] = [callback]; return true;}
this.msg[event].push(callback);
}
remove(event, callback){
//事件移除
if(!this.msg[event]) return false;
this.msg[event].filter(item=>item !== callback)
}
once(event,callback){
//注册单次执行事件
//单次执行即 执行后remove
const wrapFunc = (...args) => {
callback.apply(args);
this.off(event, callback);
}
this.on(event, callback);
}
}
const manerger = new EventCenter();//创建管理中心
const observer1 = (arg) => {console.log(111+arg)}
const observer2 = (arg) => {console.log(222+arg)}
//发布者
const publisher = (()=>{
return {update1: ()=>{
manerger.emit('update', observer1);
manerger.emit('update', observer2);
}}
})()
//订阅者
const subscriber=(()=>{
return {update: ()={
ob.on('update', observer1);
ob.on('update', observer2);
}}
})()
subscriber.update();//发布
publisher.update1('订阅被触发啦!');//订阅触发
复制代码
未完待续...