我的博客来源: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 constructor
const 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: true
for ... 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") // 允许跨域接收 cookie
options请求,是跨域请求之前的预检查;浏览器自行发起的,无需我们干预,不会影响实际的功能
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: sameorigin
npm init -y
npm install ws --save
npm install nodemon --save-dev
const { 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 通讯