Vue小白必备面试题
我的博客里还分享了小白真实面试过的题目
1.vue组件中的data必须是函数?
类比引用数据类型
Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
javascipt只有函数构成作用域(注意理解作用域,只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域),data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。
2.计算属性computed 和事件 methods 有什么区别 ?
我们可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的
不同点:
computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值对于 method ,只要发生重新渲染,method 调用总会执行该函数
需要知道值的改变后执行业务逻辑,才用 watch。
3.对比 jQuery ,Vue 有什么不同?
jQuery 专注视图层,通过操作 DOM 去实现页面的一些逻辑渲染; Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作
Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发
4.Vue 中 key 的作用
key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。key的作用主要是为了高效的更新虚拟DOM。这中间的更新过程是有一个Diff算法在的。
Diff算法:如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。
如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
5.vue-router 使用params与query传参有什么区别?
vue-router 可以通过 params 与 query 进行传参
// 传递this.
r
o
u
t
e
r
.
p
u
s
h
(
p
a
t
h
:
′
.
/
x
x
x
′
,
p
a
r
a
m
s
:
x
x
:
x
x
x
)
t
h
i
s
.
router.push({path: './xxx', params: {xx:xxx}})this.
router.push(path:′./xxx′,params:xx:xxx)this.router.push({path: ‘./xxx’, query: {xx:xxx}})
// 接收this.
r
o
u
t
e
.
p
a
r
a
m
s
t
h
i
s
.
route.params this.
route.paramsthis.route.query
params 是路由的一部分,必须要有。query 是拼接在 url 后面的参数,没有也没关系
params 不设置的时候,刷新页面或者返回参数会丢,query 则不会有这个问题
6.单项数据流
单向数据流:父级 prop 的更新会向下流动到子组件中,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值
单向数据流指只能从一个方向修改数据,一个父组件下有两个子组件1和子组件2,父组件可以向子组件传递数据。假如子组件都获取到了父组件的name,在子组件1中对name重新修改之后,子组件2和父组件中的值并不会发生改变,这正是因为Vue中的机制是单向数据流,子组件不能直接改变父组件的状态。但反过来,如果是父组件中的name修改了,当然两个子组件中的name也就改变了。
7.数据的双向绑定
主要是由MVVM框架实现,在Vue中主要由三个部分组成,View、ViewModel和Model组成,其中View和Model不能直接进行通信,他们要通过中间件ViewModel来进行。例如,当Model部分数据发生改变时,由于vue中Data Binding将底层数据和Dom层进行了绑定,ViewModel通知View层更新视图;当在视图 View数据发生变化也会同步到Model中。View和Model之间的同步完全是自动的,不需要人手动的操作DOM。
8.v-if 和 v-show 有什么区别?
v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。
9.简述Vue的响应式原理
当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。
每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
10.Vue中如何在组件内部实现一个双向数据绑定?
v-model 假设有一个输入框组件,用户输入时,同步父组件页面中的数据
具体思路:父组件通过 props 传值给子组件,子组件通过 $emit 来通知父组件修改相应的props值
11。前端如何优化网站性能?
1.减少 HTTP 请求数量
在浏览器与服务器进行通信时,主要是通过 HTTP 进行通信。浏览器与服务器需要经过三次握手,每次握手需要花费大量时间。而且不同浏览器对资源文件并发请求数量有限(不同浏览器允许并发数),一旦 HTTP 请求数量达到一定数量,资源请求就存在等待状态,这是很致命的,因此减少 HTTP 的请求数量可以很大程度上对网站性能进行优化。
CSS Sprites:国内俗称 CSS 精灵,这是将多张图片合并成一张图片达到减少 HTTP 请求的一种解决方案,可以通过 CSS background 属性来访问图片内容。这种方案同时还可以减少图片总字节数。
合并 CSS 和 JS 文件:现在前端有很多工程化打包工具,如:grunt、gulp、webpack等。为了减少 HTTP 请求数量,可以通过这些工具再发布前将多个 CSS 或者 多个 JS 合并成一个文件。
采用 lazyLoad:俗称懒加载,可以控制网页上的内容在一开始无需加载,不需要发请求,等到用户操作真正需要的时候立即加载出内容。这样就控制了网页资源一次性请求数量。
1.控制资源文件加载优先级
浏览器在加载 HTML 内容时,是将 HTML 内容从上至下依次解析,解析到 link 或者 script 标签就会加载 href 或者 src 对应链接内容,为了第一时间展示页面给用户,就需要将 CSS 提前加载,不要受 JS 加载影响。
一般情况下都是 CSS 在头部,JS 在底部。
1.利用浏览器缓存
浏览器缓存是将网络资源存储在本地,等待下次请求该资源时,如果资源已经存在就不需要到服务器重新请求该资源,直接在本地读取该资源。
2.减少重排(Reflow)
基本原理:重排是 DOM 的变化影响到了元素的几何属性(宽和高),浏览器会重新计算元素的几何属性,会使渲染树中受到影响的部分失效,浏览器会验证 DOM 树上的所有其它结点的 visibility 属性,这也是 Reflow 低效的原因。如果 Reflow 的过于频繁,CPU 使用率就会急剧上升。
减少 Reflow,如果需要在 DOM 操作时添加样式,尽量使用 增加 class 属性,而不是通过 style 操作样式。
1.减少 DOM 操作
2.图标使用 IconFont 替换
12.虚拟 DOM 实现原理
虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象
状态变更时,记录新树和旧树的差异
最后把差异更新到真正的dom中
13.jQuery如何扩展自定义方法
(jQuery.fn.myMethod=function () {
alert(‘myMethod’);
})// 或者:
(function ($) {
$.fn.extend({
myMethod : function () {
alert(‘myMethod’);
}
})
})(jQuery)
使用:
$("#div").myMethod();
14.vue 中怎么重置 data?
使用Object.assign(),vm.
d
a
t
a
可
以
获
取
当
前
状
态
下
的
d
a
t
a
,
v
m
.
data可以获取当前状态下的data,vm.
data可以获取当前状态下的data,vm.options.data(this)可以获取到组件初始化状态下的data。
复制Object.assign(this.
d
a
t
a
,
t
h
i
s
.
data, this.
data,this.options.data(this)) // 注意加this,不然取不到data() { a: this.methodA } 中的this.methodA
15.route 和 router 的区别是什么?
route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
router是“路由实例对象”,包括了路由的跳转方法(push、replace),钩子函数等。
16.Vuex 有哪几种属性?
有五种,分别是 State、Getter、Mutation、Action、Module
17.vuex 有action与没有action的区别?
vuex中有四个属性值:state、getters、mutations、actions。
在没有actions的情况下:
数据:state --> data
获取数据:getters --> computed
更改数据:mutations --> methods
视图通过点击事件,触发mutations中方法,可以更改state中的数据,一旦state数据发生更改,getters把数据反映到视图。
那么有actions,可以理解处理异步,而单纯多加的一层。
在vue中,通过click事件,触发methods中的方法。当存在异步时,而在vuex中需要dispatch来触发actions中的方法,actions中的commit可以触发mutations中的方法。同步,则直接在组件中commit触发vuex中mutations中的方法。
vue的生命周期都做了什么?
beforeCreate:
在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法
created:
data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作
beforeMount:
执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
mounted:
执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
beforeUpdate:
当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步
updated:
页面显示的数据和data中的数据已经保持同步了,都是最新的
beforeDestory:
Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁
destroyed:
这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。
Vue.js 生命周期 (opens new window)主要有 8 个阶段:
创建前 / 后(beforeCreate / created):在 beforeCreate 阶段,Vue 实例的挂载元素 el 和数据对象 data 都为 undefined,还未初始化。在 created 阶段,Vue 实例的数据对象 data 有了,el 还没有。
载入前 / 后(beforeMount / mounted):在 beforeMount 阶段,Vue 实例的 $el 和 data 都初始化了,但还是挂载之前为虚拟的 DOM 节点,data 尚未替换。在 mounted 阶段,Vue 实例挂载完成,data 成功渲染。
更新前 / 后(beforeUpdate / updated):当 data 变化时,会触发 beforeUpdate 和 updated 方法。这两个不常用,且不推荐使用。
销毁前 / 后(beforeDestroy / destroyed):beforeDestroy 是在 Vue 实例销毁前触发,一般在这里要通过 removeEventListener 解除手动绑定的事件。实例销毁后,触发 destroyed。
v-model语法糖的升级版
<template>
<div>
<button @click="increase(-1)">减 1</button>
<span style="color: red;padding: 6px">{{ currentValue }}</span>
<button @click="increase(1)">加 1</button>
</div>
</template>
<script>
export default {
name: 'InputNumber',
props: {
value: {
type: Number
}
},
data () {
return {
currentValue: this.value
}
},
watch: {
value (val) {
this.currentValue = val;
}
},
methods: {
increase (val) {
this.currentValue += val;
this.$emit('input', this.currentValue);
}
}
}
</script>
v-model 的升级1 model
在 model 选项里,就可以指定 prop 和 event 的名字了,而不一定非要用 value 和 input,因为这两个名字在一些原生表单元素里,有其它用处。
<template>
<div>
<button @click="increase(-1)">减 1</button>
<span style="color: red;padding: 6px">{{ currentValue }}</span>
<button @click="increase(1)">加 1</button>
</div>
</template>
<script>
export default {
name: 'InputNumber',
props: {
number: {
type: Number
}
},
model: {
prop: 'number',
event: 'change'
},
data () {
return {
currentValue: this.number
}
},
watch: {
value (val) {
this.currentValue = val;
}
},
methods: {
increase (val) {
this.currentValue += val;
this.$emit('number', this.currentValue);
}
}
}
</script>
升级2:.sync 修饰符
<template>
<div>
<button @click="increase(-1)">减 1</button>
<span style="color: red;padding: 6px">{{ value }}</span>
<button @click="increase(1)">加 1</button>
</div>
</template>
<script>
export default {
name: 'InputNumber',
props: {
value: {
type: Number
}
},
methods: {
increase (val) {
this.$emit('update:value', this.value + val);
}
}
}
</script>
父亲用例:
<template>
<InputNumber :value.sync="value" />
</template>
<script>
import InputNumber from '../components/input-number/input-number.vue';
export default {
components: { InputNumber },
data () {
return {
value: 1
}
}
}
</script>
看起来要比 v-model 的实现简单多,实现的效果是一样的。v-model 在一个组件中只能有一个,但 .sync 可以设置很多个。.sync 虽好,但也有限制,比如:
不能和表达式一起使用(如 v-bind:title.sync=“doc.title + ‘!’” 是无效的);
不能用在字面量对象上(如 v-bind.sync="{ title: doc.title }" 是无法正常工作的)
v-show 与 v-if 区别?
v-show 只是 CSS 级别的 display: none; 和 display: block; 之间的切换,而 v-if 决定是否会选择代码块的内容(或组件)。
回答这些,已经可以得到 50 分了,紧接着我会追问,什么时候用 v-show,什么时候用 v-if ?
频繁操作时,使用 v-show,一次性渲染完的,使用 v-if,只要意思对就好。
第二问可以得到 80 分了,最后一问很少有人能答上:那使用 v-if 在性能优化上有什么经验?
这是一个加分项,要对 Vue.js 的组件编译有一定的理解。说一下期望的答案:
因为当 v-if=“false” 时,内部组件是不会渲染的,所以在特定条件才渲染部分组件(或内容)时,可以先将条件设置为 false,需要时(或异步,比如 $nextTick)再设置为 true,这样可以优先渲染重要的其它内容,合理利用,可以进行性能优化。
vue的修饰符
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
.once执行一次就被销毁了
.stop阻止冒泡
.prevent
.capture
.self
keep-alive的理解
https://www.jianshu.com/p/9523bb439950
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
场景:比如在tab切换的时候你想在两个组件切换的时候,把组件的状态保存下来。
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<router-view></router-view>
</keep-alive>
include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。
render函数:
什么是 Render 函数,它的使用场景是什么。
createElement 是什么?
Render 函数有哪些常用的参数?
说到 Render 函数,就要说到虚拟 DOM(Virtual DOM),Virtual DOM 并不是真正意义上的 DOM,而是一个轻量级的 JavaScript 对象,在状态发生变化时,Virtual DOM 会进行 Diff 运算,来更新只需要被替换的 DOM,而不是全部重绘。
用render函数替代
render: function (createElement) {
return createElement('h1', this.blogTitle)
}
createElement 参数
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
babel原理
https://blog.csdn.net/qq_34210479/article/details/103547287
总结如下:将字符串生成ast树,最后将转化成浏览器能够识别的es5代码
lazy-loader原理
首先看y轴方向的判断:this.rect.top < window.innerHeight * this.options.preLoad, 是dom的顶部是否到了preload的位置;this.rect.bottom > this.options.preLoadTop 判断dom的底部是否到达了preload的位置
关于x轴方向就不做解析了,实现同y轴。
load () {
// 如果当前尝试加载图片的次数大于指定的次数, 并且当前状态还是错误的, 停止加载动作
if ((this.attempt > this.options.attempt - 1) && this.state.error) {
if (!this.options.silent) console.log('error end')
return
}
if (this.state.loaded || imageCache[this.src]) {
return this.render('loaded', true) // 使用缓存渲染图片
}
this.render('loading', false) // 调用lazy中的 elRender()函数, 用户切换img的src显示数据,并触发相应的状态的回调函数
this.attempt++ // 尝试次数累加
this.record('loadStart') // 记录当前状态的时间
// 异步记载图片, 使用Image对象实现
loadImageAsync({
src: this.src
}, data => {
this.naturalHeight = data.naturalHeight
this.naturalWidth = data.naturalWidth
this.state.loaded = true
this.state.error = false
this.record('loadEnd')
this.render('loaded', false) // 渲染 loaded状态的 dom的内容
imageCache[this.src] = 1 // 当前图片缓存在浏览器里面了
}, err => {
this.state.error = true
this.state.loaded = false
this.render('error', false)
})
}
紧接着是loadImageAsync()异步加载图片的函数
const loadImageAsync = (item, resolve, reject) => {
let image = new Image()
image.src = item.src
image.onload = function () {
resolve({
naturalHeight: image.naturalHeight, // 图片的 实际高度
naturalWidth: image.naturalWidth,
src: image.src
})
}
image.onerror = function (e) {
reject(e)
}
}
conputed的缓存
什么时候缓存?
在得知 computed 属性发生变化之后, Vue内部并不立即去重新计算出新的 computed 属性值,而是仅仅标记为dirty,下次访问的时候,再重新计算,然后将计算结果缓存起来。
好处:频繁的调用 但是并没有使用 在这个场景下就会提高了性能。
vue中的watcher有几类?
webpack的使用
https://www.cnblogs.com/chengxs/p/11022842.html
webpack是一个打包模块化js的工具,可以通过loader转换文件,通过plugin扩展功能。
1、webpack与grunt、gulp的不同?
三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。
grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。
webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
总结:(1)从构建思路来说:gulp和grunt需要开发者将整个前端构建过程拆分成多个Task
,并合理控制所有Task
的调用关系 webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工;
(2)对于知识背景:gulp更像后端开发者的思路,需要对于整个流程了如指掌 webpack更倾向于前端开发者的思路。
2.有哪些常见的Loader?他们是解决什么问题的?
(1)babel-loader:把es6转成es5;
(2)css-loader:加载css,支持模块化,压缩,文件导入等特性;
(3)style-loader:把css代码注入到js中,通过dom操作去加载css;
(4)eslint-loader:通过Eslint检查js代码;
(5)image-loader:加载并且压缩图片晚间;
(6)file-loader:文件输出到一个文件夹中,在代码中通过相对url去引用输出的文件;
(7)url-loader:和file-loader类似,文件很小的时候可以base64方式吧文件内容注入到代码中
(8)source-map-loader:加载额外的source map文件,方便调试。
有哪些常见的Plugin?他们是解决什么问题的?
(1)uglifyjs-webpack-plugin:通过UglifyJS去压缩js代码;
(2)commons-chunk-plugin:提取公共代码;
(3)define-plugin:定义环境变量。
如何利用webpack来优化前端性能?(提高性能和体验)
用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。
(1)压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件, 利用cssnano(css-loader?minimize)来压缩css。使用webpack4,打包项目使用production模式,会自动开启代码压缩。
(2)利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径
(3)删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数–optimize-minimize来实现或者使用es6模块开启删除死代码。
(4)优化图片,对于小图可以使用 base64 的方式写入文件中
(5)按照路由拆分代码,实现按需加载,提取公共代码。
(6)给打包出来的文件名添加哈希,实现浏览器缓存文件
减少 Webpack 打包时间
1.优化 Loader
对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。当然了,我们是有办法优化的
首先我们可以优化 Loader 的文件搜索范围
odule.exports = {
module: {
rules: [
{
// js 文件才使用 babel
test: /\.js$/,
loader: 'babel-loader',
// 只在 src 文件夹下查找
include: [resolve('src')],
// 不会去查找的路径
exclude: /node_modules/
}
]
}
}
当然这样做还不够,我们还可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间
- HappyPack
受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。
HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了
module: {
loaders: [
{
test: /\.js$/,
include: [resolve('src')],
exclude: /node_modules/,
// id 后面的内容对应下面
loader: 'happypack/loader?id=happybabel'
}
]
},
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory'],
// 开启 4 个线程
threads: 4
})
]
- DllPlugin
DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。
/ 单独配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 想统一打包的类库
vendor: ['react']
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].dll.js',
library: '[name]-[hash]'
},
plugins: [
new webpack.DllPlugin({
// name 必须和 output.library 一致
name: '[name]-[hash]',
// 该属性需要与 DllReferencePlugin 中一致
context: __dirname,
path: path.join(__dirname, 'dist', '[name]-manifest.json')
})
]
然后我们需要执行这个配置文件生成依赖文件,接下来我们需要使用 DllReferencePlugin 将依赖文件引入项目中
// webpack.conf.js
module.exports = {
// ...省略其他配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest 就是之前打包出来的 json 文件
manifest: require('./dist/vendor-manifest.json'),
})
]
-
代码压缩代码压缩
在 Webpack3 中,我们一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,我们可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。 -
一些小的优化点
我们还可以通过一些小的优化点来加快打包速度
resolve.extensions:用来表明文件后缀列表,默认查找顺序是 [’.js’, ‘.json’],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面
resolve.alias:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径
module.noParse:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助
减少打包的一个体积
https://blog.poetries.top/FE-Interview-Questions/excellent-docs/%E9%AB%98%E9%A2%91%E6%A8%A1%E5%9D%97.html#_25-1-%E5%87%8F%E5%B0%91-webpack-%E6%89%93%E5%8C%85%E6%97%B6%E9%97%B4
- 按需加载
- 模块的合并Scope Hoisting
- Tree Shaking
sourcemap上线之后定位bug:
https://blog.csdn.net/liwusen/article/details/79414508
我们在打包中,将开发环境中源代码经过压缩,去空格,babel编译转化,最终可以得到适用于生产环境的项目代码,这样处理后的项目代码和源代码之间差异性很大,会造成无法debug的问题。
举例来说,如果压缩等处理过的生产环境中的代码出现bug,调试的时候只能定位到压缩处理后的代码的位置,无法定位到开发环境中的源代码。
sourcemap就是为了解决上述代码定位的问题,简单理解,就是构建了处理前的代码和处理后的代码之间的桥梁。主要是方便开发人员的错误定位。这里的处理操作包括:
I)压缩,减小体积
II)将多个文件合并成同一个文件
III)其他语言编译成javascript,比如TypeScript和CoffeeScript等
如果生成 sourcemap?
通过devtool来进行设置生成sourcemap的方式
https://blog.csdn.net/weixin_39977642/article/details/111835284
devtool取值为’source-map’是会生成单独的source map文件的,取一些其它值会把source map直接写到编译打包后的文件里,不过浏览器依然可以通过它还原出编译前的原始代码。
生产环境的代码,我们都会使用插件对其进行压缩,因此可以也需要考虑到压缩插件支持完整source-map的能力。因此,生产环境一般只能从source-map,hidden-source-map和nosources-source-map这三个值中选择一个。
取source-map的话,是比较利于定位线上问题和调试代码的,但其它人都可以通过浏览器开发者工具看到原始代码,这是有严重安全隐患的,因此不推荐生产环境用这个值。
nosources-source-map安全性稍微高一些,我们仍可以通过浏览器开发者工具看到原始代码的目录结构,但不会看到具体代码内容。对于错误信息,我们可以在开发者工具的控制台看到原始代码的堆栈信息,对于调试和定位错误基本够用了。不过这种方式不是最安全的,因为仍然可以通过反编译来获取源代码。
hidden-source-map是最安全的取值,这种方式会打包出完整的source map文件,但打包出的bundle里不会有source map文件的地址,因此在浏览器开发者工具里是看不到原始代码的。要想看到原始代码,我们通常会用一些错误收集系统,将source map文件传到该系统上,然后通过JavaScript出错后上报的错误信息,错误收集系统通过source map分析出原始代码的错误堆栈。
在生产环境,除了这些选择外,我们还可以使用服务器白名单策略。我们仍然打包出完整的source map文件上传,但只有白名单的用户才可以看到source map文件。例如,我们可以通过配置服务器对指定的IP地址授权访问source map文件。
mini-css-extract-plugin 优点:
CSS 请求并行
CSS 单独缓存
SplitChunksPlugin
抽离第三方公共模块
WDS webpackDevServer
热更新? 配置 hot = true
babel,babel-ployfill,babel-runtime
了解Babel的工作原理,那首先需要了解抽象语法树,因为Babel插件就是作用于抽象语法树。首先我们编写的代码在编译阶段解析成抽象语法树(AST),然后经过一系列的遍历和转换,然后再将转换后的抽象语法树生成为常规的js代码。
babel-ployfill?
如果要解决API层面的问题,需要使用垫片。比如常见的有babel-polyfill、babel-runtime 和 babel-plugin-transform-runtime。
babel-polyfill通过向全局对象和内置对象的prototype上添加方法来实现的。所以这会造成全局空间污染
babel-polyfill使用的两种方式
1、webpack.config.js 中:
配置webpack.config.js里的entry设置为
entry: ['babel-polyfill',path.join(__dirname, 'index.js')]
2、业务 js 中:
在webpack.config.js配置的主入口index.js文件的最顶层键入
import 'babel-polyfill'
两者打印出来的大小都是一样的,打包后大小是280KB,如果没有使用babel-polyfill,大小是3.43kb。两则相差大概81.6倍。原因是webpack把babel-polyfill整体全部都打包进去了。而babel-polyfill肯定也实现了所有ES6新API的垫片,文件一定不会小。
那么有没有一种办法,根据实际代码中用到的ES6新增API ,来使用对应的垫片,而不是全部加载进去呢?
是的,有的。那就是 babel-runtime & babel-plugin-transform-runtime,他们可以实现按需加载。
babel-runtime
简单说 babel-runtime 更像是一种按需加载的实现,比如你哪里需要使用 Promise,只要在这个文件头部
import Promise from 'babel-runtime/core-js/promise'
就行了。
不过如果你许多文件都要使用 Promise,难道每个文件都要 import 一下吗?当然不是,Babel 官方已考虑这种情况,只需要使用 babel-plugin-transform-runtime 就可以解决手动 import 的苦恼了。
babel-plugin-transform-runtime
babel-plugin-transform-runtime 装了就不需要装 babel-runtime了,因为前者依赖后者。
总的来说,babel-plugin-transform-runtime 就是可以在我们使用新 API 时 自动 import babel-runtime 里面的 polyfill,具体插件做了以下三件事情:
当我们使用 async/await 时,自动引入 babel-runtime/regenerator
当我们使用 ES6 的静态事件或内置对象时,自动引入 babel-runtime/core-js
移除内联 babel helpers 并替换使用 babel-runtime/helpers 来替换
babel-plugin-transform-runtime 优点:
不会污染全局变量
多次使用只会打包一次
依赖统一按需引入,无重复引入,无多余引入
避免 babel 编译的工具函数在每个模块里重复出现,减小库和工具包的体积
使用方式
在 .babelrc 中配置:
plugins: [“tranform-runtime”]
vue中的defineProperty
在组件挂载时,会先对所有需要的属性调用 Object.defineProperty(),然后实例化 Watcher,传入组件更新的回调。在实例化过程中,会对模板中的属性进行求值,触发依赖收集。
当数据变化的时候执行 defineProperty中的set方法 set方法中的dp.notify()
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
let dp = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
// 将 Watcher 添加到订阅
if (Dep.target) {
dp.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
// 执行 watcher 的 update 方法
dp.notify()
}
})
}
在执行Dep notify sub.update()
class Dep {
constructor() {
this.subs = []
}
// 添加依赖
addSub(sub) {
this.subs.push(sub)
}
// 更新
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
// 全局属性,通过该属性配置 Watcher
Dep.target = null
在执行notify中的 sub.update()
首屏加载的解决方案
Vue-Router路由懒加载(利用Webpack的代码切割)
使用CDN加速,将通用的库从vendor进行抽离
Nginx的gzip压缩
Vue异步组件
服务端渲染SSR
如果使用了一些UI库,采用按需加载
Webpack开启gzip压缩
如果首屏为登录页,可以做成多入口
Service Worker缓存文件处理
使用link标签的rel属性设置 prefetch(这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低,prefetch通常用于加速下一次导航)、preload(preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)
白屏优化方案
- 检测页面关键DOM的是否渲染
- 通用的DOM渲染监听
- H5截图(canvas绘图)检测
- native截图(容器截屏)检测
- 利用performance.getEntries(“paint”)获取fp/fcp来感知渲染
云神指点内容:
js渲染方面 async defer
nds预加载
虚拟列表
web service
不同的图片的话 采用不同的方案
webpack 分包最优 (因为http最多支持六个并行域名 可以将我们的css js修改域名 改成一次请求六个最先加载的 http2流的概念 一旦阻塞了 会全部都阻塞 )
骨架屏先占据一定位置
Preload&Prefetch 优化前端页面的资源加载
prefetch(链接预取)是一种浏览器机制,其利用浏览器空闲时间来下载或预取用户在不久的将来可能访问的文档。网页向浏览器提供一组预取提示,并在浏览器完成当前页面的加载后开始静默地拉取指定的文档并将其存储在缓存中。当用户访问其中一个预取文档时,便可以快速的从浏览器缓存中得到。–MDN
具体来说,浏览器通过标签来实现预加载。
其中rel="prefetch"被称为Resource-Hints(资源提示),也就是辅助浏览器进行资源优化的指令。
network可以看到这个请求标记上prefetch cache,表明这次请求的资源来自prefetch缓存。这个表现验证了上文中prefetch的定义,即浏览器在空闲时间预先加载资源,真正使用时直接从浏览器缓存中快速获取。
preload与prefetch同属于浏览器的Resource-Hints,用于辅助浏览器进行资源优化。为了对两者进行区分,prefetch通常翻译为预提取,preload则翻译为预加载。
元素的rel属性的属性值preload能够让你在你的HTML页面中元素内部书写一些声明式的资源获取请求,可以指明即刻需要的资源,在页面加载的生命周期的早期阶段就开始获取,在浏览器的主渲染机制介入前就进行预加载。这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。
Vue的双向数据绑定
这个问题也是回答了vue2 以及vue3的双向数据绑定,面试官提到了proxy的缺点有哪些?
proxy的兼容性不好,无法在ie9以下进行使用
proxy的第一个参数必须是对象,无法代理基本数据类型
proxy的性能是一个很大的问题,衡量proxy带来的遍历和可能损耗的性能,进行合理的中和,来达到最佳的开发体验和用户体验
Plugin和loader的区别
Loader` 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。
Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
Webpack 的运行流程
是一个串行的过程,从启动到结束会依次执行以下流程:
初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
确定入口:根据配置中的 entry 找出所有的入口文件
编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
vue-router的hash、history模式
hash表示的是地址栏URL中#符号(也称作为锚点), hash虽然会出现在URL中, 但是不会被包含在Http请求中, 因此hash值改变不会重新加载页面.
由于hash值变化不会引起浏览器向服务器发出请求, 而且hash改变会触发hashchange事件, 浏览器的进后退也能对其进行控制, hash变化会触发网页跳转,即浏览器的前进和后退。
hash 可以改变 url ,但是不会触发页面重新加载(hash的改变是记录在 window.history 中),即不会刷新页面。也就是说,所有页面的跳转都是在客户端进行操作。因此,这并不算是一次 http 请求,所以这种模式不利于 SEO 优化。hash 只能修改 # 后面的部分,所以只能跳转到与当前 url 同文档的 url 。
hash 通过 window.onhashchange 的方式,来监听 hash 的改变,借此实现无刷新跳转的功能。
hash 永远不会提交到 server 端(可以理解为只在前端自生自灭)。
利用了HTML5新增的pushState()和replaceState()两个api, 通过这两个api完成URL跳转不会重新加载页面
同时history模式解决了hash模式存在的问题. hash的传参是基于URL的, 如果要传递复杂的数据, 会有体积限制, 而history模式不仅可以在URL里传参, 也可以将数据存放到一个特定的对象中
HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,。
对于 history 来说,确实解决了不少 hash 存在的问题,但是也带来了新的问题。具体如下:
使用 history 模式时,在对当前的页面进行刷新时,此时浏览器会重新发起请求。如果 nginx 没有匹配得到当前的 url ,就会出现 404 的页面。
而对于 hash 模式来说, 它虽然看着是改变了 url ,但不会被包括在 http 请求中。所以,它算是被用来指导浏览器的动作,并不影响服务器端。因此,改变 hash 并没有真正地改变 url ,所以页面路径还是之前的路径, nginx 也就不会拦截。
因此,在使用 history 模式时,需要通过服务端来允许地址可访问,如果没有设置,就很容易导致出现 404 的局面。
下面我们再来介绍下在实际的项目中,如何对这两者进行选择。具体如下:
to B 的系统推荐用 hash ,相对简单且容易使用,且因为 hash 对 url 规范不敏感;
to C 的系统,可以考虑选择 H5 history ,但是需要服务端支持;
能先用简单的,就别用复杂的,要考虑成本和收益。
小程序和h5的区别
1.环境不同
小程序不需要兼容五花八门的浏览器
2.开发成本不一样
小程序以css等为基础 定义了自己的wcss等等 避免的五花八门的框架浪费开发人员的开发成本等。
3.系统权限
小程序获得系统权限比较方便 而且和系统级别的权限无缝连接
跨度之间的概念差异:
一个H5应用只有一个渲染线程,因此只能通过一个page实例来操控视图(一个tab页)
一个小程序应用有多个渲染层,可以通过多个page实例操控视图。
详情:
一个h5应用默认为单标签页模式,应用在部分场景下的调用主体上升到app实例,但实际执行主体为page实例。
v8垃圾回收机制
https://juejin.cn/post/6844904016325902344
总结:
新生代 老生代
新生代采用scanvage算法空间换时间的算法:
- form未激活空间 to已经激活的空间
刚分配的变量就会进入未激活空间 进行回收的时候会将未激活空间遍历一次,未使用的就清空 使用过的就复制到to空间,都遍历过一次后就将from与to空降进行置换。 - 新生代晋升
1.经历一次算法就会晋升到老生活代
2.to空间占比达到25%