金融网站模版小游戏网页版
frontend
Vue2
学习内容参考 /在线运行
Element
学习内容参考 /视频教学
vue2
1. vue 实例
-  
当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中
 -  
但是当使用Object.freeze(),会阻止修改现有的 property,也意味着响应系统无法再追踪变化。
 
var obj = {foo: 'bar'
}
Object.freeze(obj)
new Vue({el: '#app',data: obj
}) 
 
-  
不要在选项 property 或回调上使用箭头函数,因为箭头函数并没有 this,this 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。
 
2. 模板语法
-  
render: 如果熟悉虚拟 DOM 并且偏爱 JavaScript 的原始力量,也可以不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。
 -  
在站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。因为如果用户输入内容包含恶意脚本,这些脚本会被执行,并且直接解析并渲染 HTML 内容。而{{ }}会被转义为纯文本,会更安全。
 -  
js模板表达式 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。不应该在模板表达式中试图访问用户定义的全局变量。
 
3. v-指令
-  
指令支持动态参数
 -  
不推荐同时使用 v-if 和 v-for。因为当它们一起使用时,v-if 会优先于 v-for 被处理,这可能会导致一些意外的行为和性能问题。可以使用计算属性进行过滤, 也可以将 v-if 置于外层元素
 -  
v-for 在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。如果你需要确保属性的遍历顺序是一致的, 可以使用map
 -  
替换数组: filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组
 -  
当ul标签使用v-for, 使用is渲染组件, is="todo-item" attribute。因为在 <ul> 元素内只有 <li> 元素会被看作有效内容。这样做实现的效果与 <todo-item> 相同,但是可以避开一些潜在的浏览器解析错误。这是因为这些结构有严格的子元素要求。例如,<ul> 元素只能包含 <li> 元素作为其直接子元素。查看 DOM 模板解析说明 来了解更多信息。
 -  
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
 
4. 组件
-  
组件是可复用的 Vue 实例,所以它们与
new Vue接收相同的选项 -  
,一个组件的
data选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝,否则可能影响到其他实例 -  
.prop- 作为一个 DOM property 绑定而不是作为 attribute 绑定。(差别在哪里?) -  
is渲染组件,使用.vue,template 是不会有限制的
 -  
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
但是重申一次,如果你使用字符串模板,那么这个限制就不存在了。
 -  
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
场景: 网络请求res.data中的属性可能会被提示未定义
props: ['initialCounter'], data: function () {return {counter: this.initialCounter} } -  
prop 验证
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如
data、computed等) 在default或validator函数中是不可用的 -  
组件可以接受任意的 attribute,从外部提供给组件的值会替换掉组件内部设置好的值。
 -  
