我的博客来源:https://1024bibi.com/2018/01/01/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%9F%A5%E8%AF%86%E4%BD%93%E7%B3%BB%EF%BC%88%E4%B8%80%EF%BC%89/
function debounce(fn, delay = 200) {
 let timer = 0
 
 return function () {
  if (timer) clearTimeout(timer);
  
  timer = setTimeout(() => {
   fn.apply(this, arguments); // 透传 this 和参数
   timer = 0
  }, delay)
 }
}const input = document.getElementById('input')
input.addEventListener('keyup', debounce(() => {
 console.log('发起搜索', input.value)
}), 300)例如:drag或scroll期间触发某个回调,要设置一个时间间隔
<style>
 #container {
  width: 200px;
  height: 200px;
  position: relative;
  background-color: #ccc;
 }
 #box {
  width: 100px;
  height: 100px;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-top: -50px;
  margin-left: -50px;
  background-color: blue;
 }
</style>
<div id="container">
 <div id="box"></div>
</div><div style="font-size: 20px;">
 <p style="text-indent: 2em; font-size: 40px;">首行缩进</p> // font-size: 40px; text-indent: 80px;
 <p style="text-indent: 2em;">哪吒, 算法猫叔</p> // font-size: 20px; text-indent: 40px;
</div><style>
 @media only screen and (max-width: 374px) {
  // iphone5 或者更小的尺寸,以 iphone5 的宽度(320px)比例设置 font-size
  html {
   font-size: 86px;
  }
 }
 @media only screen and (min-width: 375px) and (max-width: 413px) {
  // iphone6/7/8 和 iphone x
  html {
   font-size: 100px;
  }
 }
 @media only screen and (min-width: 414px) {
  // iphone6p 或者更大的尺寸,以 iphone6p 的宽度(414px)比例设置 font-size
  html {
   font-size: 110px;
  }
 }
 p {
  font-size: .16rem;
 }
</style><div id="div1"> div1 </div>
<div id="div2"> div2 </div>
<div id="div3"> div3 </div>
<style>
 div {
  border: 1px solid #ccc;
  margin-top: 20px;
 }
 #div1 {
  width: 10vw;
  height: 10vh;
 }
 #div2 {
  width: 10vmax;
  height: 10vmax;
 }
 #div3 {
  width: 10vmin;
  height: 10vmin;
 }
</style>const obj = {
 name: '哪吒,B站,算法猫叔',
 getName: () => {
  return this.name
 }
}
console.log(obj.getName())const obj = {
 name: '哪吒,B站,算法猫叔'
}
obj.__proto__.getName = () => {
 return this.name
}
console.log( obj.getName() )const Foo = (name, age) => {
 this.name = name
 this.age = age
}
const f = new Foo('张三', 20)
// 报错 Foo is not a constructorconst btn1 = document.getElementById('btn1')
btn1.addEventListener('click', () => {
 // console.log(this === window)
 this.innerHTMl = 'clicked'
}){
 data() { return { name:  '哪吒,B站:算法猫叔' } },
 methods: {
  getName: () => {
   // 报错 Cannot read properties of undefined (reading 'name')
  return this.name
  },
  // getName() {
  //  return this.name // 正常
  // }
 },
 mounted: () => {
  // 报错
 },
 // mounted() {
 //  正常
 // }
}for of 去遍历可以generator
const arr = [10, 20, 30]
for (let val of arr) {
 console.log(val); // 值
}
const str = 'abc'
for (let c of str) {
 console.log(c);
}
function fn() {
 for (let arg of arguments) {
  console.log(arg)
 }
}
fn(100, 200, 'aaa')
const pList  = document.getElementsByTagName('p')
// querySelectorAll('p')
for (let p of pList) {
 console.log(p)
}对象,数组,字符串可枚举的,就可以使用for ... in 循环
const obj1 = { x: 100 }
Object.getOwnPropertyDescriptors(obj1)
x:
configurable: true
enumerable: true
value: 100
writeable: truefor ... in 用于可枚举数据,如对象,数组,字符串,得到key
for ... of 用于可迭代数据,如数组,字符串,Map,Set,得到value
for await...of 用于遍历多个Promise
function createPromise(val) {
 return new Promise((resolve) => {
  setTimeout(() => {
   resolve(val)
  }, 1000)
 })
}
(async function () {
 const p1 = createPromise(100)
 const p2 = createPromise(200)
 const p3 = createPromise(300)
 // const res1 = await p1
 // console.log(res1)
 // const res2 = await p2
 // console.log(res2)
 // const res3 = await p3
 // console.log(res3)
 const list = [p1, p2, p3]
 // Promise.all(list).then(res => console.log(res))
 for await (let res of list) {
  console.log(res)
 }
})()盒子模型: width, height, padding, border, margin, box-sizing
offsetHeight offsetWidth : border + padding + content
clientHeight clientWidth: padding + content
scrollHeight scrollWidth: padding + 实际内容尺寸
const p1 = document.getElementById('p1')
class Node {}
// document
class Document extends Node {}
class DocumentFragment extends Node {}
// 文本和注释
class CharacterData extends Node {}
class Comment extends CharacterData {}
class Text extends CharacterData {}
// elem
class Element extends Node {}
class HTMLElement extends Element {}
class HTMLDivElement extends HTMLElement {}
class HTMLInputElement extends HTMLElement {}<p id="p1">
 <b>node</b> vs <em>element</em>
