HTML
CSS
JavaScirpt
柯里化
1.写一个currying,要求 add(1)(2)(3)(4) 打印10。
function add(...args) {
return args.reduce((acc, item) => acc + item);
}
function currying(fn) {
let args = [];
function _c(...newArgs) {
if (newArgs.length) {
// 合并参数
args = [...args, ...newArgs];
// 继续返回函数
return _c;
} else {
// 返回执行结果
return fn.apply(null, args);
}
}
/**
* 追加的
* =====================================
* 利用console.log()打印函会调用fn.去掉最后的()
*/
//_c.toString = () => {
// return fn.apply(null, args);
//};
return _c;
}
let addCurry = currying(add);
console.log(addCurry(1)(2)(3)(4)());
// 追加的
// console.log(addCurry(1)(2)(3)(4));
大数相加
V8
图解V8笔记
1-1. V8是如何执行一段JavaScript代码的
因为计算机只能识别二进制指令,所以要让计算机执行一段高级语言通常有两种手段,第一种是将高级代码转换为二进制代码,再让计算机去执行;另外一种方式是在计算机安装一个解释器,并由解释器来解释执行(当然最后CPU跑的还是二进制指令。只不过解析器抽象了一层。还是那句话,解决不了就再加一层。同样这也是较慢的原因)。
解释执行和编译执行都有各自的优缺点,解释执行启动速度快,但是执行时速度慢,而编译执行启动速度慢,但是执行速度快。为了充分地利用解释执行和编译执行的优点,规避其缺点,V8 采用了一种权衡策略,在启动过程中采用了解释执行的策略,但是如果某段代码的执行频率超过一个值,那么 V8 就会采用优化编译器将其编译成执行效率更加高效的机器代码。
理解了这一点,我们就可以来深入分析 V8 执行一段 JavaScript 代码所经历的主要流程了,这包括了:
- 初始化基础环境;解析源码生成 AST 和作用域;
- 依据 AST 和作用域生成字节码;解释执行字节码;
- 监听热点代码;优化热点代码为二进制的机器代码;
- 反优化生成的二进制机器代码。
这里需要注意的是,JavaScript 是一门动态语言,在运行过程中,某些被优化的结构可能会被 V8 动态修改了,这会导致之前被优化的代码失效,如果某块优化之后的代码失效了,那么编译器需要执行反优化操作。
内存回收机制
1. let obj = new Vue()。这个obj什么时候会回收。直接new Vue呢
Node.js
跨端
小程序
react-native
前端工程化
webpack
1.loader和plugin的区别是什么?
2.webpack打包优化
网络
Http、http2、http3
1. options请求是什么?有什么作用?
安全
1.xss和csrf
浏览器
浏览器工作原理
常用数据结构TS实现
链表
单向链表:用next指针将节点连接在一起。
实现思路:节点Node类+ 链表LinkedList类。
Node类
LinkedList类
双向链表:单向链表的基础上多维护一个prev指针
实现思路:继承Node类,增加一个prev指针。继承单向链表,重写与单向链表不同的相关函数
DoublyNode类 继承 Node类
DoublyLinkedList类 继承 LinkedList类
循环链表:单向链表基础上,尾节点指向头节点。
完整代码在这: blog/src/data-structure/一维数据结构/链表 at master · jolin27144/blog (github.com)
堆
将[1,2,5,12,7,17,25,19,36,99,22,24,46,92]这一组数,看成一个完全二叉树。
调整后,若所有父节点比子节点小,则为小根堆。
调整后,所所有父节点比子节点大,则为大根堆。
这种数据结构称为堆。
小根堆
/**
*
* 树的性质
* 对于每个节点有NUM_CHILDREN个子节点,而不是2个,则公式为:
* root at 0 root at 1
* Left child index*NUM_CHILDREN + 1 index*NUM_CHILDREN
* Right child index* NUM_CHILDREN + 2 index*NUM_CHILDREN + 1
* Parent (index-1)/NUM_CHILDREN index/NUM_CHILDREN
*
*/
/**
* 构建小根堆
*/
function buildMinHeap(arr) {
// 向下取整
for (let i = parseInt(arr.length / 2); i >= 0; i--) {
sink(arr, i);
}
}
/**
* @description 下沉(这里是构建小根堆,大的父节点下沉)
* @param {number[]} arr 堆
* @param {number} parentIndex 需要判断是否需要下沉的节点索引
* @returns arr
*/
function sink(arr, parentIndex) {
// 先将最小节点的索引指向父节点
let minIndex = parentIndex;
// 左节点索引
let leftNodeIndex = 2 * parentIndex + 1;
// 右节点索引
let rightNodeIndex = 2 * parentIndex + 2;
// 判断是否有左节点
const hasLeftNode = () => arr[leftNodeIndex] !== undefined;
// 判断是否有右节点
const hasRightNode = () => arr[rightNodeIndex] !== undefined;
// 根据完全二叉树性质,没有左子节点,则已经没有子节点,不需要判断下沉。
if (!hasLeftNode) {
return;
}
// 如果有左节点且比父节点小。
if (hasLeftNode && arr[parentIndex] > arr[leftNodeIndex]) {
// 最小节点的索引指向左节点
minIndex = leftNodeIndex;
}
// 如果有右节点且比父节点小。
if (hasRightNode && arr[minIndex] > arr[rightNodeIndex]) {
// 最小节点的索引指向右节点
minIndex = rightNodeIndex;
}
// 如果父节点不是最小的节点。
if (minIndex !== parentIndex) {
// 父节点与最小的节点交换
[arr[parentIndex], arr[minIndex]] = [arr[minIndex], arr[parentIndex]];
// 交换后,以与父节点交换的子节点的索引,作为新的父节点,继续判断是否需要下沉
return sink(arr, minIndex);
}
return arr;
}
/**
* @description 上浮
* @param {*} arr
* @param {*} index
* @returns
*/
function swim(arr, index) {
// 父节点索引
const parentIndex = parseInt((index - 1) / 2);
// 是否有父节点
const hasParent = () => arr[parentIndex] !== undefined;
// 如果有父节点,并且大于自己
if (hasParent && arr[parentIndex] > arr[index]) {
// 和父节点交换
[arr[parentIndex], arr[index]] = [arr[index], arr[parentIndex]];
return swim(arr, parentIndex);
}
return arr;
}
插入
/**
* 插入
* 尾部新增一个节点
*/
function insert(arr, el) {
arr.push(el);
// 插入叶子节点要进行上浮处理
swim(arr, arr.length - 1);
}
删除
/**
* 删除
* 删除堆顶元素
*/
function deleteLast(arr) {
// 先交换堆顶元素,和最后一个元素。
[arr[0], arr[arr.length - 1]] = [arr[arr.length - 1], arr[0]];
// 此时删除的是堆顶元素
arr.pop();
// 堆最后一个元素,被移到了堆顶,需要重新构建堆
sink(arr, 0);
}
排序
/**
* 堆排序
*/
function heapSort(heap) {
// 排序后的元素
let sort = [];
// 最小堆
let minHeap = [...heap];
while (minHeap.length !== 0) {
// 先交换堆顶元素,和最后一个元素。
[minHeap[0], minHeap[minHeap.length - 1]] = [
minHeap[minHeap.length - 1],
minHeap[0]
];
// 此时删除的是堆顶元素
sort.push(minHeap.pop());
// 堆最后一个元素,被移到了堆顶,需要重新构建堆
sink(minHeap, 0);
// [2, 5, 3, 7, 19, 17, 46, 12, 99, 22, 25, 28, 36, 92]
// console.log(heap);
}
return sort;
}
大根堆
实现思路一样,只是下沉的是小节点。上浮的是大节点。
数据结构和算法
数组
1.二分查找
function search(nums: number[], target: number): number {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = (left + right) >> 1;
if (nums[mid] === target) {
return mid;
}
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
export { search };
2.移除元素
function removeElement(nums: number[], val: number): number {
let slowIndex = 0;
let fastIndex = 0;
for (; fastIndex < nums.length; fastIndex++) {
if (val !== nums[fastIndex]) {
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
export {removeElement}
3.有序数组的平方
function sortedSquares(nums: number[]): number[] {
const result: number[] = new Array(nums.length).fill(0);
let numsLeft = 0;
let numsRight = nums.length - 1;
let resultRight = nums.length - 1;
// numsLeft和numsRight相等时,是最后一次处理
while (numsLeft <= numsRight) {
if (Math.pow(nums[numsLeft], 2) > Math.pow(nums[numsRight], 2)) {
result[resultRight] = Math.pow(nums[numsLeft], 2);
numsLeft++;
} else {
result[resultRight] = Math.pow(nums[numsRight], 2);
numsRight--;
}
resultRight--;
}
return result;
}
export { sortedSquares };
4.长度最小的子数组
function minSubArrayLen(target: number, nums: number[]): number {
let result: number, fastIndex: number, slowIndex: number, sum: number;
fastIndex = slowIndex = sum = result = 0;
for (; fastIndex < nums.length; fastIndex++) {
sum += nums[fastIndex];
while (sum >= target) {
const subLength = fastIndex - slowIndex + 1;
if (result === 0) {
result = subLength;
} else {
result = Math.min(subLength, result);
}
sum -= nums[slowIndex++];
}
}
return result;
}
export { minSubArrayLen };
5.螺旋矩阵 II
function generateMatrix(n: number): number[][] {
let matrix: number[][] = Array.from({ length: n }).map(() => new Array(n));
let left = 0;
let top = 0;
let right = n - 1;
let bottom = n - 1;
let num = 1;
while (num <= n * n) {
for (let i = left; i <= right; i++) {
matrix[top][i] = num++;
}
top++;
for (let i = top; i <= bottom; i++) {
matrix[i][right] = num++;
}
right--;
for (let i = right; i >= left; i--) {
matrix[bottom][i] = num++;
}
bottom--;
for (let i = bottom; i >= top; i--) {
matrix[i][left] = num++;
}
left++;
}
return matrix;
}
export { generateMatrix };
链表
1.移除链表元素
function removeElements(head: ListNode | null, val: number): ListNode | null {
if (head === null) {
return null;
}
let current: ListNode | null = head;
while (current) {
// 头节点的情况
if (current.val === val) {
head = current.next;
current = head;
continue;
} else if (current.next && current.next.val === val) {
current.next = current.next.next;
continue;
}
current = current.next;
}
return head;
}
2.设计链表
// 链表
class MyLinkedList {
// 链表长度
private size: number;
private head: LinkedNode | null;
constructor() {
this.head = null;
this.size = 0;
}
protected validateIndex(index: number): boolean {
return index >= 0 && index <= this.size - 1;
}
// 此方法,方便插入、删除节点
getNodeAtIndex(index: number): LinkedNode | null {
if (!this.validateIndex(index)) {
return null;
}
let i = 0;
let current = this.head as LinkedNode;
while (i !== index) {
current = current.next as LinkedNode;
i++;
}
return current;
}
get(index: number): number {
const node = this.getNodeAtIndex(index);
if (node instanceof LinkedNode) {
return node.val;
}
return -1;
}
// FIXME
addAtHead(val: number): void {
const myLinkNode: LinkedNode = new LinkedNode(val);
const head = this.head;
// TODO
// const dummyHead = new LinkedNode(0)
// dummyHead.next = this.head
// 空链表
if (!head) {
this.head = myLinkNode;
} else {
// 非空
myLinkNode.next = head;
this.head = myLinkNode;
}
this.size++;
}
addAtTail(val: number): void {
const myLinkNode: LinkedNode = new LinkedNode(val);
const head = this.head;
// 空链表
if (!head) {
this.head = myLinkNode;
} else {
// 非空
let current = head;
while (current.next) {
current = current.next;
}
current.next = myLinkNode;
}
this.size++;
}
addAtIndex(index: number, val: number): void {
if (index <= 0) {
// 如果index小于等于0,则在头部插入节点。
this.addAtHead(val);
return;
} else if (index === this.size) {
// 如果 index 等于链表的长度,则该节点将附加到链表的末尾
return this.addAtTail(val);
} else if (index > this.size) {
// 如果 index 大于链表长度,则不会插入节点
return;
}
const myLinkNode: LinkedNode = new LinkedNode(val);
// index 是有效的, 因此一定能返回节点
const pre = this.getNodeAtIndex(index - 1) as LinkedNode;
myLinkNode.next = pre.next;
pre!.next = myLinkNode;
this.size++;
}
deleteAtIndex(index: number): void {
// 0开始。无效索引返回
if (!this.validateIndex(index)) {
return;
}
if (index === 0) {
// 删除头节点
this.head = this.head!.next;
} else if (index === this.size - 1) {
// 删除尾节点, 处理倒数第二个节点
const pre = this.getNodeAtIndex(index - 1);
pre!.next = null;
} else {
// 其他情况,一定会有pre.next.next;例如删除倒数第二个节点,取倒数第三个节点,则有pre.next.next
const pre = this.getNodeAtIndex(index - 1);
pre!.next = pre!.next!.next;
}
this.size--;
}
}
3.反转链表
/*
* 1、双指针解法(我个人理解这实际上是3个指针)
* 图示意
* head->node1->node2->null
* null<-head<-node1<-node2
*
* 双指针(我个人理解这实际上是3个指针)思路:A跟B 交换一次,那么A,B就反转了。如果不保存C,跟C就断了。因此要先取到C。
* 因此这实际上是取三个节点指针,反转前两个节点。然后往后一位,重复。直到后一位是空
*/
// function reverseList(head: LinkedNode | null): LinkedNode | null {
// // previousNode 指向空(假设是第一个节点)
// let previousNode = null;
// // currentNode 指向头(当作第二个节点)
// let currentNode = head;
// while (currentNode) {
// // temp指向 第三个节点
// const temp = currentNode.next;
//
// // 反转, 令第二个节点指向第一个节点
// currentNode.next = previousNode;
//
// // 后移一位
// previousNode = currentNode;
// currentNode = temp;
// }
//
// return previousNode;
// }
/**
* 2、递归解法
* 跟循环原理一样。只是把循环改成递归。看注释
*/
function reverse(
pre: LinkedNode | null,
cur: LinkedNode | null
): LinkedNode | null {
// 结束条件,cur指向null了,返回pre
if (!(cur instanceof LinkedNode)) {
return pre;
}
// const temp = cur.next;
cur.next = pre;
// 递归就是在做这个事件
// pre = cur;
// cur = temp;
return reverse(cur, cur.next);
}
function reverseList(head: LinkedNode | null): LinkedNode | null {
// 双指针开始调用
return reverse(null, head);
}
4.两两交换链表中的节点
/**
* cur->1->2->3->4->5
*
* step1: cur.next => 2
* step2: 2.next => 1
* step3:1.next => 3
*
* 一组交换完,cur向后移动两位
*
*/
function swapPairs(head: ListNode | null): ListNode | null {
// 构造虚拟头节点
const dummyNode: ListNode = new ListNode(0, head);
let cur = dummyNode;
// 目的是将后两个节点交换,所以终止条件为后两个节点其中之一为空.
while (cur.next !== null && cur.next.next !== null) {
const temp1 = cur.next;
const temp2 = cur.next.next;
const temp3 = cur.next.next.next;
cur.next = temp2;
cur.next.next = temp1;
cur.next.next.next = temp3;
cur = cur.next.next;
}
return dummyNode.next;
}
5.删除链表的倒数第 N 个结点
哈希表
1.两数之和
/**
* @description 循环的时候,通过hash表找前面有无符合的元素(思想:缓存),有则返回,没有则添加到hash
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
function twoSum(nums, target) {
const hashMap = new Map()
for (let i = 0; i < nums.length; i++) {
const item = nums[i]
const key = target - item
if (!hashMap.has(key)) {
hashMap.set(item, i)
continue;
}
return [hashMap.get(key), i]
}
};
let nums = [2, 7, 11, 15], target = 9
const result = twoSum(nums, target)
console.log(result)
// 输入:nums = [2,7,11,15], target = 9
// 输出:[0,1]
// 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
滑动窗口
1. 无重复字符的最长子串
/**
* @description 最长无重复子串。
* 双指针,右一直加,加到有重复,就左一直加,直到无重复。(至于这个规律怎么推出来的,不知道)
* 记录每个字串长度,找出最长的。
* @param {string} s
* @return {number}
*/
function lengthOfLongestSubstring(s) {
if (!s) {
return 0;
}
if (s.length === 1) {
return 1;
}
let max = 0;
const set = new Set();
let left = 0,
right = -1;
for (; left < s.length; left++) {
if (left !== 0) {
set.delete(s[left - 1]);
}
while (right + 1 < s.length && !set.has(s[right + 1])) {
set.add(s[right + 1]);
++right;
}
max = Math.max(set.size, max);
}
return max;
}
// 输入: s = "abcabcbb"
// 输出: 3
堆
1.数组中的第k大的元素(字节)
求第k大元素:
- 任取k个元素。
- 把k个元素构建成最小堆。
- 遍历剩下的元素,若比堆顶元素大,则替换堆顶的元素。堆顶元素下沉。
- 遍历完,等到k个元素构成的最小堆,堆顶元素即为第k大的元素。
举例:
求[1,2,3,4,5] 里面第3大的元素。
则先取[1,2,3]三个数,构成最小堆。
4比堆顶元素1大,则替换掉1,同时维护堆。变成[2,4,3]
5比堆顶元素2大,则替换掉2,同时维护堆。变成[3,4,5]
堆顶元素3,即为第三大的元素。
/**
* @description 节点下沉(这里是构建小根堆,大的父节点下沉)
* @param {array} arr
* @param {number} index
* @returns arr
*/
function sink(arr, index) {
// 构建完成
let notFinish = true;
// 最小元素的索引
let minIndex;
// 有左节点并且未构建完成
while (2 * index + 1 <= arr.length && notFinish) {
// 父节点比左节点大
if (arr[index] > arr[2 * index + 1]) {
// 最小元素的索引为左节点
minIndex = 2 * index + 1;
} else {
// 否则最小元素的索引为自己
minIndex = index;
}
// 如果有右节点,则讨论右节点
if (2 * index + 2 <= arr.length) {
//左节点比右节点大
if (arr[minIndex] > arr[2 * index + 2]) {
// 最小元素的索引为右节点
minIndex = 2 * index + 2;
}
}
// 最小index不是自己,即有子节点比自己小,交换
if (minIndex !== index) {
// 交换
[arr[index], arr[minIndex]] = [arr[minIndex], arr[index]];
// 更新index为与自己交换的子节点,继续调整
index = minIndex;
} else {
// 最小索引是自己,则当前节点比两个子节点都要小了。结束
notFinish = false;
}
}
// 返回小根堆
return arr;
}
/**
* 构建小根堆
*/
function buildMinHeap(arr) {
// 向下取整
for (let i = parseInt(arr.length / 2); i >= 0; i--) {
sink(arr, i);
}
}
/**
* @description 找第k大的元素
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
function findKthLargest(nums, k) {
// 取前k个数
const arrleft = nums.slice(0, k);
// 将其构建成小根堆
buildMinHeap(arrleft);
// 剩余的数,比堆顶大则替换,重新构建最小堆
const arrRight = nums.slice(k);
// 循环比较
arrRight.forEach((item) => {
// 若比堆顶元素大
if (arrleft[0] < item) {
// 替换
arrleft[0] = item;
// 下沉,重新构建最小堆
sink(arrleft, 0);
}
});
console.log(arrleft[0]);
return arrleft[0];
}
其他
1.LRU缓存机制
/**
* @description 实际上:通过顺序判断新鲜度即可,越靠后越新鲜
* @param {number} capacity
*/
class LRUCache {
capacity;
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
/**
* @param {number} key
* @return {number}
*/
get(key) {
if (!this.cache.has(key)) {
return -1;
}
const value = this.cache.get(key);
this.cache.delete(key);
// 往最后插入
this.cache.set(key, value);
return value;
}
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else {
if (this.cache.size === this.capacity) {
// 第一个,最不新鲜
this.cache.delete(this.cache.keys().next().value);
}
}
this.cache.set(key, value);
}
}
const lRUCache = new LRUCache(2);
console.log(lRUCache);
/**
* Your LRUCache object will be instantiated and called as such:
* var obj = new LRUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/