const obj = { a:1, b:2, c:3, d:4, e:5, }
const {a:a1} = obj;
console.log(a1);// 1 解构赋值
去重
const a = [1,2,3];
const b = [1,5,6];
const c = [...new Set([...a,...b])];//[1,2,3,5,6]
const obj1 = {
a:1,
}
const obj2 = {
b:1,
}
const obj = {...obj1,...obj2};//{a:1,b:1}
Set 对象存储的值总是唯一的
| 方法 | 描述 |
|---|---|
| add | 添加某个值,返回Set对象本身。 |
| clear | 删除所有的键/值对,没有返回值。 |
| delete | 删除某个键,返回true。如果删除失败,返回false。 |
| forEach | 对每个元素执行指定操作。 |
| has | 返回一个布尔值,表示某个键是否在当前 Set 对象之中。 |
交集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
差集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set([...a].filter(x => !b.has(x))); // {1,4}
const nestedArray = [1, [2, 3], [4, [5]]];
const flattened = nestedArray.flat();
console.log(flattened); // 输出: [1, 2, 3, 4, [5]]
// 注意:深度默认为 1,所以 [5] 未被展开
数组扁平化 flat() 可以搭配Object.values()使用,Object.values(deps).flat(Infinity);其中使用Infinity作为flat的参数,使得无需知道被扁平化的数组的维度。
flatMap
扁平化和map的组合 let arr = ["科比 詹姆斯 安东尼", "利拉德 罗斯 麦科勒姆"]; console.log(arr.flatMap(x=>x.split(' '))) // ['科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆']
可选链操作符 ?. const name = obj?.name;
可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空sh) ) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。
空值合并运算符 ??
if(value??'' !== ''){
//...
}
?? 左边为null或者undefined的时候返回右边
console.time 和 console.timeEnd 两个方法是结合在一起使用的,他们接受一个相同的参数,输出两句表达式中间的代码的执行时间。
console.time('计时器1');
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < 1000; j++) {
}
}
console.timeEnd('计时器1');
console.table 会将复合数据类型(对象,数组等)在控制台中以表格的形式打印输出,并且你可以将对象数组嵌套乃至结合使用,他都能够将其解析为表格形式
console.trace
追溯你的逻辑执行过程,追溯执行过什么函数
function d(a) { console.trace(); return a; }
function b(a) { return c(a); }
function c(a) { return d(a); }
let a = b('123');

当逻辑执行到let a = b('123') 语句时,b函数会调用c函数,c函数再调用d函数,d函数中调用了console.trace(),这时console.trace()会将整个执行过程自底向上追溯打印出来。
修改数组,视图不变特殊方法 this.arr = this.arr.map(item => item) 把数组重新赋值一下
vue响应式
需要注意的是只有提前在 data 中添加的数据才是响应式数据,在其他地方往 data 中塞的数据不是响应式的,也就是未来该数据更新的时候并不会触发试图的更新。
如果后续想添加响应式数据,可以通过 Vue 提供的 set 方法。
默认对象采用函数返回
错误的
const default = {
name: '',
}
export default {
data() {
return {
a:default,
b:default
};
},
created () {
this.a.name = 'demo'
// 值的混淆
console.log(this.b) // {name:demo}
}
};
函数返回
const default = () => ({
name: ''
})
export default {
data() {
return {
a:default(),
b:default()
};
},
created () {
this.a.name = 'demo'
// 每次返回的新值
console.log(this.b) // {name:''}
}
};
箭头函数缩写

省略return的写法 const a = () => { return { demo: '123' } } 省略 const a = () => ({ demo: '123' })
if判断多个是否等于
v-if="[2,1,-1].includes(item.casestatus)"
隐性转换
只有null,undefined,0,false,NaN,空字符串,这6种情况转为布尔值的结果为false,其余都是true , 0 === false 为false而 0 == false为true ,1 === true (false) 1== true (true)同理
三元特殊情况
如果三元运算符中某一部分成立不需要任何的处理我们用null/underfined/void 0...占位即可 var num=12; num>10?num++ : null; 如果需要执行多项任务,我们用小括号包裹起来,每条语句用逗号隔开 num=10; num>10?(num++,num*=10):null;
多重三元
判断1 ? "返回1":判断2?"返回2":"否则返回3"
解构参数
function setCookie(name, value, options) {
options = options || {};
let secure = options.secure,
path = options.path,
domain = options.domain,
expires = options.expires;
//其他代码
}
因为options有多次使用到,是个对象类型,但是使用者无法知道是个对象类型,不方便。所以可以直接在参数那结构赋值。
function setCookie(name, value, {secure,path,domain,expires}) {
let secure = secure,
path = path,
domain = domain,
expires = expires;
}
也可以给整个options默认值
function setCookie(name, value, {secure,path,domain,expires}= {}) {
}
也可以给整个解构赋值默认值
function setCookie(name, value, {secure='default',path,domain,expires}= {}) {
}
Objecct.defineProperty
const person = {}
Object.defineProperty(person, 'name', {
value: 'jack'
}) // 三个参数都是必填
console.log(person.name) // jack
除了value,其他值没设置的话,writable,configurable, enumerable会默认给false writable 可编写属性,设置false,就变成只读,configurable总开关,控制是否可以删除和配置属性值,false为不能,当configurable为false时,当writable为true时,可编辑,enumerable为false时,不会出现在遍历中
const person = {}
// 必须定义一个变量来存修改的值,set中直接返回的话,会陷入无限循环
let values = null
Object.defineProperty(person, 'name', {
set (newVal) {
console.log('赋值的时候调用' + newVal)
values = newVal
},
get () {
console.log('取值的时候调用')
return values
}
})
person.name = 3
console.log(person.name) // 3
日期格式转换
日期格式化 toLocaleDateString() 可以把new Date()转化成 2021/12/6 这种。toLocaleString() 是 2021/12/6 下午2:54:54这种。toLocaleTimeString是下午2:54:54
数组处理 reduce
给数组执行回调
4个参数
1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用 reduce 的数组)
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0)
// 0是previousValue的初始值
console.log(arr, sum);
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10
ES异步
function fn (time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(time) // 3000 1000 1000 2000 500
resolve(`${time}毫秒后我成功啦!!!`)
}, time)
})
}
// 数组里面可以直接放事件, for of 循环可以带上异步,循环时会自动调动数组里面的事件
async function asyncFn () {
const arr = [fn(3000), fn(1000), fn(1000), fn(2000), fn(500)]
for await (let x of arr) {
console.log(x) // 这里输出的是resolve的值
//3000毫秒后我成功啦!!! 1000毫秒后我成功啦!!! 1000毫秒后我成功啦!!! 2000毫秒后我成功啦!!! 500毫秒后我成功啦!!!
}
}
asyncFn()
JavaScript数据类型8种,基础6种,es6加的Symbol 和 es10的BigInt
ES 数字分隔符 _
const number = 192_123_123 console.log(number)// 192123123
es Symbol 新增的基础数据类型,Symbol是唯一的,因此可以做object唯一属性,防止被覆盖。但是object取Symbol属性的时候,不能用.来读属性,只能用[],symbol属性不会被for循环循环到,也不会被Obeject.keys()循环到,Object.getOwnPropertySymbols 可以拿到对象的Symbol集合数组
prototype的constructor直接指向类的本身
position定位属性,sticky粘性定位,监听scroll事件,在滑动过程中,达到了要求粘性距离,效果就会相当于fixed定位,固定到某个位置。
hasOwnProperty新应用,直接引用会报错,新引用方法,Object.prototype.hasOwnProperty.call(obj,'aaa')
slice第一个参数是开始位置,第二个参数是结束位置,为负数的话则是从末尾开始选择 '123456' 的slice(1,-1)返回 2345
粘性布局 sticky
类似absolute和fixed定位的混合,元素在跨越特定阈值前为相对定位,之后为固定定位
.sticky-header { position: sticky; top: 10px; }
在 视口滚动到元素 top 距离小于 10px 之前,元素为相对定位。之后,元素将固定在与顶部距离 10px 的位置,直到视口回滚到阈值以下。
不生效的情况
情况1: 未指定 top, right, top 和 bottom 中的任何一个值
情况2: 垂直滚动时,粘性约束元素高度小于等于 sticky 元素高度
情况3: 粘性约束元素和容器元素之间存在 overflow: hidden 的元素
回调函数的妙用
作为参数传入