</p>
<script>
 const p1 = document.getElementById('p1')
 console.log(p1.children)
 
 console.log(p1.childNodes)
 // [b,text,em,comment]
</script>划重点:
类数组 变成 数组
const arr1 = Array.from(list)
const arr2 = Array.prototype.slice.call(list)
const arr3 = [...list]watch: {
 name(newValue, oldValue) {
  console.log('watch name', newValue, oldValue)
 }
},
computed: {
 userInfo() {
  return this.name + this.city
 }
}computed有缓存 watch没有缓存
props和$emit
$parent
自定义事件
$refs
$attr
provide/inject
vuex
---
$attrs $listeners
vue3 移除 $listeners
上一级没有接收到的
props: ['x'], // $attrs
emits: ['getX'], // $listeners
Object.keys(this.$attrs)
<l3 v-bind="$attrs"></l3>
dom结点: inheritAttrs: false
---
this.$parent
this.$refs
provide: {
 info: 'aaa'
}
provide() {
 return {
  info: computed(() => this.name)
 }
}
---
父子组件
上下级组件(跨多级)通讯
全局组件mutation: 原子操作,必须同步代码
action: 可包含多个mutation;可包含异步代码
'use strict' // 全局开启
function fn() {
 'use strict' // 某个函数开启
}垃圾回收 GC
什么是垃圾回收?
function fn1() {
 const a = 'aa'
 console.log(a)
 
 const obj = { x: 100 }
 console.log(obj)
}
fn1()function fn2() {
 const obj = { x: 100 }
 window.obj = obj
}
fn2()
function getDataFns() {
 const data = {} // 闭包
 return {
  get(key) {
   return data[key]
  },
  set(key, value) {
   data[key] = value
  }
 }
}
const { get, set } = getDataFns()
set('x', 100)
get('x')引用计数(之前)
// 对象被 a 引用
let a = { x: 100 }
let a1 = a
a = 10
a1 = null
// 循环引用
function fn3() {
 const obj1 = {}
 const obj2 = {}
 obj1.a = obj2
 obj2.a = obj1
}
fn3()
// ie6-7 内存泄漏的 bug
var div1 = document.getElementById('div1')
div1.a = div1
div1.someBigData = {}
标记清除(现代)
// JS 根 window闭包的数据是不可以被垃圾回收的
检测内存变化
const arr = []
for (let i = 0; i < 10 * 10000; i++) {
 arr.push(i)
}
function bind() {
 // 模拟一个比较大的数据
 const obj = {
  str: JSON.stringify(arr) // 简单的拷贝
 }
 window.addEventListener('resize', () => {
  console.log(obj)
 })
}
let n = 0
function start() {
 setTimeout(() => {
  bind()
  n++
  
  // 执行 50 次
  if (n < 50) {
   start()
  } else {
   alert('done')
  }
 }, 200)
}
document.getElementById('btn1').addEventListener('click', () => {
 start()
})// 标记清除算法
const data = {}
function fn1() {
 const obj = { x: 100 }
 data.obj = obj
}
fn1()const map = new Map()
function fn1() {
 const obj = { x: 100 }
 map.set('a', obj)
}
fn1()// WeakMap WeakSet 弱引用
<script>
 const wMap = new WeakMap(); // 弱引用
 function fn1() {
  const obj = { x: 100 }
  wMap.set(obj, 100) // WeakMap的key,只能是引用类型
 }
 fn1()
 // WeakSet