class和styleattribute 会稍微智能一些,即两边的值会被合并起来,从而得到最终的值:form-control date-picker-theme-dark。 -  
若想要在一个组件的根元素上直接监听一个原生事件, Vue 提供了一个
$listenersproperty,包含了作用在这个组件上的所有监听器Vue.component('base-input', {inheritAttrs: false,props: ['label', 'value'],computed: {inputListeners: function () {var vm = this// `Object.assign` 将所有的对象合并为一个新对象return Object.assign({},// 我们从父级添加所有的监听器this.$listeners,// 然后我们添加自定义监听器,// 或覆写一些监听器的行为{// 这里确保组件配合 `v-model` 的工作input: function (event) {vm.$emit('input', event.target.value)}})}},template: `<label>{{ label }}<inputv-bind="$attrs"v-bind:value="value"v-on="inputListeners"></label>` }) -  
.sync修饰符 处理组件之间的双向数据流 但vue3已移除 -  
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
 -  
具名插槽 #
 -  
在每个
new Vue实例的子组件中,其根实例可以通过$rootproperty 进行访问。// 获取根组件的数据 this.$root.foo
但是中大型应用就要使用vuex了
 -  
类似的,
$parentproperty 可以用来从一个子组件访问父组件的实例但是会失控,所以推荐依赖注入
 -  
目录树组件循环引用,可以使用webpack 的异步 import
 
5. 过渡
-  
vue在插入,更新或者移除DOM时,提供多种不同方式的应用过渡效果,包括以下工具
-  
css 过渡和动画
 -  
第三方css动画库 Animate.css
 -  
js钩子函数
 -  
第三方js动画库,如velocity.js
 
 -  
 
5.1 单元素/组件的过渡
-  
vue提供了transition的分装组件,为元素添加过度
<div id="demo"><button v-on:click="show = !show">Toggle</button><transition name="fade"><p v-if="show">hello</p></transition> </div>  <style>//元素从进入到离开 过程.fade-enter-active, .fade-leave-active{transition:opacity .5s;}//元素在刚开始进入和离开时 动作.fade-enter, .fade-leace-to{opacity: 0} </style>  
5.1.1 过渡的类名
在进入/离开的过渡中,会有 6 个 class 切换。
-  
v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。 -  
v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。 -  
v-enter-to:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter被移除),在过渡/动画完成之后移除。 -  
v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。 -  
v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。 -  
v-leave-to:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave被删除),在过渡/动画完成之后移 
5.1.2 自定义过度的类名
我们可以通过以下 attribute 来自定义过渡类名:
-  
enter-class -  
enter-active-class -  
enter-to-class(2.1.8+) -  
leave-class -  
leave-active-class -  
leave-to-class(2.1.8+) 
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">  <div id="example-3"><button @click="show = !show">Toggle render</button><transitionname="custom-classes-transition"enter-active-class="animated tada"leave-active-class="animated bounceOutRight"><p v-if="show">hello</p></transition> </div>
5.2 初始渲染的过度
<transition appear><!-- ... --> </transition>
5.2.1自定义css类名
<transitionappearappear-class="custom-appear-class"appear-to-class="custom-appear-to-class" (2.1.8+)appear-active-class="custom-appear-active-class" ><!-- ... --> </transition>
5.2.2 自定义js钩子
<transitionappearv-on:before-appear="customBeforeAppearHook"v-on:appear="customAppearHook"v-on:after-appear="customAfterAppearHook"v-on:appear-cancelled="customAppearCancelledHook" ><!-- ... --> </transition>
5.3 多个元素的过度
<transition><table v-if="items.length > 0"><!-- ... --></table><p v-else>Sorry, no items found.</p> </transition>
5.3.1 过渡模式
-  
in-out:新元素先进行过渡,完成之后当前元素过渡离开。 -  
out-in:当前元素先进行过渡,完成之后新元素过渡进入。(常用)<transition name="fade" mode="out-in"><!-- ... the buttons ... --> </transition>
 
5.4 多个组件的过度
-  
使用动态组件
<transition name="component-fade" mode="out-in"><component v-bind:is="view"></component> </transition>
 
5.5 列表过度
-  
使用transition-group
<div id="list-demo" class="demo"><button v-on:click="add">Add</button><button v-on:click="remove">Remove</button><transition-group name="list" tag="p"><span v-for="item in items" v-bind:key="item" class="list-item">{{ item }}</span></transition-group> </div> 
6. 可复用性&组合
6.1 JSX
7. 测试
-  
Vue 组件通常包含
.vue文件,里面包含模板、脚本和样式。这种单文件组件(SFC)格式并不是原生 JavaScript 或 TypeScript 能直接识别的。因此,vue-jest使用 Babel 来将.vue文件转换为可被 Jest 识别的格式。 -  
Jest 本身依赖 Babel 来处理 Vue 单文件组件及 ES 模块。
babel-jest可以帮你将非标准的 J 
7.1 步骤
-  
vue2
 
7.1.1 ts
-  
安装jest
npm install jest --save-dev
 -  
配置jest
在根目录下创建jest.config.js
module.exports = {moduleFileExtensions: ['js', 'vue'],moduleNameMapper: {'^@/(.*)$': '<rootDir>/src/$1'},transform: {'^.+\.js$': 'babel-jest','.*\.vue$': 'vue-jest'},snapshotSerializers: ['jest-serializer-vue'],// 在Vue单元测试中使用了v-model, 需要添加一下配置,请参考官方文档.// https://vue-test-utils.vuejs.org/guides/common-tips.html#mocking-components-that-use-v-model// setupFiles: ['<rootDir>/tests/unit/setup.js'],testMatch: ['<rootDir>/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'],testURL: 'http://localhost/' }-  
moduleFileExtensions 中包含了Vue文件,这是允许Vue文件被编译并测试的必要配置。
 -  
moduleNameMapper 是用来解析@符号的。
 -  
transform 配置了如何转换代码,vue-jest 转换.vue文件,babel-jest 转换.js文件。
 -  
snapshotSerializers 在snapshot test中使用,使快照测试更加容易。
 -  
testMatch 配置了测试文件的匹配规则。
 -  
testURL 配置了测试时浏览器的URL。
 
 -  
 -  
编写测试用例
import { shallowMount } from '@vue/test-utils' import HelloWorld from '@/components/HelloWorld.vue'describe('HelloWorld.vue', () => {it('renders props.msg when passed', () => {const msg = 'new message'const wrapper = shallowMount(HelloWorld, {propsData: { msg }})expect(wrapper.text()).toMatch(msg)}) }) -  
npm run jest 运行
 
7.1.2 babel
-  
安装依赖
npm install --save-dev jest @vue/test-utils babel-jest vue-jest
 
-  
配置Jest
-  
在项目根目录添加jest.config.js
module.exports = {preset: '@vue/cli-plugin-unit-jest',transform: {'^.+\\.vue$': 'vue-jest','^.+\\.js$': 'babel-jest',},moduleFileExtensions: ['js', 'vue'],testMatch: ['**/tests/**/*.spec.js'], }; 
 -  
 
* 确保Jest能够识别和处理Vue组件以及js文件
-  
创建测试文件
-  
放在test文件夹下,以.spec.js结尾
//shallowMount 用于浅层挂载一个 Vue 组件,它只渲染组件的最外层,不会递归渲染子组件,适用于组件依赖的其他子组件较复杂时进行测试。 import {shallowMount} from '@vue/test-utils'; import HelloWorld from '@/components/HelloWorld.vue'; //describe 用于组织和分组相关的测试,括号中的 'HelloWorld.vue' 作为测试套件的名称,指明我们要测试的目标是 HelloWorld.vue 组件。 describe('HelloWorld.vue', () => {//it 函数用于定义一个测试用例,括号中的 'renders props.msg when passed' 是对测试的简短描述,意思是“当传递 msg 属性时,组件应该正确渲染出来”。it('renders props.msg when passed', () => {//我们将在测试中传递msg作为组件的 props,以测试它是否正确渲染出来。 const msg = 'Hello Jest';//shallowMount 用于浅层挂载 HelloWorld 组件。 //wrapper 是一个包装器对象,它包含了挂载后的组件实例,并提供了访问和操作组件的方法,比如获取 DOM 元素、触发事件等。 const wrapper = shallowMount(HelloWorld, {propsData: {msg}});//这一行是断言(assertion),它使用 Jest 的 expect 函数来验证测试结果。//wrapper.text() 获取组件的文本内容(包括组件的所有子元素)。//toMatch 用于检查文本是否包含指定的字符串 msg,即 'Hello Jest'。这个断言期望组件渲染的文本内容中包含传递的 msg。 expect(wrapper.text()).toMatch(msg);}); }); 
 -  
 
7.2 报错
-  
由于未知原因,我的js项目竟然必须安装@types/jest,才可以正确引入jest,实在不知道为什么,先贴出来吧
 -  
yarn add --dev @types/jest
 
8. 规模化
8.1 路由
8.2 状态管理
-  
Redux 事实上无法感知视图层,所以它能够轻松的通过一些简单绑定和 Vue 一起使用
 
8.3 服务端渲染
9. 安全
-  
用户提供的 URL 永远需要通过后端在入库之前进行过滤.需要对该 URL 进行“过滤”以防止通过
javascript:来执行 JavaScript -  
Vue 要在模板内避免渲染 style 标签,避免用户伪造样式,推荐在style属性中使用对象语法,提供特定property值
 -  
向后端提供 CSRF token 为每个表单生成一个唯一的随机令牌,并要求客户端在提交表单时也附带这个令牌。
 
10. axios
封装步骤
-  
设置接口请求前缀
利用
node环境变量来作判断,用来区分开发、测试、生产环境if (process.env.NODE_ENV === 'development') {axios.defaults.baseURL = 'http://dev.xxx.com' } else if (process.env.NODE_ENV === 'production') {axios.defaults.baseURL = 'http://prod.xxx.com' } -  
跨域
在本地调试的时候,还需要在
vue.config.js文件中配置devServer实现代理转发,从而实现跨域devServer: {proxy: {'/proxyApi': {target: 'http://dev.xxx.com',changeOrigin: true,pathRewrite: {'/proxyApi': ''}}}} -  
设置请求头与超时时间
-  
大部分情况下,请求头都是固定的,只有少部分情况下,会需要一些特殊的请求头,这里将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
const service = axios.create({...timeout: 30000, // 请求 30s 超时headers: {get: {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'// 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来},post: {'Content-Type': 'application/json;charset=utf-8'// 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来}}, }) 
 -  
 -  
封装请求方法
先引入封装好的方法,在要调用的接口重新封装成一个方法暴露出去
// get 请求 export function httpGet({url,params = {} }) {return new Promise((resolve, reject) => {axios.get(url, {params}).then((res) => {resolve(res.data)}).catch(err => {reject(err)})}) }// post // post请求 export function httpPost({url,data = {},params = {} }) {return new Promise((resolve, reject) => {axios({url,method: 'post',transformRequest: [function (data) {let ret = ''for (let it in data) {ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'}return ret}],// 发送的数据data,// url参数params}).then(res => {resolve(res.data)})}) } -  
把封装的方法放在一个
api.js文件中import { httpGet, httpPost } from './http' export const getorglist = (params = {}) => httpGet({ url: 'apps/api/org/list', params })页面中就能直接调用
// .vue import { getorglist } from '@/assets/js/api'getorglist({ id: 200 }).then(res => {console.log(res) })这样可以把
api统一管理起来,以后维护修改只需要在api.js文件操作即可 -  
请求拦截器
 
请求拦截器可以在每个请求里加上token,做了统一处理后维护起来也方便
// 请求拦截器
axios.interceptors.request.use(config => {// 每次发送请求之前判断是否存在token// 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况,此处token一般是用户完成登录后储存到localstorage里的token && (config.headers.Authorization = token)return config},error => {return Promise.error(error)}) 
-  
响应拦截器
 
响应拦截器可以在接收到响应后先做一层操作,如根据状态码判断登录状态、授权
// 响应拦截器
axios.interceptors.response.use(response => {// 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据// 否则的话抛出错误if (response.status === 200) {if (response.data.code === 511) {// 未授权调取授权接口} else if (response.data.code === 510) {// 未登录跳转登录页} else {return Promise.resolve(response)}} else {return Promise.reject(response)}
}, error => {// 我们可以在这里对异常状态作统一处理if (error.response.status) {// 处理请求失败的情况// 对不同返回码对相应处理return Promise.reject(error.response)}
}) 
深入响应式原理 vue最独特的特性之一
-  
Vue 遍历data中数据对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更
这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
不能检测数组和对象的变化,,而Vue3中的proxy代理解决了这个问题
 -  
每个组件实例都对应一个 watcher
 -  
windows事件直接赋给变量,不是响应式,需要在onMounted事件中进行监听
 
面试题
1. 首屏加载
-  
首屏加载是用户体验中最重要的缓解
 
常见spa首屏优化方案
-  
减小入口文件体积
路由懒加载
 -  
静态资源本地缓存
后端返回资源问题:
-  
采用
HTTP缓存,设置Cache-Control,Last-Modified,Etag等响应头 -  
采用
Service Worker离线缓存 
前端合理利用
localStorage -  
 -  
ui框架按需加载
-  
element 按需加载
 
 -  
 -  
组件重复打包
假设
A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载解决方案:在
webpack的config文件中,修改CommonsChunkPlugin的配置minChunks: 3
minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件 -  
图片资源压缩
对于所有的图片资源,我们可以进行适当的压缩
 -  
开启GZip压缩
 -  
使用SSR
SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器
从头搭建一个服务端渲染是很复杂的,
vue应用建议使用Nuxt.js实现服务端渲染 

2. 组件通讯
小型-Vue.observable
步骤
-  
创建一个js文件
// 引入vue import Vue from 'vue // 创建state对象,使用observable让state对象可响应 export let state = Vue.observable({name: '张三','age': 38 }) // 创建对应的方法 export let mutations = {changeName(name) {state.name = name},setAge(age) {state.age = age} } -  
在.vue文件中直接使用即可
<template><div>姓名:{{ name }}年龄:{{ age }}<button @click="changeName('李四')">改变姓名</button><button @click="setAge(18)">改变年龄</button></div> </template> import { state, mutations } from '@/store export default {// 在计算属性中拿到值computed: {name() {return state.name},age() {return state.age}},// 调用mutations里面的方法,更新数据methods: {changeName: mutations.changeName,setAge: mutations.setAge} } 
3. Promise
-  
promise 异步编程解决方案
 
3.1 以往
-  
多层异步操作
 
doSomething(function(result) {doSomethingElse(result, function(newResult) {doThirdThing(newResult, function(finalResult) {console.log('得到最终结果: ' + finalResult);}, failureCallback);}, failureCallback);
}, failureCallback); 
-  
典型的回调地狱(callback hell)示例。在这种情况下,多个异步操作依赖于前一个操作的结果,导致代码的嵌套层级变得很深,从而降低了可读性和可维护性
 
3.2 使用promise
doSomething().then(function(result) {return doSomethingElse(result);
})
.then(function(newResult) {return doThirdThing(newResult);
})
.then(function(finalResult) {console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback); 
-  
链式操作减低了编码难度
 -  
代码可读性明显增强
 
3.3 promise
3.3.1 状态
promise对象仅有三种状态
-  
pending(进行中) -  
fulfilled(已成功) -  
rejected(已失败) 
3.3.2 特点
-  
对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
 -  
一旦状态改变(从
pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果 
3.3.3 流程
3.3.4 用法
-  
promise为构造函数
 
const promise = new Promise(function(resolve, reject) {}); 
-  
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject-  
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功” -  
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败” 
 -  
 
3.3.5 实例方法
promise .then() .catch() .finally()
-  
Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止 -  
Promise对象抛出的错误不会传递到外层代码,即不会有任何反应
 -  
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作 
3.3.6 使用方法
-  
Promise对象抛出的错误不会传递到外层代码,即不会有任何反应 
const someAsyncThing = function() {return new Promise(function(resolve, reject) {// 下面一行会报错,因为x没有声明resolve(x + 2);});
}; 
-  
通过
all()实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading即可 
3.3.7 案例
-  
异步插入500w条数据
 
const {v4: uuidv4} = require("uuid");
const User = require('./models/user')
const logger = require('../logger/logger')
const getRequestData = require('./data-generator');/*** 异步插入500w条数据*/(async function ormInsert() {try {await User.sync(); // 确保表结构已创建const batchPromises = [];for (let i = 0; i < 10; i++) {batchPromises.push((async () => {const dataList = getRequestData(25);await User.bulkCreate(dataList, { ignoreDuplicates: true });})());}await Promise.all(batchPromises);} catch (error) {logger.error('Error inserting data:', error);}
})() 
-  
通过
race可以设置图片请求超时面试官:你是怎么理解ES6中 Promise的?使用场景? | web前端面试 - 面试官系列
 
3.4 async/await与promise关系
-  
async/await和Promise是 JavaScript 中用于处理异步操作的两种相关机制。 -  
async函数始终返回一个Promise,而在async函数内部,可以使用await关键字来等待一个Promise完成。 -  
async/await是对Promise的语法糖,它使得处理异步操作的代码看起来更像是同步代码 -  
使用
await关键字可以避免嵌套的回调函数,使代码结构更清晰。 
// 使用 Promise
fetchData().then(data => process(data)).then(result => display(result)).catch(error => handleError(error));// 使用 async/await
async function main() {try {const data = await fetchData();const result = await process(data);display(result);} catch (error) {handleError(error);}
}
 
 
4. axios源码
4.1 简易版axois
4.1.1 axios({})
-  
构建一个axios构造函数,核心代码为request
 
class Axios {constructor() {}request(config) {return new Promise(resolve => {const {url = '', method = 'get', data = {}} = config;// 发送ajax请求const xhr = new XMLHttpRequest();// XMLHttpRequest的方法,onload为异步,所以设置在send发出之前异步接收成功的结果xhr.open(method, url, true);xhr.onload = function() {console.log(xhr.responseText)resolve(xhr.responseText);}xhr.send(data);})}
} 
-  
导出axios实例
 
//最终导出的axios方法,即实例的request方法
function CreateAxiosFn(){
let axios = new Axios();
//使用bind()方法创建一个新的函数,这个函数是axios.request的绑定版本,这样做的目的是确保在调用req时,this上下文是中指向axios实例
return axios.request.bind(axios)
} 
4.1.2 axios.method()
//定义包含常见HTTP方法的数组get,post...,挂载到Axios原型上
const methodsArr = ['get','delete','head','options','put','patch','post'];
//遍历方法数组
methodArr.forEach(met => {Axios.prototype[met] = function() {console.log('执行'+met+'方法')if(['get', 'delete', 'head', 'options'].includes(met)){return this.request({method:met;url:arguments[0],...arguments[1] || {}                })}else{//post、put、patch 等方法return this.request({method:met,url:arguments[0],data:arguments[1] || {}...arguments[2] || {}})}}
}) 
 
4.1.3 this指向
-  
肥肠关键
//最终导出的axios方法,即实例的request方法 function CreateAxiosFn(){ let axios = new Axios(); //使用bind()方法创建一个新的函数,这个函数是axios.request的绑定版本,这样做的目的是确保在调用req时,this上下文是中指向axios实例 return axios.request.bind(axios) } -  
then
将Axios.prototype 上的方法搬运到request上
创建工具
const utils = { // 定义一个包含工具方法的对象extend(a, b, context) { // 定义 extend 方法,接收目标对象 a、源对象 b 和上下文对象 contextfor (let key in b) { // 遍历源对象 b 的所有可枚举属性if (b.hasOwnProperty(key)) { // 确保属性是源对象 b 自身的属性if (typeof b[key] === 'function') { // 判断属性值是否是函数a[key] = b[key].bind(context); // 将函数绑定到指定上下文 context,并赋值给目标对象 a} else { // 如果属性不是函数a[key] = b[key]; // 直接将属性值复制到目标对象 a}}}} }; -  
修改导出的方法
function CreateAxiosFn() {let axios = new Axios();let req = axios.request.bind(axios);// 增加代码utils.extend(req, Axios.prototype, axios)return req; } 
4.1.4 构造拦截器
1. 构造函数
class InterceptorsManage { // 定义一个名为 InterceptorsManage 的类constructor() { // 构造函数,用于初始化类的实例this.handlers = []; // 初始化一个空数组,用于存储拦截器处理函数}use(fullfield, rejected) { // 定义 use 方法,用于添加新的拦截器this.handlers.push({ // 将新的拦截器对象添加到 handlers 数组中fullfield, // 添加成功处理函数rejected // 添加失败处理函数});}
}
 
-  
通过使用数组,Axios 能够支持添加多个请求拦截器。这意味着用户可以定义多个函数,每个函数可以在请求发送之前处理请求配置。这样设计可以使得不同的拦截器可以分别处理不同的逻辑,例如:
-  
一个拦截器用于添加授权信息。
 -  
另一个拦截器用于记录请求日志。
 -  
还有一个拦截器可以用于请求参数的序列化。
 
 -  
 
2. 实现axios.interceptors.response.use和axios.interceptors.request.use
//会在构造函数事,interceptor时初始化一个构造函数
class Axios {constructor() {// 新增代码this.interceptors = {request: new InterceptorsManage,response: new InterceptorsManage}}request(config) {...}
} 
4.1.5 搬到request
function CreateAxiosFn() { // 定义一个名为 CreateAxiosFn 的函数let axios = new Axios(); // 创建一个 Axios 类的实例并赋值给 axioslet req = axios.request.bind(axios); // 绑定 axios 的 request 方法到 req,确保 this 指向 axios 实例// 混入方法,处理 axios 的 request 方法,使之拥有 get、post 等方法utils.extend(req, Axios.prototype, axios); // 将 Axios 原型上的方法混入 req,确保可以通过 req 调用这些方法// 新增代码utils.extend(req, axios); // 将 axios 实例的方法和属性混入 req,确保 req 也能访问 axios 实例的属性return req; // 返回混合后的 req,作为最终的 axios 方法
}
 
4.1.6 拦截器执行顺序
-  
首先将执行
ajax的请求封装成一个方法 
request(config) {this.sendAjax(config)
}
sendAjax(config){return new Promise(resolve => {const {url = '', method = 'get', data = {}} = config;// 发送ajax请求console.log(config);const xhr = new XMLHttpRequest();xhr.open(method, url, true);xhr.onload = function() {console.log(xhr.responseText)resolve(xhr.responseText);};xhr.send(data);})
} 
-  
获得
handlers中的回调request(config) {// 拦截器和请求组装队列let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理// 请求拦截this.interceptors.request.handlers.forEach(interceptor => {chain.unshift(interceptor.fullfield, interceptor.rejected)})// 响应拦截this.interceptors.response.handlers.forEach(interceptor => {chain.push(interceptor.fullfield, interceptor.rejected)})// 执行队列,每次执行一对,并给promise赋最新的值let promise = Promise.resolve(config);while(chain.length > 0) {promise = promise.then(chain.shift(), chain.shift())}return promise; } 
4.2 源码分析
4.2.1 目录

面试官:你了解axios的原理吗?有看过它的源码吗? | web前端面试 - 面试官系列
5. 权限管理
有空改一下class-select
6. 前端解决跨域
6.1 同源策略
-  
协议相同(protocol)
 -  
主机相同(host)
 -  
端口相同(port)
 
6.2 解决方式
6.2.1 JSONP
-  
vue中不主要
 
6.2.2 CORS
-  
CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应
 -  
CORS实现起来非常方便,只需要增加一些HTTP头,让服务器能声明允许的访问来源只要后端实现了
CORS,就实现了跨域 -  
前端在 HTTP 头配置 CORS 是无效的,CORS(跨域资源共享,Cross-Origin Resource Sharing)的机制主要依赖于服务器端的配置。浏览器基于同源策略,会自动检查服务器是否允许跨域请求。因此,如果后端没有正确配置 CORS,前端自己设置 HTTP 头并不能绕过浏览器的跨域限制。
 -  
具体来说,以下是一些关键的响应头,必须由后端设置:
-  
Access-Control-Allow-Origin:指定允许访问的域名,或者使用*表示允许所有域名。 -  
Access-Control-Allow-Methods:指定允许的 HTTP 方法(如 GET、POST、PUT、DELETE 等)。 -  
Access-Control-Allow-Headers:指定允许的自定义请求头。 -  
Access-Control-Allow-Credentials:如果需要发送凭证(如 Cookies),还需要设置此项为true。 
 -  
 
6.2.3 Proxy
-  
开发环境可以,生产环境不行
 -  
无论如何,需要后端配置
 
7. history模式下有问题404
this
从头创建vue2 项目
1. 步骤
-  
安装vue cli
npm install -g @vue/cli
 -  
创建新项目
vue create yhs-web
 -  
选择特性
 
2. 网络监听
构建工具
1. vue-cli
2. webpack
-  
当你使用命令
vue create my-project创建一个 Vue 项目时,Vue CLI 会自动生成一个项目结构,并且会包含默认的 Webpack 配置。这些配置用于开发、构建、打包应用。 
2.1 配置
-  
Vue CLI 内部集成了 Webpack 并提供了预配置好的 Webpack 配置。通常在 Vue CLI 项目中,你不需要手动修改 Webpack 配置,Vue CLI 会在开发和生产模式下自动处理以下内容:
-  
处理单文件组件(
.vue文件)。 -  
处理 JavaScript、CSS、图像等静态资源的打包。
 -  
提供热更新(Hot Module Replacement, HMR)功能来提高开发体验。
 -  
通过
webpack-dev-server提供本地开发服务器。 
 -  
 -  
当你在开发环境下运行
npm run serve或yarn serve,Vue CLI 会:-  
调用 Webpack 配置,启动
webpack-dev-server。 -  
Webpack 负责编译、打包代码,并在本地服务器上提供应用。
 
当你在生产环境下运行
npm run build或yarn build时,Vue CLI 会:-  
调用 Webpack,进行代码的优化和压缩。
 -  
将最终的应用程序打包输出到
dist/目录中。 
 -  
 -  
可选配置
虽然 Vue CLI 提供了默认的 Webpack 配置,但它也允许开发者通过
vue.config.js文件自定义 Webpack 配置。你可以在vue.config.js文件中对 Webpack 进行扩展、修改,比如:-  
修改别名(alias)
 -  
添加插件
 -  
更改打包规则(loaders)
 
 -  
 
2.2 案例
-  
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({devServer: {host: '0.0.0.0', // 绑定到所有网络接口port: 8081, // 设置端口号open: false, // 自动打开浏览器},transpileDependencies: true })-  
使用了
defineConfig,用于指定 Vue CLI 的配置。 -  
devServer的配置:-  
host: '0.0.0.0':将开发服务器绑定到所有网络接口,意味着可以从本地局域网中的其他设备访问该服务器。 -  
port: 8081:将开发服务器的端口设置为8081(默认是8080)。 -  
open: false:禁用了自动打开浏览器的功能。 
 -  
 
 -  
 -  
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({lintOnSave: false,transpileDependencies: true// devServer:{// //devServe在发送请求时,会先走道before指定的函数中进行处理,如果before中没有对应的移动路由,才会请求外网// setupMiddlewares:require('./src/mock/index')// } })-  
禁用了
lintOnSave(保存时的 ESLint 检查) -  
transpileDependencies配置为了确保通过 Babel 转译依赖项以提高兼容性。 -  
注释掉了开发服务器的中间件配置,可能用于本地模拟数据。
-  
setupMiddlewares指定了中间件处理函数,路径指向./src/mock/index,这表明项目可能使用了一个本地的模拟数据服务器,用来处理请求。如果注释去掉,它将通过自定义的中间件处理请求(如使用 mock 数据),只有当该中间件无法处理请求时,才会请求外部 API。 
 -  
 
 -  
 
2.3 正确示例
-  
Webpack 需要通过
entry、output、rules和plugins来进行大量配置,尤其在处理不同类型的资源(如 JS、CSS、图片等)时需要加载不同的loader。 
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {entry: './src/index.js',output: {filename: 'bundle.js',path: path.resolve(__dirname, 'dist'),},module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: 'babel-loader',},{test: /\.css$/,use: ['style-loader', 'css-loader'],},],},plugins: [new HtmlWebpackPlugin({template: './src/index.html',}),],devServer: {contentBase: './dist',hot: true,},
};
 
 
3. vite
3.1 与webpack区别
3.1.1 webpack
Webpack 是一个基于打包(bundling)的工具,主要通过以下步骤工作:
-  
静态分析依赖关系:Webpack 从入口文件(如
src/index.js)开始,递归地构建依赖图,将项目中所有的模块(包括 JS、CSS、图片等)都打包成一个或多个文件。 -  
打包和优化:Webpack 会将所有依赖打包成少量的文件(通常是
bundle.js),并应用一些优化策略(如代码分割、压缩、Tree Shaking 等)。 -  
开发模式和热更新:Webpack 开发时会启动
webpack-dev-server,它会将代码加载到内存中,并支持热模块替换(HMR),当源代码发生变化时,只更新变化的部分模块,而不是刷新整个页面 
3.1.2 vite
Vite 的主要目标是提高开发效率,特别是在大型项目中。它采用了一种与 Webpack 不同的工作方式:
-  
即时启动(Instant Server Start):Vite 使用原生 ES 模块(ESM)支持的浏览器来直接加载 JavaScript 文件,而不是像 Webpack 一样打包所有文件。它会根据需要按需加载模块,而不是一次性打包整个项目。
 -  
模块按需编译:在开发时,Vite 只编译正在使用的模块,而不是打包整个项目。这大大减少了启动时间和内存占用。
 -  
生产模式下打包:在生产环境中,Vite 仍然使用 Rollup 进行打包(Rollup 和 Webpack 类似,是另一种打包工具),生成优化后的静态资源文件。
 
3.2 案例
案例1
import path from "path"
import { fileURLToPath, URL } from "url"import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import { viteSingleFile } from "vite-plugin-singlefile"
// 构建配置会根据process.env.LTB来决定是否构建为库
//LIB值定义在语句build-lib,运行该命令设置lib值自动打包为库
const build = process.env.LIB? {lib: {entry: path.resolve(__dirname, "src/components/index.ts"),name: "pev2",fileName: "pev2",},rollupOptions: {external: ["vue"],output: {// Provide global variables to use in the UMD build// Add external deps hereglobals: {vue: "Vue",},},},}: {outDir: "dist-app",target: "esnext",assetsInlineLimit: 100000000,chunkSizeWarningLimit: 100000000,cssCodeSplit: false,brotliSize: false,rollupOptions: {output: {inlineDynamicImports: true,},},}// https://vitejs.dev/config/
export default defineConfig({build: build,plugins: [
//使用了 vue 插件,用于支持 Vue 单文件组件。vue({template: {compilerOptions: {whitespace: "preserve",},},}),
//使用 viteSingleFile 插件,将所有资源内联到一个单文件中。viteSingleFile(),],
//路径别名resolve: {alias: {"@": fileURLToPath(new URL("./src", import.meta.url)),},},define: {
//定义了 __APP_VERSION__ 来读取 package.json 中的版本号__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
//定义了 process.env.NODE_ENV 以便根据环境切换配置。"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),},
})
 
-  
根据LIB值决定构建模式
-  
当
process.env.LIB为true时:构建为库lib:-  
entry: 指定库的入口文件为src/components/index.ts,这是库的主要导出文件。 -  
name: 定义库的名称为pev2,这个名称会在 UMD(Universal Module Definition)构建中使用。 -  
fileName: 定义输出文件的名称为pev2。 
rollupOptions:-  
external: 指定外部依赖vue,意味着在打包时 Vue 不会被包含在输出文件中,而是期望在运行时通过全局变量Vue提供。 -  
output:
-  
globals: 定义 UMD 构建时的全局变量,用于引用外部依赖。在这里,vue被指定为全局变量Vue,这使得使用这个库的应用能够在全局上下文中访问 Vue。 
 -  
 
 -  
 -  
当
process.env.LIB不存在或为false时:构建为普通应用outDir: 指定构建输出目录为dist-app,构建结果会输出到这个目录下。target: 指定构建目标为esnext,表示构建输出将使用最新的 ECMAScript 语法特性。assetsInlineLimit: 设置资产内联的限制,这里设定为一个非常大的值(100000000),意味着几乎所有的资源(如小于这个大小的图像、CSS 文件等)都会被内联。chunkSizeWarningLimit: 设定 chunk 大小警告的限制,设置为一个非常大的值以避免在构建时收到 chunk 大小的警告。cssCodeSplit: 设置为false,表示禁用 CSS 代码分割,所有的 CSS 会被打包成一个文件。brotliSize: 设置为false,表示不计算 Brotli 压缩的大小。rollupOptions:-  
output:
-  
inlineDynamicImports: 设置为true,表示所有动态导入的模块都将内联到单个输出文件中,而不是生成多个分离的 chunk。 
 -  
 
 -  
 
 -  
 -  
构建配置
-  
根据build决定是否构建为库
 -  
使用vue插件用于支持vue单文件组件
 
 -  
 -  
全局常量
-  
__APP_VERSION__:-  
这个常量被定义为当前项目的版本号,它是从
package.json文件中读取的version字段(通过process.env.npm_package_version)。 -  
通过
JSON.stringify
处理后,它会转换为一个字符串,这样在应用中可以直接引用,例如:
javascript复制代码 console.log(__APP_VERSION__); // 输出项目的版本号
 -  
这对于版本管理、调试和在 UI 中展示版本号很有用。
 
 -  
 -  
process.env.NODE_ENV:-  
这个常量代表当前的运行环境(如开发、生产或测试)。通常在开发工具链中,这个值会被设置为
development或production。-  
通过
JSON.stringify
处理后,引用时会是一个字符串。例如:
javascript复制代码if (process.env.NODE_ENV === 'production') {// 执行生产环境特定的代码 } 
 -  
 -  
使用
NODE_ENV变量的好处是可以在代码中根据不同的环境执行不同的逻辑,比如启用调试工具、日志记录等。 
 -  
 
 -  
 
全局常量
-  
可以配置的地方
-  
vite define
 
 -  
 
-  
根实例中
 
// main.js
import { createApp } from 'vue';
import App from './App.vue';const app = createApp(App);
// 获取版本号
const APP_VERSION = process.env.npm_package_version; // 访问环境变量
const NODE_ENV = process.env.NODE_ENV || 'development'; // 获取当前环境变量app.provide('appVersion', __APP_VERSION__);
app.provide('env', process.env.NODE_ENV);app.mount('#app');
 
组件中可以使用this.appVersion
-  
使用vuex
 
// store.js
import { createStore } from 'vuex';const store = createStore({state: {appVersion: __APP_VERSION__,env: process.env.NODE_ENV,},
});export default store;
 
-  
在环境变量文件中设置
 
# .env VITE_APP_VERSION=1.0.0
在组件中访问
console.log(import.meta.env.VITE_APP_VERSION); // 访问环境变量
案例2
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
// https://vitejs.dev/config/
export default defineConfig({plugins: [
//支持vue单文件组件vue(),AutoImport({resolvers: [ElementPlusResolver(),// 自动导入图标组件IconsResolver({prefix: 'Icon',}),],}),Components({resolvers: [ElementPlusResolver(),// 自动注册图标组件IconsResolver({enabledCollections: ['ep'],}),],}),Icons({autoInstall: true,}),
],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url)),"@/ts": "./src/*.ts","@/tsDeep": "./src/**/*.ts"}}
})
 
差异
-  
第一个项目使用了
viteSingleFile,使其可以将项目打包成一个单文件,适合需要在某些特殊场景下进行部署的项目。 -  
第二个项目更加专注于提高开发体验,使用了自动导入、组件自动注册和图标插件,特别适合使用
Element PlusUI 框架的项目。 -  
全局变量:第一个项目有更多针对版本号和环境的全局变量定义,而第二个项目没有这方面的配置。
 
3.3 正确示例
-  
Vite 的配置相对简洁,因为 Vite 默认支持现代浏览器的 ESM 规范,很多复杂的配置(如模块加载、热更新)都是开箱即用的,开发服务器和打包的配置都很简洁。
 
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({plugins: [vue()],server: {host: '0.0.0.0',port: 3000,open: true, // 启动时自动打开浏览器},build: {outDir: 'dist', // 打包输出目录},
});
 
TS
1. tsconfig.json配置
GIT
-  
更换仓库
 -  
更改远程仓库地址
git remote set-url origin 新地址 git remote set-url origin git@github.com:DHWhale01/yhs-web.git https://gitee.com/code-ql/yhs-web.git
 -  
正常提交就可以了
 
