bLanK's blog

攻无不克,战无不胜

首屏速度

源码上

  1. 路由懒加载(动态导入) ()=>import('/path')

    • 不要再导入异步组件了,因为路由组件本身就是动态导入,不需要组件写出异步组件了
    • 当前跳到了某路由,其对应组件才会下载。
  2. 异步组件(按需导入) defineAsyncComponent

    • const Some = defineAsyncComponent(()=>import('/pat'))
      ...
      <Some v-if="isShow"></Some>
      

      首屏没有加载,用到时再向服务器发送请求

  3. 老版的库更新以支持tree-shaking,少用第三方库

  4. 服务器渲染SSR

  5. 服务器gZip压缩

  6. webpack打包压缩代码,禁止大图片转base64

网络上

静态资源放在cdn上
注意本地webpack要配置external 排除cdn引入的包

用户感知上

骨架屏/loading

prefetch 和 script

  • script加载

按需引入

切换需要等待

  • <link rel='prefetch' as='script' href='' />

预加载,切换无感

浪费带宽

prefetch 和 preload 不同:
preload是一种立即获取资源的策略,用来声明一个高优先级资源强制浏览器提前请求。
prefetch是一种在后台缓存资源的策略,(缓存的资源并不是当前页面需要的)以便在需要时立即使用。

渲染性能

  • vue
  1. keep-alive 缓存
  2. v-show

1. http 状态码

  • 1XX 提示信息
    • 101:允许切换协议
  • 2XX
    • 200: OK
    • 204: Not Content, 服务器无返回内容
    • 206: Partial Content, 无响应体
  • 3XX
    • 301: Moved Permanently,永久重定向
    • 302: Found,临时重定向
    • 304: Not Modified, 浏览器可以使用本地缓存
  • 4XX
    • 400: Bad Request,请求报文错误
    • 401: Unauthorized, 未授权
    • 403: Forbidden, 禁止访问
    • 404: Not Found, 找不到资源
    • 405: Method Not Allowed, 请求方法不允许,可以用OPTIONS预检请求查看服务器允许的请求
  • 5XX
    • 500: Internal Server Error,
    • 501:Not Implement, 客户端请求不支持
    • 502: Bad Gateway, 服务器作为网关或代理返回的错误
    • 503: Service Unavilable, 服务器在忙

2. CDN

CDN

CDN的作用:

  1. 加速资源
  2. 负载均衡

3. Content-type

详见🔗content-type

4. http

详见🔗http详解

5. cookie

  1. 默认跨域不会发送cookie
    但是可以通过 withCredentials=true 设置
  2. expire设置为0,代表仅一次会话有效
  3. 一个cookie大小4KB
  4. secure=true表示只有https才附带cookie
  5. same-site
    1. strict : refer未其他域不发cookie
    2. Lax: get请求可以发,其他不发
    3. None:无限制
  6. http-only 表示此cookie对js不可见
    1. document.cookie无法获取,可防止xss攻击。

JWT

jwt常用于身份认证和信息交换。jwt生成的token由header+payload+signature组成。可以加密信息并防止篡改。

为什么要使用token?

以前用cookie+session模式,cookie存放sessionID,服务器拿到sessionID在数据库里查找对应用户的登录信息。这样做的缺点是:

  1. 用户信息存在服务端,增加服务器负担。
  2. cookie历史包袱重,空间大小只有4KB,携带cookie的规则复杂,跨站不携带cookie。不灵活。容易遭受csrf、xss攻击
  3. 在分布式服务架构上,一个服务器上的session不方便与其他服务器共享

而token

  1. 存储在localstorage里相对安全。
  2. 使用更加灵活
  3. 便于在微前端中传输用户信息。

history路由模式【pushed】

vue利用history.pushState来管理路由状态。当刷新页面时,浏览器把地址栏当作请求地址向服务器发起请求,服务器没有对应路由则出现404错误

解决:

  1. 修改为hash模式,hash值是指以#开头的一串,是不会发送给服务器的
  2. 设置nginx和express所有未匹配到的路由都返回index.html。这样,当刷新页面时,服务器将始终返回index.html,然后由Vue Router接管路由处理