</script>function ajax1(url, sucessFn) {
 const xhr = new XMLHttpRequest();
 xhr.open("GET", url, false);
 xhr.onreadystatechange = function () {
  // 这里的函数异步执行
  if (xhr.readyState == 4) {
   if (xhr.status == 200) {
    successFn(xhr.responseText);
   }
  }
 }
 xhr.send(null);
}
function ajax2(url) {
 return fetch(url).then(res => res.json());
}1. FIN ->
2. ACK <-
3. FIN <-
4. ACK -><link> <img> <script> <iframe>加载第三方资源// www.aaa.com网页
<script>
 window.onSuccess = function(data) {
  console.log(data)
 }
</script>
<script src="https://www.bbb.com/api/getData"></script>
// https://www.bbb.com... 返回了一段字符串
'onSuccess({ errno: 0, data: {} })'// CORS 配置允许跨域(服务端)
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8011") // 或者"*"
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With")
response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")
response.setHeader("Access-Control-Allow-Credentials", "true") // 允许跨域接收 cookieoptions请求,是跨域请求之前的预检查;浏览器自行发起的,无需我们干预,不会影响实际的功能
const p = document.createElement('p')
p.innerHTML = 'new paragraph'
document.body.appendChild(p)
const list = document.getElementsByTagName('p')
console.log('length---', listh.length)
console.log('start')
// 渲染之后
setTimeout(() => {
 const list = document.getElementsByTagName('p')
 console.log('length on timeout---', list.length) // 2
 alert('阻塞 timeout')
})
// 渲染之前
Promise.resolve().then(() => {
 const list = document.getElementsByTagName('p')
 console.log('length on promise.then---', list.length) // 2
 alert('阻塞 promise')
})
console.log('end')// 同步任务 -> 异步任务 -> 宏任务
// 微任务要比宏任务要快
// Event Loop
<script>
 console.log('start')
 setTimeout(() => {
  console.log('timeout')
 })
 Promise.resolve().then(() => {
  console.log('promise then')
 ))
 console.log('end')
 // ajax(url, fn) // 300ms
 
 // Event Loop 继续监听
 // 宏任务 MarcoTask Queue
 // () => {
 //   console.log('timeout')
 // }
 // fn
 
 // DOM 渲染
 // 微任务 MicroTask Queue
 // () => {
 //   console.log('promise then')
 // }
