1、自定义校验规则
定义在哪里?
methods中
data中,与return平齐
QA****
1、axios发请求时如何发参数****
post请求用data; data: parameter
get请求用params params: parameter
2、自定义校验可以写在哪里****
data中
methods中
script标签首层
4、vue路由传参的区别****
传的参数如果出现在浏览器地址栏内,刷新页面就不会丢失
5、localstorage****
引自Wikipedia关于Web存储的文章:
可以简单地将Web存储视为Cookie的改进,提供更大的存储容量(Google Chrome中每个来源10 MB([plus.google.com/u/0/+Franco…] Mozilla Firefox和Opera; Internet Explorer中每个存储区域10 MB)
5、修改计算属性****
计算属性默认是只读的,如果需要修改,必须提供set
****
6、v-if导致click事件失效****
Vxe 可编辑表格,通过v-if控制各个表格的显示,发现一旦给表格加上v-if就不能编辑了****
****
解决办法:用v-show****
7、v-if和v-show****
v-show: 设置display:none来控制隐藏,不能用在template上面
v-if:直接从DOM中删除元素,在浏览器的element中看不到
opacity:看不见,能触发点击事件
visibility: 看不见,不能触发点击事件
8、v-for****
v-for要添加key,但不能在template上添加key,应该在真实元素上添加key
9、设置datePicker的值****
this.formData.constructionStart = moment(res.data.constructionStart);
根据返回的字符串设置日期框的值,不转换为moment类型的话会报错
10、生命周期****
1、beforeCreated:什么都没有。
2、created:vue对象实例初始化完成,完成数据(data,methods,computed)的初始化。
可以访问data, compued,watch,methods上的方法和数据。
使用场景:初始化完成时事件可以写在这里,少量的异步请求也可以在这里调用(请求不宜过多,避免白屏时间太长)
3、beforeMounted:dom元素还不能得到,但vue挂载的根节点(例如#app)已经创建。
4、mounted:挂载完成,dom元素已经渲染完成。
使用场景:可在这发起后端请求,拿回数据,配合路由钩子做一些事情。
5、beforeUpdate:在虚拟dom重新渲染和patch之前被调用,虽然没有立即更新数据,但dom中的数据会改变。
6、updated:
在数据更新后,完成虚拟DOM的重新渲染和patch被调用。
使用场景:组件DOM已完成更新,可执行依赖的DOM操作。
注意:不要再此函数中操作数据(修改属性),否则会触发beforeUpdate、updated这两个生命周期,陷入死循环。
不管是通过父组件props接收的数据还是组件本身data里的数据,只要在页面中使用这些数据,这些数据变化,都会触发组件的updated生命周期;如果数据不在页面中使用,那么不会触发组件的updated生命周期。
11、 vue的watch可以监听哪些值****
data props computed emit inject
12、FromData****
const formData = new FormData();
FormData是H5的内容
添加数据用append,
13、JSON. stringify实现深拷贝的弊端****
14、 下载****
1、a标签实现的下载
这种方式只适用于文件已经存在,并且地址已知的情况
只需把href路径指向文件路径,注意HTML5新增download属性能让我们指定浏览器下载时采用新的文件名称,也就是在客户端重命名下载文件。而不是链接上原始的文件名称,同时download名称的后缀可以改变,比如.zip可以变为.text,但是与原文件后缀不同下载后将无法使用
< a href="Swiper-2.7.6.zip" download="下载我.zip" > 下载 </ a >
2、以文件流的形式返回,前端
content-disposition 就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名
这种方式适用于文件的地址不确定,需要根据用户的参数动态获取文件地址
export function downloadResult (parameter) {
return request({
url: steelAmountApi.download,
method: 'post',
data: parameter,
responseType: 'blob' //重点1
});
};
downloadResult(parameter).then(res=>{
resolveBlob(res)
}).catch(error=>{
});
export function resolveBlob (res) {
const blob = new Blob([res.data], { type: 'application/zip' });//重点2
const filename = res.headers['content-disposition'];
const downloadElement = document.createElement('a');
const href = window.URL.createObjectURL(blob); // 创建下载的链接 //重点3
downloadElement.href = href;
downloadElement.download = decodeURI(filename.split('=')[1]); //重点4,解决文件名为中文时乱码的问题
document.body.appendChild(downloadElement);
downloadElement.click(); // 点击下载
document.body.removeChild(downloadElement); // 下载完成移除元素
window.URL.revokeObjectURL(href); // 释放blob对
}
15、 子组件作为弹框使用时只触发一次created的问题****
解决办法:给modal外面再包一层div,并且使用v-if
16、 v-model****
17、 事件总线****
18、 过滤器filter中无法访问this****
过滤器中无法访问到applicationArr,改成方法可以实现
19、 如何判断对象是否为空****
方法很多,把对象转为空字符串、遍历对象属性、Object.keys()等,唯独不能用转布尔值的方法,因为所有对象的布尔值都是true,
20、 组件的递归****
通过组件的name属性实现递归
2 1 、window .open 和window .location.href****
window.open 用来打开新窗口
window.location 用来替换当前页,也就是重新定位当前页
2 2 、如何在template中使用组件之外定义的公共方法****
vue组件的模板中只能使用本组件中export default导出的变量和函数,如果是一些比较常用的函数,一般都是写在一个公共的文件中,可以采用如下两种办法,使用导入进来的方法
1、mixins
定义混入
2、如果是定义在util.js中的公共方法,导入之后,还需要在method中引用一下,才可以在template中使用
import { i18nRender } from '@/locales'
2 3 、如何把prop作为data****
首先,需要明确的是,prop不一定非得作为data或者计算属性才能使用,如果要直接在模板中使用prop也是ok的,data属性、计算属性、prop,这三个都是可以直接在模板中使用的。
有三种方式在模板中使用prop:
一、直接在模板中使用prop,父组件中的值变化时,子组件的模板会自动更新
二、使用 prop 定义 一个data属性
如果只是需要prop传递一个初始值,这时可以定义一个本地data,用prop给data赋值
这里又分为两种情况:
1、定义data时用prop赋初值,在模板中使用data,监听prop(如果需要的话),prop变化后给data赋值,如图:
直接在子组件的data属性中重新定义一个新属性,用来接收prop,这种存在一个问题:父组件中的值变化时,该data属性不会更新(prop仍然会更新,只是data不会更新,注意:只针对基本类型,如果是引用类型,仍旧是变化的,因为在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,父子组件中的值始终是同步的,所以不能在子组件中修改引用类型的prop,会使得数据流传递方向混乱,为了严格起见,就规定在子组件中不能修改prop,无论是基本类型还是引用类型)
如果是只需要父组件传入一个初始值的情况,就可以只传入prop并赋值给data,不watch(拓扑录入demo中,table中选取一个节点,传递给Dialog组件一个row值,就是这种情况);
如果父子组件同时出现,在父组件中的操作需要实时更新到子组件,就需要监听prop,如下demo中,点击父组件修改数据,子组件、孙组件要同步更新自己的内容,子组件1展示的data是用基本类型的prop赋值的,所以点击父组件时,子组件1不变;子组件4展示的data是用引用类型的prop赋值的,所以点击父组件时,子组件4会变
此时为了更新子组件1中的data属性,就需要使用侦听器来监听prop的变化。
或者
2:定义data时赋空值,监听prop,immediate设置为true(否则初次加载子组件时不能触发监听回调),prop变化后给data赋值(既能拿到初始值又能响应变化);
根据自己的情况,如果只是想拿到初始值,就没必要监听prop;
三、使用prop定义一个c omputed 属性
如果prop以原始值传入,且需要转换,这种情况下最好定义一个计算属性。计算属性是基于它们的响应式依赖进行缓存的,所以不需要监听,计算属性会自动随着prop的改变而改变。
对比之下,用prop定义的计算属性,既能拿到初始值,又能自动响应prop的变化,不需要watch,比用prop定义data属性更便利
2 4 、数据流真的是单向的吗?****
从该demo可以看出,如果父组件传给子组件的prop是引用类型,在子组件中修改了该prop,会影响到父组件,所以数据流是可以双向的,但这种双向流动会引起混乱,所以vue中强行规定,不能在子组件中修改prop,直接修改prop会报错
2 5 、子组件如何修改prop****
上一节中说过,在子组件中直接修改prop,vue会报错(仅限于基本类型的prop,如果是引用类型的prop,修改后不会报错);但有时候确实又是存在这种需求的,最典型的就是关闭弹框(参考拓扑录入demo,点击不用的按钮,弹出对应的弹框):父组件中引用了一个弹框组件,在弹框中的表单输入完成后,点击确定时关闭弹框,本来控制弹框显示的变量是定义在父组件中的,但是关闭按钮是在子组件中,这时候子组件就需要修改prop
2 5 、element UI 中Dialog和 M essage B ox有什么区别****
2 6 、气泡确认框和模态确认框有什么区别****
1、模态确认框,即Modal.confirm弹出的确认框在屏幕中居中显示,即使删除按钮在最右侧,也得滑到中间来点击
2、popconfirm在目标元素附近弹出浮层提示,询问用户。
和 ‘confirm’ 弹出的全屏居中模态对话框相比,交互形式更轻量。
2 7 、项目中同时引入element UI 和antd会有哪些问题?****
1、使用this.$message.xxx会报错
因为两套组件中message的使用基本是一致的,执行这句时编译器不知道该用哪个组件
2 8 、前端生成二维码****
后端生码虽然也可以实现功能,但没必要后端生码,最low的方式就是后端生成二维码图片,再把图片转成base64编码发给前端,前端用img标签,设置src属性来接收base64字符串。因为base64只是编码,并没有压缩,一个50M的图片编码还是50M的字符串,前后端直接传送这么大的图片,会占用带宽,而且生码的功能放在后端,会增加服务端的压力,如果有上千万的用户同时要生码,肯定会造成系统瘫痪,就得增加硬件来缓解服务端的压力
<img
:src="imgSrc"
/>
this.imgSrc = 'data:image/png;base64,' + base64Img
前端有很多可以生码的插件,比如qrcodejs2,vue-qr等,只要传入生码的内容(例如设备id)及二维码样式设置参数,就可以在前端生成二维码
如果想实现铭牌(包含文字信息和二维码),可以用html2canvas实现
2 8 、vuex持久化****
2 9 、props和attrs的区别****
1.props 要先声明才能取值,attrs 不用先声明
2.props 声明过的属性,attrs 里不会再出现
3.props 不包含事件,attrs 也不包含事件
4.props 支持 string 以外的类型,attrs 只有 string 类型
文档里面是这么规定的,但实际调试代码发现attrs对象的属性也可以是其他类型
attrs的定义:包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。所以attrs是相对于父子组件才存在的,并且只在子组件中才能访问到
30 、自定义选项****
31 、r esolve 和reject之后还需要return吗****
return resolve(lang)是resolve(lang);return;的简写,return的目的是防止resolve之后继续执行其他代码;执行了reject和resolve,promise的状态状态就凝固了,所以,reject一个fulfilled的promise或者fulfill一个rejected的promise是没有任何效果的
reject和resolve都无法让函数剩下的部分暂停执行,那函数中包含的其他代码可能会对我们造成干扰
32、el-upload相关的问题****
一、上传要么用action自动上传;要么用http-request=’fn’自定义上传
二、何时使用action,何时使用http-request。
看后台,如果后台只给了个拼接网址的接口时候,没提文件时候,用action。
如果后台给多个参数包含文件地址等,或者需要三方文件服务器oss等时候。用http-reques
(1)当默认:auto-upload="true" ,选取了文件就走了action或者http-request的接口方法;
当:auto-upload="false" ,需要通过this.refs.upload.submit(),自己写个接口上传
(2)当 :auto-upload="false"的时候,要注意以下问题
1、选完文件,http-request="fn" 的fn(file)是不存在file的,因为没走接口,所以没有回调
2、使用before-upload可以限制文件大小和格式, :auto-upload="false"时候,这个钩子是不会被触发的,可以使用on-change进行处理。同样都有file入参。
使用了on-change方法 直接赋值this.fileList = fileList
三、file-list参数表示已上传文件列表(就是后面打了绿色勾勾的,已经上传成功了的);uploadFiles表示已选择文件列表
this.$refs.upload.uploadFiles.push(newFile);
四、before-upload回调返回false还会自动触发before-remove和on-remove,且不会再触发http-request回调;before-upload回调返回true会自动触发http-request回调
33、el-dialog相关的问题(参考拓扑录入demo中的 AddPvStringDialog.vue)****
1、:visible.sync=”someDataProp”,此处的.sync是el-dialog监听了它内部的@update:visible=” someDataProp=$event”事件
查看el-dialog源码
3 4 、构建和打包的区别****
https://www.cnblogs.com/soymilk2019/p/11209904.html****
构建过程应该包括 预编译、语法检查、词法检查、依赖处理、文件合并、文件压缩、单元测试、版本管理等;
打包工具更注重打包这一过程,主要包括依赖管理、版本管理
3 5 、预编译和编译的区别****
1、预编译又称为预处理。如处理#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等。为编译做的预备工作的阶段。
2、编译(compilation , compile):编译阶段是检查语法,生成汇编的过程。 编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。
3 6 、e l 属性和$ mount 方法****
所以实例生命周期create就是创建vue实例,mount就是把模板内容挂载到页面的某个元素上
VUE****
1、k eep-alive****
keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,从而节省性能,由于是一个抽象组件,所以在页面渲染完毕后不会被渲染成一个DOM元素
如包裹两个组件:组件A和组件B。当第一次切换到组件A时,组件A的created和activated生命周期函数都会被执行,这时通过点击事件改变组件A的文字的颜色,在切换到组件B,这时组件A的deactivated的生命周期函数会被触发;在切换回组件A,组件A的activated生命周期函数会被触发,但是它的created生命周期函数不会被触发了,而且A组件的文字颜色也是我们之前设置过的。
2、再次切换回k eep-alive 的组件,哪些生命周期不会被执行****
如1中所述,再次切换回组件A时,不会被触发的生命周期包括:beforeCreate、created、beforeMount、mounted
3、被keep-alive包围的组件,有哪些生命周期从来不会被触发****
beforeDestroy和destroyed
4、被vue2.0生命周期、计算属性、watch的执行顺序
watch 和created的顺序,要看watch中immediate的值
immediate:true 先watch 后created
immediate:false 先created 后watch
5、先有路由还是先有组件
先有路由,所有路由导航结束后,才会显示组件,依次执行组件的生命周期
6、如何监听路由的改变
两种情况下可以监听到$route的改变:一是在父组件中监听,二是启用了keep-alive
watch: {
'$route': function(to, from) {
//this.doSomething();
}
},
但是必须要注意的是,把以上watch写在父组件里,写在子组件里是监听不到的
7、事件修饰符
vue中的事件修饰符并不是vue独创,原生js中本来就有
(1)原生javascript在addEventListener的时候提供第三个参数,可以是一个对象,这个对象的key
(2) event.preventDefault()
(3) event.stopPropagation()
8、prop的类型为函数
在父组件中,我们在子组件中给他绑定一个属性,而这个属性是一个函数
在子组件中我们接收父组件传递过来的这个属性,并在某个时刻调用,调用时还可以传入参数,父组件中会接收到这个参数(父组件中修改为handleSomethFun(val){ console.log('我是父组件中的方法')}来接收参数)
9、.sync修饰符
一、历史
.sync修饰符以前存在于vue1中,后来在vue2.0中移除了。
但在vue2.3.0中又加回来了,但这次它只是作为一个编译时的语法糖存在
二、用法
参考dialog组件的封装:blog.csdn.net/qq_41206305…
在子组件会变更父组件的情况下,例如,封装的Dialog组件AddPvStringDialog
,父组件中调用了AddPvStringDialog组件
如果用子组件触发事件向父组件传递参数的方式,父组件中就需要有handleClose的事件监听,子组件点击了关闭按钮时需要触发handleClose事件
父组件:
子组件:
如果用.sync的方式向父组件传递参数,父组件中不需要做事件监听(因为.sync会被扩展为一个自动更新父组件属性的v-on监听器),子组件只能使用update:myPropName 的模式触发事件
父组件:
或者:
子组件:
三、如果已经使用了emit事件的方式向父组件传参,就不要再使用.sync修饰符了,多余。
“vue 修饰符sync的功能是:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定。 如果我们不用.sync,我们想做上面的那个弹窗功能,我们也可以props传初始值,然后事件监听,实现起来也不算复杂。
四、.sync的原理
就是个语法糖,原理还是v-bind和v-on:update:title
10、watch监听第一次不触发的解决
watch用来监测data、prop等的“改变”,子组件第一次接收到父组件的参数时默认并不会触发watch,但设置了immediate:true时,子组件中的watch也可以在第一次接收到父组件的参数时就触发handler
父组件:
子组件:
11、vue中有哪些钩子
1、组件生命周期钩子
2、路由导航钩子
3、自定义指令
12、插件
1、对象形式:必须提供install方法
2、函数形式
13、组件间传参
1、初级办法:props,一层层用props往下传,代码量大,可维护性不强
2、中级办法:
(1)$attrs,兼顾了props和vuex的缺点,比较适中的方式
(2)上下文传值:provide/inject, 允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效
3、重量级办法:vuex,在小项目中用vuex,有点杀鸡用牛刀的感觉,大材小用
props、provide/inject是选项,需要在实例上预先定义,attrs访问
14、v-bind.prop和$attrs的关系
首先搞清楚HTML attribute 和 DOM property的区别
(1)如果父组件在给子组件传递属性A时用了.prop修饰符,属性A就不会出现在attributes 绑定上,而是和attributes属性平级;对于attributes属性上的子属性,会被attr瓜分掉,attrs上
$props中包含了父作用域中作为prop被识别 (且获取) 的attributes 绑定
$attrs中包含了父作用域中不作为 prop 被识别 (且获取) 的 attributes 绑定 (class 和 style 除外)。
1、使用.prop
(1)使用了.prop修饰符的属性不会出现在html中,可以防止污染html解构,避免暴露数据
(2)使用了.prop修饰符的属性不会作为attributes的子属性,所以既不会出现在attrs中
(3)父组件中v-bind 加了prop修饰符后,在子组件中接收该属性,获得的值为undefined,$attr中也没有该参数
(4)
(5)父组件中v-bind 加了prop修饰符后,在子组件中不接收该属性,$attr中也没有该参数
,
2、不用.prop
(2)不使用.prop的属性会出现在html中
(3)不使用.prop的属性,传递的数据才会绑定到dom对象的attributes属性上,然后attr会把attributes属性值瓜分掉,prop不接收的才会流到$attrs上
15、构建模式****
[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
翻译一下就是您正在使用Vue的仅运行时版本,因而模板编译器不可用。 可以将模板预编译为渲染函数,也可以使用包含编译器的内部版本
解决办法:
1、vuecli 2.0版本写法
/* 带webpack显性配置的 */
//webpack.config.js
//其实就是取别名,找到以 vue 结尾的,就去node_modules重新查一下路径
module.exports = {
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 用 webpack 1 时需用 'vue/dist/vue.common.js'
}
}
}
2、vuecli 3.0版本写法
/* webpack隐性配置的 */
//vue.config.js
//true 就是完整版的(即runtime-compiler)
module.exports = {
runtimeCompiler: true //ture: runtime-compiler false: runtime-only
}
16 、vue运行时和编译器****
vue有两种代码版本:只包含运行时的vue、编译器+运行时的完整版vue
webpack用到了vue-loader,vue-loader用到了vue-template-compiler
使用运行时版本的好处是打包后的代码中不包含编译器,用户下载的js文件更小
编译器:编译器的作用仅仅是编译模板字符串,但是却占到了vue体积的30%,所以默认vue项目都是只包含运行时的版本,项目中用到了template属性时、或者new 一个vue实例的时候提供了el属性时,就需要用到编译器版本或者完全版本
运行时:项目中都是.vue文件时,或者new 一个vue实例的时候提供了render属性,且手动mount()
做个试验
如下图,webpack中配置打包时使用运行时版本
main.js中有el和template,说明需要编译器
所以浏览器就显示警告,且页面空白
修改一下实例的配置,如下
浏览器也能正常显示了
1 7 、vue和vue -template-compiler 的关系,为什么必须版本一致****
vue-template-compiler 的代码是从 vue 源码中抽离的!接着,我们对比一下 vue-template-compiler 和 vue 关于编译的 API。发现对于 compile 等函数是一致,只是 vue-template-compiler 开放的参数和方法更多。因此,vue 和 vue-template-compiler 的版本必须一致(同一份源码)!
1 8 、vue- router ,为什么使用了path导航时,就不能用params****
首先要搞清楚什么是动态路由
动态路由定义:动态路径参数以冒号开头
动态路由在浏览器导航栏中的效果:参数直接跟在url后面,
类似于localhost:8080/#/user/123
要想让params参数有用,定义的路由文件中必须有动态路由
如果定义路由文件时,用的是{ path: '/user/:id', component:User}这种动态路由,而导航时又用的是this.$router.push({ path: '/user', params: { userId }})这种方式,浏览器会跳转到localhost:8080/#/user,页面不显示任何内容,因为路由文件里没有定义
{ path: '/user', component:User}这种路由,浏览器匹配不到对应的组件。
如果定义路由文件时,用的是{ path: '/user', component:User}这种非动态路由,只能接受query参数,而导航时用的是this.$router.push({ path: '/user', params: { userId }})这种方式,虽然传递了参数,但是参数并不会被接受,因为这时候并没有匹配到动态路由,
19、.native修饰符****
.native主要是给自定义的组件添加原生事件,可以理解为该修饰符的作用就是把一个vue组件转化为一个普通的HTML标签,并且该修饰符对普通HTML标签是没有任何作用的
1、只能用在自定义组件上,用在原生html(如div,h1,label等)标签上没用
2、只能跟在原生事件(如click,input,focus等)后面,跟在自定义事件后面没用
3、$listeners中不含.native修饰符修饰的事件(因为native修饰的事件直接在父组件中处理了,并不需要在子组件中处理)
4、原理:当我们使用了此修饰符后,vue会帮我们给组件的根标签添加一个事件监听器,我们便可以正常使用该事件了
5、如果要监听的事件在根元素压根不存在(例如根元素是label,要监听focus事件,label根本就没有focus事件),加了native也没用,这时可以借助$listeners,将事件监听器指向这个组件的某个特定的子元素
如上图,想监听自定义组件Step1的click事件,直接写@click=”sayHello”的话,点击页面时没有任何反应;添加native修饰符后才可以触发回调。组件直接绑定click事件,Vue会把click视为一个自定义事件 无法通过点击触发,只能通过$emit方法触发
如上图,native如果用在原生标签上,会引发告警
20、 vue2 响应式原理-watcher类型有哪些****
响应式系统的核心,就是依赖收集和触发更新
掌控DOM的渲染watcher(或者叫render watcher),一种是执行回调函数的,我将其称为 回调watcher,又分为watch和computed
这也很好理解,最源头的数据不是data就是prop,如果data/prop改变了,要么直接影响页面的渲染效果,要么会触发watch或者computed
收集依赖的时候实例化watcher,init的时候、mounted之前会收集依赖
21、 vue2 的编译原理****
vue的渲染函数render(h) 生成的是虚拟dom,也就是
template --> 抽象语法树 --> render(h) --> 虚拟dom-->UI
22、 vue2 是在哪个生命周期开始编译的?响应式原理和编译的关系?react有没有依赖收集****
zhuanlan.zhihu.com/p/82912814?…
cloud.tencent.com/developer/a…
执行init后完成了响应式的依赖收集
created生命周期之后开始编译,将初始数据和compile函数(编译函数)进行结合,创建出虚拟DOM
React不收集依赖,只有2个已知条件:
这个state属于哪个组件
这个state变化只会影响对应子树
子树范围对于最终视图更新需要的DOM操作而言太大了,需要细化(diff)**
Vue和react相比,多了一个收集依赖的过程,也就是vue的响应式原理中所讲的内容,二者都是把模板编译生成ast,再生成渲染函数,再产生虚拟dom,diff算法对比新旧虚拟dom,因为vue之前收集了依赖,所以可以更精准的更新dom**
细粒度的依赖收集是精确DOM更新的基础(哪些数据影响哪个元素的哪个属性),无需做额外的猜测和判断,框架如果明确知道影响的视图元素/属性是哪些的话,就可以直接做最细粒度的DOM操作
23、如何在vuex的action中访问vue实例****
方式一、最简单粗暴的是在action中直接引入vue实例
然后在action中
import vue from '@/main'
方式二:在调用this.$store.diapatch, 把this当做参数传入即可解决 (确保当前this指向vue实例对象)
24、v-slot****
先了解一下什么是具名插槽和作用域插槽
在没有v-slot之前,具名插槽的写法如下:
父组件提供slot属性
子组件提供slot标签,slot标签有name属性,和父组件的slot属性值对应,把父组件的对应内容分发到不同的插槽里:
有了v-slot指令后,就可以这样写:
没有v-slot之前,作用域插槽slot-scope的写法如下:
有了v-slot之后,slot属性和slot-scope属性就可以用如下的简写形式
25、作用域插槽的具体应用****
具名插槽是带有参数的插槽
作用域插槽实际上是带有数据的插槽 可以获取到子组件传递的参数,在父组件中可以使用子组件中的数据,这样就允许父级组件自定义部分布局
以antDesignVue组件为例,我们在父组件中使用Table组件时,只传入了dataSource数组,Table组件内部肯定是使用了v-for指令遍历data生成ul和li标签,每一个item都是Table组件内的数据,父组件是不能访问该数据的,但是有了slot-scope我们就可以在父组件中使用item了,就可以对列的内容进行定制。具体原理就是下图中红框中的做法,将tode (也就是上文提到的item)作为 元素的一个 attribute 绑定上去
或者也可以参考官网的如下解释
可以在一个slot上绑定多个attribute如下,给default插槽绑定了两个属性
在父组件中,使用v-slot把这两个属性都接收到alotProps对象上
26、slots****
这二者都是父组件传给子组件的插槽内容,都只能在子组件中访问
插槽分两类,一类是具名插槽,也就是不带值的插槽,另一类是作用域插槽,也就是带值的插槽
$scopedSlots是个函数,可以传入参数,和给slot标签绑定属性是一个作用
27、插槽总结****
1、v-slot取代了原来的slot属性和slot-scoped属性,缩写是#,用在父组件调用子组件的时候
有了v-slot,上面的代码就可以改为如下
<template v-slot:default="slotProps">
{{ slotProps.msg }}
或者
<template v-slot="slotProps">
{{ slotProps.msg }}
或者
<template #="slotProps">
{{ slotProps.msg }}
2、子组件的slot标签有name属性,若在子组件的name:’item’ 的slot标签上绑定了若干个值,则父组件的’item’插槽上就会收到对应的值,就可以在父组件中对要展示的信息进行定制。
2、参考前面scopedSlots
28、组件按需加载****
28、如何实现再次登录时跳转到退出时所在的页面****
退出时,将当前路由的fullPath保存在query中,此处参数名不一定要用‘redirect’,其他任何字符串都可以,只要和后面的from.query.xxx保持一致就行
在路由导航钩子中,from指的是退出时所在的路由,
JS****
1 、forEach遍历数组时存在的问题****
forEach不能正确响应break、continue和return语句,使用中发现使用break会报错,使用return也不能退出循环
在项目中使用forEach进行遍历,无法实现:达到某一条件,希望跳出循环,代码不继续执行。原因:forEach()无法在所有元素都传递给调用的函数之前终止遍历。也就是说forEach始终都会遍历完所有项才会结束。如果想中途跳出循环,只能放弃forEach(),改用其他办法,例如for
forEach还有个比较特殊的地方,就是:使用return不会报错(也不会退出循环,前面已经分析过原因了),在for循环中直接使用return是会报错的,本来return语句只能出现在函数体内,出现在代码中的其他任何地方都会造成语法错误,在循环体中使用时就会报错
var arr = [1,2,3,4,5];
var num = 3;
arr.forEach(function(v){
if(v == num) {
return true;//break或者return或者return false
}
console.log(v);
});
var arr = [1,2,3,4,5];
var num = 3;
for(let i = 0;i< arr.length;i++){
if(arr[i] == num) {
return true;
}
console.log(arr[i]);
}
function bianli(){
var arr = [1,2,3,4,5];
var num = 3;
for(let i = 0;i< arr.length;i++){
if(arr[i] == num) {
return true;
}
console.log(arr[i]);
}
}
bianli();
2、 js中有哪些遍历数组的方法****
for,for of,forEach,map,filter,every,some,
3、对象没有length属性****
4 、递归替换for循环****
5 、数组的reduce方法****
没有初始值:total为65,num为44
var numbers = [65, 44, 12, 4];
function getSum(total, num) {debugger;
return total + num;
}
function myFunction(item) {
var result = numbers.reduce(getSum);
}
传递初始值:total为100,num为65
var numbers = [65, 44, 12, 4];
function getSum(total, num) {debugger;
return total + num;
}
function myFunction(item) {
var result = numbers.reduce(getSum,100);
}
调用myFunction(numbers)
6 、发布订阅模式****
class EventEmitter {
constructor () {
// 用一个对象来储存事件订阅信息,同一个事件可被订阅多次,故事件的回调函数用数组储存。
//subs是一个对象,该对象的每个属性值是一个数组
this.subs = Object.create(null)
}
// 注册事件 handler对应一个观察者
$on (eventType, handler) {
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
// 触发事件
$emit (eventType) {
if(this.subs[eventType]){
this.subs[eventType].forEach(handler => {
handler()
})
}
}
}
const em = new EventEmitter()
//’propertyName’对应vue中对象的属性,会有多个地方监听这个属性,只是执行的回调不一样
//get里面执行
em.$on('propertyName', () => {
console.log('click1')
})
//set里面执行
em.$emit('propertyName')
7 、观察者模式****
//发布者-目标,以下代码针对对象的一个属性,每个属性都需要实例化一个dep对象
class Dep {
constructor () {
// 记录所有的订阅者
//subs是一个数组,数组的每一项是一个watcher对象,该对象具有update方法,update方法中是具体要执行的操作
this.subs = []
}
// 添加订阅者
addSub (sub) {
// 在添加之前,要确保订阅者存在且具有update方法
if (sub && sub.update) {
this.subs.push(sub)
}
}
// 发布通知
notify () {
// 找到所有的订阅者并调用它们的update方法,注意:是所有的观察者,all
this.subs.forEach(sub => {
sub.update()
})
}
}
/* 对象的每个属性转为响应式属性,源码的Observer对象的walk方法中执行,walk方法会循环这个对象的每个属性,为每个属性进行defineReactive的处理 */
let dep = new Dep()// 订阅者-观察者,每个属性都会生成一个Dep实例dep
//defineReactive的get里面执行
class Watcher {
update () {
console.log('update')
}
}
let watcher = new Watcher()
dep.addSub(watcher)
//set里面执行
dep.notify()
8、indexOf方法****
上传组件upload必须配置:file-list="fileList"属性,否则在删除文件列表时会导致indexOf的结果始终为-1
9、set Timeout 回调函数为立即执行函数的情况****
1、setTimeout()的回调函数是一个立即执行函数,会立即执行,没有延迟效果;
2、setTimeout()的回调函数是一个立即执行函数return一个function,setTimeout函数执行的就是这个function
for循环搭配setTimeout函数,可以实现和setInterval相同的效果
10、防抖和节流的几个理解****
zhuanlan.zhihu.com/p/382302987
相同点:都是针对高频事件的优化处理
不同点:概念、实现思路、应用场景不同
1、 防抖
概念: 触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
实现思路:每次触发事件的时候,清除定时器,然后从新设置新的定时器
应用场景:主要用在需要节约资源并且只需要“最后触发一次”的场合
n 输入框search搜索联想,用户在不断输入值时触发input事件,用防抖来节约请求资
n window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
onDebounce() {
// timer是该对象全局定义的,每次执行onDebounce函数,调用的相同的timer**
clearTimeout(this.timer);
this.timer = setTimeout(()=>{
this.getAjax(); // 后台请求ajax信息**
}, 2000);
},
2、 节流
概念: 高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。(如果n秒内高频事件再次被触发,不会重新计算时间),所以节流总能保证每隔n秒执行一次
实现思路1:每次触发事件的时候,清除定时器,然后从新设置新的定时器
onThrottle() {
if( !this.throttleFlag) return;
this.throttleFlag = false;
this.timer = setTimeout(()=>{
this.getAjax();
this.throttleFlag = true;
}, 2000);
}
实现思路 2:不使用定时器,直接比较时间戳差值。
onThrottle() {
let now = Date.now();
if (now - this.lastTime > 5000) { // 5s**
this.getAjax();
this.lastTime = now; // update时间**
}
}
应用场景:主要用在需要节约资源并且只需要“最后触发一次”的场合
n 鼠标不断点击触发,click/mousedown(单位时间内只触发一次),例如淘宝抢购
n 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断(在润和做的监控大屏项目,实际上还没用到节流)
11、数组的map和filter方法会改变原始值吗****
结论:要看写法,普通数组不会,但是对象数组会,中间有赋值的过程,就会改变原数组
map方法,回调函数必须有return,或者用箭头函数
Filter方法:
12、尾递归优化****
尾递归本身就可以完全等效于一个无栈的循环,写成尾递归除了强行炫技以外没有任何实际意义,除了是在一些没有循环的纯函数式语言
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。当然如果实际业务场景并不会出现栈溢出的情况,也可以不用优化,还是用原来的递归调用就可以。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。 这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
13、JSON.stringify()和qs.stringify()的区别****
qs.stringify()与JSON.stringify()区别
qs.stringify、JSON.stringify虽然都是序列化,但他俩却不是一个东西。
qs是nodejs的一个模块
JSON.stringify是js自带的方法,是将json对象转换为json字符串
如:
var a={"a1": "hello", "a2": "hi"}
qs.stringify(a);
// 结果是:a1=hello&a2=hi
JSON.stringify(a);
// 结果是:‘{"a1": "hello", "a2": "hi"}’
14、类型检测的扛把子****
Object.prototype.toString.call(param)方法,功能简单但是确是类型检测界的“扛把子”
Object.prototype.toString.call(obj).slice(8, -1)可以取到obj的真正类型
console.log(Object.prototype.toString.call("jerry"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
Antd使用****
1、Form对比****
| Form | Model-form | |
|---|---|---|
| 对整个表单进行校验的方法 | validateFields,若 fieldNames 参数为空,则校验全部组件 | validate |
| 对部分表单字段进行校验的方法 | validateFields,fieldNames 参数不为空 | validateFieldthis.$refs["baseForm"].validateField('principal'); |
| 自定义校验规则 | ||
2、表格****
slot-scope中的text为当前列的值
3、upload校验****
upload组件没有v-model,所以即使给form-item设置了prop也没用(双向绑定表单要给form设置model属性来绑定一个表单数据对象objectA,然后给每个form-item双向绑定objectA中的一个属性)
正是因为upload没有v-model,所以upload其实都不算form-item,所以即使你已经上传了一张图片,点击提交表单的时候 this.$refs[formName].validate(valid => {,这里的valid是false
反正是很矛盾,想要小红点就得加required校验,但是加了required校验之后又会导致提交表单的时候即使上传了也给你报没有上传的错(因为upload没有v-model,所以上传的图片其实并没有和objectA的某个属性绑定,而this.$refs[formName].validate其实就是在校验objectA里的每个属性值是否满足校验条件,当校验到upload时,还是个空数组,肯定就会提示没有上传)
最终方案:用一个隐藏的具有v-model的input元素盛放上传的图片,
4、样式覆盖****
5、input的change事件****
input的change事件仅当获取/失去焦点时触发,通过赋值方式改变value时是不能触发change事件的
Mock的使用****
1、****
正则****
1、****
路由****
1、 this.$router 只能在组件中使用****
也就是说,只能在vue实例(.vue文件)中使用,
如下,在公共函数文件(.js文件)中使用时报错
2 、add Routes 废弃****
addRoutes的使用方法:
router.addRoutes(newRoutes)
addRoute的使用方法:
for (let x of newRoutes) {
router.addRoute(x)
}
3 、路由守卫和组件生命周期的调用顺序****
导航确认之后才开始渲染组件,所以一定是在导航的afterEach调用之后才开始组件的生命周期
假设,离开路由1,要去路由2,其中路由2对应的组件中包含了子组件
4 、组件内的守卫****
不是每个组件内都可以触发以下三个守卫
只有在router.js文件的routes属性中和路由直接对应的组件才能触发这三个守卫
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
5 、守卫和钩子的区别****
和守卫不同的是,钩子不会接受 next 函数也不会改变导航本身
afterEach就属于钩子,而不属于守卫
6 、before E ach死循环问题****
原因:在该导航钩子中必须执行不带任何参数的next(),才能跳出循环,否则就会导致死循环
不带任何参数的next()不会再触发beforeEach()
async await****
1、get和post****
await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。
如果await后面是 promise对象会造成异步函数停止执行并且等待 promise 的解决,
如果await后面是 正常的表达式则立即执行。
2 、p romise 和异步是不同的概念****
axios****
1、get和post的区别****
空行,通知服务器请求头部至此结束。 请求消息中的空行,用来分隔请求头部与请求体
区别:
1、只有 POST 请求才有请求体,GET 请求没有请求体!
2、get请求只能发送字符串,post请求可以发送任何类型的数据
post请求有两种发送参数的形式:Request Payload, Form Data;决定参数形式的是请求头里面的Content-Type
请求体里面传对象:Request Payload
在使用axios发送post请求时,默认的请求头Content-Type的属性值为application/json,所以post请求发出去的参数都是Request Payload;
根据axios的官网文档,如果使用了qs做序列化,Content-Type就会变成application/x-www-form-urlencoded format,所以参数就会变成Form Data发送;
针对Request Payload和Form Data,后端的处理方式是不一样的
2 、拦截器****
什么时候response会进入拦截器?
只要状态码不是200,就会进入拦截器
babel****
babel.config.js
高德地图****
1、Amap.service和Amap.plugin
vuex****
namespaced
自定义指令****
1、 < input v-focus >
2 、 <el-button v-hp="'allProjct_edit'">****
给指令绑定值,用binding.value可以取得绑定的值
3、 <a-dropdown v-action:delete>****
传给指令的参数,用binding.arg可以取得参数,如上为”delete”
富文本编辑器****
1、tinymce -vue****
父元素AddDefect.vue用v-model,相当于既给子组件TinymceEditor传递了初始值content,又监听了子组件的input事件
国际化****
1 、 哪些内容需要国际化****
系统中需要国际化的总体分为两部分:1、页面内容;2、ant 组件的国际化
1、Ant 组件的国际化一定要用 LocaleProvider 组件
2、页面内容的国际化可以通过$t(key)或者i18nRender(key)实现
3、在.jsx文件中的国际化用$t会报错?
2、 两种实现方式****
1、一次性加载所有语言包,切换语言按钮时更改 VueI18n 实例的 locale 属性的值
更改 VueI18n 实例的 locale 属性的值,可以直接用this.$i18n.locale,也可以把i18n实例导入进来,因为定义该实例时用的const,所以他是个全局变量,在任何地方修改,效果都一样
2、延迟加载语言包(用动态导入import()函数)
一次加载所有翻译文件是过度和不必要的。
具体官网文档都有参考案例
动态加载必须要用到i18n.setLocaleMessage(‘语言类型’, ‘对应的语言包’)
权限管理****
看一遍自己写的auth demo,基本就能明白了
权限管理要实现的目标是每个用户只能访问自己能访问的资源(菜单、路由、‘按钮’、数据)
1、 权限包括哪些****
1、菜单权限
2、路由权限
菜单权限和路由权限基本是一一对应的
3、按钮权限,
一般按钮、菜单项、tab页,等都算是按钮级权限,通过自定义指令或者自定义函数(参考ant design vue pro项目中v-action和$auth);
指令权限默认从 store 中获取当前已经登陆的用户的角色和权限信息进行比对,所以也要对指令权限的获取和校验 Action 权限部分进行自定义。
在某些情况下,不适合使用 v-action,例如 Tab 组件,只能通过手动设置 v-if 来实现。
<a-tab-pane :tab="auth('dashboard.delete')">
$auth是函数形式的自定义指令,接收字符串,返回true或者false
4、数据权限
后端的事情
2、 a uth 项目中全局的路由数据 asyncRouterMap 被修改的问题****
路由用了GenerateRoutes生成的权限路由,菜单用了/config/router.config.js中定义的asyncRouterMap;本来这时候菜单中就应该加上getPermission判断;但调试代码发现,即使不加getPermission也照样能只显示mock中配置的菜单
经过调试,最终发现原因为:/config/router.config.js中定义的asyncRouterMap是全局变量,执行GenerateRoutes中的filterAsyncRouter的会改变asyncRouterMap,所以菜单渲染时导入的asyncRouterMap已经不是全量菜单,而是权限菜单,所以即使不加getPermission也可以
如果路由改为配置全量路由的方式,就没有地方触发GenerateRoutes中的filterAsyncRouter,
asyncRouterMap是全量菜单;这时候菜单渲染时就得加getPermission
3、 permission .js 文件和route r . c onfig .js 文件,路由白名单****
export default new Router({
mode: 'history',
// routes: constantRouterMap,
routes: constantRouterMap.concat(asyncRouterMap)
})
(1)constantRouterMap:所有用户都能访问的路由。包括必须登录后才能访问的,比如首页等;也有不需要登录就能访问的,比如登录、注册等,这部分路由就是白名单
(2)asyncRouterMap:跟用户角色相关的路由,需要结合后台管理系统对该用户配置的角色-权限来生成(后台管理系统可以只返回permissionID数组,也可以返回简单的permissionListd对象数组,也可以返回包含组件路径的详细权限信息)
permission.js文件中可以设置一个whiteList白名单, 白名单中只有constantRouterMap中不需要登录就能让所有用户访问的路由,如果当前要进入的路由在白名单中,直接next();
4、 根路由redirect时要注意的事情****
根路由redirect的路由必须是所有用户都有权限访问到的,调试时mock数据注释了dashboard,导致页面显示报错;因为执行GenerateRoutes中的filterAsyncRouter的会改变asyncRouterMap,dashboard/workplace路由已经被过滤掉了,但是又重定向到了dashboard/workplace
5、 菜单/路由权限方案有哪些****
先定义几个概念:
routes:系统中存在的所有登录后可以访问的路由(不针对某个用户)。跟1中asyncRouterMap作用基本一样
asyncRoutes:(经过处理后得到的)当前用户登录后可以访问的路由
路由方案:包括了路由器routes属性的值、路由导航钩子的逻辑
菜单方案:v-for遍历用什么数据,遍历过程中是否需要判断getPermission的返回
最终想要通过路由方案和菜单方案实现用户登录后只能看到自己有权限看到的菜单和按钮,只能访问自己有权限访问的路由,如果直接在浏览器中输入越权路由,路由导航也能拦截
| All: 不用router.addRoutes直接给routes属性配置全部路由 | 使用router.addRoutes动态加入权限路由 | ||
|---|---|---|---|
| 完全从后台管理系统返回 | 关键信息从后台管理系统返回 | ||
| All:遍历所有路由routes | √,推荐这是最简单的方式,参考海航法务管理系统导航钩子判断能否进入;菜单渲染时也要执行getPermission; | 不推荐路由(路由name、url、组件路径、展示名称必填)都配置在后台管理系统中了,后台管理系统返回的就是不需要筛选逻辑处理的asyncRoutes,前台系统中也就不用定义routes了,目前没觉得这种完全从后台返回的方式有什么好处,反而是开发中比较麻烦 | ×后台管理系统的‘菜单管理’模块要严格按照前台系统的设计图来录入,还需要和前台管理系统约定统一的路由name或者其他meta标识字段,前台系统写筛选逻辑时需要用到该标识。geiInfo时后台可以只返回permissionID数组,也可以返回简单的permissionListd对象数组,前台系统写筛选逻辑,从routes中筛选出筛选出权限路由,但如1中所述,筛选的过程中会改变全局变量,所以执行了筛选逻辑后routes已经不是All了 |
| 遍历asyncRoutes | √理论上可行,但筛选逻辑有时候还是挺复杂的,获取permissionID比较简单,与其用这种方式还不如用all+all | √参考案例库后台管理系统,渲染菜单时直接用后台管理系统返回的asyncRoutes,前台系统中需要处理一下加载组件的逻辑(require);菜单渲染时不需要getPermission | √原理同上,参考自己写的auth,渲染菜单时用的就是筛选逻辑得到asyncRoutes;菜单渲染时不需要getPermission |
三种方案:
1、最简单的,参考海航法务管理系统,前端写死路由和菜单,后端返回权限标识,做路由导航控制和菜单显示隐藏判断
2、中等难度的,参考ant-design-pro,前端存一份全量路由,每个页面的信息都是在前台写死的,后台系统通过tree组件做配置,根据后端返回的权限标识过滤,最后利用vue-router提供的动态路由方法addRouter添加上去
3、最高难度,参考牛二的案例库后台系统case-admin,前端啥都不存,如下图,每个页面的信息都是动态从后台系统配置的(路由、组件路径,是否作为菜单),后台系统通过tree组件做配置,返回给前台系统后,利用vue-router提供的动态路由方法addRouter添加上去
6、 按钮级权限控制****
v-if和自定义指令实现对比:
参考ant-design-vue-pro项目中v-action的
先删除v-if
D:\test-longi\ant-design-vue-pro\src\mock\services\user.js中返回的数据就是权限数据
从数据可以看出,列表页只有四种权限按钮:add, import,get,update,而v-action:delete中的delete并不在这个范围内,所以不显示
指令的核心就是removeChild
7、 不能用v- action 自定义指令实现时该怎么办****
在某些情况下,不适合使用 v-action,例如 Tab 组件,只能通过手动设置 v-if 来实现。
<a-tab-pane :tab="auth('dashboard.delete')">
$auth是函数形式的自定义指令,接收字符串,返回true或者false
U NI-APP****
发行****
貌似第一次用uniapp发行的时候并没有让安装微信开发者工具,但10月25日再次上手练习的时候竟然让安装微信开发者工具。
1、 手动复制安装包
打包完可以将apk复制到手机中,在手机的“文件管理”中找到apk,手动安装;
2、或者打包成功后点击“一键上传到uniCloud”,会把安装包上传到uniCloud,上传成功后会在IDE的控制台返回安装包下载地址,把这个地址复制到草料二维码,生成二维码图片,别人扫码就可以下载APK
第一次点击“一键上传到uniCloud”后,没有云空间,需要点“+新建”,下图中“uni”就是新建出来的一个云空间
C SS****
1、绝对定位****
1、绝对定位中,top /left/right/bottom 是参考谁确定的?****
父元素相对定位,子元素绝对定位
left/right/top/bottom的值是父元素的padding外侧 与 子元素的margin外侧的距离,
2 、position:absolute;相当于display:inline-block;****
包裹内容,可以设置宽和高,左上角位置不动(设置了top、bottom、left、right左上角位置才会动)
2、 如何围住绝对定位的元素****
4、同样是脱离文档流,浮动和定位有什么区别****
浮动和定位的区别:
浮动还会占据原来的位置,浮动还在原来的文档流中,只是会移动位置
定位会脱离文档流,不占据原来的位置,相当于直接脱离了原来的文档流,视觉上相当于换了一个z-index
准确的说,float浮动属于半脱离文档流, float虽然脱离了文档流但是仍然会占据位置,和父元素的原点还是没有变,父元素相对定位到新的地方,浮动的子元素也会跟着移动,只是父元素没法围住float的子元素
子元素绝对定位后彻底脱离文档流,子元素的高度不会再撑开父元素
5、设置绝对定位absolute后设置的百分比宽度和高度是以谁为参照呢?****
设置了绝对定位的元素,给该元素的宽高设置的百分比是相对于有设置定位属性的父级元素来计算的,但是如果它的父级元素中没有人设置了定位,就默认以浏览器上屏幕高度来计算,具体应该是以window.innerHeight(谷歌浏览器下测试的)这个值为参照计算的!!!
不是以body的高度来计算的!!!
2、浮动****
1 、设置浮动float后设置的百分比宽度和高度是以谁为参照呢?****
以父元素为参照
2 、行内元素设置float相当于display: block;****
用jQuery可以在控制台直观看出display属性
$(‘span’).css(‘display’)
3、 父元素absolute,子元素float,父元素为什么会被撑开?****
利用父元素overflow: hidden;撑开
利用父元素absolute撑开;
其实父元素absolute并不是撑开了,只是类似于inline-block元素紧紧包裹住子元素而已
3、width****
给某个元素设置了width,仅仅是指内容区域的宽度
4、在一个元素上同时使用绝对定位和flex布局的坑****
5、垂直对齐vertical-align和基线****
.box {
border: 2px solid skyblue;
height: 200px;
}
.child {
display: inline-block;
width: 100px;
height: 100px;
margin-right: 10px;
}
.first {
background-color: green;
}
.second {
background-color: purple;
}
.third {
background-color: orange;
}
结果并没有对齐
原因
我们知道,行内块元素默认对齐方式是基线对齐 verticle-align:base-line
对于一个inline-block行内块元素,如果内部没有inline内联元素,或者overflow不是visible,则该元素的基线就是它margin的底边缘
上面例子绿色和黄色盒子的基线是他们的底边,紫色盒子的基线由数字2决定了,所以他们基线对齐之后就乱了
解决办法
加 verticle-align: top / middle / bottom**
.child { vertical-align: top; }
6、换肤****
1、在common.scss中定义颜色变量,用到了自定义属性theme-mode,属性值选择器。自定义css属性(变量)前面以--开头
2、在全局样式index.scss中引入common.scss
3、在main.js中引入全局样式index.scss
import './styles/index_dark.scss'
4、 在store的state中设置skinType状态,并给一个默认值
skinType:'light'//'dark'和'light'
5、 切换皮肤后,改变store.state.skinType的值
6、在APP.vue中监听skinType的值,改变body的theme-mode值,浏览器中就会加载对应的css变量
7、echarts图表需要重新绘制,因此绘制echarts的地方需要监听skinType,重新执行画图操作
8、行内样式、组件内的样式,都可以直接用var(--variable-name)的方式引用定义的全局css变量。
js文件中需要使用getComputedStyle(document.body).getPropertyValue('--variable-name')的方式引用定义的全局css变量。有时候也可以用var(--variable-name),但这种方式不稳定。
Vue3****
1、 main.js
const app = createApp(App);
2、 响应式原理
熟悉Vue2的同学都知道Vue2的响应式是在get中收集依赖,在set中触发依赖,Vue3想必也不例外,按照这个思路我们的实现步骤如下:
1、在触发get时收集effect函数传入的回调,这里我们称这个回调为ReactiveEffect
2、在set和deleteProperty...时触发所有的ReactiveEffect
3、www.cnblogs.com/msjhw/p/157…文中说响应式在性能方面的优化其实是体现在把嵌套层级较深的对象变成响应式的场景。在 Vue 2 的实现中,在组件初始化阶段把数据变成响应式时,遇到子属性仍然是对象的情况,会递归执行 Object.defineProperty 定义子对象的响应式;而在 Vue 3 的实现中,只有在对象属性被访问的时候(自己调试一下vue3响应式demo就能理解)才会判断子属性的类型来决定要不要递归执行 reactive,这其实是一种延时定义子对象响应式的实现,在性能上会有一定的提升。
blog.csdn.net/weixin_5078…响应式原理
区别
| vue2 | vue3 | |
|---|---|---|
| 响应式原理 | 数据劫持(Object.defineProperty)+观察者模式 | 数据代理(ES6中的Proxy)+发布订阅模式 |
| 如何把嵌套层级较深的对象变成响应式 | 需要递归到底,一次性计算量大不管一个data对象嵌套多深,也不管该属性是否被订阅,只要是data中存在的属性,都会在最开始初始化的时候递归调用observe,把他转为访问器属性, | 只有在对象的属性被订阅的时候才会判断子属性的类型来决定要不要递归执行 reactive |
| ü 监听对象的新增属性 | 无法监听新增属性(需要vue.set) | 可以监听对象属性的新增 |
| ü 监听对象的删除 | 无法监听删除属性(需要vue.delete),访问器属性只有get和set,没有删除相关的函数,这是语法层面的限制,而发布订阅模式又是依赖于get去收集订阅者,在set中发布消息,delete操作既不会触发get,也不会触发set。延伸阅读:vue.delete | 可以监听对象属性的删除,proxy 有专门针对属性删除的方法 deleteProperty,可以在对象属性被删除时触发。 |
| 当你利用索引直接设置一个数组项时 | 出于性能考虑,没有做 | 不仅会显示最新增加的数据项,而且会显示数组长度也改变 |
| 当你修改数组的长度时 | 无法监听 | 可以监听 |
| ü 监听数组的改变 | 数组的 push、pop、shift、unshift、splice、sort,reverse是无法触发 set 方法的,需要用到变体方法 | proxy ****可以且不需要对数组的方法进行重载 |
3、 v-model
4、 vue2、vue3在监听数组和对象方面的区别
vue中实现响应式的核心代码:
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log(get key: ${key} val: ${val})
return val
},
set(newVal) {
console.log(set key: ${key} val: ${newVal})
val = newVal
}
})
}
function observe(data) {
Object.keys(data).forEach((key)=> {
defineReactive(data, key, data[key])
})
}
Object.keys只会获取数组index属性('0', '1', '2'等,),然后使用Object.defineProperty重新定义为访问器属性
1、检测数组方面的区别:
vue2不能检测的数组变化包括
(1)当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue(注意不是vm.items[indexOfItem].property = newValue)
这点不是因为defineProperty的局限性,而是出于性能考量的
执行vm.items[2]={message: '777'}后,控制台显示如下
Vue.set(vm.items, indexOfItem, newValue)
vm.$set(vm.items, indexOfItem, newValue)
例如:
vm.items.splice(indexOfItem, 1, newValue)
例如:vm.items.splice(2, 1, {message: '777'})
在vue2中想检测数组的这类变化,可以用Vue.set或者变更方法实现
但在vue3的页面中,执行vm.items[2]={message: '777'}后显示如下
不仅会显示最新增加的数据项,而且会显示数组长度也改变
(2) 当你修改数组的长度时,例如:vm.items.length = newLength
这是因为Object.keys只会获取数组index属性('0', '1', '2'等,),不能获取到length属性,且数组的length属性是不能修改为访问器属性的,所以改变数组的length,是不会触发set函数的
<meta charset="UTF-8">
<div id="array-rendering">
-
{{ item.message }}
-->
<!--
<script src="unpkg.com/vue@next">
const app = Vue.createApp({
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
})
const vm = app.mount('#array-rendering')
执行vm.items.length = 6后,vue2中还是显示2
但在vue3中会显示6
2、检测对象方面的区别:
vue2不能检测的对象变化包括:Vue 不能检测对象属性的添加或删除,必须借助Vue.set和Vue.delete这两个API来实现:
Vue.delete( vm.obj, 'name' ),Vue.set( vm.obj, 'age', 55 )
Vue 无法检测到对象属性的添加或删除。
添加:由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的.所以新添加的属性不具有响应式;而vue3并不是在初始化时,也不是getter/setter 转化,而是代理,只要模板中用到了该属性,都能通过代理进行get/set
删除:访问器属性只有get和set,但proxy有deleteProperty
delete vm.obj.name,vm.obj.age=55
4、在template上使用v-for
vue2中,在template上使用v-for不能添加key,应该在真实元素上添加key
但在vue3中,在template上使用v-for时可以直接在template上添加key
- {{ item.msg }}
<template v-for="item in items" :key="item.msg">
<li class="divider" role="presentation">
5、v-for与v-if一同使用
| vue2 | Vue3 | |
|---|---|---|
| v-for和v-if同处一个节点时 | v-for 的优先级> v-if 的优先级 | v-if的优先级> v-for 的优先级 |
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
以上代码在vue2中能正确执行,但在vue3中会报错,因为vue3中优先执行v-if,就会报“todo”在渲染的时候被访问了,但是实例上并没有定义
****
6、事件监听
( 1) 新增emits属性****
和 prop 类似,现在可以通过 emits 选项来定义组件可触发的事件,这个属性可有可无
( 2) 多事件处理器****
事件处理程序中可以有多个方法
<button @click="one(event)">
Submit
// ...
methods: {
one(event) {
// 第一个事件处理器逻辑...
},
two(event) {
// 第二个事件处理器逻辑...
}
}
( 3 ) 删除sync修饰符
7、组件
( 1) 注册组件****
| vue2 | Vue3 | |
|---|---|---|
| 注册组件 | const app = Vue.createApp({...})app.component('my-component-name', { /* ... */}) |
( 1) 组合式A PI****
区别于vue2.x中的选项式API或者配置项式API
是对mixin的改进(为什么)
只有被return的变量才能在模板中显示
8、provide/inject
( 1) 添加响应式方式不同****
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,可以通过一些方法来实现响应式
在vue2中,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的
在vue3中,为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 ref 或 reactive
9、ref和reactive
所谓响应式,就是get或者set对象的属性值时,都能触发对应的回调,在回调里进行某些操作
二者都是用来定义响应式数据的
1、ref的原理****
const toReactive = (value) => isObject(value) ? reactive(value) : value;
如果ref传入多层对象,底层也是会去调用reactive去把它变成响应式;
如果传入基本类型,就会通过class的取值函数和存值函数,拦截属性的读取行为(注意:不是Object.defineProperty中的get和set),其中trackRefValue是用来收集依赖的,用到了观察者模式。如下,ref函数返回的是一个对象,该对象是RefImpl类的实例,该对象有value属性
· reactive你如果放普通类型,也就是非对象会直接返回,这个原因可以从我之前的博文中查找,或者直接看源码
· ref的出现就是因为reactive处理不了基本类型
15、toRef和toRefs
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')//只能将响应式对象上的某个 property 新创建一个 ref
const stateAsRefs = toRefs(state)//不全是因为要用在解构赋值的时候
1 、vue 3 中为什么需要to Refs ?****
参考哈默的bilibili视频讲解
解构后我们就不需要用 对象.属性 了,而是可以直接使用属性名,这样就简化模板;但是直接对响应式对象(reactive封装得到的proxy对象)进行结构赋值,会使对象会失去响应性,所以要使用toRefs再包装一下
2、为什么对响应式对象进行解构赋值,会使对象会失去响应性****
vue3的响应式是通过Proxy实现的,所谓响应式对象,就是vue3中的reactive函数返回的proxy对象。
先看最简单情况
const obj = {
count: 1
};
const proxy = new Proxy(obj, {
get(target, key, receiver) {
console.log("这里是get");
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log("这里是set");
return Reflect.set(target, key, value, receiver);
}
});
执行
proxy.count = 2;
打印出
然后解构出来再赋值试试
let {count} = proxy;
count = 2;
打印出
可见解构后得到的变量,给这个变量设置值时并不能触发set回调,说明解构后的变量失去了响应性
再看一个稍微复杂点的情况
const obj = {
a: {
count: 1
}
};
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
console.log("这里是get");
if (typeof target[key] === "object") {
return reactive(target[key]);
};
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log("这里是set");
return Reflect.set(target, key, value, receiver);
}
});
};
const proxy = reactive(obj);
执行
proxy.a.count = 2;
打印出
先只解构一次看看
执行
let { a } = proxy;
a.count = 3;
打印出
也很好理解,因为reactive函数中做了判断,如果属性还是个对象,会递归调用reactive,将该属性转为proxy;如果不是对象,就不会再转换,而是直接返回值;因为a属性还是对象,所以最终生成的targetMap中有一个集合项就是a对象,所以只解构一次得到的变量a任然是个proxy对象;可以调试一下vue3的源码demo,看看targetMap
再解构到底试试
执行
let { a } = proxy;
let { count } = a;
count = 3;
打印出
核心原因是,在响应式转换的过程中,a.count不是对象,所以不会再递归调用reactive函数,响应式转换是递归调用reactive函数的过程,相当于给obj穿上一层层衣服;解构是一层层脱衣服,穿衣服的过程中没有被转为proxy的属性,脱衣服的过程中当然也不会是proxy
3 、 t o Ref 的内部原理****
如下,toRef(target,key)函数返回的是一个对象,该对象是ObjectRefImpl类的实例,该对象有value属性,value值就是target[key]
const fooRef = toRef(state, 'foo')
fooRef.value = state.foo //更改源属性state.foo也会更新该 fooRef.value
在模板中使用ref时,不用写.value
ObjectRefImpl相当于我们将某一个key对应的值,转化为ref,就是暴露出一个属性的代理出去,并不会做其它事情,所以这个属性是否是响应式的,取决于代理的对象target是否是响应式的。
5、 t o Refs 的内部原理****
遍历对象属性,循环调用toRef
{
age:23,
name: ‘test’,
calss:3
}
{
age: toRef(obj, ‘age’),
name: toRef(obj, ‘name’),
calss: toRef(obj, ‘class’),
}
最后解构上面的对象就是
return
{
age: toRef(obj, ‘age’),
name: toRef(obj, ‘name’),
calss: toRef(obj, ‘class’),
}
在模板中就可以使用age,name,class了
6、 to Refs 可以接收普通对象吗****
可以,典型的例如const { user } = toRefs(props),
toRefs的主要作用就是为了不需要用 对象.属性 了,而是可以直接使用属性名
但是,通过toRef或者toRefs定义的变量,只是对原对象属性的一种引用,通过该变量定义的计算属性或者watch并不是响应式的,如下图中的test,如果是等于rGreet2.value,点击按钮改变rGreet2.value,也不会改变页面上的test,也监听不到456
10、全局API
( 1)Vue.observable 被Vue .reactive 取代****
( 1) off和$once实例方法已经被移除****
11、自定义指令
( 1) 钩子函数****
2) 钩子函数参数****
钩子函数参数
| vue2 | vue3 | |
|---|---|---|
| 钩子函数 | bind、inserted、update、componentUpdated、unbind | created、beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted |
| 钩子函数参数 | el、binding、vnode 和 oldVnode | el、binding、vnode 和 prevVnode |
12、全局属性
( 1) $ attrs****
attrs
$attrs 现在包含 class and style attribute
13、三大框架的区别
1. 在Angular中是通过脏值检查流程来实现变化侦测;
2. 在React是通过对比虚拟DOM来实现变化侦测;
3. vue中通过js提供的Object.defineProperty来实现变化侦测;
14、异步组件
异步组件是指在需要时才会被加载的组件。
在Vue2中,异步组件也是通过Vue.component(‘id’, 工厂函数)来定义的。工厂函数中可以使用require()或者import()来返回一个Promise对象
在Vue 3中,异步组件可以是通过使用import()函数动态导入组件,也可以通过使用defineAsyncComponent()方法创建异步组件。
15、ref和reactive响应式数据赋值问题
1、用ref更简单
const formState = ref({
name: '',
description: ''
});
const detail = await procedureTelDetail({ procedureId: props.id });
formState.value = detail?.data || {};
2、用reactive时可以一个一个属性赋值
const formState = reactive({
name: '',
description: ''
});
const detail = await procedureTelDetail({ procedureId: props.id });
formState.name = detail?.data?.name;
formState.description = detail?.data?.description;
formState.procedureId = detail?.data?.procedureId;
或者如下
16、defineExpose
导出的变量或者方法只能在mounted生命周期后使用
Type Script****
1、import type和import
import type 是编译阶段用来协助进行类型检查和声明的,通过这种方式导入的东西在运行时是完全不存在的
待解决****
1 、iframe中的fixed定位****
参考高德地图的按钮部分
E S6****
1、对象中的属性能不能是箭头函数****
最好不要,因为箭头函数的this比较特殊,在对象的属性中使用箭头函数虽然不至于报错,但有时候得不到预期的效果,ES6文档中也有说明
2、globalThis****
在浏览器中运行,window就是globalThis
D htmlx Gannt****
1、task和project的progress****
默认task和project的progress是没有关系的,需要各自明确指定自己的进展,如果你想根据task的进度自动计算project的进度,需要自己编码实现