如何封装一个通用组件和hooks
- 我这里说一个最典型的吧,因为我们项目比较大嘛,流程也是比较多的,就比如我们那个某个详情模块吧,这个模块不光详情会用到,包括申请/审批/申请/流程/挂接资源/挂接完资源还有服务/等等...详情好多页面都用到了这个这个详情的模块,如果说每次都重新写一遍这个组件的话,真的是又臭又长,代码冗余太多,维护起来也比较困难。所以就把这个详情组件给抽离了出来,通过传入一个详情的数据,然后在这个组件内部做一些判断处理(因为有些信息展示有些信息不展示嘛,还要在组件内部做一些极个别的逻辑处理)。这时候组件是抽离出来了,但是获取详情的接口调的还不是同一个接口,每个页面获取详情的接口还不一样,但是每次都要单独调用接口来获取详情,这样也会造成代码的冗余,然后当时项目挺紧急地,我也没有去研究vue3是否支持ahooks,我就自己写了一个调用接口的hooks,在组件内部通过组件外部传入的请求参数和接口的url以及接口类型,然后传入这个hooks,然后在hooks里进行根据传过来的数据调用接口,然后把一些loading,详情,还有一些其他乱七八糟的数据返回,然后在这个组件里获取详情数据进行一个处理(这个和具体业务有些出入,主要是这个大概思路)。
高阶组件
- 高阶组件:是一个函数式组件,他可以接收一个组件作为参数,并且在该函数内部对这个传过来的组件进行一些加工处理,然后返回一个新的并且具有新功能的函数,就比如我有一个组合式列表的组件,上面有搜索项下面有列表表格,这个组件就接收一个form组件和一个Table组件,然后在这个高阶组件内部写一些搜索的逻辑
项目的性能优化
- 项目优化一般不都是分为,代码性能优化和打包优化嘛,像一些react和vue稍微比较大的项目的话,一般打包优化都是由我们团队特定成员处理的,我们主要针对的是代码层面的性能优化的话,就是一些逻辑复用,合理的组件抽离,包括尽量去使用一些框架内部提供的一些方法的优化,就比如computed和usememo以及usecallback,还有react.lazy,import()=>动态引入,等等方法来进行一个代码层面的优化。真说涉及到的一些打包优化的话,就比如一些分包处理,动态加载,以及动态引入,就比如说uniapp吧,它是通过pages.json里的subpackges和preloadRule来定义分包和分包预下载的,当然如果说webpack的话是需要借助一些插件的,要配置一下plugin,比如terserPlugin,OptimizeCSSAssetsPlugin来进行一个代码的压缩,通过cache.type: 'filesystem'来实现一个持久化的缓存从而节省一些项目构建的时间。
闭包和垃圾回收机制
- 闭包:函数return一个函数,如果函数外部能访问到函数内部的属性就形成了闭包。一般函数执行完之后就会被浏览器垃圾回收机制回收掉,但是闭包函数外面用到了函数内部的状态,浏览器这时候也不知道后面还会不会引用到这个函数的属性,所以这个函数就不会被回收掉,如果刚好函数内部处理了一些沉重的数据就可能会引起内存泄漏,除非手动将这个函数赋值为null,这样就可以告诉浏览器你可以将这个函数回收掉。
- 垃圾回收机制:在代码执行完上下文的时候,浏览器会根据不同情况来触发垃圾回收机制,会将没有用的变量或者函数回收掉,从而释放内存空间。
浅拷贝和深拷贝
- 浅拷贝:比如我要基于一个对象重新创建一个对象,或者把一个对象赋值给另一个对象就是浅拷贝。这时候修改新对象的属性,旧对象也会受影响,因为只是复制了这个对象的引用,但是用的还是同一个内存。
- 深拷贝:就是另外又开辟了一个内存空间,这两个对象不共享一个内存空间,修改新对象就对象不会受影响。可以使用lodash的cloneDeep/json.stringfy(因为字符串不存在深浅拷贝,但是对象里的函数无法复制)/使用递归复制对象里面的每一个属性。
vue和react的区别
- vue是mvvm框架,也就是model-view-viewModel使用的是双向绑定,通过viewModel这个桥梁用于组件和数据之间进行绑定,从而视图可以驱动数据更新,数据也可以驱动视图更新,可以实现组件和数据之间的双向绑定。可以实现组件的局部更新
- react,使用的是单向数据流,是由数据驱使组件的更新,每次状态更新之后,然后都会重新执行render函数。会使整个组件都重新渲染
- 优缺点:
- react使用了hooks函数式组件开发,使每个函数都可以return一个组件,开发起来较为灵活;并且react社区比较庞大支持很多第方三的库,可以开发一些相对来说比较复杂的页面。开发灵活带来的缺点是react在协作开发时必须要统一代码风格,不然这一块那一块较零散的代码,很容易写成屎山,导致不好维护,对于刚接手项目的不太好友好,熟悉项目周期会更长一点。
- vue使用双向绑定可以使组件状态进行局部更新,代码风格开发规范以及生态都比较成熟,维护起来比较好维护,只要按照开发规范来就不容易写成屎山代码。但是这个优点同时也算是vue的一个缺点,开发起来不太灵活,代码格式较为死板,但是vue3中加了Composition API以及setup语法糖既可以实现模块化的开发又可以使代码写的更加灵活,同时也使框架性能进一步更高。
路由拦截和请求拦截
- 通常用户登录之后,后端会返回一个token值,然后前端把这个token值和用户的登陆信息保存在loaclStorage中
- 路由拦截:在用户进行路由跳转的时候,会去loaclStorage里面拿到token值或者用户的登录信息,如果拿不到就跳转到登陆页面,或者可以做一些其他操作。
- 请求拦截:在用户调用接口的时候,会在请求头里面携带一个这个token值,如果调用需要鉴权的接口后端会返回当前cookie是否还可以使用,如果不可以根据后端返回的code来进行判断跳转到登陆页或者做一些其他操作。
- 如果每个页面初始的时候都调用了需要鉴权的接口的话,就不需要在进行路由拦截了。
ajax和axios和fetch的区别
- ajax 只是一种实现接口请求的思想,内部使用的是XMLhttpRequest实现接口请求
- axios 是随着vue兴起出现的一个第三方库,基于的是XMLhttpRequest二次封装,可以通过requestuse和responsuse实现请求拦截和响应拦截
- fetch 是es6出的一个api,返回的是个promise,fetch支持所有promise的方法。用法fetch(url,请求体).then
promise原理
- promise是ES6新增的特性,它接受一个excutor函数,excutor接受两个回调函数作为参数,一个是请求成功的回调resolve和一个请求失败的回调reject。还有一个状态属性state,这个状态只有在pending状态下可变。当执行了resolve方法的时候,这个状态就会变resolved状态。当执行了reject方法的时候,这个状态就会变成rejected状态。promise提供了.then,.all,.race等方法。
- .then方法接收两个回调函数,promise执行成功就会执行第一个函数,执行失败就会调用第二个函数。并且返回的也是一个promise对象,从而实现链式调用
- .all方法是,传入多个promise返回一个promise对象,如果传入的promise全部执行成功,就会以数组的形式返回执行结果,否则判为失败
- .rase方法是,传入多个promise,谁先返回执行结果,就以先返回的结果作为最终结果。
localStorage和sessionStorage和cookie的区别
- localStorage:是永久保存在本地的,只能去手动清除,大小有5M。
- sessionStorage:是关闭标签页/浏览器之后会自动进行清除,并且只有在当前域名下面才能访问到。
- cookie:是关闭标签页/浏览器之后会自动清除,大小只有4kb,同时还可以直接设置过期时间,在所有域名下面都能访问到。
vue的生命周期
- beforeCreate:组件初始化之前,这时候啥数据都拿不到
- created:组件初始化完成,并且完成了数据观察,可以访问到数据并且可以操作数据,但是视图不会更新
- mounted:dom已经挂载完成,可以调用接口,可以获取到dom元素
- beforeUpdate:组件更新之前,能获取到更新之前的状态
- updated:组件更新完成,能获取到最新的状态
- beforeUnmount:组件销毁之前,通常用于清除一些监听订阅等方法
- unmounted:组件已经销毁,通常用于清除一些监听订阅等方法
观察者模式
- vue2是通过object.defindProperty()实现的,vue3是通过proxy代理对象实现的
// vue3
function observer(value) {
const proxyHandler = {
get(target, key) {
const obj = target[key]
if (typeof obj === "object") {
return observer(obj)
}
console.log('我访问了', key)
return obj
},
set(target, key, newValue) {
if (target[key] !== newValue) {
target[key] = newValue
if (typeof newValue === "object") {
observer(newValue)
}
console.log('我修改了', key, '为:', newValue)
}
return true
}
}
const proxy = new Proxy(value, proxyHandler)
return proxy
}
const object = {
name: 'lwt',
age: '27',
children: {
name: 'xxx',
age: '-2',
run: ['haha', 'xxx']
}
}
const objectRef = observer(object)
// vue2
const observer = (target) => {
// 为对象的每一项都进行监听
for (key in target) {
let internalValue = target[key];
Object.defineProperty(target, key, {
get() {
if (typeof internalValue === 'object') {
return observer(internalValue)
}
return internalValue
},
set(newValue) {
if (typeof internalValue === 'object') {
observer(internalValue)
}
internalValue = newValue
}
})
}
return target
}
const object = {
name: 'lwt',
age: '27',
children: {
name: 'xxx',
age: '-2',
run: ['haha', 'xxx']
}
}
const v = observer(object)
面向对象编程
-
面向对象编程最熟悉的就是发布订阅和观察者模式,一般这两种模式都是结合着一起使用的。就相当于是我在数据发生变化或者对象发生变化之后,我可以将逻辑提出来单独去做一些针对性的处理,可以是普通新加进来的逻辑,或者是将订阅的事件进行派发。如果是普通编程思维的话,我就得写很多的判断逻辑,代码量大的话维护起来就很吃力,虽然我这个时候可以将通用逻辑以hooks的形式提出来,但是这样并不能实现一个合理的逻辑分离,业务逻辑和代码逻辑会非常的混乱,面向对象编程就大大降低了这样的维护成本,业务和逻辑能进行一个很好的分割,代码看起来非常的干净,而且也降低了维护成本,增加了代码的可拓展性。
-
例子:比如我们平时使用的promise就是面向对象编程,promise当有异步任务进来的时候。promise的then方法通过订阅这个异步的回调任务,然后将这个异步回调任务保存起来,当执行了resorve或者reject的时候再通过监听state来进行这个异步回调任务的派发。还有就是我在工作中,使用的请求拦截以及路由拦截使用的都是这个面向对象的模式,通过监听路由的变化,或者接口的调用,去进行一些针对性的处理。
函数式编程
-
函数式编程,我的理解就是,只要是代码中使用了纯函数就可以说是函数编程,因为纯函数就是一个没有副作用的函数,不会去修改外部数据,并且还可以反回自己想要的数据,所有逻辑都是在函数内部做处理,可以将它看成是一个个功能,并且一个功能必须是在一个函数中处理,然后就是多个功能需要多个函数去做处理,然后使用一个高阶函数将这些功能函数进行一个合并。这样就可以进行一个合理的逻辑分离,可以使代码拥有清晰并且具备模块化。如果说那个节点出问题单独去出问题的函数里面修复就行了,这样就提高了代码的可维护性。但是函数式编程也有一个缺点,就是函数式编程会产生过多的闭包,所以如果说不能合理的运用的话也会造成性能上的一个过大的开销
-
例子:就比如平时使用的高阶函数,通过传入一个函数或者一个组件,然后做一些处理返回一个具有新功能的函数或者组件。还有像平时我们使用的map,reduce和filter方法底层实现就是利用的函数式编程,通过传入一个回调然后在函数内部去做处理,并返回一个新的结果。
v-for和v-if同时使用,vue2和vue3的区别
- vue2同时使用,v-for优先级>v-if,这样会导致在list每次渲染的时候,都会重新触发一次遍历,性能会受到影响。
- vue3同时使用,v-if优先级>v-for,这样会导致v-if拿不到某个元素的变量,因为有可能该元素还没循环到,会导致冲突。
- 一般不建议同时使用,可以先将数组根据条件过滤掉之后再进行渲染,或者使用v-show进行条件判断
diff算法,vue2和vue3的区别,以及有key和无key的区别
-
diff算法:1.新旧虚拟dom做对比,dom属性和位置有更新的话新的会把旧的替换掉。2.如果发现多的,会进行新增。2.发现少的会进行删除。
-
有key和无key的区别:有key的话对比会根据key作对比,无key的话会根据dom对象的属性和位置进行对比,有key的话可以更精准更快速的进行dom对象的对比,性能和效率要比无key的要好很多
-
vue2的diff算法:利用前序算法和尾序算法,也就是说有两个指针同时进行新旧dom的对比,然后再根据新旧变化进行一个增删改。
-
vue3的diff算法:多了个”最长递增子序列算法“,这一算法能够更有效地识dom对象的变化,减少不必要的对比。因为这个算法会找到连贯的多个没有变化的dom对象,然后把些连贯的dom对象看做成一个整体。将来要移动或者删除时,直接操作这个整体就行了,从而减少了没有必要的比较。
静态提升
- 首先vue在编译的时候会把我们的组件编译成render函数。静态提升就是在编译的时候vue会把组件的静态节点以及静态属性单独提出来然后赋值给某个变量,不让它在render函数中运行。因为vue每次执行render函数或重新渲染的时候都会去创建一个vnode,所以静态节点是不会变的没有必要每次render的时候去创建,使用静态提升的话就只会创建一次vnode然后赋值给某个变量,然后在render函数中直接使用这个变量就行了。
//vue2
render(){
createVnode('H1',XXX,'xxx内容')
}
//vue3
const staticObject = createVnode('H1',XXX,'xxx内容')
render(){
//render里面直接使用staticObject就行
}
ref和shallowRef的区别
- ref:使用ref响应式数据修改数据时候必须 object.value.xxx = xxx 才能修改属性(深层的响应)
- shallowRef:使用shallRef响应式数据修改数据的时候需要object.value = { xxx:xxx }才能更新数据以及更新视图,如果使用object.value.xxx = xxx,只会更新数据,但是视图不会更新(浅层的响应)
- 但是如果说,一个方法里同时修改ref和shallowRef的响应式数据,shallowRef会被影响(意思是这个时候两者都使用object.value.xxx = xxx 这种方式修改属性,会影响到shallowRef的视图更新,因为修改ref响应式数据的时候会触发依赖的更新,然后在重新渲染的时候它会把所有的视图依赖都进行更新,所以就把shallowRef的依赖一块更新了)
ref和reactive的区别
-
ref:在使用的时候需要加上object.value.xxx、ref支持传入任何类型
-
reactive:在使用时只需object.xxx、reactive只支持传入引入类型(对象类型),reactive的proxy对象不能直接赋值,否则会破坏响应式的规则,会导致无法监听到对象的变化,导致视图无法进行准确的更新。如果真的要修改的话必须将reactive传入一个对象包裹的数据,从而修改reactive响应式数据的对象属性
-
为什么ref要.value,而reactive不需要:ref返回的是一个RefImpl对象,通过class类的get value 和set value属性用来获取更新响应式数据,监听的是value属性,所以要使用.value来操作响应式。reactive不需要因为,reactive返回的是个直接就是一个proxy代理对象,直接能操作对象里面的属性
-
本质上其实也没有什么区别,只不过ref是用来定义基本数据类型响应式,reactive是用来定义引用数据类型响应式,如果ref硬要传入引用数据类型的话,其实走的还是创建reactive响应式数据的逻辑。
-
ref的简洁的源码实现
const toReactive = (value) => {
return new Proxy(value, {
get(v, key) {
return v[key]
},
set(v, key, newValue) {
v[key] = newValue
return true
}
})
}
class Reflect {
_value
constructor(value) {
this._value = value
if (typeof value === 'object') {
this._value = toReactive(value)
}
}
get value() {
return this._value
}
set value(newValue) {
this._value = newValue
}
}
const createRef = (value) => {
return new Reflect(value)
}
const ref = (value) => {
if (false) {
return value
}
return createRef(value)
}
- reactive的简洁源码实现
const valueMap = new WeakMap()
function myReactive(value: object) {
// 检查传入的参数是否是一个对象
if (typeof !== 'object') {
// 如果不是就直接返回
return value
}
// 如果存在缓存值就直接返回缓存里的值
if (valueMap.has(value)) {
return valueMap.get(value)
}
// 如果以上都不成立,就会new一个代理对象,第一个参数为要代理的对象,第二个参数为代理规则(拦截器,可以添加一些扩展方法,而不像Object.defineProperty()只有getter和setter方法)
const newValue = new Proxy(value, {
// 当访问当前对象值的时候会调用get方法,并将这个值当作依赖收集起来, get接收当前传入的对象,还有当前访问的key值,返回当前返回的值
get(value, key) {
return value[key]
},
// 当要改变当前访问的数据,会触发依赖更新,调用set方法,将获取到最新的数据重新赋值给当前操作的对象中的某个值
set(value, key, newValue) {
value[key] = newValue
return true
}
})
Object.defineProperty()和Proxy代理对象来实现数据响应式有什么区别与提升
- Object.defineProperty()使用的是get和set方法进行依赖的收集和更新,收集的对象里每个属性,需要对对象里面的每个属性都进行监听,会将对象里的每个属性都要遍历一遍,如果某个值改变之后,再赋值从而会非常的损耗性能。并且Object.defineProperty()是在js对象创建,以及赋值之前就已经被监听了,所以无法对响应式对象进行新增和删除属性的处理,因为在新增和删除属性之前,监听操作已经监听结束了。
- proxy代理会监听整个对象,在访问响应式数据对象某个值得时候,通过get和set等方法拦截到对象的某个值然后做收集和更新处理,这样即避免了遍历整个对象又可以准确的追踪到key值,从而单独对某个key值进行取值或赋值,减少了计算的损耗
- 这个也是vue3相对于vue2的一个巨大的提升吧
nextTick
- nextTick:我的理解是类似于生命周期的勾子。一般任务执行或者修改数据之后DOM会立即进行更新,这样的话有可能会造成浏览器执行任务的混乱,页面渲染元素过的话也可能会造成渲染的阻塞和页面的卡顿,所以nextTick就是解决了这个问题,就是将要执行的任务放在异步任务队列中去,然后当DOM渲染更新任务执行完毕之后再去执行这个异步队列中的任务。
toRef和toRefs
- toRef:就是能将响应式对象或者普通对象里的属性单独提出来,并创建一个新的响应式对象,用法:toRef(proxyObj,'key'),如果传个普通对象的话数据会更新,但是视图不会更新,因为虽然toRef底层和Ref基本一样,只不过toRef的底层没有调用“track”和”trigger“更新视图的方法,没有收集视图依赖和触发视图依赖的更新的操作,所以就只有数据更新但是触发视图的更新。
- toRefs:就是内部将代理对象的每个键值对做toRef的循环处理,循环创建新的引用响应式对象,可以实现数据的解构,用法:const {a,b,c} = toRefs(proxyObj)
- toRaw:就是将代理对象脱掉外层proxy,将代理对象转换为原始对象,用法toRaw(proxyObj),也就是说只是通过"__v_raw"这个key从代理对象中将原始对象取出来了
computed计算属性的原理
- computed也通过Proxy代理的get和set方法将计算依赖的值进行收集更新。在取值或者计算的时候,它会先从缓存里拿值计算,如果缓存里没有就把计算的值和依赖项以及计算逻辑保存在缓存里,只有依赖发生改变的时候才会重新计算。某种意义上讲vue3的computed类似于react的useMemo,都是通过监听依赖并进行缓存,只有依赖项发生变化时才会重新进行计算,都起到性能优化的作用。但是computed监听不到异步数据的变化,因为计算属性的依赖变化计算结果也要更新,为了不影响计算的结果,所以必须是同步计算。
- 相对于vue2的computed来说,vue3的computed进行了很大的优化,vue3利用了proxy代理对象的依赖追踪和更新机制,减少了不必要的对象遍历,并能更准确的追踪到依赖的变化。并使用缓存机制,减少了不必要的计算,使代码的性能得到了进一步的提升。
watch监听属性的原理
- watch可以监听组件内部的任何属性,只要传入的属性有任何变化,都会执行watch函数。然后通过拿到新旧值做对比去执行一些方法,watch也可以监听到异步数据的变化,但是内部没有像computed一样做缓存。如果是ref响应式,wacth不会深度监听,必须通过deep属性来实现深度监听。某种意义上和useEffect相似,只有传入的数据有变化才会执行某个回调,但是watch返回的是一个停止监听的函数,可以通过调用此函数来停止对某些数据的监听,而且watch是一个惰性函数,可以通过immediate属性来使watch创建时就默认执行一次回调,第一次的旧值是undefinde。同时onTrack / onTrigger也方便开发时的调试。需要注意的是在使用watch监听属性的单个属性的时候,依赖需要传入一个函数,该函数返回这个属性的单个函数
- 相对于computed来说,watch的使用场景更加灵活,可以监听异步的数据,也可以通过监听到新值旧值做对比来决定下一步代码该怎么执行,但是watch没有使用缓存机制,导致每次执行要多消耗一部分的内存。所以在操作异步数据并且需要进行逻辑判断的情况下推荐用watch,否则直接用computed就可以了。
watchEffect
- watchEffect会自动追踪函数内部使用的相使用的数据,当内部追踪的数据变化之后会重新执行,watchEffect的基本原理和watch相同,内部同样没有使用缓存机制,但相比watch写法更加灵活简洁,从而降低开发的心智。但是watch可以获取到某个属性的新值和旧值,能准确的监听到某个单个属性的变化。
setup语法糖和setup函数的区别
- 使用setup函数的编译结果是原封不动的,他会所有组件内部定义的函数以及状态全部都暴露出来,这就给了外部可以操作组件内部的属性机会,开发可以在组件外部直接通过ref拿到某个组件的实例,然后通过$ref.xxx操作组件内部的状态从而也就给了开发违反开发规范的机会,因为平时在开发中组件中的状态最好是只在组件内部进行处理的。
- 使用setup语法糖的编译结果会多出一个expose函数,这个是用于手动暴露组件内部的状态,默认暴露出去的是一个空对象,表示没有暴露任何组件内部的状态,如果真的需要暴露组件内部状态就需要通过definExpose方法将需要暴露的状态暴露出去,从而也就极大可能的规避了外部修改内部状态的机会。
redux原理
- 流程:如果组件需要更新状态首先,通过dispatch派发一个action,然后通过reducer进行state的修改,然后反馈给组件进行状态更新
- 异步:使用thunk中间件,允许action是一个函数不再是一个对象,这个函数可以接收到dispatch和state,这样就可以在合适的时机去进行异步处理,这样就可以在store中进行串联逻辑处理,在组件中直接使用state就行了。
react_fiber
- fiber:就是可以暂停渲染,然后去处理一些高优先级的任务,然后再恢复渲染。就比如我页面渲染任务很繁重的时候,为了不造成很直观的卡顿。fiber会将渲染任务进行一个分块处理,然后每一块都进行各自的渲染,当用户操作页面或者有高优先级的任务要执行的时候,就会将某个渲染块暂停,待高优先级任务执行结束之后再进行继续渲染。
Vuex核心API
- state:公共数据源 用法:$store.state.xxx
- mutation:修改公共数据源 用法:$store.commit('xxx':方法,props:传参)
- action:处理异步数据 用法:$store.dispatch('xxx') ---> 调用mutation中的方法修改数据
- getter:包装state但是不修改state 用法:$store.getter.xxx
- mudules:进行仓库的分割,操作共享数据的时候分别去对应的仓库拿
- 组件通过commit调用mutation里的方法更新state
同步流程:compontent-->[commit]-->mutation-->state-->compontent
- 组件通过dispatch调用action里的方法通过commit调用mutation里的方法更新state
异步流程:compontent-->[dispatch]-->action-->[commit]-->mutation-->state-->compontent
- 持久化:1.手写方式:loacstorage;2.引入插件persistedstate,然后在store里根据文档配置下plugins
pinia核心API
- state:公共数据源(是个函数,返回状态)
- getter:相当于是computed计算,可以直接返回计算结果
- action:相当于是method,可以是异步,也就是说这里可以处理异步数据
- persist:持久化。如果是小程序必须借助piniaPluginPersistedstate插件,注意需要使用pinia单独再注册(use)一下
piniaPluginPersistedstate插件实现持久化必须有两个方法,getItem和setItem来实现读取和更新
slot插槽
- 默认插槽:就是父组件向子组件传递一些node节点或者任意内容给子组件,然后子组件把接收到的节点通过渲染出来
- 具名插槽:和默认插槽一样,只不过会多个name属性,可以控制我要插的具体位置
- 作用域插槽:就是在子组件渲染节点的时候,父组件可以拿到子组件传递过来的参数
slot说白了就是一个占位符,再深入一点其实就是一个个函数,函数名字就是插槽的name属性,当父组件给子组件传递参数的时候会调用creatVnode根据传过来的参数去创建一个节点,并且把返回的节点插到子组件中,本质上和react的函数式组件接收children props差不多,只不过children是默认位置,slot要放在固定位置
为什么要有模块化开发
- 因为现在的页面差不多都是单页面应用,不像之前是页面之间跳来跳去,所以得进行模块分割。
commonjs和muduls
- 对于服务端来说node使用的就是commonjs规范,因为node本身是运行在服务端的,对于node来读取一些东西,就相当于是同步读取的,所以没有什么影响,当然commonjs也有回调式API比如require.ensure('xxx',callback)。对于前端来说es6之前前端使用的AMD规范,es6之后前端用的是muduls规范。muduls在编译的时候就会对模块进行解析,并抛出错误,commonjs是在运行时才会进行解析。
vite和webpack理解
- vite相比于webpack的优势:vite按需动态编译,在启动的时候不需要进行编译,在进行模块请求的时候再按需进行编译,但是只针对的是开发环境所以vite只是一个构建工具,并不是打包工具,只是在开发时比较爽,打包的时候其实用的是rollup打包的;webpack是静态打包,配置好入口和出口文件之后,会读取所有模块,然后递归构建依赖关系图,然后编译,然后再运行,都是将各个模块进行编译,前端的代码编译成浏览器可识别的代码
webpack常见的plugin
- html-webpack-plugin:用于自动生成html文件
- clear-webpack-plugin:清理数据缓存,保证每次打包都是新的数据,不会造成数据污染
- min-css-plugin:就是将css文件单独提出来,就是将js代码和css代码完全分离
loader和plugin的区别
- loader:允许在导入模块的时候进行预处理,对模块的源代码进行转换,针对模块级预编译
- plugin:用于扩展构建工具的插件,可执行自定义构建任务,针对项目级打包过程
webpack性能优化
- 缩小搜索的文件范围方面
- 1.使用resolve.alias在引入文件的的时候直接通过/@src就可以自动匹配到对应文件;
- 2.使用reslove.module可以让文件顺序查找,就不需要引入相对路径,减少匹配文件的路径;
- 3.使用module.noParse,告诉webpack哪些文件是不需要解析的,一般配置一些大型的第三方库;
- 4.使用exclude来排除哪些文件不需要处理,includes来指定哪些文件要被处理
- 5.使用tree shaking删除无用import组件引入。
- 缓存优化方面
- 1.使用Dllplugin和DllReferensPlugin生成的manifest.js来告诉webpack哪些文件已经打包,从而不会重复打包,节省打包时间
- 2.使用CachePlugin,对打包结果的缓存
- loader优化方面
- 1.使用happyPack来进行loader的多线程转换。因为js是单线程,所以要使用happypack来分配多个子线程来进行loader的转换
- 2.使用swc-loader来实现js或者ts代码编译的速度提升,因为swc使用rust写的,相对于babel-laoder,swc编译js/ts的速度是非常的快的,并且编译完之后会有js的缓存,所以在后续启动的过程就会利用缓存进行动态编译,也就是说只编译更新过的代码,所以后续启动项目就会非常的快。
- 开发体验方面
- 1.开启热更新,devServer里的hot设为true
- 2.使用webpackbar实时监控项目运行进度
- 代码压缩方面
- 1.压缩js代码使用terse-webpack-plugin
- 2.压缩css代码使用assets-webpack-plugin
- 3.压缩image图片使用image-webpack-plugin
微前端
-
什么是微前端:微前端就是对大型项目的拆分,每一块都可以是一个独立的应用,每个应用状态都单独在各自应用里维护,然后通过基座将多个打包结果整合起来运行
-
iframe:缺点刷新页面之后url会丢失;如果子应用里面使用了弹框组件的话,弹框组件只能挂载到子应用上,导致ui不同步;加载缓慢,每次刷新页面的时候都会重新建立iframe上下文;用法简单,直接通过src绑定路由就可以实现微前端的引入。
-
single-spa:打包出来的包体积会比较大,js和css代码无法实现隔离,导致加载时间缓慢,并且没有提供通信方法,使用比较复杂。
-
qiankun:提供了比较成熟的api;能同时兼容多种技术栈;有资源预加载和js和css代码的隔离,打开页面的速度有显著提升;使用HTML entry方式嵌入,可以像iframe一样使用起来简单。
- 基座:主应用,负责集成所有子应用,提供一个入口能够访问所有子应用的页面
- sub:子应用,负责打包成一个umd模块,提供给主应用加载
-
qiankun微前端如何使用:1.定义好微前端:app=[];2.注册微前端:regesterMicorApps();3.使用微前端:start(),然后在子应用webpack里面配置一下config,就是为了将子应用打包成umd模块,利于子应用读取生命周期,然后通过子应用的render函数接收的props.conteners属性判断当前运行环境是微前端还是独立应用。
-
qiankun生命周期:bootstrap只有在微前端初次调用的时候会进入这个生命周期;mount每次进入都会调用这个生命周期;unmount在微应用切出/卸载的时候调用
-
qiankun子应用跳转:使用hash路由跳转#url,使用history.pushState()跳转,将主应用的路由实例通过props传给子应用,然后子应用用这个传过来的路由实例进行跳转。
-
qiankun如何实现应用之间的通信:通过自定义事件,在主应用中通过new CustomEvent,创建一个自定义事件然后触发,然后在子应用中监听这个事件,然后做一些相应的逻辑处理。通过全局状态管理;在主应用中创建一个全局状态管理的store,然后子应用通过监听onGlobalStateChange事件,使用setGlobalState修改状态;loacstorage传参或者url传参;如果主应用和子应用不在一个项目下的话需要通过注册的微前端进行render的时候通过props进行传参
** hash路由和history路由的区别 **
-
hash路由url有#符号拼接,兼容性较好,并且如果回退页面的时候不需要调接口
-
history路由没有#符号拼接,在进行页面跳转的时候是利用浏览器的历史记录进行跳转,history.go()/history.push()跳转,history.replec()替换路由;并且在跳转路由的时候会重新调接口。
uniapp
-
uniapp的上线流程是什么:前后端拉会进行需求同步,将大概小程序逻辑梳理清楚,先口头定义接口,拿到设计稿和图标之后,后端同步接口文档,然后前端先mock假数据进行前端页面的开发;待前后端都开发完成之后进行联调,联调完之后,进行系统测试,然后改bug,当小程序达到预期要求后要进行一个打包配置包括应用名称,版本号,以及应用权限等,准备好截图和应用描述和隐私政策,然后发布到对应渠道进行审核,审核通过后,将应用正式发布上线。
-
uniapp应用的生命周期:onLanch():应用初始化时调用;onShow():引用从后台切换至前台时调用;onHiden():应用切换至后台时调用;onError():应用发生错误时调用
-
uniapp页面的生命周期:onLoad():页面加载时触发;onShow():页面显示时触发;onHiden():页面隐藏时触发;onReady():页面初次挂载完成后触发;onUnload():页面关闭时触发
-
uniapp如何获取安全距离:通过wx.getSystemInfoSync()获取边界信息然后动态调整页面布局,一般都保持10rpx
-
uniapp如何进行分包:1.首先manifest.json文件中配置subPackages;2.通过pages.json的subPackages来实现要分包的,首先配置好分包的根目录,然后配置好需要分包的路由。
-
uniapp如何进行按需加载:1.首先manifest.json文件中配置app-preload-rule字段;2.通过pages.json的preloadRule来实现分包的预加载,首先配置好预加载的分包路径,然后配置好预下载的根目录
-
uniapp是如何进行持久化:小程序不能直接使用persist实现持久化,必须要借用pinia的piniaPluginPersistedstate插件,配置这个插件的时候必须包含getItem和setItem两个方法,这个官网上也有说明
-
uniapp路由跳转:_uni.navigateTo():保留当前页面缓存然后再跳转使用uni.navigateBack()可以回到上个页面;_uni.redirectTo():关闭当前页面再跳转使用uni.navigateBack()不可以回到上个页面; _uni.reLaunch():关闭所有页面,然后打开某个页面
浏览器
-
渲染引擎:负责解析HTML和CSS,构建页面的DOM树和CSS树然后合并成渲染树(render Tree),然后将渲染树转换成屏幕的像素点
-
javaScript引擎:通过浏览器的引擎比如V8,解析js代码,然后实现页面逻辑的交互
-
网络层:负责处理网络请求和响应,包括HTTP请求和DNS解析和TCP连接,然后通过解析获取页面的各种资源进行加载,比如HTML/CSS/JS/图片资源等
-
浏览器存储:loacoStorage,sessionStorage,cookie
-
缓存策略强缓存:在发送某个请求结束之后,浏览器会把当前返回的数据缓存起来,在一定时间内在请求相同的接口就直接从缓存里获取数据,通过设置请求头里的Catch-control:max-age=xxxx毫秒或者在请求头设置expires:xxx-xx-xx具体缓存的日期,因为后者根据用户的时间来进行计算的所以会有误差,一般都是使用Catch-control,如果两者同时设置,Catch-control优先级会高于exprires。
-
缓存策略协商缓存:是服务端和浏览器之间进行一个沟通,浏览器根据服务端返回数据的唯一标识Last-Modified或者Etag进行对比,如果服务端返回的数据没有变化浏览器将继续使用缓存,反之就将返回最新的数据。
Event Loop
-
javaScript是一个单线程语言在同一时间内只能执行一个主线程任务。为了服务端的请求不引起阻塞和用户的体验,浏览器采用eventloop机制来处理异步任务。在执行同步任务的时候,会将异步任务放在待执行异步队列之中进行排队,如果说当前主线程同步任务执行完毕,再将异步任务放到主线程中去执行。
-
task queue:异步任务又分为宏任务和微任务,宏任务比如setTimeout,微任务比如promise.then等等方法,微任务的优先级要大于宏任务
node以及代理服务器
-
代理服务器,就是浏览器有个同源策略,url协议和端口必须一样才能去访问某个接口,否则就可能会出现跨域问题,这样我们就可以通过代理服务器进行一个跨域的解决,就是通过node去定义一个访问接口,然后在这个服务器里去请求跨域的服务,因为服务器之间请求数据是没有跨域这一说的,然后将跨域的服务器返回的数据在这个代理服务器中进行返回,然后页面通过请求这个代理服务器的接口拿到数据
-
常见的代理服务器实现方法。比如:express,通过定义一个接口服务,再定义一个端口,然后监听这个服务和端口,然后这个代理服务器的请求头要允许跨域。然后在这个代理服务器里进行一个跨域接口的请求,然后拿到请求数据之后,通过send返回给请求这个代理服务器的页面。
express和egg框架
-
express是基于nodejs的一个框架,用于快速搭建一个后端服务。通过app.get定义路由,app.use注册中间件,app.licein监听端口。不过一些中间件的逻辑需要自己手动去实现。通常用于开发轻量级的项目
-
egg是基于koajs的一个企业级框架,因为他通过最佳的实践并且有自己的约定,所以它相对于express要成熟稳定的多。定义好路由之后通过contrller将这个路由进行关联。内部集成了一些插件可以提供给开发直接使用。通常用于开发一些大型复杂的项目。
webSocket
-
webSocket允许前端在服务端去订阅一些消息,然后当服务端有反馈的时候就可以主动通知给前端。就比如一个聊天的功能,前端根据webSocket发送给后端一些信息,然后后端通过调用相应的操作,比如接受发送信息或者是保存聊天记录,当服务端接收到聊天记录的时候再通过webSocket将信息返回给前端。还有比如审核通知,我有个数据需要审核,但是前端需要知道我实时审核的节点,当时就是使用webSocket前端去进行一个消息的订阅,当服务端有审核结果的通知的时候,就可以主动告诉前端当前的审核进度
自我介绍:面试官您好,我叫xxx,首先感谢贵公司能给我笨次面试机会,我当前从事前端行业已经三年半了,主要熟悉的技术栈是vue3和react以及uniapp,并且能够将js特性,和es5/es6新增的属性方法合理运用到项目中去,并且可以编写出高维护性代码,并且拥有相扎实的基础知识,我的自我介绍完毕面试可以问我技术型问题了。
亮点难点提炼:我就从大往小了说了。首先从公司层面,我肯定是以公司的业务和产品为中心,能够主动发现项目中存在的bug并且及时作出修改。日常开发中发现代码可以优化的地方并能及时的进代码逻辑上的优化。然后是团队层面,推动团队的技术分享,参加code reviwe。然后是个人层面,在开发业务的时候将一些通用逻辑进行封装然后沉淀到业务组件库,就比如一些常用到的组合式表单,组合式列表以及拖拽组件。并且在项目后期维护为了减少维护成本并且为了方便本地调试,又写了一个按下 command或者ctrl键+鼠标右键,然后递归获取react-fiber的节点源代码信息,将这些源代码信息保存起来,然后以弹框的形式将这些连接进行拼接(vscode://file/{obj.lineNumber}:${obj.columnNumber}`}),然后保存在弹框中,点击某个连接就直接能跳转到代码对应的行数