数组扁平化
let flat = arr => arr.reduce((list, v) => list.concat(Array.isArray(v) ? flat(v) : v), [])
v2
function flat(arr) {
let res = []
for (let item of arr) {
if (Array.isArray(item)) {
res = res.concat(flat(item))
} else {
res.push(item)
}
}
return res
}
类型
加法操作符的基本规则
- 两者都为数字 进行普通的数字相加
- 一方为字符串 则转换另一方为字符串后进行字符串拼接
- 一方为对象类型 转换为字符串后 继续应用上一条规则
- null + 1 = 1 原因是null被转换成了数字 这和红宝书上有冲突,应该null undefined会和另外一项进行自适应 如果是数字 自己也变数字,如果是字符 自己也变字符
相等操作符的运算规则
[] == ![] -> [] == false -> [] == 0 -> [].valueOf() == 0 -> [].toString() == 0 -> ‘’ == 0 -> 0 == 0 -> true
继承
寄生组合式继承
function Person(name) {
this.name = name
}
Person.prototype.sayName = function() {
console.log(this.name)
}
function Boy(name) {
this.sex = 'box'
Person.call(this, name)
}
let prototype = Object.create(Person.prototype)
prototype.constructor = Boy
Boy.prototype = prototype
let boy1 = new Boy("TOM")
boy1.sayName()
特点 继承的只是方法,父类实例不共享变量,并且不需要New一个多余的父类变量,只需要继承父类的原型。这里面的构造函数没有被覆盖,只是建立再一个新的对象上。
用Class
class Person {
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
class Boy extends Person {
constructor(name) {
super(name)
}
}
let boy1 = new Boy("Tom")
boy1.sayName()
算法
快速排序
function quick_sort1(arr) {
if (!arr || arr.length < 2) return arr
const pivot = arr.pop()
const left = arr.filter(v => v <= pivot)
const right = arr.filter(v => v > pivot)
return quick_sort1(left).concat([pivot], quick_sort1(right))
}
function quickSort2(arr) {
if (arr.length <= 1) {
return arr
}
let pi = Math.floor(arr.length / 2)
let p = arr.splice(pi, 1)[0]
let left = []
let right = []
let i = arr.length
while (i--) {
let cur = arr[i]
if (cur < p) {
left.push(cur)
} else {
right.push(cur)
}
}
return quickSort2(left).concat([p], quickSort2(right))
}
function quick_sort3(arr, start, end) {
let mid = arr[start],
p1 = start,
p2 = end
while (p1 < p2) {
swap(arr, p1, p1 + 1)
while (compare(arr[p1], mid) >= 0 && p1 < p2) {
swap(arr, p1, p2--)
}
p1++
}
if (start < p1 - 1) quick_sort3(arr, start, p1 - 1)
if (p1 < end) quick_sort3(arr, p1, end)
}
异步
简单的Promise(无法.then连续返回新Promise 不符合A+规范)
const PromiseStateMap = {
pending: 'pending',
resolved: 'resolved',
rejected: 'rejected'
}
class Promise {
static resolve(val) {
if (val instanceof Promise) {
return val
}
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(val)
})
})
}
static reject(val) {
if (val instanceof Promise) {
return val
}
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(val)
})
})
}
constructor(fn) {
this.val = null
this.state = PromiseStateMap.pending
this.resolvedCbs = []
this.rejectedCbs = []
fn(
res => {
this.val = res
this.state = PromiseStateMap.resolved
for (let fn of this.resolvedCbs) {
fn(this.val)
}
},
res => {
this.val = res
this.state = PromiseStateMap.rejected
for (let fn of this.rejectedCbs) {
fn(this.val)
}
}
)
}
then(onResolved, onRejected) {
if (typeof onResolved === 'function') {
this.resolvedCbs.push(onResolved)
}
if (typeof onRejected === 'function') {
this.rejectedCbs.push(onRejected)
}
}
}
进程与线程
进程是 CPU 资源分配的最小单位;线程是 CPU 调度的最小单位
可以认为一个进程就是一个正在运行中的程序,而一个线程是这个程序中的执行流,现在的操作系统都是多进程的,可以同时运行多个程序。
手写call,apply,bind
Function.prototype.mycall = function(ctx, ...args) {
if (!ctx || typeof ctx !== 'object') {
console.error('ctx must be a object!')
}
let fn = this
let key = Symbol()
ctx[key] = fn
let res = ctx[key](...args)
return res
}
Function.prototype.myapply = function(ctx, args) {
if (!ctx || typeof ctx !== 'object') {
console.error('ctx must be a object!')
}
let fn = this
let key = Symbol()
ctx[key] = fn
let res = ctx[key](...args)
return res
}
Function.prototype.mybind = function(ctx) {
if (!ctx || typeof ctx !== 'object') {
console.error('ctx must be a object!')
}
let fn = this
return function(...args) {
let key = Symbol()
ctx[key] = fn
let res = ctx[key](...args)
return res
}
}
实现instanceOf
instanceOf的原理是沿着left对象的原型链进行检查 看是否和right构造函数的原型对象相等
function myInstanceOf(left, right) {
let targetProto = right.prototype
let curProto = Object.getPrototypeOf(left)
while (curProto) {
if (curProto === targetProto) {
return true
} else {
curProto = Object.getPrototypeOf(curProto)
}
}
return false
}
VDOM
VDOM是一种技术,一种理念,他把真实的DOM与JS中的对象进行一种映射,也就是说在JS这一端与真实DOM端建立了一层抽象
他的好处有如下几点
- 增强Diff的性能 可以通过对比前后VnodeTree来找到更新的节点,进行局部更新。
- 建立抽象层,方便移植到多平台,进行SSR
打开浏览器 发生
大纲
DNS解析 多级缓存
获取IP 进行TCP链接 三次握手 SYN SYN_ACK ACK
如果是https 进行TLS加密
获取到过程底层其实是按包来的
获取HTML文件 中间加载各种资源 几乎并发 最多6个套接字同时进行 资源的缓存机制强缓存与协商缓存 资源可能会gzip
合并两棵树 DOM 和 CSSDOM树 进行合并
进行首次绘制 用户看到内容
算法部分
按位实现加法
function sum(a, b) {
if (a == 0) return b
if (b == 0) return a
let newA = a ^ b
let newB = (a & b) << 1
return sum(newA, newB)
}
所有的排序
function swap(arr, a, b) {
let temp = arr[a]
arr[a] = arr[b]
arr[b] = temp
}
let testArr = [10, 3, 50, 11, 88, 2390, 1, 2, 11]
// 冒泡排序
function bsort(arr) {
let len = arr.length
arr = arr.slice()
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1)
}
}
}
return arr
}
// 插入排序
function iSort(arr) {
let len = arr.length
for (let i = 1; i < len; i++) {
let cur = arr[i]
let j = i - 1
while (j >= 0 && arr[j] > cur) {
arr[j + 1] = arr[j]
j--
}
arr[j + 1] = cur
}
return arr
}
// 快速排序
function qsort(arr) {
let len = arr.length
if (len <= 1) {
return arr
}
let pivotIndex = Math.floor(len / 2)
let pivot = arr.splice(pivotIndex, 1)[0]
let left = []
let right = []
for (item of arr) {
if (item < pivot) {
left.push(item)
} else {
right.push(item)
}
}
return qsort(left).concat([pivot], qsort(right))
}
// 选择排序
function ssort(arr) {
let len = arr.length
arr = arr.slice()
for (let i = 0; i < len; i++) {
let index = i
for (let j = i; j < len; j++) {
if (arr[j] < arr[index]) {
index = j
}
}
swap(arr, i, index)
}
return arr
}
// 归并排序
function msort(arr) {
let len = arr.length
if (len <= 1) {
return arr
}
let middle = Math.floor(len / 2)
let left = msort(arr.slice(0, middle))
let right = msort(arr.slice(middle, len))
let start1 = 0,
start2 = 0,
end1 = middle,
end2 = len - middle
let res = []
while (start1 < end1 && start2 < end2) {
left[start1] < right[start2]
? res.push(left[start1++])
: res.push(right[start2++])
}
while (start1 < end1) {
res.push(left[start1++])
}
while (start2 < end2) {
res.push(right[start2++])
}
return res
}
颜色排序算法
function tSort(arr) {
let len = arr.length
let left = -1
let right = len
for (let i = 0; i < right; i++) {
if (arr[i] === 0) {
swap(arr, i, ++left)
}
if (arr[i] === 2) {
swap(arr, i--, --right)
}
}
return arr
}
线性统计 获取第K大的值
小心splice[]
注意 p = left.length + 1
function select(nums, i) {
let len = nums.length
if (len <= 1) {
return nums[0]
}
let pivotIndex = Math.floor(len / 2)
let pivot = nums.splice(pivotIndex, 1)[0]
let left = []
let right = []
for (num of nums) {
if (num < pivot) {
left.push(num)
} else {
right.push(num)
}
}
let p = left.length + 1
if (p === i) {
return pivot
}
if (i < p) {
return select(left, i)
} else {
return select(right, i - p)
}
}
堆排序 最大最小堆
建堆的时候小心顺序 i = floor(len / 2) i >=0 i --
一定要从后往前建
小心heapsize--的顺序
let array = [5, 2, 6, 1, 6, 8, 2, 39, 2, 6, 89, 5, 6, 4, 7]
const swap = (arr, a, b) => {
let temp = arr[a]
arr[a] = arr[b]
arr[b] = temp
}
const left = i => i * 2
const right = i => i * 2 + 1
function HEAPIFY(arr, i) {
let l = left(i)
let r = right(i)
let largest = i
if (l < arr.heapsize && arr[l] > arr[largest]) {
largest = l
}
if (r < arr.heapsize && arr[r] > arr[largest]) {
largest = r
}
if (largest !== i) {
swap(arr, i, largest)
HEAPIFY(arr, largest)
}
}
function BUILD_HEAP(arr) {
arr.heapsize = arr.length
for (let i = Math.floor(arr.length / 2); i >= 0; i--) {
HEAPIFY(arr, i)
}
return arr
}
function HEAP_SORT(arr) {
BUILD_HEAP(arr)
for (let i = arr.length - 1; i >= 0; i--) {
swap(arr, 0, i)
arr.heapsize--
HEAPIFY(arr, 0)
}
return arr
}
单向链表 以及 反转功能
// 单项链表
class ListNode {
constructor(val, next) {
this.val = val
this.next = next
}
}
class LinkList {
constructor() {
this.head = null
this.tail = null
}
add(val) {
if (!this.head || !this.tail) {
this.head = this.tail = new ListNode(val, null)
} else {
this.tail.next = new ListNode(val, null)
this.tail = this.tail.next
}
}
reverse() {
if (!this.head | !this.tail) return
let pre = null
let current = this.head
let next = null
while (current) {
next = current.next
current.next = pre
pre = current
current = next
}
let temp = this.head
this.head = this.tail
this.tail = temp
}
}
let linklist = new LinkList()
linklist.add('a')
linklist.add('b')
linklist.add('c')
linklist.add('d')
linklist.reverse()
树的遍历 递归和迭代实现
function TreeNode(val) {
this.val = val
this.left = this.right = null
}
function traverse(root) {
if (root) {
console.log(root)
if (root.left) {
traverse(root)
}
if (root.right) {
traverse(root)
}
}
}
function pre(root) {
if (root) {
let stack = []
stack.push(root)
while (stack.length > 0) {
let item = stack.pop()
console.log(item)
if (item.right) {
stack.push(right)
}
if (item.left) {
stack.push(left)
}
}
}
}
function mid(root) {
if (root) {
let stack = []
stack.push(root)
while (stack.length >= 1) {
if (root) {
stack.push(root)
root = root.left
} else {
let item = stack.pop()
console.log(item)
root = item.right
}
}
}
}
function pos(root) {
if (root) {
let stack1 = []
let stack2 = []
stack1.push(root)
while (stack1.length >= 1) {
let item = stack1.pop()
stack2.push(item)
if (item.right) {
stack1.push(item.right)
}
if (item.left) {
stack1.push(item.left)
}
}
while (stack2.length >= 1) {
console.log(stack2.pop())
}
}
}
前驱节点 后驱节点
前驱先看Left 后续全部right
后续先看right 后续全部left
function successor(node) {
if (node.right) {
return findLeft(node.right)
} else {
let parent = node.parent
while (parent && parent.left === node) {
node = parent
parent = node.parent
}
return parent
}
}
function findLeft(node) {
while (node) {
if (!node.left) {
return node
}
node = node.left
}
}
function predecessor(node) {
if (node.left) {
return getRight(node.left)
} else {
let parent = node.parent
while (parent && parent.right === node) {
node = parent
parent = node.parent
}
return parent
}
}
function getRight(node) {
while (node) {
if (!node.right) {
return node
}
node = node.right
}
}
获取树的最大深度
function MAX_DEPTH(root) {
if (!root) {
return 0;
}
return Math.max(MAX_DEPTH(root.left), MAX_DEPTH(root.right)) + 1;
}
帅的不行Fib
const fib = n =>
Array(n)
.fill(1)
.reduce(nums => [nums[1], nums[0] + nums[1]], [0, 1])[0]
最小硬币算法
/**
* @param {*} coins 硬币数组
* @param {*} m 目标金额
* @returns {number} 如果拥有解则为一个整数 如果没有则为正无限
*/
function min_coins(coins, m) {
let table = [0]
let i = 1
while (i <= m) {
table[i] = Infinity
for (coin of coins) {
if (i >= coin) {
table[i] = Math.min(table[i], table[i - coin] + 1)
}
}
i++
}
return table[m]
}
console.log(min_coins([3, 6, 8], 16))
01背包问题
想象出一个表格 行是物品 列是当前容量
外循环是物品 里面是金额
注意哦 一层是物品 二层是空间
空间要从0开始 一层直接开始
/**
* @param {*} w 物品重量
* @param {*} v 物品价值
* @param {*} C 总容量
* @returns
*/
function knapsack(w, v, C) {
let len = w.length
let table = new Array(len).fill(new Array(C + 1).fill(0))
for (let j = 0; j <= C; j++) {
table[0][j] = j >= w[0] ? v[0] : 0
}
for (let i = 1; i < len; i++) {
let cw = w[i]
for (let j = 0; j <= C; j++) {
table[i][j] = table[i - 1][j]
if (j >= cw) {
table[i][j] = Math.max(table[i][j], v[i] + table[i - 1][j - cw])
}
}
}
return table[len - 1][C]
}
console.log(knapsack([1, 2, 3], [3, 7, 12], 5))
最长递增子序列
要注意最后的Max哦
第一层循环从1开始
function lis(n) {
let len = n.length
let array = new Array(len).fill(1)
for (let i = 1; i < len; i++) {
for (let j = 0; j < i; j++) {
if (n[i] > n[j]) {
array[i] = Math.max(array[i], array[j] + 1)
}
}
}
return Math.max.apply(Math, array)
}
console.log(lis([0, 3, 4, 17, 2, 8, 6, 10, 11]))
你在工作中遇到的兼容性问题
- URLSearchParams 兼容性问题 在IE和Edge下不可用 解决 使用polyfill
- 微信IOS下不会主动加载音频文件 解决 先放置一个空的mp3文件 然后首次加载直接load方法+永远不摧毁这个audio
- 微信的登录跳转缓存问题 解决 将初始化函数传入到一个全局变量中 登录完成后手动触发所有回调
转美式3个数字一个,
function commafy(num) {
num = num.toString().split('.')
let head = num[0]
let tail = num[1]
head = head
.split('')
.reverse()
.map((v, i) => (i && i % 3 === 0 ? v + ',' : v))
.reverse()
.join('')
return head + '.' + tail
}
随机化数组
注意,Math.random() 是无法获取一个等于1的数字的 排除1
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5)
}
function shuffle(arr) {
arr = arr.slice()
let len = arr.length
for (let i = 0; i < len; i++) {
let swapIndex = getRandomInt(0, i)
let temp = arr[i]
arr[i] = arr[swapIndex]
arr[swapIndex] = temp
}
return arr
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
严格模式
使用"use strict"可以开启严格模式,在严格模式下将会开启更加严格的代码错误检查,很多在非严格模式下允许的操作会在严格模式下被禁止
列如
- 阻止那些意外被创建的全局变量,message = 1 如果该变量没有被声明,则不会帮你自动创建
- 不允许重复的对象key
3 不允许重复的参数名
4 evel将剥脱在外部作用域创建变量的能力
闭包
闭包是指一个有权访问另外一个函数作用域中的变量的函数
创建闭包最简单的方法就是从一个函数里面返回另一个匿名函数
闭包是本质是其内部的函数引用了外部函数的活动对象。只要内部函数的引用不释放,这个活动对象不会被清除引用和回收。内部函数将会一直有能力访问其外部引用的变量。
闭包的真正用途是为了保存状态,让变量不被回收。
这里可以谈一下著名的SICP 里面用闭包实现了序对,某种意义上说,他和OOP的对象很类似,都能存放状态。
函数节流与函数防抖
function debounce(fn, dealy) {
let timer = null
return function(...args) {
const ctx = this
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(ctx, args)
}, dealy)
}
}
function throttle(fn, interval) {
let last = 0
return function(...args) {
const now = Date.now()
const ctx = this
if (now - last > interval) {
last = now
fn.apply(ctx, args)
}
}
}
function compose(fn, dealy, interval) {
let d = debounce(fn, dealy)
let t = throttle(fn, interval)
return function(...args) {
let ctx = this
d.apply(ctx, args)
t.apply(ctx, args)
}
}
function compose(fn, dealy, interval) {
let timer = null
let last = 0
return function(...args) {
const ctx = this
const now = Date.now()
const run = () => {
fn.apply(ctx, args)
}
if (now - last > interval) {
run()
last = now
return
}
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(run, dealy)
}
}
谈谈什么算全栈工程师
一 基本掌握前后端开发的某种技术,如一门后端语言+前端框架
二 在思维方式上 要做到没有局限,不会只注意某种环节或者技术,可以从更高的层次观察整个产品,学历能力要非常出色,出现什么问题能够迅速找到相关技术学习并解决问题