JavaScript篇
原型和原型链
实例对象
的__proto__和其构造函数
的prototype和其是指向同一个地方的,这个地方叫做原型对象
。
__proto__的路径
就叫原型链。
闭包
上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。
应用场景
- 函数作为一个函数的参数,或函数作为一个函数的返回值
- 防抖、节流
存在问题:内存泄漏
闭包
导致了内部定义的函数引用数始终为1
,垃圾回收机制就无法把它销毁。
防抖、节流
防抖
指定时间间隔内,频繁触发一个事件,只会执行最后一次事件。如防止重复点击查询按钮触发事件等。
源码实现:
function debouce (fn, time) {
let args = arguments
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimout(() => {
fn.call(this, args)
}, time)
}
}
为什么防抖要用到闭包?
- 把timer变量放在内部函数中不能达到防抖的目的,因为每次执行内部的函数,都会创建一个新的timer变量;
- 用立即执行函数创造闭包可以实现防抖,是因为每次执行内部的函数,都是在操作同一个timer变量,由外部立即执行函数创建的timer变量。
节流
指定时间间隔内,只会执行一次事件。如下拉刷新。
源码实现:
function throttle (fn, time) {
let args = arguments
let canRun = true
return function () {
if (!canRun) return
canRun = false
setTimeout(() => {
fn.call(this, args)
}, time)
}
}
this的指向
在绝大多数情况下,函数的调用方式决定了this
的值(运行时绑定),this永远指向最后调用它的那个对象。
- 全局的this非严格模式指向window对象,严格模式指向undefined
- 对象的属性方法中的this指向对象本身
- apply、call、bind可以变更this指向为第一个传参
- 箭头函数中的this指向它的父级作用域,它本身不存在this
改变this的指向
- 使用ES6的箭头函数
- 在函数内部使用_this=this
- 使用
apply
、call
、bind
- new实例化一个对象
箭头函数
- 箭头函数内的this指定的是函数定义的对象,而不是函数执行时所在的对象;
- 箭头函数不能用作构造函数,因为它没有自己的this,无法实例化;
- 箭头函数不存在arguments对象
// ES5
var add = function (a, b) {
return a + b;
};
// 使用箭头函数
var add = (a, b) => a + b;
// ES5
[1,2,3].map((function(x){
return x + 1;
}).bind(this));
// 使用箭头函数
[1,2,3].map(x => x + 1);
细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的。当你函数返回有且仅有一个表达式的时候可以省略{} 和 return;
apply,call,bind的区别
相同点
- 都是改变this指向的
- 第一个参数都是this要指向的对象
- 都可以利用后续参数传参;
区别
- call和bind的参数是依次传参,一一对应
- apply只有两个参数,第二个参数为数组
- call和apply都是对函数直接调用,而bind方法返回的是一个函数
a.say.call(b,'男','学习');
a.say.apply(b,['男','学习'])
bind可以向cally一样传参:
例如:
a.say.bind(b,'男','学习')();
但由于bind返回的仍然是一个函数,所以我们还可以在调用的时候再进行传参。
例如:
a.say.bind(b)('男','学习');
浏览器的事件循环
- JavaScript是单线程语言
- Event Loop是JavaScript执行机制
宏任务
:整体script、setTimeout()、setInterval()、setImmediate()、I/O、UI渲染、用户交互操作
微任务
:Promise.then() catch()、async/await、process.nextTick(node)、new MutationObserver()
宏任务和微任务都是异步任务。宏任务
是由宿主(浏览器/node)
发起的,微任务
是由js引擎
发起的。
执行顺序:同步代码(主线程)➡微任务➡DOM渲染➡宏任务
解读: - 同步和异步任务分别进入不同的执行“场所”,同步的进入主线程,异步的进入Event Table并注册函数
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue
- 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行
- 上述过程会不断重复,也就是常说的
Event Loop(事件循环)
Event Loop执行
Event Loop中,每一次循环称为tick,每一次tick的任务如下:
- 执行栈(Call Stack)选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
- 检查是否存在微任务,有则会执行微任务队列为空
- 如果宿主为浏览器,可能会渲染页面
- 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)
JavaScript中数据在栈和堆中的存储方式
栈
:基本数据类型(大小固定且操作简单):String、Number、Boolean、Null、Undefined、Symbol,先进后出
堆
:引用数据类型(大小不确定,将它们放入堆内存中,在申请内存时确定大小):Object、Array、Function、正则(RegExp)和日期(Date),堆可以被看成是一棵树,先进先出
- 栈的效率高于堆
- 栈内存中变量在执行环境结束后会立即进行垃圾回收,而堆内存中需要变量的所有引用都结束才会被回收
v8垃圾回收
标记清除算法
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
- 然后从各个根对象开始遍历,把不是垃圾的节点改成1
- 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
- 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收
分代式垃圾回收
新老生代
新生代的对象为存活时间较短的对象,简单来说就是新产生的对象,通常只支持1~8M的容量,而老生代的对象为存活时间较长或常驻内存的对象,简单来说就是经历过新生代垃圾回收后还存货下来的对象,容量通常比较大。
新生代垃圾回收
新生代采用空间换时间的scavenge算法:整个空间分为两块,变量仅存在其中一块,回收的时候将存活变量复制到另一块空间,不存活的回收掉,周而复始轮流操作
老生代垃圾回收
老生代使用标记清楚和标记整理,标记清除:遍历所有对象标记可以访问到的对象(活着的),然后将不活的当作垃圾进行回收。回收完后避免内存的断层不连续,需要通过标记整理将活着的对象往内存一端进行移动,移动完成后再清理边界内存。
JS、CSS是否阻塞DOM渲染和解析
- CSS的加载不会阻塞DOM解析,因为CSS解析成CSSOM树和DOM tree是两个并行的进程;但是会
阻塞DOM渲染
,因为CSS会阻塞render tree的生成,进而会阻塞DOM的渲染 - JS的加载和执行会阻塞DOM的解析和渲染
- CSS的加载会阻塞JS的执行,CSS的渲染GUI线程和JS运行线程互斥,所以说CSS的加载也会阻塞DOM渲染
- 浏览器遇到
<script>
标签且没有defer或async属性时会触发渲染,以便获得最新的样式给JS代码
async和defer的区别
一般情况下,当执行到script标签时会进行下载+执行两步操作,这两步会阻塞HTML的解析;
async和defer能将script的下载阶段
变成异步执行(和html解析同步进行)
async
下载完成后立即执行js,此时会阻塞HTML解析;
defer
会等全部HTML解析完成且在DOMContentLoaded事件之前执行;
浏览器事件机制
DOM事件流三阶段:
- 捕获阶段:事件最开始由不太具体的节点最早接受事件,而最具体的节点(触发节点)最后接受事件。为了让事件到达最终目标之前拦截事件。
比如点击一个div,则click事件会按这种顺序触发:document=><html>
=><body>
=><div>
,即由document捕获后沿着DOM树依次向下传播,并在各节点上触发捕获事件,直到到达实际目标元素。 - 目标阶段
当事件到达目标节点,事件就进入了目标阶段。事件在目标节点上被触发(执行事件对应的函数),然后会逆向回流,直到传播至最外层的文档节点。 - 冒泡阶段
事件在目标元素上触发后,会继续随着DOM树一层层往上冒泡,直到到达最外层的根节点。
所有事件都要经历捕获阶段和目标阶段,但有些事件会跳过冒泡阶段,比如元素获得焦点focus和失去焦点blur不会冒泡。
阻止事件冒泡:e.preventDefault()
new的过程
- 创建一个空的简单JavaScript对象(即
{}
); - 为步骤1新创建的对象添加属性
__proto__
,将该属性链接至构造函数的原型对象; - 将步骤1新创建的对象作为
this
的上下文; - 如果该函数没有返回对象,则返回
this
。
变量提升
函数和变量在执行之前都提升到了代码开头。
函数提升优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被同名变量赋值后覆盖。
实际上变量和函数声明在代码里的位置是不会改变的,而且是编译阶段在JavaScript引擎放入内存中。
console.log(a); //undefined
var a = 1;
因为有变量提升的缘故,上面代码的实际运行顺序为:
var a;
console.log(a);
a = 1;
promise
- promise是js
异步编程
的新的解决方案 - 是一个
构造函数
- 用来封装一个异步操作,并可以获得结果
promise三个状态
- pending(进行中)
- resolved(成功)
- rejected(失败)
一旦状态改变,就不会再变。
promise api
- promise.all()
所有promise都完成后执行,如果有一个promise执行失败,则promise.all()会立即返回rejected状态。 - promise.allSettled()
所有promise都有结果了就执行,不论是成功还是失败。 - promise.any()
任意一个promise成功了就执行。 - promise.race()
返回一个新的promise, 第一个完成的promise的结果状态就是最终的结果状态。 - promise.reject()
- promise.resolve()
async/await
async
是更为优雅的异步编程解决方案,函数的返回值的是一个promise对象。用同步的方式,执行异步操作。
await
用来暂停等待异步函数的执行结果,如果是Promise,也就是等待它的settled状态,并且await只能在出现在async function内部,不可单独使用。
深拷贝/浅拷贝
浅拷贝
一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值;如果拷贝的是引用数据类型,拷贝的就是内存地址。
深拷贝
复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变。
手写实现深拷贝:
function deepClone (source) {
if (source instanceof Object == false) return source
let target = Array.isArray(source) ? [] : {}
for (let i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === 'object') {
target[i] = deepClone(source[i])
} else {
target[i] = source[i]
}
}
}
return target
}
typeOf/instanceOf
区别:typeOf的返回值是一个字符串,用来说明变量的数据类型;instanceOf的返回值是布尔值,用于判断一个变量是否属于某个对象的实例。
总结:
- instanceOf在查找的过程中会遍历左边变量的原型链,直到找到右边变量的prototype,如果查找失败,则会返回false
- typeOf在对值类型number、string、Boolean、undefined以及引用类型的function的反应是精准的;但是对于对象{}、数组[]、null都会返回object,但对NaN返回的是number类型。为了弥补这一点,instanceOf从原型的角度,来判断某引用属于哪个构造函数,从而判定它的数据类型。
Map、Set
Map
Map是一组键值对的结构,和JSON对象类似。
Set
Set对象类似于数组,且成员的值都是唯一的。常用于数组去重。
区别:
- Map需要的是一个二维数组,而Set需要的是一维Array数组
- Map和Set都不允许键重复
- Map的键不能修改,但是对应的值是可以修改的;Set不能通过迭代器改变Set的值,因为Set的值就是键
- Map是键值对的存在,值也不作为键;而Set的value就是key
Vue篇
vue组件通信方式
- props/$emit
$children
/$parent
- provide/reject
- ref/refs
- eventBus
- Vuex
- localStorage/sessionStorage
$attrs
与$listeners
- 父子组件通信:
props
;$parent
/$children
;provide
/inject
;ref
;$attrs
/$listeners
- 兄弟组件通信:
eventBus
/vuex - 跨级组件通信:
eventBus
/vuex;provide
/inject
;$attrs
/$listeners
vuex
专为Vue.js应用程序开发的状态管理模式+库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
vuex各个模块
state
:用户数据的存储,是store中的唯一数据源getters
:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算mutations
:类似函数,改变state数据的唯一途径,且不能用于处理异步事件actions
:类似于mutation
,用于提交mutation
来改变状态,而不直接变更状态,可以包含任意异步操作modules
:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
vue渲染列表为什么要加key?
Vue列表加key的目的是为diff算法添加标识,因为diff算法判断新旧VDOM是否相同的依据是节点的tag和key。如果tag和key相同则会进一步进行比较,使得尽可能多的节点进行复用。此外,key绑定的值一般是一个唯一的值,比如id。如果绑定数组的索引index,则起不到优化diff算法的作用,因为一旦数组内元素进行增删,后续节点绑定的key也会发生变化,导致diff进行多余的更新操作。
vue3和vue2的区别
双向绑定
vue3使用proxy代理整个对象,vue2使用Object.defineProperty代理对象的属性,如果要监听整个对象,需要遍历对象的所有属性。
vue2中是无法检测数组和对象的属性新增,无法检测通过索引改变数组的操作。
生命周期
Vue2(选项式api) | Vue3(setup) | 描述 |
---|---|---|
beforeCreated | - | 实例创建前 |
created | - | 实例创建后 |
beforeMounted | onBeforeMount | DOM挂载前调用 |
mounted | onMounted | DOM挂载完成调用 |
beforeUpdate | onBeforeUpdate | 数据更新之前被调用 |
updated | onUpdated | 数据更新之后被调用 |
beforeDestory | onBeforeMount | 组件销毁前调用 |
destoryed | onUnmounted | 组件销毁完成调用 |
composition api(组合式api)和选项式api
composition api(组合式api)
<template>
<div @click="changeMsg">{{msg}}</div>
</template>
<script>
import { ref, defineComponent } from "vue";
export default defineComponent({
setup () {
const msg = ref('hello world')
const changeMsg = () => {
msg.value = 'hello juejin'
}
return {
msg,
changeMsg
};
},
});
</script>
- reactive API
reactive
方法用来创建响应式对象
,接收一个对象/数组参数,返回对象的响应式副本,当该对象的属性值发生变化,会自动更新使用该对象的地方。
import { reactive } from 'vue'
let reactiveObj = reactive({ name : 'Chris1993' });
let setReactiveObj = () => {
reactiveObj.name = 'Hello Chris1993';
}
let reactiveArr = reactive(['a', 'b', 'c', 'd']);
let setReactiveArr = () => {
reactiveArr[1] = 'Hello Chris1993';
}
- ref API
ref
的作用是将一个基本数据类型
转换成一个带有响应式特性的数据类型
,基本数据类型:String、Number、Boolean、Null、Undefined、Symbol
import { ref } from 'vue'
let refValue = ref('Chris1993');
let setRefValue = () => {
refValue.value = 'Hello Chris1993';
}
let refObj = ref({ name : 'Chris1993' });
let setRefObj = () => {
refObj.value.name = 'Hello Chris1993';
}
选项式api
<template>
<div @click="changeMsg">{{msg}}</div>
</template>
<script>
export default {
data () {
return {
msg: 'hello world'
}
},
methods: {
changeMsg () {
this.msg = 'hello juejin'
}
}
}
</script>
vue3相对vue2的响应式优化
- vue2对于数据响应式的实现上是有一些局限性的,比如:
- 无法检测数组和对象的新增
- 无法检测通过索引改变数组的操作
- Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,并返回一个新的对象;
- proxy不仅可以代理对象,还可以代理数组,还可以代理动态增加属性
Vue2.0 不能监测数组和对象的变化原因以及解决方案
Vue3为何使用Proxy实现数据监听
vue diff算法
虚拟DOM
虚拟DOM表示真实DOM的JS对象,包含标签名、标签的属性以及子标签的名称、属性和文本节点。
<div>
<p>123</p>
</div>
对应的virtual DOM(伪代码):
var Vnode = {
tag: 'div',
children: [
{ tag: 'p', text: '123' }
]
};
diff算法
本质就是比较两个JS对象的差异。
比较流程如下图:
updateChildren
关键点:1、同级比较;2、深度优先;3、首尾指针法
比较过程:
- 依次比较,当比较成功后退出当前比较;
- 渲染结果以newVnode为准;
- 每次比较成功后start点和end点向中间靠拢;
- 当新旧节点中有一个start点跑到end点右侧时终止比较;
- 如果都匹配不到,则旧虚拟DOM key值去比对新虚拟DOM的key值,如果key相同则复用,并移动到新虚拟DOM的位置。
vue双向绑定原理
响应式原理
Vue的核心特性之一,数据驱动视图,修改了数据之后视图随之响应更新。
Vue2.x
是借助Object.defineProperty()
实现的,而Vue3.x
是借助Proxy
实现的,Proxy相比Object.defineProperty在处理数组和新增属性的响应式处理上更加方便。
双向绑定
vue.js采用数据劫持
结合发布者-订阅者模式
,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调。
Vue2.x实现双向绑定核心是通过三个模块:Observer
监听器、Watcher
订阅者和Compile
编译器。
实现原理:首先监听器会监听所有的响应式对象属性,编译器会将模板进行编译,找到里面动态绑定的响应式数据并初始化视图;watcher会去收集这些依赖;当响应式数据发生变更时Observer就会通知Watcher;watcher接收到监听器的信号就会执行更新函数去更新视图。
v-model原理
v-model是用来监听用户事件然后更新数据的语法糖。
其本质还是单向数据流,内部是通过绑定元素的value值向下传递数据,然后通过绑定input事件,向上接收并处理更新数据。
// 比如
<input v-model="sth" />
// 等价于
<input :value="sth" @input="sth = $event.target.value" />
computed和watch
computed
- 计算值
- 可以计算和处理data、props或$emit的值
- 具有缓存性
watch
- 观察的动作
- 监听props、$emit或本组件的执行异步操作
- 无缓存性
$nextTick原理
作用
Vue在更新数据时是异步执行的,当数据变化
会产生一个异步更新队列
,要等异步队列结束后才会统一进行更新视图
,所以改了数据之后立即去拿dom还没有更新就会拿不到最新数据。所以提供了一个nextTick函数,它的回调函数会在DOM更新后立即执行。
原理
nextTick
的本质就是创建了一个微任务,将其回调函数推入微任务队列
,放到下次DOM更新之后执行。
nextTick之所以能够拿到DOM更新后的结果,是因为我们传入的callback
会被添加到队列刷新函数(flushSchedulerQueue)
的后面,这样等队列内部的更新函数都执行完毕,所有DOM操作也就结束了,callback自然能够获取到最新的DOM值。
所以项目中不使用$nextTick的话也可以直接使用Promise.then
或者setTimeout
实现相同的效果。
Vue异步更新
Vue更新DOM是异步更新
的。只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。
为什么要用虚拟DOM
频繁地修改DOM元素是非常影响性能
的,严重的话甚至会导致整个页面掉帧、卡顿甚至失去响应。其实很多DOM操作是可以打包
(多个操作压成一个)和合并
(一个连续更新操作只保留最终结果),同时JS引擎的计算速度要快得多,所以可以把DOM操作先通过JS计算完成后统一来一次操作DOM,所以有了虚拟DOM的概念。
虚拟DOM操作的核心是diff算法,通过比较变化前后VNode的不同,计算出最小的DOM操作来改变DOM,提升性能。
Vuex流程&原理
Vuex利用vue的mixin机制,在beforeCreated钩子前混入了vuex init方法,这个方法实现了将store注入vue实例当中,并注册了store的引用属性$store
,所以可以使用this.$store.xxx
去引入vuex中定义的内容。
然后state是利用vue的data,通过new Vue({data:{{$$state: state}}
将state转换成响应式对象,然后使用computed函数实时计算getter。
Vue.use
Vue.use
本质就是执行需要注入插件的install
方法。
编写一个vue插件
要暴露一个install
方法,第一个参数是Vue
构造器,第二个参数是一个可选的配置项对象
Myplugin.install = function(Vue, options = {}) {
// 1、添加全局方法或属性
Vue.myGlobalMethod = function() {}
// 2、添加全局服务
Vue.directive('my-directive', {
bind(el, binding, vnode, pldVnode) {}
})
// 3、注入组件选项
Vue.mixin({
created: function() {}
})
// 4、添加实例方法
Vue.prototype.$myMethod = function(methodOptions) {}
}
运行npm run xxx的时候发生了什么
- 运行npm run xxx的时候,npm会先在当前目录的
node_modules/.bin
查找要执行的程序,如果找到则运行; - 没有找到则从
全局的node_modules/.bin
中查找,npm i -g xxx
就是安装到全局目录; - 如果全局目录还是没找到,那么就从
path环境变量
中查找有没有其他同名的可执行程序。
vue route/router区别
$route对象
$route对象表示当前的路由信息,包含了当前URL解析得到的信息。包含当前的路径、参数、query对象等。
$router对象
$router对象是全局路由的实例,是router构造方法的实例。
路由实例方法:push、go、replace
vue 路由传参
1. params传参(显示参数)
在url中会显示出传参的值,刷新页面不会失去拿到的参数,使用该方式传值需要子路由提前配置好参数
//路由配置
{
path: '/child/:id',
component: Child
}
//编程式使用
this.$router.push({
path:'/child/${id}',
})
2. params传参(不显示参数)
//路由配置
{
path: '/child',
component: Child,
name:Child
}
//编程式使用
this.$router.push({
name:'Child',
params:{
id:123
}
})
3. query传参
//路由配置
{
path: '/child',
component: Child,
name:Child
}
//编程式使用
//name方式
this.$router.push({
name:'Child',
query:{
id:1
}
})
//path方式
this.$router.push({
path:'/child',
query:{
id:1
}
})
跨域问题
1. proxy 设置代理服务器
- vue.config.js设置
devServer: {
host: '0.0.0.0',
proxy: {
'/router': {
target: URL,
changeOrigin: true
}
}
}
- axios设置baseUrl
// axios实例
const httpClient = axios.create({
baseURL: '/router/rest',
timeout: 10000,
headers: { 'format': 'JSON' }
})
2. CORS【后端】
目前最主流、最简单的方案,直接让后端设置响应头,允许资源共享就ok了
//调用方法创建一个服务器
const app = express()
//解析json格式的请求体
app.use(express.json())
//解析查询字符串格式的情趣
app.use(exoress.urlencoded({extended:true}))
//第一种: 使用中间件函数来设置cors允许资源共享
app.use((req.res.next)=>{
//设置响应头 告诉浏览器任何地址都可以访问这个接口
res.setHeader('Access-Cosntrol-Allow-Origin','*')
//告诉浏览器支持这些方式
res.setHeader('Access-Cosntrol-Allow-Methods','GET,POST,DELETE,PUT')
next()
})
//第二种 推荐使用插件
const cors = require('cors')
app.use(cors())
3. JSONP
曾经很流行,专治各种跨域问题,需要前后端配合使用。
使用原理:通过script标签的src来发请求没有跨域限制来获取资源
注意:JSONP只支持get请求,不支持post,请求回来的东西当做js来执行
插槽
1. 内容插槽
插槽内可以包含普通文本、任何模板代码(包括HTML)、其他组件、使用数据。
//home.vue
<test>
Hello Word
</test>
//test.vue
<a href="#">
<slot></slot>
</a>
当组件渲染的时候,<slot></slot>
会被替换为Hello Word
2. 后备内容(默认内容)插槽
3. 具名插槽
<slot>
元素有一个特殊的特性:name
,这个特性可以用来定义额外的插槽。
<div>
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
这时候,我们就可以使用name属性:
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
4. 作用域插槽
父组件拿到子组件slot的值
//test.vue
<div>
<!-- 设置默认值:{{user.lastName}}获取 Jun -->
<!-- 如果home.vue中给这个插槽值的话,则不显示 Jun -->
<!-- 设置一个 usertext 然后把user绑到设置的 usertext 上 -->
<slot v-bind:usertext="user">{{user.lastName}}</slot>
</div>
//定义内容
data(){
return{
user:{
firstName:"Fan",
lastName:"Jun"
}
}
}
然后在home.vue
中接收传过来的值:
//home.vue
<div>
<test v-slot:default="slotProps">
{{slotProps.usertext.firstName}}
</test>
</div>
keep-alive
keep-alive是一个抽象组件,主要用于缓存内部组件实例
参数
include
:可传字符串、正则表达式、数组,名称匹配成功的组件会被缓存exclude
:可传字符串、正则表达式、数组,名称匹配成功的组件不会被缓存max
:可传数字,限制缓存组件的最大数量
include和exclude,传数组情况居多
总结
- 缓存
- 首次渲染的时候设置缓存
- 缓存渲染的时候不会执行组件的created、mounted等钩子函数,而是对缓存的组件执行patch过程,最后直接更新到目标元素
- 使用LRU缓存策略对组件进行缓存
- 命中缓存,则直接返回缓存,同时更新缓存key的位置
- 不命中缓存则设置进缓存,同时检查缓存的实例数量是否超过max
Vue的设计模式
单例模式
确保一个类只有一个实例对象,并提供一个全局访问点供其访问。
场景:登录浮窗、Vue的axios实例、全局状态管理store、线程池、全局缓存
发布-订阅者模式
又叫观察者模式。场景:双向绑定
装饰器模式
在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责;若直接修改函数体,则违背了“开放封闭原则”,也违背了我们的“单一职责原则”,简单的说就是允许向现有的函数添加新的功能,同时不改变其结构。
场景:vue中的表单验证与表单提交,遵循封闭开放原则。
vue模板编译
vue模板编译
:模板-AST树-render-虚拟节点DOM-真实节点DOM
CSS篇
什么是BFC
BFC(Block Formatting context)块级格式上下文。
BFC是一个独立的渲染区域,相当于一个容器,在这个容器中的样式布局不会受到外界的影响。
创建BFC
- 浮动元素(float值不为none)
- 绝对定位元素(position值为absolute或fixed)
- display为flex/inline-flex/inline-block/table/table-cell/table-caption
- overflow!=visible
解决了什么问题
- 使用float脱离文档流,高度塌陷
- margin边距重叠
- 两栏布局
flex
容器的属性
- flex-direction
- flex-wrap
- flex-flow
- justify-content
- align-items
- align-content
flex-direcition
决定主轴的方向(即项目的排列方向)
.box {
flex-direction: row | row-reverse | column | column-reverse;
}
flex-wrap
默认情况下,项目都排在一条线(又称“轴线”)上。
flex-wrap
定义如果一条轴线排不下,如何换行
.box {
flex-wrap: nowrap | wrap | wrap-reverse
}
flex-flow
flex-flow
属性是flex-direction
属性和flex-wrap
属性的简写形式,默认值为row nowrap
.box {
flex-flow: <flex-direction> || <flex-wrap>;
}
justify-content
定义了项目在主轴上的对齐方式
.box {
justify-content: flex-start | flex-end | center | space-between | spaca-around;
}
align-items
定义项目在交叉轴上如何对齐
.box {
align-items: flex-start | flex-end | center | baseline | stretch;
}
align-content
定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
.box {
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}
项目的属性
- order
- flex-grow
- flex-shrink
- flex-basis
- flex
- align-self
order
定义项目的排列顺序。数值越小,排列越靠前,默认为0。
flex:1
flex:1 = flex: 1 1 0%; 可扩大,可缩小,flex-basis为0
flex属性是flex-grow
,flex-shrink
和flex-basis
的简写,默认值是0 1 auto。
flex-grow
是如果有剩余空间,是否扩大,1为扩大,0为不扩大
flex-shrink
是如果剩余空间不够,是否缩小,1为缩小
flex-basis
为项目本身的大小,默认值是auto
垂直水平居中
1. 父元素使用flex布局,并设置相关的属性值为center
这种方式要求父元素的高度是确定的,百分比形式的高度将不能生效。
.par-work {
height: 100vh;
display:flex;
justify-content:center;
align-items:center;
}
2. 使用绝对定位absolute+transform,给子元素添加如下样式
这种方式比较常用,父子元素都不确定宽高的情况也适用。
如果子元素的宽高确定的话,translate中的值也可以设置为子元素宽高的一半,即tranform:translate(-100px,-100px);
.work {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
3.使用绝对定位absolute+margin,给子元素添加如下样式
这种方式适合子元素宽高确定的情况,给margin-top设置百分比的大小将不生效,即margin-top: -50%不能达到垂直居中的效果。
.work1 {
positon: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: -100px;
}
4. 使用绝对定位absolute+margin:auto,给子元素添加如下样式
父子元素宽高都未知时也适用。
.work2 {
postion: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
margin: auto;
}
5. 使用table-cell实现
这种方式需要父元素的宽高都是确定的,才能保证子元素在父元素中垂直水平都居中。
.par-work2 {
height: 500px;
width: 500px;
display: table-cell;
vertical-align: middle;
text-align: center;
}
.son-work2 {
display: inline-block;
}
6.使用grid布局
这种方式适用于父元素高度确定的情况。
.par-work3 {
display: grid;
height: 500px;
}
.son-work3 {
align-self: center; /* 设置单元格内容的垂直位置 */
justify-self: center; /* 设置单元格内容的水平位置 */
}
重绘、重排
重绘
重绘
:当元素的一部分属性发生改变,如外观、背景、颜色等不会引起布局变化,只需要浏览器根据元素的新属性重新绘制,使元素呈现新的外观。
重排
重排
:当render树中的一部分或者全部因为大小边距等问题发生改变而需要DOM树重新计算的过程。
重绘不一定需要重排(比如颜色的改变),重排必然导致重绘(比如改变网页位置)。
transition、transform和animation
transition
过渡。用于平滑改变css的属性,如盒子的长宽等,鼠标移入悬浮产生动效的情况等。
常用属性
- transiton-property:指定过滤的性质,比如background就是指background参与这个过渡
- transition-duration:指定这个过渡的持续时间
- transition-delay:延迟过渡时间
- transition-timing-function:指定过滤类型,有ease|linear|ease-in|ease-out|ease-in-out|cubic-bezier
transform
变化。常用来实现拉伸,压缩,旋转,偏移等。
常用属性:
- rotate
- translate
- scale
- skew扭曲
animation
动画,与transition区别是,transition只允许在两个状态中切换,而animation可以利用关键帧来实现多个状态,而且transition一定要有一个事件触发才会产生效果,就像是hover,click等。但是animation不需要任何事件的触发也可以随着时间的变化去改变css属性值,从而产生一种动画的效果。
src和href区别
- src是source的简写,目的是要把文件下载到html页面中,src用于替换当前内容
- href是Hypertext Reference的简写,表示超文本引用,指向网络资源所在的位置。href用于在当前文档和引用资源之间确立联系。
浏览器解析方式
- 当浏览器遇到href会
并行下载资源
并且不会停止对当前文档的处理
。(同时也是为什么建议使用link方式加载CSS,而不是使用@import方式,@import需要网页完全载入以后加载) - 当浏览器解析到src,会暂停其他资源的下载和处理,直到该资源加载或执行完毕。(这也是script标签为什么放在底部而不是头部的原因)
浏览器和网络篇
输入URL到页面显示的过程
浏览器有以下几个进程:
浏览器进程
。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。渲染进程
。把从网络下载的HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面。网络进程
。主要负责页面的网络资源加载。插件进程
。负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。GPU进程
。GPU绘制页面。
1. 用户输入
2. URL请求过程
浏览器进程会通过进程间通信(IPC)
把URL请求发送至网络进程,网络进程接收到URL请求后,会在这里发起真正的URL请求流程。
- 网络进程会查找
本地缓存
是否缓存了该资源。如果有缓存资源,那么直接返回资源给浏览器进程;如果在缓存中没有找到资源,那么直接进入网络请求流程。请求前的第一步是进行DNS解析
,获取请求域名的服务器IP地址
。如果请求协议是HTTPS,那么还需要建立TLS连接。 - 利用IP地址和服务器建立
TCP连接
(三次握手)。连接建立之后,浏览器端会构建请求行、请求头等信息,并把和该域名相关的cookie等数据附加到请求头中,向服务器发送请求。 - 服务器接口到请求信息后,会生成响应数据,并发给网络进程。
3. 准备渲染进程
通常情况下,打开新的页面都会使用单独的渲染进程。
如果协议
和根域名
相同,则属于同一站点
。新页面和当前页面属于同一站点的话,新页面会复用
父页面的渲染进程。
4. 提交文档
浏览器进程
将网络进程
接收到的HTML数据提交给渲染进程
。
5. 渲染进程
- 渲染进程将HTML内容转换为能够读懂的
DOM树
结构 - 渲染引擎将CSS样式表转换为浏览器可理解的
styleSheets
,计算出DOM节点的样式 - 创建
布局树
,并计算元素的布局信息 - 对布局树进行分层,并生成
分层树
- 为每个图层生成
绘制列表
,并将其提交到合成线程 - 合成线程将图层分成
图块
,并在光栅化线程池
中将图块转换成位图
- 合成线程发送绘制图块命令
DrawQuad
给浏览器进程 - 浏览器进程根据DrawQuad消息
生成页面
,并显示到显示器上
TCP/UDP
TCP
:面向连接、传输可靠、速度慢。TCP/IP模型的传输层协议。
一般用于文件传输,发送或接收邮件,远程登录等。
UDP
:面向非连接、传输不可靠、速度快。TCP/IP模型传输层的无连接协议。
一般用于即时通信,在线视频,语音电话,传输音频、视频文件等。
HTTPS
HTTP超文本传输协议,应用层协议。主要用于Web上传输超媒体文本的底层协议,经常在浏览器和服务器之间传递数据。通信就是以纯文本的形式进行。
HTTPS=HTTP+SSL/TLS
SSL是安全层,TLS是传输层安全,是SSL的继承。使用SSL或TLS可确保传输数据的安全性。
安全层:对发起的HTTP请求的数据进行加密操作
和对接收到HTTP的内容进行解密操作
。
HTTPS的工作流程
- 客户端发起
HTTPS请求
并连接到服务器的443端口
,此过程和HTTP请求一样,进行三次握手
; 服务端向客户端发送数字证书
,其中包含公钥、证书颁发者、到期日期;
现比较流行的加解密码对,即公钥和私钥。公钥用于加密,私钥用于解密。所以服务器会保留私钥,然后发送公钥给客户端。客户端收到证书,会验证证书的有效性
。验证通过后会生成一个随机的pre-master key
。再将密钥通过接收到的公钥加密然后发送给服务端;- 服务端接收后使用私钥进行解密得到pre-master key;
- 获取pre-master key后,服务器和客户端可以使用主密钥进行通信。
HTTP 1/2/3 有啥区别
HTTP1.1
缺陷:
- 高延迟-队头阻塞(Head-Of-Line Blocking)
带来页面加载速度的降低。队头阻塞是指当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,会导致客户端迟迟收不到数据。尝试通过以下办法解决:
将同一页面的资源分散到不同域名下,提升连接上限。
Chrome有个机制,对于同一个域名,默认允许同时建立6个TCP持久连接。合并小文件减少资源数
。精灵图Sprite内联(Inlining)资源
减少请求数量
- 无状态特性-阻碍交互
无状态是指协议对于连接状态是没有记忆能力。
纯净的HTTP是没有cookie等机制的,每一个连接都是一个新的连接。 - 明文传输-不安全性
- 不支持服务端推送
HTTP2新特性**
- 二进制传输
HTTP/2传输数据量的大幅减少,主要有两个原因:以二进制方式传输
和Header压缩
。
HTTP/2将请求和响应数据分割为更小的帧,并且他们采用二进制编码。 - Header压缩
HTTP/2并没有使用传统的压缩算法,而是开发了专门的“HPACK”算法
,在客户端和服务器两端建立“字典”,用索引号表示重复的字符串
,还采用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率。 - 多路复用
同个域名只需要占用一个TCP连接,使用一个连接并行发送多个请求和响应
,这样整个页面资源的下载过程只需要一次慢启动,同时也避免了多个TCP连接竞争带宽所带来的问题。并行交错地发送多个请求/响应
,请求/响应互不影响。- 在HTTP/2中,每个请求都可以带一个31bit的优先值,0表示最高优先级,数值越大优先级越低。
如上图所示,多路复用的技术可以只通过一个TCP连接就可以传输所有的请求数据。
- Server Push
服务器推送
:浏览器刚请求HTML的时候就提前把可能会用到的JS、CSS文件发给客户端,减少等待的延迟。 - 提高安全性
HTTP/2的缺点
虽然HTTP2解决了很多之前旧版本的问题,但是它还是存在一个巨大的问题,主要是底层支撑的TCP协议造成的。
- TCP以及TCP+TLS建立连接的延时
- TCP的
队头阻塞并没有彻底解决
- 多路复用导致
服务器压力上升
- 多路复用
容易Timeout
总结
- HTTP1有两个主要的缺点:性能不足(队头阻塞)和安全不足
- HTTP2完全兼容HTTP1,是更安全的HTTP、更快的HTTPS,二进制传输、头部压缩、多路复用、服务器推送等技术充分利用带宽,降低延迟,从而大幅度提高上网体验;
- HTTP3:QUIC基于UDP实现,是HTTP3的底层支撑协议,该协议基于UDP,又取了TCP中的精华,实现了即快又可靠的协议。
http缓存
在浏览器加载资源的时候,会根据请求头的
expires
和cache-control
判断是否命中强缓存策略,判断是否向远程服务器请求资源还是去本地获取缓存资源。
强缓存
强缓存分为cache-control
(http1.1规范)、expires
(http1规范)
cache-control
优先级高于expires。在cache-control有常见的几个响应属性值:
指令 | 参数 | 说明 |
---|---|---|
max-age | 必需,值如3600 | 表示(当前时间+3600秒)内不与服务器请求新的数据资源 |
public | 可省略 | 所有内容都将被缓存(客户端和代理服务器都可缓存) |
private | 无 | 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存) |
no-cache | 可省略 | 缓存前必须确认其有效性 |
no-store | 无 | 不缓存请求或响应的任何内容 |
expires
用来表示资源的过期时间的请求头字段,是一个绝对时间,由服务器端返回。如:Fri, 02 Sep 2022 08:03:35 GMT
协商缓存
ETag
优先级大于Last-Modified
ETag/If-None-Match
Etag
是一个文件的唯一标识符,当资源发生变化时这个ETag就会发生变化。If-None-Match
是浏览器请求数据时带上的字段,值是上次服务器返回的ETag
Last-Modified/If-Modified-Since
Last-Modified
表示本地文件最后修改时间,由服务器返回If-Modified-Since
是浏览器载请求数据时返回的,值是上次浏览器返回的Last-Modified
Webpack篇
webpack的核心概念
Entry
:入口,webpack执行构建的第一步从Entry开始,可抽象成输入。告诉webpack要使用哪个模块作为构建项目的起点,默认为./src/index.js
Output
:出口,告诉webpack在哪里输出它打包好的代码以及如何命名,默认为./dist
Module
:模块,在webpack里一切皆模块,一个模块对应着一个文件。webpack会从配置的Entry开始递归找出所有依赖的模块。
Chunk
:代码块,一个Chunk由多个模块组合而成,用于代码合并与分割。
Loader
:将webpack不认识的内容转化为认识的内容
Plugin
:拓展插件,可以贯穿webpack打包的生命周期,执行不同的任务。
webpack的基本功能
代码转换
:TypeScript编译成JavaScript、scss编译成css等
文件优化
:压缩JavaScript、css、html代码,压缩合并图片等
代码分割
:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
模块合并
:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
自动刷新
:监听本地源代码的变化,自动构建,刷新浏览器
代码校验
:在代码提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
自动发布
:更新完代码后,自动构建出线上发布代码并传输给发布系统
常用loader
css-loader
:读取合并css文件
style-loader
:把css内容注入到JavaScript里
sass-loader
:解析sass文件(安装sass-loader,node-sass)
postcss-loader
:自动添加浏览器兼容前缀(postcss.config配置)
url-loader
:将文件转换为base64 URL
vue-loader
:处理vue文件
常见Plugins
html-webpack-plugin
:自动在打包结束后生成Html文件,并引入bundle.js
clean-webpack-plugin
:打包自动删除上次打包文件
webpack-bundle-analyzer
:可视化webpack输出文件的体积(业务组件、依赖第三方模块)
webpack打包优化
- 压缩js、css代码,压缩图片;svg图片转化为base64格式图片
- 本地跑项目代码,实现代码热更新
- DllPlugin避免重复编译第三方库
TreeShaking
剔除无用js代码- gzip压缩
webpack-bundle-analyse
分析打包文件大小
preload、prefetch
- preload主要用于加载当前页面需要的资源;
- prefetch主要用于加载将来页面可能需要的资源
性能优化
路由懒加载
将路由全部改成懒加载,按需加载。
路由懒加载的原理
懒加载前提的实现:ES6的动态加载模块-import()
调用import()之处,被作为分离的模块起点,被请求的模块和它引用的所有子模块,会分离到一个单独的chunk中。
要实现懒加载,就得先将懒加载的子模块分离出来,打包成一个单独的文件。
webpackChunkName作用是webpack在打包的时候,对异步引入的库代码(lodash)进行代码分割时,设置代码块的名字。webpack会将任何一个异步模块与相同的块名称组合到相同的异步块中。
组件懒加载
使用场景:
- 该页面的JS文件体积大,导致页面打开慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如
首页
) - 该组件不是一进入页面就展示,需要一定条件下才触发(比如
弹框组件
) - 该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的JS文件大小(比如表格组件、图形组件等)
图片优化
- 图片转base64格式
- App.vue使用的全局小猴子loading.gif去除,替换成vant自带的loading
- 大图片文件压缩,并放到CDN加载
第三方组件加载优化
- momentjs替换为dayjs,替换后体积从400多k缩小至2kb
- lodash按需引用
- echarts按需引用
非主要进程延后加载
- OA申请H5 流程反馈模块延后加载
- 获取产品负责人信息延后加
Tree-shaking
作用:消除无用的js代码
骨架屏
让页面看起来更快地加载出来
长列表虚拟滚动
虚拟滚动:指的是只渲染可视区域的列表项,非可见区域的不渲染,在滚动时动态更新可视区域,该方案在优化大量数据渲染时效果是很明显的。
虚拟滚动基本原理
计算出totalHeight列表总高度,并在触发滚动事件时根据scrollTop值不断更新startIndex以及endIndex,以此从列表数据listData中截取对应元素。