1. 行级元素和块级元素

行级元素

  1. 不会独占一行
  2. 不能包括块级元素
  3. 宽高由内容决定,无法设置
  4. 上下margin和上下padding 不起作用

特殊<a>, 它的祖先元素可以放块级元素,那么它也可以放块级元素

块级元素

  1. 独占一行
  2. 可以包含块级和行级元素
    • p标签除外,不可以包括块级元素

行内块

  1. 不独占一行
  2. 可以设置width、height、padding、margin
  3. 宽高默认由内容决定

常见包括: <input>``<img>``<td>``<hr>

行内块元素之间有空隙,怎么解决?

  1. 移除代码标签之间的空格
  2. 负margin
  3. font-size: 0
  4. letter-spacing: - px
  5. word-spacing: - px

垂直居中

  • 行内+行内块
    • vertical-align: middle 可以对其图片和表格单元内容
    • line-height 单行文本
  • flex
    • align-items: center
    • align-self: center
  • grid
    • align-items: center
    • align-self: center
  • 定位+translate

HTML5 新特性

一、html5新特性之用于绘画的canvas元素
二、html5新特性之更加丰富强大的表单
三、html5新特性之用于媒介的video和audio元素
四、html5新特性之html5地理定位
五、html5新特性之html5拖放
六:html5新特性之html5 Web存储
七、html5新特性之html5应用程序缓存
八、html5新特性之html5 Web Workers
九、html5新特性之html5服务器发送事件
十、html5新特性之html5 WebSocket

HTML 语义化标签

  1. article
  2. section
  3. aside
  4. hgroup
  5. header - main - footer
  6. nav
  7. time
  8. mark
  9. figure - figcation
  • figure代表一段独立的内容
  • figcation 定义说明内容
  1. menu
  • 与ul没有区别
  • <menu-item> 已经被完全弃用
  1. details - summary
  • 展开和折叠
  1. fieldset - legend
  • <fieldset> 元素将一个 HTML 表单的一部分组成一组,内置了一个 <legend> 元素作为 fieldset 的标题

dl - dt - dd

  • 包含术语定义以及描述的列表
    track
  • 可以当作媒体元素的子元素来使用。它允许指定时序文本字幕(或者基于时间的数据),例如自动处理字幕

1. 隐式类型转换

img

  • P 表示toPrimitive
    • 先调用 valueOf 若返回不是原始类型
      • 原始类型的包装类型调用valueOf会返回原始类型(例如String,BigInt)
    • 再调用 toString 若返回不是原始类型
    • 则报错

所有比较都有转化为数字类型的趋势

2. 实现函数缓存

1
2
3
4
5
6
7
8
9
const memoize = function (fun, content) {
let cache = Object.create(null)
content = content || this
return (...key)=> {
if(!cache[key])
cache[key] = fun.apply(content, key)
return cache[key]
}
}

3. requestAnimationFrame

window.requestAnimationFrame(callback)

  • 作用: 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画
  • 参数: callback回调函数,第一个参数是回调函数被触发时的时间戳。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const element = document.getElementById("some-element-you-want-to-animate");
let start, previousTimeStamp;
let done = false;

function step(timestamp) {
if (start === undefined) {
start = timestamp;
}
const elapsed = timestamp - start;

if (previousTimeStamp !== timestamp) {
// 这里使用 Math.min() 确保元素在恰好位于 200px 时停止运动
const count = Math.min(0.1 * elapsed, 200);
element.style.transform = `translateX(${count}px)`;
if (count === 200) done = true;
}

if (elapsed < 2000) {
// 2 秒之后停止动画
previousTimeStamp = timestamp;
if (!done) {
window.requestAnimationFrame(step);
}
}
}

window.requestAnimationFrame(step);

4. 严格模式

1
"use strict"
  1. 禁止使用关键字声明变量
1
2
3
4
5
let x = true;
if(x){
function g() {} // 相当于没有用关键字声明变量
}
console.log(g) //

5. this指向

