一、v-show 和 v-if 区别?
- v-show 通过 css display 控制显示与隐藏
- v-if 组件真正的渲染和销毁,而不是显示与隐藏
- 频繁切换显示状态用 v-show,否则用 v-if
二、为何在 v-for 中用 key?
- 为了提升渲染性能(减少渲染次数)
- key 最好避免是 rendom index 索引这种
- 在 diff 算法中,对比新旧 vnode 时 (children 节点),根据元素 target 和 key 判断是否是 sameNode, 是否更新渲染
// 通过 diff 算法中进行 patch 打补丁时, 对比 children 节点时 通过 tag 和 key 来判断,是否是 sameNode(相同节点)
三、描述 Vue 组件生命周期(父子组件)?
- 单组件生命周期(11 个生命周期钩子)
/* 生命周期 */
// 1. 生命周期就是组件实例出来到销毁的一系列过程。分为挂载阶段、更新阶段、销毁阶段。
// 2.
// 挂载阶段 -- 挂载阶段主要是组件的实例化,然后模板解析 然后render生成vnode节点(这时候已经实现响应式了),最后替换页面指定el(挂载到页面el上),第一次渲染完成
// beforeCreate
// created, --初始化data数据(实现响应式)
// beforeMount --在挂载开始之前被调用
// mounted --vnode节点挂载到页面空el上【patch(ele, vnode)】,渲染DOM完成
// --官方说法:el 被新创建的 vm.$el(vue实例) 替换,并挂载到实例上去之后调用该钩子。
// 更新阶段 -- 更新阶段就是,data变化触发响应式setter方法,重新render生成vnode节点,通过diff算法对比新旧vnode节点,进行patch打补丁,更新渲染完成
// beforeUpdate --这时还没生成vnode节点; 可以修改状态不会触发二次渲染。(react 中这里setState设置容易出现死循环)
// updated --这时已经生成vnode节点,通过diff算法对比新旧vnode,进行patch打补丁【patch(vnode, newVnode)】,重新更新渲染DOM
// 卸载阶段
// beforeDestory --销毁前;一般销毁自定义事件、DOM事件
// destoryed
// 其他钩子
// activated(活跃状态) --keep-alive 当前组件显示时执行的生命周期。
// deactivated(缓存状态) --keep-alive 当前组件缓存时执行的生命周期。
// errorCaptured --当捕获一个来自子孙组件的错误时被调用
/* */
// Has 'el' option?
// --如果在实例化vue时指定el,则该vue将会渲染在el对应的DOM中
// --反之 没有指定el,则vue实例会处于一种“未挂载”的状态,此时通过vm.$mount来手动执行挂载
- 父子组件生命周期关系(created 和 mounted 关系,update 关系,destory 关系)
// 1.挂载阶段 --组件第一次渲染(创建js模型 ==> 渲染)
父beforeCreate ==> 父created ==> 父beforeMount
==> 子beforeCreate==> 子created ==> 子beforeMount ==> 子mounted
==> 父mounted
// 2.更新阶段
父beforeUpdate ==> 子beforeUpdate ==> 子updated ==> 父updated
// 3.卸载阶段
父beforeDestroy ==> 子beforeDestroy ==> 子destroyed ==> 父destroyed
四、vue 组件如何通讯(常见)?
- 父子组件 props 和 this.$emit
- 自定义事件
event.$on 、event.$off 、event.$emit
// - 实例化Vue (vue就可以支持自定义事件)
import Vue from "vue";
export default new Vue();
// - 绑定自定义事件
event.$on("onAddTitle", this.addTitleHandler);
// - 调用自定义事件
event.$emit("onAddTitle", this.title);
// - 及时销毁,否则可能造成内存泄露
event.$off("onAddTitle", this.addTitleHandler);
- vuex
五、描述组件渲染和更新的过程?
- 简化说
- 初次渲染的过程
模板解析成 render 函数, 然后执行 render 生成 vnode 节点(这时候已经实现响应式了),最后挂载到页面空 el 上,第一次渲染完成
// 解析模板为 render 函数–>触发响应式–>执行 render 函数,生成 vnode–>执行 patch(ele, vnode),渲染 DOM - 更新过程
data 变化触发响应式,重新 render 生成 vnode,通过 diff 算法对比新旧 vnode 节点,进行 patch 打补丁,重新更新渲染
// data 变化,触发 setter(在 getter 已被监听的)–>render 函数,生成 newVnode–>执行 patch(vnode, newVnode),更新 dom
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BFWoV1Mc-1600405704198)(./img/update.jpg)]
- 照图说
dep: 主要是用来在数据更新的时候通知 watchers 进行更新;
watcher 接受到通知之后,会通过回调函数进行更新。
- 解析模板为 render 函数 -> 触发响应式(监听 data 属性的 getter 的依赖收集,也即是往 dep 里面添加 watcher 的过程)
-> 执行 render 函数 -> 渲染 DOM - 修改 data,setter(必需是初始渲染已经依赖过的)调用 Dep.notify(),将通知它内部的所有的 Watcher 对象进行视图更新
-> 重新执行 render 函数-> 更新 DOm
六、双向数据绑定 v-model 的实现原理?
- v-model 语法糖
- 如 input 元素 给 value 设置 this.name
- 绑定 input 事件 this.name = $event.target.value
- data 更新触发 re-render
七、MVVM 的理解?
- 一种数据驱动视图思想
- M:Model(数据层) V:View(视图层) ViewModel(好比两者之前的桥梁,做关联)
数据变化会 通过 VM 更新视图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B55Xz3N1-1600405704203)(./img/mvvm.jpg)]
八、computed 有何特点?
- 计算属性,data 里的数据依赖变化会重新计算,如果没变化会读取缓存
- 提高性能
九、为何组件 data 必须是一个函数?
- 因为函数可以构成作用域,避免每个组件的 data 数据共享;
- 如果 data 是个对象,导致所有 vue 的实例(也就是所有组件的实例)可以共享一份数据(一个变了全部变),可以间接改变其他组件的 data(原型继承–指向相同,所有实例都会改变)
十、ajax 请求应该放在那个生命周期?
- 为了保证逻辑统一 一般放在 mounted
- mounted 可以获取 dom 节点操作,created Dom 还没渲染,一般保证逻辑统一性 放 mounted 里
- 当也会有人放在 created 里,无法操作 DOM, 零外 created 也就比 mounted 早调用几微秒而已,性能没啥提高
十一、如何将组件所有 props 传递给子组件?
细节知识点
- $props
<User v-bind="$props"/>
l
十二、如何自己实现 v-model?
// 1.单选复选框事件change事件 checked,输入框input事件 value
// 2.父组件( 父组件 直接加v-model)
<CustomVModel v-model="name"/> //自定义 v-model
// 3.子组件(v-modle语法糖)
// 比如给input, value设置变量 这个值对应(moldel里的props值 还有和props接受的值一致)
// 绑定input事件 让它$emit调用事件,($event.target.value) 这个事件名对应(moldel里对应event值)
<input type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
/>
// v-modle语法糖
export default {
model: {
prop: 'text1', // 对应 props text1
event: 'change1'
},
props: {
text1: String,
default() {
return ''
}
}
}
十三、多个组件有相同逻辑,如何抽离?
- mixin(js 文件 写法和 vue script 里一致)
- mixin 缺点
// 1. 多个mixin可能会造成命名冲突
// 2. 变量来源不明确,不利于阅读
// 3. mixin和组件可能出现多对多的关系,复杂度较高
// 4. vue3.0提出的Composition API(组合)旨在解决这些问题
import myMixin from "./mixin";
export default {
mixins: [myMixin] // 可以添加多个,会自动合并起来
};
十四、何时使用异步组件?
- 有时页面需要加载的内容比较多,卡顿不利于用户体验(类似首屏加载),可以对大组件懒加载(编辑器、图表)
- 路由懒加载(切换路由)
十五、何时使用 keep-alive?
- 需要缓存的组件(不需要再重复渲染)(如:展示的 tab 页面)
- 性能优化
// 1.在动态组件中的应用
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<component :is="currentComponent"></component>
</keep-alive>
// 2.在vue-router中的应用
// 利用meta属性
<keep-alive>
<router-view v-if="this.$route.meat.keepAlive"></router-view> // 这里是会被缓存的组件
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
// 3.include定义缓存白名单(name属性) exclude定义缓存黑名单 max定义缓存组件上限
// 4.钩子 activated deactivated
// 5.keepAlive缓存的组件:
// 第一次进入:
// beforeMount -> mounted -> actived
// 后续进入时:
// deactivated -> activated -> deactivated
// 组件被销毁/离开路由:
// beforeRouteLeave-> 全局beforeEach -> 全局afterEach -> deactived
// 组件销毁时deactived可以替换beforeDestory做一些事
十六、何时使用 beforeDestory?
- 解绑自定义事件 event.$off
- 清除定时器
- 解绑自定义 DOM 事件(addEventListener),如 window scroll 等
十七、什么是插槽?
// 1. 插槽:是vue一种内容分发机制, 可以理解为一个占位符
// 将slot元素作为 承载分发内容的出口(Vue实现的一套内容分发的API)
// 2. 作用域插槽 --插槽的一种(slot元素里定义一个data数据,然后v-slot接受使用)
//定义:
<slot :d="msg" :flag="true">默认值!!!</slot>
<slot :slotData="website">
{{website.subTitle}}
</slot>
const website= {subTitle:'xxxx', title: 'yyy'} // 数据源
// 使用:
<div slot-scope="scope">
{{ scope.d }} {{ scope.flag }}
</div>
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
// 2.普通插槽 (slot元素作为占位符,使用时替换slot元素)
// 定义:
<slot>默认值!!!</slot>
// 使用:
<msg-box>
// 传递过去的内容
<h2>您是否确定删除?</h2>
</msg-box>
// 3.具名插槽 (相对于普通插槽 slot元素设置了name属性,使用时用v-slot标明哪个name属性)
// 定义:
<slot name="title"> </slot>
// 使用:
<div slot="title"> xxx </div>
<template v-slot="title">
xxx
</template>
十八、介绍 vuex?
/* 1. 介绍vuex */
// 是基于vue的一个状态管理工具,主要解决组件间状态共享的问题
/* 2.几个核心概念 */
// 1. store 容器,存储状态的
// 2. state 定义共享的状态变量,vuex store实例的状态对象
// 3. getter 读取器,相当于store的计算属性
// 4. mutation 修改器,用于修改state的数据
// --state的数据只能通过mutations中的方法修改
// 5. action 方法,做异步操作,内部可以整合多个mutations方法
// 6. module 模块化, 可以将state模块化(nameSpaced 命名空间)
/* 3.action和mutation有何区别 */
// 1.action里要处理异步,mutation不可以
// 2.mutation 做修改store数据的操作
// 3.action可以整合多个mutation
//---------------------------------------------------------------
/* 4.为什么vuex mutation 和 redux reducer中不能做异步操作 */
// 异步操作的话,没法知道状态是何时更新(异步回调),调试带来困难,无法很好地进行状态追踪(devtools)
十九、Vue-router 常用的路由模式?
// 1. hash模式(默认)、H5 history模式
// 2. hash特点:
// hash变化会触发网页前进后退
// hash变化不会刷新页面(SPA单页面)
// hash永远不会提交到server端
// 3. H5 history
// 用url规范的路由,但跳转时不刷新页面
// 依赖服务端 返回index.html格式的路由
// 通过设置history.pushState,打开新路由 页面不会刷新
// 通过设置window.onpopstate,回调监听浏览器前进后退
// 4. 两者区别
// 形式上/#/
// 是否依赖服务端配置
// 5. 两者选择
// 看对url规范敏感不,不敏感可以用hash(B系统),C系统可以考虑H5 history
/* H5 history */
// 页面初次加载,获取 path
document.addEventListener("DOMContentLoaded", () => {
console.log("load", location.pathname);
});
// 打开一个新的路由,浏览器不会刷新页面
// 【注意】用 pushState 方式
document.getElementById("btn1").addEventListener("click", () => {
const state = { name: "page1" };
console.log("切换路由到", "page1");
/* history.pushState */
history.pushState(state, "", "page1"); // http://127.0.0.1:8001/page1
});
// 监听浏览器前进、后退
/* window.onpopstate */
window.onpopstate = event => {
console.log("onpopstate", event.state, location.pathname); // location.pathname: /xxx.html
};
二十、如何配置 Vue-router 异步加载?
- import
// 1. 路由懒加载/异步加载
// () => import(/* webpackChunkName: "dashboard" */'@/views/dashboard/index')
// 2. 路由配置细节
import Router from 'vue-router'
export const constantRouterMap = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import(/* webpackChunkName: "dashboard" */'@/views/dashboard/index'),
name: 'Dashboard',
meta: {
title: 'dashboard',
icon: 'dashboard',
}
]
},
]
const createRouter = () => new Router({
// mode: 'history', // require service support
routes: constantRouterMap
})
const router = createRouter()
router.$addRoutes = (params) => { // 解决路由表重复
router.matcher = createRouter().matcher // 重置 router
router.addRoutes(params)
}
export default router
二十一、请用 vnode 描述一个 DOM 结构?
// 1. htmldemo
<div id="div1" class="container">
<p>vdom</p>
<ul style="font-size:20px;">
<li>a</li>
</ul>
</div>;
// 2. vnode模拟DOM(tag/target/sel, props/data, text, children[])
// tag/target/sel、props/data 写法自定义
const vdom = {
tag: "div",
props: {
className: "container",
id: "div1"
},
children: [
{ tag: "p", children: "vdom" },
{
tag: "ul",
props: { style: "font-size: 20px;" },
children: [
{ tag: "li", children: "a" }
// ....
]
}
]
};
二十二、描述下 vue 的响应式?
/* 1. 响应式实现原理 */
// 1.原理是拦截data的getter/setting方法(vue2.——通过Object.defineProperty, vue3.0——通过proxy)
// 2.监听getter方法,data变化会 通知setter方法 然后更新渲染视图
// 发布订阅者模式,getter方法里里订阅,setter方法里发布通知,让所有订阅者完成响应
// https://segmentfault.com/a/1190000023949535
/* 2. vue2.x和vue3.0实现响应式不同点 */
// 1.Proxy能解决Object.defineProperty的问题(原生监听数组、新增、删除属性)
// 2.Proxy无法兼容所有浏览器,无法polyfill(ie11)
// 3.Proxy实现响应式 深度监听性能更好(get获取的时候,深度递归),vue2.x通过一次性深度递归,消耗性能
/* 3. vue监听数组变化 */
// 1. Proxy可以原生支持监听数组的变化(vue3.0)
// 2. Object.defineProperty不能原生监听数组变化, 需要重新定义数组原型 重写push pop...方法 实现监听
// 重新Object.create 一个Array原型, 然后重写这个新Array原型的push,pop方法,最后让监听的数组的原型 指向 新Array原型
const oldArrayProperty = Array.prototype;
const arrProto = Object.create(oldArrayProperty) // Object.create(Array.prototype)
[
// 重写push pop...方法
("push", "pop", "shift", "unshift", "splice")
].forEach(methodName => {
// arrProto.push = function(){ oldArrayProperty.push.call(this, ...arguments)}
arrProto[methodName] = function () {
updateView(); // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments);
// Array.prototype.push.call(this, ...arguments)
};
});
// 判断数组
if (Array.isArray(target)) {
target.__proto__ = arrProto; // 原型指向arrProto
}
/* 4. vue2.x 实现深度监听及监听数组 */
// 触发更新视图
function updateView() {
console.log("视图更新");
}
/* 重新定义数组原型 */
const oldArrayProperty = Array.prototype;
const arrProto = Object.create(oldArrayProperty);
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
["push", "pop", "shift", "unshift", "splice"].forEach(methodName => {
// arrProto.push = function(){ oldArrayProperty.push.call(this, ...arguments)}
arrProto[methodName] = function () {
updateView(); // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments);
// Array.prototype.push.call(this, ...arguments)
};
});
/* 重新定义属性,监听起来 */
function defineReactive(target, key, value) {
/* 深度监听 */
observer(value);
// 数组不走这个方法
Object.defineProperty(target, key, {
get() {
return value;
},
set(newValue) {
if (newValue !== value) {
/* 深度监听 */
observer(newValue); // 防止 data.xxx = {name: 12} ==> data.xxx.name = 122
value = newValue; // 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
updateView(); // 触发更新视图
}
}
});
}
/* 监听对象属性 */
function observer(target) {
// 非对象或数组
if (typeof target !== "object" || target === null) {
return target;
}
// 污染全局的 Array 原型
// Array.prototype.push = function () {
// updateView()
// ...
// }
// 判断数组
if (Array.isArray(target)) {
target.__proto__ = arrProto; // 原型指向arrProto
}
// 重新定义各个属性(for in 也可以遍历数组)
for (let key in target) {
defineReactive(target, key, target[key]);
}
}
// 准备数据
const data = {
name: "zhangsan",
age: 20,
info: {
address: "北京" // 需要深度监听
},
formData: {
lists: [
{
value: 123
}
]
},
nums: [10, 20, 30]
};
// 监听数据
observer(data);
// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
// data.nums.push(4) // 监听数组
/* 5.vue3.0实现深度监听、数组 */
// 创建响应式
function reactive(target = {}) {
if (typeof target !== "object" || target == null) {
// 不是对象或数组,则返回
return target;
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
console.log("get", key); // 监听
}
const result = Reflect.get(target, key, receiver);
// 深度监听
// 性能如何提升的?(通过get获取的时候,深度递归, vue2.x通过一次性深度递归)
return reactive(result);
// return result
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true;
}
const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
console.log("已有-key", key);
} else {
// 新增的属性
console.log("新增-key", key);
}
const result = Reflect.set(target, key, val, receiver);
console.log("set", key, val);
// console.log('result', result) // true
return result; // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
console.log("delete-property", key);
// console.log('result', result) // true
return result; // 是否删除成功
}
};
// 生成代理对象
const observed = new Proxy(target, proxyConf);
return observed;
}
// 测试数据
const data = {
name: "zhangsan",
age: 20,
info: {
city: "beijing",
a: {
b: {
c: {
d: {
e: 100
}
}
}
}
}
};
const list = [10, 11, 12];
const proxyData1 = reactive(data);
const proxyData2 = reactive(list);
proxyData1.info.city;
/* 打印结果 */
// get info
// get city
proxyData1.info.a.add = "嵌套新增属性";
/* 打印结果 */
// get a
// 新增-key add
// set add 嵌套新增属性
proxyData1.add = "新增属性";
/* 打印结果 */
// 新增-key add
// set add 新增属性
proxyData2.shift(); // 删除首位
/* 打印结果 */
// get length
// get 0
// get 1 // 1->0
// 已有-key 0
// set 0 11
// get 2 // 2->1
// 已有-key 1
// set 1 12
// delete-property 2 // delete 2
// 已有-key length
// set length 2
二十三、diff 算法?
/* 1. diff算法时间复杂度 */
// 1.在vue和react中运用到了diff算法,对比新旧vnode
// 2.优化了时间复杂度O(n),在O(n^3)基础上做了调整(只比较同一层级,target不相同直接删掉重建,target相同对比下一级children)
/* 2. diff算法对比vnode过程 */
// 1.对比新旧vnode变化 只比较同一层级,不跨级比较
// 2.如果target不相同,会删掉 重建(不再深度比较)
// 3.如果target相同,会对比下一级children(如果一个有值一个没值,就对应添加和删除)
// 4.根据target和key是否相同,来判断是否是相同节点,不是相同节点 继续深度比较
// -----------------------------------------------------------------------------
// patch(elem,vnode)和patch(vnode,newVnode)
// patch: 对比vnode的变化 更新DOM
// patch的两种用法(把vnode渲染到空的ele上、新的vnode更新旧的)
二十四、Vue 为何是异步渲染,$nextTick 何用?
- vue 异步执行 DOM 更新(异步渲染)可以提高渲染性能,整合 data 修改 一次性渲染
- $nextTick DOM 更新完成之后,立即触发回调
二十五、Vue 常见性能优化方法?
- 合理使用 v-show 和 v-if,频繁切换用 v-show
- 需要组件之间来回切换的一些场景(展示静态展示 tab 数据),使用 keep-alive 取缓存,减少渲染次数
- 定义 data 数据时,层级控制不要太深(除非像 tree 的数据),减少深度比较
- v-for 时加 key 避免取 random index 索引,可以减少渲染次数 避免与 v-if 使用
- DOM 事件、自定义事件 组件销毁时需要解绑
- 页面加载内容多 卡顿影响用户体验,可以进行组件懒加载,提升渲染性能(如:优化首屏加载)
//在首屏渲染、用户交互的过程中,要巧用同步环境及异步环境;首屏展现的内容,尽量保证在同步环境中完成;其他内容,拆分到异步中,从而保证性能、体验。 - 路由里面也进行懒加载
- webpack 层面的优化
// 1. 小图片可以base64编码 babel-loader 开始缓存
module: {
rules: [
// js
{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'], // 开启缓存
include: srcPath,
// exclude: /node_modules/
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 5 * 1024,
// 小于 5kb 的图片用 base64 格式产出,
// 否则,依然延用 file-loader 的形式,产出 url 格式
outputPath: '/img1/',
// >=5kb 打包到配置出口根目录的 img1/
// 打包到 img1 目录下
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
}
]
}
// 2.打包js文件时bundle加hash(提高加载效率,js代码没更新 可以取缓存)
output: {
// filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
path: distPath,
// publicPath: 'http://cdn.abc.com'
// 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
// 3. 抽离css文件时加hash
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
new MiniCssExtractPlugin({
// filename: 'css/main.[contentHash:8].css'
filename: 'css/[name].[contentHash:8].css'
})
// 4. 打包文件过大,分割代码 抽离js文件(splitChunk、 htmlWebpackPlugin引用)
plugins:[
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['chunk-vendors', 'chunk-elementUI', 'index'] // 要考虑代码分割
}),
],
optimization:{
splitChunks: {
/**
* initial 入口chunk,对于异步导入的文件不处理
async 异步chunk,只对异步导入的文件处理
all 全部chunk
*/
chunks: "all",
// 缓存分组
cacheGroups: {
vendors: {
name: "chunk-vendors",
test: /[\\\/]node_modules[\\\/]/,
priority: 10,
chunks: "initial"// 只打包初始时依赖的第三方
},
elementUI: {
name: "chunk-elementUI",// 单独将 elementUI 拆包
priority: 20,// 权重要大于 libs 和 app 不然会被打包进 libs 或者 app
test: /[\\\/]node_modules[\\\/]element-ui[\\\/]/
},
echarts: {
name: "chunk-echarts",// 单独将 elementUI 拆包
priority: 20,// 权重要大于 libs 和 app 不然会被打包进 libs 或者 app
test: /[\\\/]node_modules[\\\/]echarts[\\\/]/
},
commons: {
name: "chunk-comomns",
// test: resolve("src/components"),// 可自定义拓展你的规则
minChunks: 2,// 最小共用次数
priority: 5,
reuseExistingChunk: true
}
}
}
}
// 5. 避免引入无用模块(ingorePlugin moment)
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
// 5. 压缩代码 去console debugger
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
optimization: {
minimizer: [
new TerserJSPlugin({ // js压缩
// cache: true,
parallel: true,
sourceMap: false, // Must be set to true if using source-maps in production
terserOptions: {
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
},
},
}),
new OptimizeCSSAssetsPlugin({})] // css压缩
}
// 6. 使用CDN加速(设置静态资源)
output: { // 设置所有所有静态资源cdn
// filename: 'bundle.[contentHash:8].js',
filename: '[name].[contentHash:8].js',
path: distPath,
publicPath: 'http://cdn.abc.com'
// 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
module: {
rules: [
{ // 设置部分静态资源cdn(png jpg jpeg gif 图片)
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 5 * 1024,
outputPath: '/img1/', // >=5kb 打包到配置出口根目录的 img1/ // 打包到 img1 目录下
publicPath: 'http://cdn.abc.com'
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
}
}
}
]
}
// 合理使用 computed
// 使用 vue-loader 在开发环境做模板预编译(预编译)
// 前端通用的性能优化,如图片懒加载
// 使用 SSR
二十六、vuex 和 redux 区别?
// 1. vuex借鉴了redux,在这基础上进行修改 简化了一些流程,对状态管理更加明确(副作用 基于vue)
// 2. 修改state方式: vuex, 同步修改state 在mutation中修改, 异步修改state,通过action 再整合mutation修改
// 3. redux,无论同步还是异步修改state,都必须通过action 再通知reducer
// 4. vuex自带getter这种衍生属性
// ---------------------------------------------------------------------------------------------------
// 5. vuex是基于vue的,响应式 有自动渲染的功能,所以不需要手动更新,redux subscribe监听store变化,通过store.getState()方法获取最新状态 再触发
二十七、vue 和 react 区别?
// - 共同点
// 1. 都支持组件化
// 2. 都是数据驱动视图
// 3. 都是用vdom操作DOM
// - 不同点
// 1. React使用jsx 拥抱js, Vue使用模板编译 拥抱html
// 2. React函数式编程( setState 传入state 返回jsx), Vue声明式编程(声明变量, data.a data.b 操作)
// 3. vue有很多衍生属性(watch computed 指令),react没有需要自力更生
二十八、vue2.x 和 vue3.0 区别?
// 1. 用ts重写(响应式、vdom、模板编译等)
// 2. 响应式实现方式不一样(Proxy存在浏览器兼容性问题,且不能polyfill)
// 3. 提升性能,打包体积减少
// 4. 新增composition api( 围绕一个新的组件选项setup而创建。setup() 为 Vue 组件提供了状态、计算值 和生命周期钩子)
/*
2-0.生命周期钩子改变了
使用setup() 使用setup() onBeforeMount onMounted onBeforeUpdate onUpdated
onBeforeUnmount onUnmounted
2-1. reactive 生命 创建响应式对象,非包装对象 --没有包装属性value(类似vue2.x data中声明的变量)
<h1>{{title.name}}</h1>
import { reactive } from 'vue'
setup(){
const title = reactive({
name:'xxxxxx
})
return { title }
}
2-2. ref 声明 创建一个包装式对象 --含有一个响应式属性value
<h1>{{title.name}}</h1>
<h1>{{user}}</h1>
import { reactive, ref } from 'vue'
setup(){
const title = reactive({
name:'xxxxxx
})
const user = ref('yyyy')
user.value = 'YYYYY' // 修改值 通过value
return { title, user }
}
2-3. computed变化 生命周期钩子onMounted
<div>当前count:{{ computedCount }}</div>
<button @click="increment">修改count</button>
import { reactive, ref, computed } from 'vue'
setup(){
const count = ref(0)
const increment = ()=>{
count.value ++
}
// 生命周期
onMounted(()=>{
console.log('mounted')
})
const computedCount = computed(()=> count.value*10 )
return { count, increment, computedCount }
}
// ----------------------------------------------------------------------
2-4. 事件处理
*/
二十九、vue3.0 composition api 与 reatct hooks 区别?
// 1. Vue Composition API:围绕一个新的组件选项setup而创建。setup() 为 Vue 组件提供了状态、计算值 和生命周期钩子
// 2. react hooks 纯函数组件、无副作用( hooks加入了状态,useEffect里执行副作用代码)
// 3. 代码的执行
// vue setup() 在created 钩子之前被调用(beforeCreate之后)
// Vue setup() 只在组件创建时 调用
// React hooks 组件渲染时候就会 调用(render方法必须遵守一些规则)
// hooks只能出现在函数作用域的顶级,不能出现在条件语句、循环语句中、嵌套函数中。
// ------------------------------------------------------------------------------------
// 4. 声明状态
// react用useState 声明状态
// vue 用 reactive ref声明状态