</script>进程 process vs 线程 thread 进程,OS 进行资源分配和调度的最小单位,有独立内存空间 线程,OS 进行运算调度的最小单位,共享进程内存空间 JS是单线程的,但可以开启多进程执行,如WebWorker js 不可以开启一个线程
为何需要多进程?
nodejs如何开启多进程
// console.info(process.pid)
const http = require('http')
const server = http.createServer()
server.listen(3000, () => {
 console.log('localhost: 3000')
})
console.info(process.pid)
// WebWorker 进程
// fork
const http = require('http')
const server = http.createServer((req, res) => {
 if (req.url === '/get-sum') {
   console.info('主进程 id', process.id)
   
   res.end('hello')
 }
})
server.listen(3000, () => {
 console.info('localhost: 3000')
})
// cluster 进程// 子进程,计算
function getSum() {
 let sum = 0
 for (let i = 0; i < 10000; i++) {
  sum += i
 }
 return sum
}
process.on('message', data => {
 console.log('子进程 id', process.pid)
 console.log(‘子进程接受到的信息:', data)
 const sum = getSum()
 // 发送消息给主进程
 process.send(sum)
})const http = require('http')
const fork = require('child_process').fork
const server = http.createServer((req, res) => {
 if (req.url === '/get-sum') {
  console.info('主进程 id', process.pid)
   // 开启子进程
  const  computeProcess = fork('./compute.js')
  computeProcess.send('开始计算')
  computeProcess.on('message', data => {
    console.info('主进程接受到的信息:', data)
    res.end('sum is' + data)
  })
  computeProcess.on('close', () => {
    console.info('子进程因报错而退出')
    computeProcess.kill()
    res.end('error')
  })
 }
})
server.listen(3000, () => {
 console.info('localhost: 3000')
})const http = require('http')
const cpuCoreLength = require('os').cpus().length
const cluster = require('cluster')
if (cluster.isMaster) {
 for (let i = 0; i < cpuCoreLength; i++) {
  cluster.fork() // 开启子进程
 }
 cluster.on('exit', worker => {
  console.log('子进程退出')
  cluster.fork() // 进程守护
 })
} else {
 // 多个子进程会共享一个 TCP 连接,提供一份网络服务
  const server = http.createServer((req, res) => {
  res.writeHead(200)
  res.end('done')
 })
 server.listen(3000)
}开启子进程 child_process.fork 和 cluster.fork 使用 send 和 on 传递消息
// 封装 JS-bridge
const sdk = {
 invoke(url, data = {}, onSuccess, onError) {
   const iframe = document.createElement('iframe')
   iframe.style.visibility = 'hidden'
   document.body.appendChild(iframe)
   
   iframe.onload = () => {
    const content = iframe1.contentWindow.document.body.innerHTML
   }
 }
}由React fiber引起的关注
区别
<p>requestAnimationFrame</p>
<button id="btn1">change</button>
<div id="box"></div>
<script>
const box = document.getElementById('box')
document.getElementById('btn1').addEventListener('click', () => {
 let curWidth = 100
 const maxWidth = 400
 function addWidth() {
  curWidth = curWidth + 3
  box.style.width = `${curWidth}px`
  if (curWidth < maxWidth) {
   window.requestIdleCallback(addWidth) // 时间不用自己控制
  }
 }
})
</script>start
end
timeout
requestAnimationFrame
requestIdleCallback
window.onload = () => {
 console.info('start')
 setTimeout(() => {
  console.info('timeout')
 })
 // 宏任务,顺序交换也一样
 // 高优
 window.requestAnimationFrame(() => {
  console.info('requestAnimationFrame')
 })
 // 低优
 window.requestIdleCallback(() => {
  console.info('requestIdleCallback')
 })
 console.info('end')
}<keep-alive>
 <Child1 v-if="num === 1"></Child1>
 <Child2 v-else></Child2>
</keep-alive>
// Child1 2
created() {
 console.log() // keep-alive 中只创建
}
activated() {}
deactivated() {}
// 创建一次被缓存child1 created
child1 activated
child2 created
child1 deactivated
child2 activated
child2 deactivated
child1 activated$nextTick
mounted() {
 this.$nextTick(function () {
  // 仅在整个视图都被渲染之后才会运行的代码
 })
}import { onUpdated, onMounted } from 'vue'
export default {
 setup() {
  onMounted(() => {
 
  })
  onUpdated(() => {
  
  })
 }
}介绍diff算法 diff算法很早就有
tree diff优化 只比较同一层级,不跨级比较 tag 不同则删掉重建(不再去比较内部的细节) 子节点通过key区分(key的重要性)
vue3最长递增子序列 vue2 双端比较 React 仅右移
Hash, WebHistory, MemoryHistory( v4 之前叫做 abstract history)
FastClick原理 监听touchend事件(touchstart touchend会先于click触发) 使用自定义DOM事件模拟一个click事件 把默认的click事件(300ms之后触发)禁止掉
现代浏览器的改进
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>如有严格管理用户信息的需求(保密,快速封禁)推荐session 如没有特殊要求,则使用jwt
网络协议 HTT P协议在应用层 TCP UDP 协议再传输层 严格来说,应该拿TCP和UDP进行比较
OSI的体系结构
7. 应用层
6. 表示层
5. 会话层
4. 运输层
3. 网络层
2. 数据链路层
1. 物理层TCP/IP的体系结构
1. 应用层(各种应用层协议,如DNS,HTTP,SMTP等)
2. 运输层(TCP或UDP)
3. 网际层(IP)
4. 网络接口层TCP协议
UDP协议
HTTP是应用层,TCP UDP是传输层 TCP有连接,有断开,稳定传输 UDP无连接,无断开,不稳定传输,但效率高
HTTP S 加密传输 HTTP明文传输 HTTP S 加密传输 HTTP+TLS/SSL
<script src="xxx.js" async 或 defer></script>
无:HTML暂停解析,下载JS,执行JS,再继续解析HTMl defer:HTML继续解析,并行下载JS,HTML解析完再执行JS async: HTML继续解析,并行下载JS,执行JS,再解析HTM L
preload和prefetch preload资源在当前页面使用,会优先加载 prefetch资源在未来页面使用,空闲时加载
<head>
<link rel="preload" href="style.css" as="style">
<link rel="preload" href="main.js" as="script">
<link rel="prefetch" href="other.js" as="script">
<link rel="stylesheet" href="style.css">
</head>
<body>
<script src="main.js" defer></script>
</body>dns-prefetch 和 preconnect dns-prefetch即DNS预查询 preconnect即DNS预连接
多个域名时,当前已经解析完,预查询,预连接
<link rel="dns-prefetch" href="域名">
<link rel="dns-preconnect" href="" crossorigin></link>prefetch 是资源预获取(和preload相关) dns-prefetch 是DNS预查询(和preconnect相关)
const newStr = str.replaceAll('<', '<').replaceAll('>', '>')
if (top.location.hostname !== self.location.hostname) {
 alert("您正在访问不安全的页面,即将跳转到安全页面!“)
 top.location.href = self.location.href
}
hearders
X-Frame-Options: sameoriginnpm init -y
npm install ws --save
npm install nodemon --save-devconst { WebSocketServer } = require('ws')
const wsServer = new WebSocketServer({ port: 3000 })
wsServer.on('connection', ws => {
 console.info('connected')
 
 ws.on('message', msg => {
  console.info('收到了信息', msg.toString)
  
  // 服务端向客户端发送信息
  setTimeout(() => {
   ws.send('服务端已经收到了信息:' + msg.toString())
  }, 2000)
 })
})
<button id="btn-send">发送消息</button>
const ws = new WebSocket('ws://127..0.0.1:3000')
ws.onopen = () => {
 console.info('opened')
 ws.send('client opened')
}
ws.onmessage = event = {
 console.info('收到了信息', event.data)
}
const btnSend = document.getElementById('btn-send')
btnSend.addEventListener('click', () => {
 console.info('clicked')
 ws.send('当前时间' + Date.now())
})WebSocket 连接过程 先发起一个 HTTP 请求 成功之后再升级到 WebSocket 协议,再通讯
WebSocket 和 HTTP 区别 WebSocket 协议名是 ws:// , 可双端发起请求 WebSocket 没有跨域限制 通过send和onmessage通讯(HTTP通过req和res)
ws可升级为 wss (像https)
import { createServer } from 'https'
import { readFileSync } from 'fs'
import { WebSocketServer } from 'ws'
const server = createServer ({
 cert: readFileSync('/path/to/cert.pem'),
 key: readFileSync('/path/to/key.pem')
})
const wss = new WebSocketServer({ server })扩展:实际项目推荐socket.io, api更简洁
io.on('connection', socket => {
 socket.emit('request', /*...*/)
 io.emit('broadcast', ...)
 socket.on('reply', () => {})
})const { WebSocketServer } = require('ws')
const wsServer = new WebSocketServer({ port: 3000 })
const list = new Set()
wsServer.on('connection', curWs => {
 console.info('connected')
 list.add(curWs)
 curWs.on('message', msg => {
  console.info('received message', msg.toString())
  
  // 传递给其他客户端
  list.forEach(ws => {
   if (ws === curWs) return
   ws.send(msg.toString())
  })
 })
})步骤: 网络请求: DNS查询(得到IP),建立TCP连接(三次握手) 浏览器发起HTTP请求 收到请求响应,得到HTML源代码
继续请求静态资源 解析HTML过程中,遇到静态资源还会继续发起网络请求 JS CSS 图片 视频等 注意:静态资源可能有强缓存,此时不必请求
解析:字符串 -> 结构化数据 HTML构建DOM树 CSS构建CSSOM树(style tree) 两者结合,形成 render tree
渲染 解析过程很复杂 CSS 可能来自 <style> <link>JS 可能内嵌,或外链,还有 defer async 逻辑 img 可能内嵌(base64),可能外链
优化解析 CSS放在<head>中,不要异步加载CSS JS放在<body>最下面(或合理使用 defer async)<img> 提前定义 width height
渲染:Render Tree 绘制到页面 计算各个DOM的尺寸,定位,最后绘制到页面 遇到JS可能会执行(参考 defer async) 异步CSS,图片加载,可能会触发重新渲染
网络请求:DNS解析,HTTP请求 解析:DOM树,CSSOM树,Render Tree 渲染:计算,绘制,同时执行JS
重绘 repaint
重排 reflow
区别
减少重排的方法 1/2
减少重排的方法 2/2
扩展:BFC
触发 BFC 的条件 1/2
<html>触发 BFC 的条件 2/2
使用 WebSocket
通过 localStorage 通讯
通过 SharedWorker 通讯