1
2
(obj.fn2)(); // 函数内this指向obj
(obj.fn2 = obj.fn2)(); //函数内this指向全局作用域
  • 获取url参数
    1
    2
    let searchUrl = link.search.substr(1); // 获取问号后面字符串
    let hashUrl = link.hash.substr(1); // 获取#后面的值
    1
    2
    3
    4
    5
    6
    7
    let URL = "http://www.baidu.com?name=elephant&age=25&sex=male&num=100"
    function queryURLParams(URL) {
    let url = URL.split("?")[1];
    const urlSearchParams = new URLSearchParams(url);
    const params = Object.fromEntries(urlSearchParams.entries());
    return params
    }

6. 闭包

作用

  1. 模拟私有变量
  2. 阻止变量被回收

闭包导致的内存泄露

  1. 持有不需要的函数引用,导致函数关联词法环境无法销毁

    1
    2
    3
    4
    5
    function useArr(){
    const arr = [1];
    return ()=> arr;
    }
    const arr = useArr();
  2. 当多个函数共享词法环境,导致出现无法触及也无法回收的内存

    1
    2
    3
    4
    5
    6
    7
    8
    function useArr(){
    const arr = [1];
    function fn(){
    arr;
    }
    return ()=> [];
    }
    const arr = useArr();

继承

🔗几种继承方式

7. class

class 是ES6的新的实现继承的方式,他的本质还是寄生式组合继承

特点:

  1. class 必须用new调用
  2. class 没有变量提升

1. diff 算法的优化

增加了静态标记, 被标记为-1的节点不会参与diff比较。

因为为vue3可以实现精准定位到动态节点,即那些可能需要重新渲染的节点。

曾经diff需要一层层比较整个新旧虚拟dom树找到哪些值改变。

  • 现在vue3在编译时,利用静态标记patchFlag 标记节点的动态属性。

例如class、style、props,含有动态属性的节点就是动态节点。

  • 每个组件的顶层vnode会有一个dynamicChildren属性,以数组形式存放该节点下所有动态节点。

  • 在运行render后,会遍历这个数组进行靶向更新。也就是说只需要比较动态节点的新旧变化了

2. 静态提升

对于静态节点,把创建dom节点的放在render函数之外。这样就只用创建一次,多次复用。

1
2
3
4
5
6
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("span", null, "你好"),
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}

提升后

1
2
3
4
5
6
7
8
9
10
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}

// Check the console for the AST

3. 事件监听缓存

用一个cache缓存事件处理函数

1
2
3
4
5
6
7
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
}, "点我")
]))
}

4. Tree shaking

tree shaking 是一种清除多于代码的优化打包体积的技术。

  • vue2中所有功能代码都被打包,因为vue2是单例模式,所有方法都在vue构造函数生成的单例上,treeshaking无法区分某个属性、方法是否用到。
  • vue3利用ES6 module。 静态编译时就可以把未使用的功能排除

5. 响应式系统

  • vue2 使用 defineProperty 来劫持对象,深度遍历所有属性给每个属性添加 getter setter
  • vue3 使用 Proxy 代理对象。
    • defineProperty 只能劫持对象属性,所以需要遍历所有属性。而Proxy是直接代理对象。
    • vue2由于defineProperty 而不能监听数组,所以要重写数组方法。
    • 当一个对象的属性还是一个对象时,Proxy 采用懒递归,在getter发现读到的属性时对象时才会创建该对象的代理并返回。

普通流

BFC

可以看作是一个独立的布局区域

触发条件

  1. <html>
  2. float: !none
  3. position: absolute | fixed
  4. display: inline-block | table ...
  5. overflow: !(visible | clip) && 是块级元素
  6. containe: layout | content | paint
  7. 多列容器 column-count | column-width

作用

  1. 避免margin塌陷 (包括水平和垂直)
  2. 清除浮动
  3. 避免被浮动元素覆盖

浮动流

绝对定位

在原生上实现v-model效果

1
<input type="number" :value="msg" @input="(e) => { msg = parseInt(e.target.value) }" />

事件第一个参数是事件对象

Vue3.4以前 在组件上实现v-model效果

  • 自定义事件
    • 事件名为 update:value

App.vue

1
<Comp :value="msg" @update:value="val => msg = val"></Comp>

Comp.vue

1
<input :value="props.value" @input="emits('update:value', $event.target.value)"></input>