背景图调整
根据屏幕大小,自动调整背景图 background-size: cover 也可以设置高度固定,宽度自适应background-size: 100% 140px;
import引用
import 语句用于引入资源,而是否需要在 data 中定义这些资源取决于它们如何被使用。如果资源是组件、常量、函数或全局状态的一部分,通常不需要在 data 中定义;如果资源是组件的本地状态或需要被模板绑定的数据,则需要在 data 中定义。
map根据条件判断return
map本身除了抛错不能中断循环,可以加条件再return,但是会返回underfine,这时候可以跟filter配合,.filter(item => item) 可以去除数组里面的null,'',undefined
this.menuLists = menu.map(res => {
if (res.meta?.hideInMenu !== true) {
return { name: res.meta.title, url: res.path }
}
}).filter(item => item)
根据屏幕大小自适应字体大小
用VW
node版本过高,导致项目启动报错
error:0308010C:digital envelope routines::unsupported
最近在升级nodejs版本到v18.7.0后启动项目报digital envelope routines::unsupported因为 node.js V17版本中最近发布的OpenSSL3.0, 而OpenSSL3.0对允许算法和密钥大小增加了严格的限制,可能会对生态系统造成一些影响。故此以前的项目在升级 nodejs 版本后会报错
解决方法
方法一:
export NODE_OPTIONS=--openssl-legacy-provider **(试过好像无效)**
方法二:
修改package.json,在相关构建命令之前加入set NODE_OPTIONS=–openssl-legacy-provider
"scripts": {
"serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build"
},
Object.defineProperty(obj, prop, descriptor)
定义新属性
//默认是不可枚举的(for in打印打印不出来),可:enumerable: true
//默认不可以修改,可:wirtable:true
//默认不可以删除,可:configurable:true
let person = {}
let personName = 'lihua'
//在person对象上添加属性namep,值为personName
Object.defineProperty(person, 'namep', {
//但是默认是不可枚举的(for in打印打印不出来),可:enumerable: true
//默认不可以修改,可:wirtable:true
//默认不可以删除,可:configurable:true
get: function () {
console.log('触发了get方法')
return personName
},
set: function (val) {
console.log('触发了set方法')
personName = val
}
})
//当读取person对象的namp属性时,触发get方法
console.log(person.namep)
//当修改personName时,重新访问person.namep发现修改成功
personName = 'liming'
console.log(person.namep)
// 对person.namep进行修改,触发set方法
person.namep = 'huahua'
console.log(person.namep)
可以添加get set方法来监听属性变化
如果要监听多个属性,就只能进行遍历,但是会导致栈溢出,在访问person身上的属性时,就会触发get方法,返回person[key],但是访问person[key]也会触发get方法,导致递归调用,最终栈溢出。,我们需要设置一个中转Obsever,来让get中return的值并不是直接访问obj[key]。
滚动条样式修改
overflow:auto 是根据超出不超出显示滚动条,scroll是一直显示滚动条
设置overflow:auto的时候注意,会上下左右都能移动,如果只要横向,只用设置overflow-x:auto就行了
innerbox是有滚动条的元素
.innerbox {
width: 100px;
height: 300px;
overflow-x: hidden;
overflow-y: auto;\
}
/*滚动条样式*/
.innerbox::-webkit-scrollbar {/*滚动条整体样式*/
width: 4px; /*高宽分别对应横竖滚动条的尺寸*/
height: 4px;
}
.innerbox::-webkit-scrollbar-thumb {/*滚动条里面滚动条*/
border-radius: 5px;
\-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background-color: rgba(0,0,0,0.2);
}
.innerbox::-webkit-scrollbar-track {/*滚动条里面轨道(背景)*/
\-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 0;
background-color: rgba(0,0,0,0.1);
隐藏滚动条 .demo::-webkit-scrollbar { width: 0 !important; }
localStorage,sessionStorage
localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。
sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了。
不同浏览器无法共享localStorage或sessionStorage中的信息。相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享sessionStorage的信息。这里需要注意的是,页面及标 签页仅指顶级窗口,如果一个标签页包含多个iframe标签且他们属于同源页面,那么他们之间是可以共享sessionStorage的。
calc无效
1. height:calc(100vh-60); 无效
2.height:calc(100vh-60px); 无效
3.height:calc(100vh - 60px); 终于起效
总结calc()使用
- 必须加上单位
- 必须在运算符左右用空格隔开
删除字符串中的字符
使用replace()方法
string.replace('characterToReplace', '');
使用splice()方法
// 删除第一个字符
string.splice(1);
// 删除最后一个字符
string.splice(0, string.length - 1);
给默认参数之外添加参数
<template slot="demo" slot-scope="text, record, index" >
<a-date-picker @change="dateChange.call(this, ...arguments, 自定义参数1, 自定义参数2)" />
</template>
// or
<template slot="demo" slot-scope="text, record, index" >
<a-date-picker @change="dateChange.apply(this, [...arguments, 自定义参数1, 自定义参数2])" />
</template>
对应的事件代码:
dateChange(date, dateString, 自定义参数1, 自定义参数2) {
// ...
}
CSS3变量
css3变量可以放到root里面,html文档任何地方都能访问,也可以放在class里面,其他class访问不到,并且自定义属性值可以继承父元素,如果本身未定义自定元素的话
css3备用值,background-color: var(--my-var, var(--my-background, pink));当自定义属性无效时,可以使用第二个备用值,备用值可以使用逗号,从第一个逗号到最后的全部内容,都当做备用值的一部分
修改css3值
getPropertyValue //获取元素的某个css属性值
setProPerty //设置元素的css属性
removeProperty //移除元素的css属性
例如:
document.documentElement.style.getPropertyValue('font-size') //获取字体大小js
document.documentElement.style.getPropertyValue('--font-size') //也可以是css变量
这种方式只能获得内联样式,如果无内联样式则获取到的值为空,与document.documentElement.style.fontSize相同,都是只能获取内联样式属性值
getComputedStyle(node) //此方法获取元素的所有样式表,是一个大对象
//如果getPropertyValue方法用在此处则可以获取到元素的真实样式
//(即哪个样式的权重高,生效的样式,获取的就是那个值)
getComputedStyle(document.documentElement).getPropertyValue()
vue2中使用css变量
<template>
<!-- 如果要该组件都可以使用,则必须放置在根元素下 -->
<div class="hello" :style="styleVar">
<div class="child-1">I am Child 1</div>
<div class="child-2">I am Child 2</div>
<div @click="onClick">Change Red TO Blue</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
data() {
return {
styleVar: {
"--colorBlue": "blue",
"--colorRed": "red",
"--fontSize": "30px",
"--fontSizeTest": "30px",
},
};
},
methods: {
onClick() {
this.styleVar["--fontSizeTest"] = "40px";
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child-1 {
color: var(--colorBlue);
font-size: var(--fontSize);
}
.child-2 {
color: var(--colorRed);
font-size: var(--fontSizeTest);
}
</style>

权限控制
简单讲下我司的系统(大型B端后台):
1、权限控制: 根据不用用户编辑不同的页面权限 - 精确到按钮权限、甚至某一个文案的权限;
2、用户登录(刷新)后会请求权限接口,分为menulist 和 codelist;
3、menuList返回的参数较多,可用于动态路由等等参数,当前也包含当前路由是否有权限或 无权限菜单接口干脆就不返回;
4、codelist 就是个string []格式,如某个权限为"ec#app.store.stylize.shareSetting" , 当然,按钮权限也可以同时满足多个codelist 条件控制,(个人认为接口返回json格式,权限 "ec#app.store.stylize.shareSetting" 为key更优);
5、利用前端localhost或其他简单做一下权限缓存;
6、然后在页面渲染的时候,判断下页面路径、按钮、或者某个字段的权限是否展示。
npm run build 之后生成的index.html页面打开为空白
这个报错很显然是找不到文件,因此我猜可能是构建项目过程中路径出错了。打开index.html的源码来看,发现路径用的是’/‘,但index.html文件和js文件夹是同级目录,如果从index.html进入到js文件夹内的文件,需要用’./'。
也就是说,对比dist文件夹结构可以看到资源路径的引入是错误的,应该用’./‘而不是’/'。
结合了百度,发现一个有效的解决方案:
在项目根目录下创建一个vue.config.js文件,写入:
module.exports = {
publicPath: './',
}
然后再次npm run build进行打包。打包完成后,再打开dist文件夹内的index.html文件,就可以正常显示项目了。
手机浏览器看报错信息
在页面加入两行代码即可搞定:
<script src="*https:* //cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script>
修改placeholder 颜色
::placeholder
.ant-time-picker-input[disabled]::placeholder {
color:red; //颜色
visibility: hidden !important; // 隐藏
}
lib包打包
npm run lib
npm publish
js异常处理
可以抛异常 throw new Error('异常信息') 然后处理异常
try {
foo()
console.log('我不会继续执行')
} catch (error) {
console.log('error:', error.message)
} finally {
console.log('我无论是否有异常抛出都会执行')
}
updated 生命周期
不管是通过父组件props接收的数据还是组件本身data里的数据,只要在页面中使用这些数据,这些数据变化,都会触发组件的updated生命周期;如果数据不在页面中使用,那么不会触发组件的updated生命周期。
grdi设定每格占位
grid-column-start: span 3 设定元素占3小格
页面自适应
function () {
// 当前浏览器字体大小
const resizeEvent = 'orientationchange' in window ? 'orientationchange' : 'resize'
const reCalc = function () {
// 根节点
const documentElement = document.documentElement
// 当前屏幕宽度,通过获取当前浏览器宽度来修改font-szie,以此来修改rem大小
const clientWidth = documentElement.clientWidth
if (!clientWidth) return
documentElement.style.fontSize = (clientWidth < 1920 ? clientWidth : 1920) + 'px'
}
reCalc()
if (!document.addEventListener) return
// false为冒泡,true为捕获
window.addEventListener(resizeEvent, reCalc, false)
// 当一个 HTML 文档被加载和解析完成后,DOMContentLoaded 事件便会被触发。
document.addEventListener('DOMContentLoaded', reCalc, false)
}
css 里面
.fontSize (@size) {
// 因为1rem 等于html的font-size大小,在上面被修改成了屏幕实际宽度大小,所以字体大小是根据实际宽度来的
font-size: calc(@size / 1920);
}
.more {
.fontSize(13rem);
}
yarn build打包出现map文件
运行:npm run build,随后会生成一个dist文件夹
项目打包之后,代码都是经过压缩加密的,如果运行报错,输出的错误信息无法准确得知是哪里的代码报错
有了.map文件就可以像未加密的代码一样,准确的输出是哪一行哪一列有错,但是.map文件通常比较大,如果不需要这个文件可以去掉
说白了,.map文件可以指出哪里报错
在vue.config.js文件中可以配置,以便打包的时候不会生出.map文件
配置以下代码就不会生出.map文件
productionSourceMap:false
此时在进行打包的时候就不会生出.map文件
v-for从1开始,不是从0开始
使用axios添加请求头的几种方式
传入配置参数
单次请求
const config = {
headers:{
header1: value1,
header2: value2
}
};
const url = "api endpoint";
const data ={
name: "Jake Taper",
email: "taperjake@gmail.com"
}
//GET
axios.get(url, config)
.then(res=> console.log(res))
.catch(err=> console.log(err))
//POST
axios.post(url, data, config)
.then(res => console.log(res))
.catch(err => console.log(err))
多次请求
//给所有请求添加请求头
axios.defaults.headers.common['Authorization'] = `Bearer ${localStorage.getItem('access_token')}`;
//给所有POST请求添加请求头
axios.defaults.headers.post['Authorization'] = `Bearer ${localStorage.getItem('access_token')}`;
2.创建axios实例
let reqInstance = axios.create({
headers: {
Authorization : `Bearer ${localStorage.getItem(‘access_token’)}`
}
}
})
reqInstance.get(url);
3.使用axios拦截器
axios.interceptors.request.use(
config => {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('access_token')}`;
return config;
},
error => {
return Promise.reject(error);
}
);
使用拦截器的另一个好处是可以设置token过期时自动刷新token:
const refreshToken= ()=>{
// gets new access token
}
// 403请求时自动刷新access token
axios.interceptors.response.use(
response => {
return response;
},
error => {
if(error.response.status == 403){
refreshToken()
}
}
);
less
后代

子代选择器以及伪元素和伪类选择器
可以使用 & 来代替父元素

deep scoped
scoped是什么意思呢?其实就是给每条样式的 最后面 加上一个属性选择器[哈希值]

deep 能够让被scoped限制住的样式属性穿透到子组件
页面渐变
如果页面有收起展开按钮,可以给加一个渐变
transition: width 300ms linear;
表示元素的宽度将在300毫秒内线性变化。
解释如下:
transition: 是CSS过渡效果属性,可以将一个元素的某个属性从一种样式逐渐改变为另一种样式。width: 是你要改变的元素的宽度属性。300ms: 是过渡效果的持续时间,即从开始变化到变化完成的时间。linear: 是过渡效果的速度曲线,表示变化的速度在全程中保持一致。
请注意,为了让这个过渡效果能够起作用,你需要确保这个元素的宽度是可改变的,例如通过JavaScript或者其它CSS属性(如max-width, min-width等)来控制。同时,需要至少有两种状态(即元素的宽度需要有变化),否则这个过渡效果不会有任何效果。
添加下划线,上划线,删除线
text-decoration: none:默认值,表示没有任何装饰,文本正常显示。
underline:在文本下方添加一条线。
overline:在文本上方添加一条线。
line-through:在文本中间添加一条线,通常用于表示删除或取消的文本。
blink:使文本闪烁。需要注意的是,一些浏览器(如IE、Chrome或Safari)不支持这个值。
下划线距离文本间隔 text-underline-offset: 5px
vue项目中axios请求数据的时候请求失败,出现跨域问题。
blog.csdn.net/zmx729618/a… 跨域文章
axios.defaults.withCredentials = true
这一条是要求在请求头里带上cookie,如果前端配置了这个withCredentials=true,后段设置Access-Control-Allow-Origin不能为 " * ",必须是源地址
文字不可选中
\-webkit-user-select: none;
/\* Safari */
\-moz-user-select: none;
/* Firefox */
\-ms-user-select: none;
/* IE10+/Edge */
user-select: none;
/* 标准语法 \*/
echarts二次渲染有残留问题
比如嵌套饼图切换成普通饼图,有嵌套残留 在echarts进行 setOption()的方式时,有3个参数,其中第一个为要设定的option内容,而第2第3个是对setOption的参数进行配置:
chart.setOption( option , notMerge , lazyUpdate );- option:图表的配置项和数据
- notMerge:可选,是否不跟之前设置的option进行合并,默认为false,即合并。(这里是导致二次渲染不成功的根本)
- lazyUpdate:可选,在设置完option后是否不立即更新图表,默认为false,即立即更新。
问题解决:
在渲染图片时,将notMerge参数配置为true,(默认是false),问题解决
在 Vue 中,slot(插槽)是一种高级功能,允许你自定义组件的某一部分内容。通过使用插槽,你可以让组件更加灵活和可重用。
Vue 提供了两种插槽:默认插槽(匿名插槽)和具名插槽。
git拉代码无效路径
git config --global http.sslVerify false
1. 默认插槽(匿名插槽)
默认插槽没有名字,它会在组件的 <slot> 标签的位置被渲染。
组件定义:
子组件
<template>
<div>
<h2>我是组件的标题</h2>
<slot>默认内容</slot> <!-- 这里是默认插槽的位置 -->
</div>
</template>
<script>
export default {
name: 'MyComponent'
}
</script>
使用组件:
父组件
<template>
<MyComponent>
<p>这是默认插槽的内容。</p>
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue'
export default {
components: {
MyComponent
}
}
</script>
2. 具名插槽
具名插槽有一个名字,你可以在组件内部使用 <slot name="xxx"></slot> 来定义它,然后在父组件中使用 <template v-slot:xxx> 或简写为 <#xxx> 来指定内容。
组件定义:
<template>
<div>
<h2>我是组件的标题</h2>
<slot name="header"></slot> <!-- 具名插槽 header -->
<slot></slot> <!-- 默认插槽 -->
<slot name="footer"></slot> <!-- 具名插槽 footer -->
</div>
</template>
<script>
export default {
name: 'MyComponent'
}
</script>
使用组件:
<template>
<MyComponent>
<template v-slot:header>
<p>这是 header 插槽的内容。</p>
</template>
<p>这是默认插槽的内容。</p>
<template v-slot:footer>
<p>这是 footer 插槽的内容。</p>
</template>
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue'
export default {
components: {
MyComponent
}
}
</script>
注意:在 Vue 3 中,v-slot 的语法有所变化,可以简写为 #,如 <#header>。但在 Vue 2 中,你需要使用完整的 v-slot:header 语法。
插槽从父组件传到孙组件

数组所有值异步操作
// 异步过滤函数
async function asyncFilter(array, predicate) {
const results = await Promise.all(array.map(predicate));
return array.filter((_value, index) => results[index]);
}
// 示例
async function isOddNumber(n) {
await delay(100); // 模拟异步操作
return n % 2 !== 0;
}
async function filterOddNumbers(numbers) {
return asyncFilter(numbers, isOddNumber);
}
filterOddNumbers([1, 2, 3, 4, 5]).then(console.log); // 输出: [1, 3, 5]
在JavaScript中,Array.prototype.map() 方法通常用于创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。当你将一个异步函数(如返回Promise的函数)作为map()的参数时,map()函数会立即执行这个函数,但因为它不会等待Promise解决(即不会等待异步操作完成),所以map()的返回值会是一个包含所有Promise对象的数组。
在你提供的asyncFilter函数中,map()函数被用来对array中的每个元素调用predicate函数,这里predicate是一个异步函数。重要的是,map()本身不会等待这些Promise解决;它只是创建了它们并返回了一个Promise数组。
javascript复制代码
const results = await Promise.all(array.map(predicate));
这一行是关键。Promise.all()方法接受一个Promise的iterable(例如数组)作为输入,并返回一个Promise,该Promise在所有给定的Promises都被解决时解决,并且解决结果是一个数组,该数组包含所有输入Promises的解决值(按相同的顺序)。
因此,await Promise.all(array.map(predicate))会等待所有由map()创建的Promise都解决,并将解决的值收集到一个新的数组中,这个数组被赋值给results。
然后,array.filter((_value, index) => results[index])部分使用filter()函数来根据results数组中的值(即每个Promise的解决结果)来过滤原始数组array。这里,_value参数实际上在过滤逻辑中没有被使用,因为results[index]已经包含了过滤决策所需的所有信息(即每个元素是否满足predicate条件)。
需要注意的是,虽然map()里直接“丢”方法(即传递一个函数)是常见的做法,但在处理异步函数时需要特别小心,确保你正确地处理了异步操作的结果。在这个例子中,通过Promise.all()来实现了这一点。
此外,如果predicate函数不是异步的(如isOddNumber),那么使用map()和Promise.all()可能会引入不必要的性能开销,因为你可以直接使用filter()来处理同步逻辑。但在需要处理异步谓词(predicate)的情况下,这种方法是非常有用的。
domainName: selectedData.map(item => item.title)?.join(';') map里面其实是一个匿名函数,
sessionStorage 不能在多个窗口或标签页之间共享数据,但是当通过 window.open 或链接打开新页面时(不能是新窗口),新页面会复制前一页的 sessionStorage。 sessionStorage 就是会话级别的存储(关键在于会话)
如何定义一个会话?
在A页面点击超链接或者在控制台window.open打开页面B,都是属于当前页面的延续,属于一个会话。
在A页面已经打开的前提下,然后在新tab打开同域页面C,此时C和A页面无直接关系,不属于一个会话。
sessionSession并不是共享的,而是复制的。
也就是说,B页面打开的时候复制了A页面的sessionSession,仅仅是复制
此时,无论修改A页面的sessionStorage还是修改B页面的SessionStorage,都不会彼此影响。
也就是说两个页面存储的SessionStorage数据都不会同步变化(各自都是自己的存储,存储独立存在)。
try catch执行时间
try catch是同步的,无法捕捉异步的报错,例如 setTimeout promise(promise有自带的catch)
如果想捕捉到promise报错,必须给promise加上 await
不加async await时,promise执行完,在等待执行promise中的回调时,try catch已经执行完了; 加了async await时,try catch需要等待promise中的回调执行完才能往下走,所以被catch捕获到异常
日期过滤调休日和周末节假日
// 引入 moment.js const moment = require('moment');
// 定义一个包含节假日的数组(格式为YYYY-MM-DD) const holidays = [ '2023-10-01', // 示例节假日 // ... 其他节假日 ];
// 定义一个包含调休日的数组(格式为YYYY-MM-DD) const adjustedWorkdays = [ '2023-10-07', // 示例调休日(周末上班) // ... 其他调休日 ];
// 函数:检查给定日期是否是节假日或周末 function isNonWorkingDay(date, holidays, adjustedWorkdays) { const dayOfWeek = date.isoWeekday(); // 1表示星期一,7表示星期日 const isHoliday = holidays.includes(date.format('YYYY-MM-DD')); const isAdjustedWorkday = adjustedWorkdays.includes(date.format('YYYY-MM-DD'));
return (dayOfWeek === 6 || dayOfWeek === 7) && !isAdjustedWorkday || isHoliday; }
// 函数:计算第n个工作日后的日期 function getNthWorkday(startDate, n, holidays, adjustedWorkdays) { let currentDate = moment(startDate); let workdayCount = 0;
while (workdayCount < n) { currentDate = currentDate.add(1, 'days');
if (!isNonWorkingDay(currentDate, holidays, adjustedWorkdays)) {
workdayCount++;
}
}
return currentDate; }
// 示例使用 const currentDate = moment(); const tenthWorkday = getNthWorkday(currentDate, 10, holidays, adjustedWorkdays); console.log(tenthWorkday.format('YYYY-MM-DD')); // 输出第10个工作日的日期
获取字符串某个位置的值
demo.charAt(0) 获取第0个值
v-show 和 v-if 的区别?
触发生命周期不同。v-show由 false 变为 true 的时候不会触发组件的生命周期;v-if由 false 变为 true 的时候,触发组件的beforeCreate、created、beforeMount、mounted钩子,由 true 变为 false 的时候触发组件的beforeDestory、destoryed钩子。
通过key值来重新加载组件
在 Vue 里,组件可以通过key值来实现重新加载。在 Vue 中,key是一个特殊的属性,它主要用于给 Vue 一个提示,以便在比较新旧虚拟节点时能更准确、高效地识别节点,进而决定是否复用节点。当key值发生变化时,Vue 会认为这是一个全新的组件实例,从而销毁旧的组件实例并重新创建一个新的。
实现原理
Vue 在进行虚拟 DOM 的 Diff 算法比较时,会根据key值来判断节点是否可以复用。如果key值改变,Vue 会认为当前节点是一个全新的节点,从而销毁旧节点并创建新节点,这样组件就会重新加载。
示例代码
下面是一个简单的示例,展示如何通过改变key值来重新加载组件:
<template>
<div>
<!-- 点击按钮时,改变componentKey的值,从而触发组件重新加载 -->
<button @click="reloadComponent">重新加载组件</button>
<!-- 动态绑定key属性 -->
<MyComponent :key="componentKey" />
</div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
data() {
return {
// 初始化key值
componentKey: 1
};
},
methods: {
reloadComponent() {
// 每次点击按钮,让key值加1
this.componentKey++;
}
}
};
</script>
MyComponent.vue组件代码
<template>
<div>
<!-- 显示当前组件的加载时间 -->
<p>组件加载时间: {{ loadTime }}</p>
</div>
</template>
<script>
export default {
data() {
return {
// 记录组件的加载时间
loadTime: new Date().toLocaleString()
};
}
};
</script>
-
App.vue组件:- 在模板中,有一个按钮和一个
MyComponent组件,MyComponent组件绑定了key属性,其值为componentKey。 - 在
data选项中,初始化了componentKey的值为 1。 - 定义了
reloadComponent方法,每次点击按钮时,componentKey的值会加 1。
- 在模板中,有一个按钮和一个
-
MyComponent.vue组件:- 在
data选项中,定义了loadTime变量,用于记录组件的加载时间。
- 在
当点击 “重新加载组件” 按钮时,componentKey的值会改变,Vue 会销毁旧的MyComponent实例并重新创建一个新的实例,此时MyComponent组件中的loadTime会更新为新的时间,从而实现组件的重新加载。
箭头函数
const fn = () => {}
// 如果只有一个参数,可以省略括号 const fn = name => {}
// 如果函数体里只有一句return const fn = name => { return 2 * name } // 可简写为 const fn = name => 2 * name // 如果返回的是对象 const fn = name => ({ name: name })
普通函数和箭头函数的区别:
- 1、箭头函数不可作为构造函数,不能使用new
- 2、箭头函数没有自己的this
- 3、箭头函数没有arguments对象
- 4、箭头函数没有原型对象
find 和 findIndex
- find:找到返回被找元素,找不到返回undefined
- findIndex:找到返回被找元素索引,找不到返回-1
const findArr = [
{ name: '科比', no: '24' },
{ name: '罗斯', no: '1' },
{ name: '利拉德', no: '0' }
]
const kobe = findArr.find(({ name }) => name === '科比')
const kobeIndex = findArr.findIndex(({ name }) => name === '科比')
console.log(kobe) // { name: '科比', no: '24' }
console.log(kobeIndex) // 0
await 后面跟foreach
await 后面不能直接跟 forEach 来达到等待其完成后再执行后续代码的目的,这是因为 forEach 本身并不支持异步操作,它不会等待异步操作完成,而是会立即遍历数组并执行回调函数,所以会导致后续代码先执行。下面为你详细分析原因并给出解决办法。
原因分析
forEach 是数组的一个迭代方法,它会为数组中的每个元素同步执行提供的回调函数。当回调函数中包含异步操作时,forEach 不会等待这些异步操作完成,就会继续执行后续代码。当 await 用于这种情况时,它实际上等待的是 forEach 方法的返回值(forEach 返回 undefined),而不是其中的异步操作完成,因此后续代码会先执行。
示例代码
function asyncTask(item) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`处理 ${item}`);
resolve();
}, 1000);
});
}
async function main() {
const arr = [1, 2, 3];
console.log('开始处理数组');
await arr.forEach(async (item) => {
await asyncTask(item);
});
console.log('数组处理完成');
}
main();
在上述代码中,main 函数里使用 await 和 forEach 尝试处理数组元素。forEach 会立即开始遍历数组,并为每个元素调用 asyncTask 函数。由于 forEach 不会等待异步操作完成,所以 await 不会等待所有异步任务结束,'数组处理完成' 会在所有异步任务完成之前输出。
解决方案
可以使用 for...of 循环来替代 forEach,因为 for...of 循环支持 await,它会等待每次迭代中的异步操作完成后再进行下一次迭代。
function asyncTask(item) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`处理 ${item}`);
resolve();
}, 1000);
});
}
async function main() {
const arr = [1, 2, 3];
console.log('开始处理数组');
for (const item of arr) {
await asyncTask(item);
}
console.log('数组处理完成');
}
main();
代码解释
在这个改进后的代码中,使用 for...of 循环遍历数组。await 会等待每次迭代中的 asyncTask 异步操作完成后,才会进行下一次迭代。这样,当所有异步操作都完成后,才会输出 '数组处理完成'。
但是for of不支持下标索引,需结合entries() 方法会返回一个新的 Array Iterator 对象,该对象包含数组中每个索引的键 / 值对。entries() 方法会遍历稀疏数组中的所有位置,包括那些没有赋值的空位,空位的值会被当作 undefined
const array = ['apple', 'banana', 'cherry'];
// 使用 for...of 循环结合 entries() 方法
for (const [index, value] of array.entries()) {
console.log(`Index: ${index}, Value: ${value}`);
}
Array Iterator 对象是一个符合迭代器协议的对象。迭代器协议规定,一个对象必须实现一个 next() 方法,该方法返回一个包含 value 和 done 两个属性的对象。
value:表示当前迭代位置的值。done:是一个布尔值,true表示迭代结束,false表示还有更多元素可以迭代。
创建方式
可以通过数组的一些方法来创建 Array Iterator 对象,常见的方法有:
entries():返回一个包含数组每个索引位置的键值对[index, value]的迭代器对象。
const arr = ['a', 'b', 'c'];
const iteratorEntries = arr.entries();
keys():返回一个包含数组所有索引的迭代器对象。
const iteratorKeys = arr.keys();
values():返回一个包含数组所有值的迭代器对象。
const iteratorValues = arr.values();
使用扩展运算符(...)
扩展运算符可以将迭代器中的元素展开成一个数组。
const arr = ['apple', 'banana', 'cherry'];
const iterator = arr.values();
const newArr = [...iterator];
console.log(newArr); // 输出: ['apple', 'banana', 'cherry']
通过上述方式,你可以方便地使用 Array Iterator 对象来遍历数组元素。
可选链作用于数组下标判断
可以,可选链操作符(?.)可以用于数组的下标判断,以此来确定数组中指定位置的元素是否存在。下面为你详细介绍使用方法和示例。
语法和原理
可选链操作符允许在访问对象属性或调用方法时,在引用为 null 或 undefined 的情况下不会抛出错误,而是直接返回 undefined。对于数组来说,我们可以将其看作是一种特殊的对象,数组的下标就是对象的属性名,所以可选链操作符同样适用。
在 JavaScript 中,数组下标是从 0 开始的,所以第二个元素的下标为 1。以下是使用可选链操作符判断数组第二个元素是否存在的代码示例:
// 示例数组
const arr1 = [10, 20, 30];
const arr2 = [10];
const arr3 = [];
// 使用可选链操作符判断数组第二个元素是否存在
const element1 = arr1?.[1];
const element2 = arr2?.[1];
const element3 = arr3?.[1];
console.log('arr1 的第二个元素:', element1);
console.log('arr2 的第二个元素:', element2);
console.log('arr3 的第二个元素:', element3);
arr1数组:arr1包含三个元素,使用arr1?.[1]时,由于arr1存在且下标1对应的元素(即第二个元素)为20,所以element1的值为20。arr2数组:arr2只包含一个元素,使用arr2?.[1]时,虽然arr2存在,但下标1对应的位置没有元素,所以element2的值为undefined。arr3数组:arr3是一个空数组,使用arr3?.[1]时,由于arr3存在但没有下标为1的元素,所以element3的值为undefined。
为了让判断结果更具可读性,你还可以结合空值合并操作符(??)来为 undefined 或 null 情况提供默认值,示例如下:
const arr = [10];
const element = arr?.[1]?? '元素不存在';
console.log('数组的第二个元素:', element);
在这个示例中,当数组的第二个元素不存在时,element 的值会被设置为 '元素不存在'。
||= 和 &&=
或等于(||=) a ||= b 等同于 a || (a = b);
且等于(&&=) a &&= b 等同于 a && (a = b);
属性选择器
在 CSS 里,属性选择器的确要用方括号 [] 包裹起来。属性选择器能够依据元素的属性及其值来选择 HTML 元素。下面为你介绍几种常见的属性选择器及其用法:
[attribute]
选择带有指定属性的元素。
/* 选择所有带有 title 属性的元素 */
[title] {
color: blue;
}
[attribute=value]
选择属性值精确等于指定值的元素。
css
/* 选择所有 lang 属性值为 "en" 的元素 */
[lang="en"] {
font-style: italic;
}
[attribute^=value]
选择属性值以指定值开头的元素。
/* 选择所有 class 属性值以 "btn-" 开头的元素 */
[class^="btn-"] {
background-color: lightgray;
}
[attribute$=value]
选择属性值以指定值结尾的元素。
/* 选择所有 src 属性值以 ".jpg" 结尾的元素 */
[src$=".jpg"] {
border: 1px solid black;
}
[attribute*=value]
选择属性值包含指定值的元素。
/* 选择所有 href 属性值包含 "example" 的元素 */
[href*="example"] {
text-decoration: underline;
}
监听元素尺寸变化,区别去resize 一般用于大屏
const element = document.getElementById('your-element');
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const { width, height } = entry.contentRect;
console.log(`Element size changed to: ${width}px x ${height}px`);
}
});
observer.observe(element);
背景图亮度变化
/* 基础样式 */
.container {
background-image: url('example.jpg');
background-size: cover;
}
/* 应用亮度滤镜 */
.brightened {
filter: brightness(1.2); /* 亮度提升20% */
}
.darkened {
filter: brightness(0.7); /* 亮度降低30% */
}
大屏动画效果
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.leftBox {
animation: fadeInLeft 1s ease forwards;
opacity: 0;
}
.centerBox {
animation: fadeIn 1s ease 0.3s forwards;
opacity: 0;
}
.rightBox {
animation: fadeInRight 1s ease 0.6s forwards;
opacity: 0;
}
扩展组件的方法
1、mixins
2、slots
<!-- MyComponent.vue -->
<template>
<div>
<slot name="user" :user="userInfo" :role="roleInfo"></slot>
</div>
</template>
<script>
export default {
data() {
return {
userInfo: {
name: '张三',
age: 25
},
roleInfo: {
name: '张三',
age: 25
}
}
}
}
</script>
<template>
<MyComponent>
// 另一种写法 #user="{user,rule}"
<template #user="slotProps"> // slotProps是个对象,包含子组件传过来的参
<p>姓名:{{ slotProps.user.name }}</p>
<p>年龄:{{ slotProps.role.age }}</p>
</template>
</MyComponent>
</template>
3、 extend
1. Vue.extend() 的作用
Vue.extend() 是 Vue 构造函数的一个静态方法,它接收一个组件选项对象(比如 axUploadModal),返回一个新的子类构造函数(可以理解为 “组件类”)。
这个新的构造函数继承了 Vue 的所有功能,但预先包含了传入的组件选项(数据、方法、模板等)。后续可以通过 new 关键字创建该类的实例,每个实例都是一个独立的 Vue 组件实例。 个人理解是把一个vue组件变成了动态创建多个独立实例,每个实例有独立的状态(数据,方法)
- 这里的
axUploadModal是什么?
axUploadModal 是导入的组件选项对象(通常来自单文件组件 .vue),它包含了该组件的所有配置:
template/render:组件的结构data:组件内部数据(比如控制弹窗显示的visible)methods:组件方法(比如getUploadedFileList)props/computed/ 生命周期钩子等
后续使用:实例化组件
有了 AxUploadModal 这个构造函数后,就可以通过 new 创建实例:
// 创建组件实例,传入参数(会合并到组件的 data 中)
const instance = new AxUploadModal({
data: { title: '上传文件' } // 这里的参数会覆盖组件原 data 中的同名属性
})
// 手动挂载并插入 DOM
instance.$mount() // 生成 DOM 节点(未插入页面)
document.body.appendChild(instance.$el) // 插入到页面中显示
总结:Vue.extend(axUploadModal) 的核心是将组件配置转换为可实例化的 “组件类”,为后续动态创建、挂载组件提供了基础,这是实现全局弹窗、动态加载组件的常用手段(类似 Element UI 的 this.$alert 实现原理)。
vue绑定事件带不带()的区别
-
1.
@click="init()"(带括号) -
这是显式调用函数的写法,相当于直接执行
init()函数 -
特点:
- 可以主动传递自定义参数,例如
@click="init(id, name)" - 如果需要用到事件对象
$event,必须显式传入:@click="init($event)" - 即使函数有默认参数(如事件对象),如果没显式传递,函数内部无法获取到
$event
- 可以主动传递自定义参数,例如
-
2.
@click="reset"(不带括号) -
这是绑定函数引用的写法,Vue 会自动将事件对象
$event作为参数传递给函数 -
特点:
-
函数会自动接收事件对象
$event,例如: methods: { reset(e) { console.log(e) // 这里能获取到点击事件对象 } } -
不能直接传递自定义参数(如果需要传参,必须用带括号的写法)
-
总结对比
| 写法 | 函数参数默认值 | 能否自定义传参 | 适用场景 |
|---|---|---|---|
@click="init()" | 无(需显式传 $event) | 能 | 需要传递自定义参数时 |
@click="reset" | 自动接收事件对象 $event | 不能 | 只需要事件对象或无参数时 |
有时候e可能输出undefined,这是因为如果是自定义组件,内部可能没有把原生点击事件传递出来,this.$emit('click'),这种情况下,父组件接收不到事件对象 e,所以 reset(e) 中的 e 会是 undefined this.$emit('click', e) 传递事件对象给父组件,或者使用 .native 修饰符直接绑定原生点击事件(仅 Vue 2 支持):
<!-- 绑定原生事件,绕过组件内部的事件处理 -->
<ax-button @click.native="reset">重置</ax-button>
内部元素disabled,导致点击事件无效
<template>
<div class="inputParent" style="position: relative;">
<!-- 双击覆盖层 - 始终能接收双击事件 -->
<div
class="dblclick-overlay"
@dblclick="dblclick"
v-if="currentInput === 'a-textarea' && propsOptions.disabled"
></div>
<component
:disabled="propsOptions.disabled"
>
// 内部组件
</component>
<a-modal
v-model="visible"
:title="propsOptions.label"
:footer="null"
:width="800"
>
// 点击弹窗
</a-modal>
</div>
</template>
<style lang="less" scoped>
.inputParent {
position: relative;
}
.dblclick-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1; /* 确保在输入框上方但不遮挡其他交互元素 */
pointer-events: auto; /* 确保能接收鼠标事件 */
}
</style>
-
- 根本问题:禁用元素的 “事件吞噬” 特性
当输入框(component 渲染的 a-input/a-textarea)设置 disabled=true 时,浏览器会默认让该元素完全拦截所有鼠标事件(包括 dblclick、click、mouseover 等),且这些事件不会向上冒泡到父容器。
这就导致你原本绑定在父容器 inputParent 上的 dblclick 事件,只有在点击父容器的 “空白边缘”(未被禁用输入框覆盖的区域)时才能触发 —— 因为点击输入框区域时,事件直接被禁用的输入框 “吞掉” 了,根本传不到父容器。
-
- 解决方案:添加 “事件代理覆盖层”
我们新增的 .dblclick-overlay 元素,本质是一个漂浮在禁用输入框上方的 “透明事件接收器” ,它的作用是:
-
位置覆盖:通过
position: absolute+top/left/right/bottom: 0,让覆盖层的尺寸与父容器完全一致,刚好 “盖” 在禁用的输入框上方(但不影响视觉,因为它没有背景和边框)。 -
事件接管:覆盖层本身没有
disabled状态,能正常接收dblclick事件;我们把原本绑定在父容器的dblclick逻辑,转移到覆盖层上 —— 这样用户双击输入框区域时,实际是点击了覆盖层,事件能被正常捕获。 -
条件显示:通过
v-if="currentInput === 'a-textarea' && propsOptions.disabled"控制,只在 “文本域类型” 且 “禁用状态” 下显示覆盖层,避免影响其他正常状态(如可编辑输入框)的交互。 -
- 关键细节:z-index 层级控制
覆盖层的 z-index: 1 是核心细节:
- 它的层级高于下方的输入框(默认
z-index: auto,相当于 0),所以能优先接收鼠标事件; - 同时层级不会过高(比如不设置
z-index: 999),避免遮挡输入框的prefix/suffix等内置元素,保证 UI 完整性。
父元素设置了margin-top: -18px;子元素会有一部分点不到
给父元素设置position: relative,
当父元素设置 position: relative 后,子元素原本点不到的区域变得可点击,核心原理与CSS 定位上下文对 “点击区域(hitbox)” 的影响有关。具体来说:
- 默认情况下(父元素无定位):
父元素未设置 position 时,其 “点击区域” 严格限制在自身的盒模型边界内(content + padding + border)。
- 当父元素设置
margin-top: -18px向上偏移后,它在文档流中的 “占位空间” 被压缩(相当于顶部被 “切掉” 18px)。 - 若子元素向上延伸超出了父元素的原始盒模型边界(比如子元素高度较高,或有向上的偏移),超出的部分会脱离父元素的 “点击范围”—— 即使视觉上可见,也不会触发父元素或子元素的点击事件(因为这部分不在父元素的盒模型边界内)。
- 此外,若父元素的父元素(祖父元素)有
overflow: hidden,超出部分还会被裁剪,进一步导致点击失效。
- 父元素设置
position: relative后:
position: relative 会让父元素成为一个 “定位容器(containing block)”,此时其 “点击区域” 规则发生变化:
- 父元素的点击区域不再局限于自身的盒模型边界,而是会包含所有子元素(即使子元素超出父元素的视觉边界) 。
- 原因是:定位容器会 “管理” 其内部所有子元素的布局和交互范围,无论子元素是否超出父元素本身的尺寸,只要子元素没有被其他更高层级的元素(
z-index更高)遮挡,其点击事件都会被正常捕获。 - 同时,
position: relative不会改变父元素在文档流中的占位空间,但会建立一个局部的坐标系,让子元素的定位(如position: absolute)以父元素为基准,间接避免了子元素因父元素偏移而 “脱离管理” 的问题。
简单总结:
position: relative 给父元素 “赋予了管理子元素交互范围的能力”,让父元素的点击区域从 “自身盒模型边界” 扩展到 “包含所有子元素的范围”,从而解决了子元素超出部分无法点击的问题。
props绑定值导致弹窗异常
仅修改子组件的 props(key 不变)→ 不会触发 created/mounted,也不会 “重建” 子组件,只会触发 “更新渲染” :
- ✅ 会发生:子组件执行
updated钩子、重新渲染模板(更新视图); - ❌ 不会发生:
created/mounted钩子执行、组件实例销毁 / 重建。
只有当 key 变化,或子组件被 v-if 从 false 变 true(销毁后重建),created/mounted 才会重新执行。
如果弹窗的 v-model 直接绑定 props 的值,哪怕变化的是其他 props(比如 plantCode/sType),也一定会导致弹窗显隐异常 —— 这是 Vue 单向数据流规则和组件更新机制共同作用的必然结果。
一、核心结论:一定会导致弹窗异常(且是最主要诱因)
哪怕你只是修改了 plantCode/sType 这类和弹窗显隐无关的 props,只要弹窗的 v-model 直接绑定 props(比如 v-model="editDialog1",而 editDialog1 是 props),就会触发弹窗异常,原因如下:
二、为什么会异常?(拆解核心逻辑)
1. 先明确:v-model 的本质(Vue2 自定义组件)
自定义组件的 v-model 等价于:
vue
<!-- 你写的:v-model="editDialog1"(editDialog1 是 props) -->
<!-- Vue 实际解析为: -->
<Invest
:value="editDialog1" <!-- 把 props 传给子组件的 value -->
@input="val => editDialog1 = val" <!-- 监听子组件的 input 事件试图修改 props -->
/>
而 Vue 规定:props 是单向数据流,子组件绝对不能修改 props(只能由父组件修改)。
2. 其他 props 变化触发更新渲染时,矛盾就爆发了
当你修改 plantCode/sType 时:
-
子组件触发更新渲染(执行
updated钩子、重新解析模板); -
Vue 解析模板时发现:
v-model绑定的是只读的 props(editDialog1); -
此时 Vue 的响应式系统会陷入「状态不一致」:
- 一方面,子组件想通过
v-model维护弹窗的显隐状态,但 props 是只读的,无法修改; - 另一方面,更新渲染时 Vue 会强制对比「模板绑定值」和「props 实际值」,一旦发现不匹配,就会强制重置弹窗的 visible 状态(比如默认设为
true,或继承 props 的初始值);
- 一方面,子组件想通过
-
最终表现:弹窗毫无征兆地显示 / 隐藏,也就是你遇到的 “异常”。
三、举个通俗的例子帮你理解
把弹窗的 v-model 绑定 props 比作:
- 你(子组件)想控制一个房间的灯(弹窗显隐),但灯的开关(
editDialog1)是房东(父组件)锁死的(props 只读); - 你只是换了房间的窗帘(修改
plantCode/sType),但房东(Vue)来检查时发现你碰了锁死的开关(v-model绑定 props),于是直接把灯的状态重置了(弹窗异常)—— 哪怕你没碰开关,只是换了窗帘。
四、如何彻底避免这种异常?(唯一正确的写法)
核心原则:子组件永远不要把 v-model 直接绑定到 props 上,必须内部维护弹窗状态,步骤如下(针对你的弹窗):
vue
<template>
<!-- 1. v-model 绑定子组件内部状态 innerVisible -->
<vxe-modal v-model="innerVisible">
<!-- 弹窗内容 -->
</vxe-modal>
</template>
<script>
export default {
props: {
// 接收父组件传递的弹窗显隐状态(仅接收,不直接绑定)
editDialog1: {
type: Boolean,
default: false
},
plantCode: String,
sType: Array
},
data() {
return {
// 2. 内部维护弹窗状态
innerVisible: this.editDialog1
};
},
watch: {
// 3. 监听父组件传递的 props,同步到内部状态(单向数据流)
editDialog1(newVal) {
this.innerVisible = newVal;
},
// 4. 内部状态变化时,通知父组件更新(双向绑定的本质)
innerVisible(newVal) {
this.$emit("update:editDialog1", newVal);
}
}
};
</script>
父组件使用时,用 .sync 实现双向绑定:
vue
<Invest
:editDialog1.sync="parentDialogVisible"
:plantCode="parentPlantCode"
:sType="parentSType"
/>
总结
- 核心结论:弹窗
v-model绑定 props 值时,任何 props 变化(哪怕是无关的)都会触发弹窗异常 —— 因为违反了 Vue 单向数据流规则,更新渲染时会导致状态不一致; - 根本原因:
v-model会试图修改绑定值,但 props 是只读的,更新渲染时 Vue 会强制重置弹窗状态; - 唯一解法:子组件内部维护弹窗显隐状态(如
innerVisible),通过watch同步父组件的 props,再通过$emit回传状态,绝对不要直接绑定 props 到v-model。
这种写法能保证:哪怕你频繁修改 plantCode/sType,弹窗的显隐状态也只由父组件的 editDialog1 控制,不会再出现任何异常。