Vue3.4 defineModel

帮助我们封装组件

  • 可以同步
  • 一个组件可以同时使用多个v-model
  • 宏指令
    • 用于setup语法糖里
    • 编译时

App.vue

1
<Computer v-model:first-num="val1" v-model:second-num="val2"></Computer>

Computer.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
const firstNum = defineModel('firstNum')
const secondNum = defineModel('secondNum')
//如果父组件的v-model没有arg v-model="val" , 则 const val = defineModel()
</script>

<template>
<div>
<form>
<input type="number" v-model.number="firstNum"></input> +
<input type="number" v-model.number="secondNum"></input> =
{{ firstNum + secondNum }}
</form>
</div>
</template>

自定义指令手写v-mode

Question

  1. 为什么不能用 binding.value
    • 因为binding.value 是值而不是引用,不能实现 view—>model 层的流动
  2. 为什么要用 toRef(binding.instance, binding.arg)
    1. 首先不能 binding.instance[binding.arg] 它会被解包成值类型
    2. 所以创建一个ref, 它与 外部传进来的ref 保持同步
  3. 为什么要defineExpose({msg})
    • setup语法糖要手动暴露自身内部变量
    • defineExpose暴露出去的变量才会挂载到binding.instance 即这个组件实例上

已解决

  1. 双向绑定
  2. 同时使用多个v-oh-model

依然无法解决的问题

  1. 写法不同 必须要加args,但是value可以省略。 写成:v-oh-model:msg
  2. defineExpose <script setup> 下必须要手动暴露
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script setup lang='ts'>
import { ref, toRef, watchEffect } from "vue"
const VOhModel = {
mounted(el, binding) {
const _val = toRef(binding.instance, binding.arg)
// model ---> view
el.$onWatchEffect = watchEffect(() => {
el.value = _val.value
})
// view ---> model
el.$onInput = (e) => {
_val.value = el.value
}
el.addEventListener('input', el.$onInput)
},
unmounted(el, binding) {
el.removeEventListener('input', el.$onInput)
el.$onWatchEffect()
}
}

const msg = ref("Hello Vue.js")
defineExpose({ msg })

</script>

<template>
<input v-oh-model:msg="msg" type="text" />
<p>{{ msg }}</p>
<button @click="msg += 1"> + </button>
</template>

总结

根据这次体验,我感觉自动解包有时候真是副作用。
或许这就是Vue的设计模式,但是也确实限制了程序员的发挥。

如果这里能直接拿到msg变量的引用,哪还需要这么麻烦

nextTick 作用

在一轮里,对一个数据进行多次修改时,不会每次都渲染而是一轮结束时渲染。
nextTick里的回调会在当前轮次渲染结束后在执行

1
2
nextTick(()=> {})
nextTick().then()

原理

  1. updated生命周期之后立即执行
  2. 为了避免不必要的计算,数据更新会放入微队列,vue会在下一个tick里执行已去重的任务
  3. nextTick本身不会主动触发渲染

兼容

  1. promise
  2. MutationObserver
  3. setInterval
  4. setTimeout

在初见postMessage的时候被其强大的功能震撼到了,可是自己用起来却总是莫名报错

原来是我把几种postMessage搞混了,现在来和我一起捋清楚吧

阅读全文 »

  1. 本地存储监听

    监听 同源 localStorage 的修改 (如果setItem 的值没变,则不会触发storage事件

    1
    2
    3
    window.onStorage = function(e) {
    console.log(e.data)
    }
  2. BroadcastChannel.postMessage

    同源

    1
    2
    3
    4
    5
    6
    7
    8
    //标签1
    const bc = new BroadcastChannel('common channel')
    bc.postMessage('hello')
    //标签2
    const bc = new BroadcastChannel('common channel')
    bc.onmessage = (e)=> {
    console.log(e.data);
    }
  3. 详见 🔗几种postMessage

  4. WebSocket

  5. shared worker
    🔗详见

  6. IndexDB 轮询 / cookie 轮询

  7. window.open 返回一个打开的页面对象,通过window.opener 得到

{% if theme.CloudCalendar.enable %} {% endif %}