北京4网站建设,沈阳软件开发培训,wordpress在线安装插件在哪里,网站制作一薇前端代码整洁与开发技巧
为保证前端人员在团队项目开发过程中的规范化、统一化#xff0c;特建立《前端代码整洁与开发技巧》文档#xff0c;通过代码简洁推荐、开发技巧推荐等章节来帮助我们统一代码规范和编码风格#xff0c;从而提升项目的可读性和可维护性。
目录 …前端代码整洁与开发技巧
为保证前端人员在团队项目开发过程中的规范化、统一化特建立《前端代码整洁与开发技巧》文档通过代码简洁推荐、开发技巧推荐等章节来帮助我们统一代码规范和编码风格从而提升项目的可读性和可维护性。
目录 文章目录 前端代码整洁与开发技巧目录一、代码整洁推荐1.1 三元(三目)运算符1.2 短路判断简写1.3 变量声明简写1.4 if真值判断简写1.5 For循环简写1.6 对象属性简写1.7 箭头函数简写1.8 隐式返回简写1.9 模板字符串1.10 默认参数值1.11 解构赋值简写1.12 多条件判断简写1.13 多变量赋值简写1.14 解构时重命名1.15 对象替代switch1.16 链判断运算符1.17 Null 判断运算符1.18 逻辑运算符1.19 扩展运算符1.20 运算符隐式转换1.21 v-for中使用解构1.22 使用Set给数组去重1.23 Object[key] 重用代码块1.24 禁用不必要的嵌套块1.25 具有默认值的函数参数应该放到最后1.26 **组件模板应该只包含简单的表达式复杂的表达式则应该重构为计算属性或方法**1.27 计算属性使用解构赋值减少this滥用提升性能 二、开发技巧推荐2.1 vue开发技巧2.1.1 使用v-bindobj将所有属性传给子组件2.1.2 使用$on(‘hook:’)2.1.3 如何为Vue组件添加非响应式数据2.1.5 适时使用$options2.1.6 如何解决Vue在main.js全局引入scss文件组件里使用scss变量报错问题 2.2 javascript开发技巧2.2.1 数组操作1、使用 includes 简化 if 判断2、使用every判断是否所有项都满足条件3、使用some判断是否有一项满足条件4、使用reduce遍历数组处理求和等复杂逻辑 2.2.2 对象操作1、向对象有条件的添加属性2、检查属性是否存在对象中3、使用Object.entries() 返回一个给定对象自身可枚举属性的键值对数组4 、使用Object.fromEntries( ) 方法把键值对列表转换为一个对象5、使用Object.freeze冻结对象可在vue data定义非响应式变量使用 2.2.3 字符串操作1、字符串不满两位补零2、判断字符串前缀、后缀3、使用replaceAll 方法替换所有匹配到的字符 2.3 css开发技巧2.3.1 巧妙使用伪类生成表单必填标识2.3.2 增强用户体验使用伪元素实现增大点击热区2.3.3 利用伪类实现鼠标移入时下划线向两边展开的效果 三、其它技巧3.1.1 适时使用provide和inject3.1.2 利用key值解决vue就地复用策略的问题 一、代码整洁推荐
1.1 三元(三目)运算符
如果只想在一行中编写if…else语句时这是一个很好的节省代码的方式。
常规
const x 20
let answer
if(x 10) {answer 大于10
}else {answer 小于等于10
}简写
const x 20
const answer x 10? 大于10 : 小于等于10嵌套版三元运算
const x 20
const answer x 10? 大于10 : x 5? 小于5 : 在5和10之间// 第二层加上增强可读性
const answer x 10? 大于10 : (x 5? 小于5 : 在5和10之间)注意三元运算不要超过2层嵌套否则可读性不强如果是嵌套两层要求第二层加上帮助提升可读性。
1.2 短路判断简写
将变量值分配给另一个变量时可能希望确保源变量不为nullundefined或为空。这时候可以编写带有多个条件的长 if 语句也可以使用短路判断。
常规
if(type ! null || type ! undefined || type ! ) {let wtType type
} else {let wtType 01
}简写
let wtType type || 01注意如果type值为false或者数字0将取值为字符串’01’。
1.3 变量声明简写
在进行连续性的变量声明操作时应尽可能的使用 一次声明语句来提升程序运行效率。
常规
let x
let y
let z 1简写
let x, y, z 11.4 if真值判断简写
这可能是微不足道的但值得一提。在执行“if 检查”时有时可以省略全等运算符。
常规
if(likeJavascript true) {...
}
if(likeNode ! true) {...
}简写
if(likeJavascript) {...
}
if(!likeNode) {...
}1.5 For循环简写
常规
const arr [1, 2, 3]
for(let i 0; i arr.length; i) {console.log(i, arr[i])if(arr[i] 1) {break}
}
// 0, 1
// 1, 2如果不需要访问索引请执行以下操作
for(let item of arr) {console.log(item, item)if(item 1) {break}
}
// 1
// 2如果只想访问索引请执行以下操作
for(let index in arr) {console.log(index)
}
// 1
// 2
// 3如果要访问对象中的键请执行以下操作
const obj {name: zhangsan, age: 18}
for(let key in obj) {console.log(key,obj[key])
}
// name
// age注意不能使用for…of 来遍历对象。
1.6 对象属性简写
ES6提供了一种更简单的方法来为对象分配属性。如果变量名称与对象键相同则可以使用简写表示法。
常规
let signType 1
let params {signType: signType
}简写
let signType 1
let params {signType
}1.7 箭头函数简写
经典函数以简单的形式易于读写但是一旦你开始将它们嵌套在其他函数调用中它们往往会变得有点冗长和混乱。
常规
function sayHello(name) {console.log(Hello, name)
}
setTimeout(function() {console.log(Loaded)
}, 2000)
list.forEach(function(item) {console.log(item)
})简写
const sayHello name console.log(Hello, name)
setTimeout(() console.log(Loaded), 2000)
list.forEach(item console.log(item))1.8 隐式返回简写
Return 是我们经常使用的关键字用于返回函数的最终结果。具有单个语句的箭头函数将隐式返回其执行结果函数可以省略大括号{}用()省略return关键字具体可参考下例。
要返回多行语句例如对象必须使用 () 而不是 {} 来包装函数体。这可确保将代码执行为单个语句。
常规
function sum(x, y) {return x y
}
function makeInfo(name, age) {return {name, age}
}简写
const sum (x, y) x y
const makeInfo (name, age) ({name, age})1.9 模板字符串
您是否厌倦了使用 ‘’ 将多个变量连接成一个字符串有没有更简单的方法如果你能够使用ES6那么你很幸运。您需要做的就是使用反引号并使用 ${} 来包含变量。
常规
const name zhangsan
const age 18
let des name 今年 age 岁简写
const name zhangsan
const age 18
let des ${name}今年${age}岁1.10 默认参数值
您可以使用if语句定义函数参数的默认值。在ES6中您可以在函数声明本身中定义默认值。
常规
function volume(l, w, h) {if(w undefined) {w 3}if(h undefined) {h 4}return l * w * h
}简写
const volume (l, w 3, h 4) l * w * h
volume(2)
// 24注意只有wh为undefined时默认值才会生效。
结构赋值中的默认值也是如此如果值为null默认值不会生效如
const res {list: null,code: undefined
}
const { list [], code 1 } res
console.log(list) // null
console.log(code) // 1let num list.length // Uncaught TypeError: Cannot read properties of null (reading length)注意如果直接取list数组的长度或者利用[index]取里面的值在res.list为null或者不为数组的时候可能会发生报错针对该问题大家可以在解构的时候不赋默认值在解构的下方利用null判断运算符来处理默认值如
const res {list: null
}
let { list , code 1 } res
list list ?? []
console.log(list) // []let num list.length // 01.11 解构赋值简写
对数组和变量进行解构可以减少变量滥用提升程序的运行效率。
常规
let params {wtId: this.transferDetail.wtId,newPic: type 01 ? this.selectPersonId : , // type是方法入参oldPic: this.transferDetail.picOld,recordId: this.applyId, // 转派记录replacementPicStatus: 02, // 转派状态 01-转派中 02-转派结束replacementReason: this.replacementReason
}简写
const { selectPersonId, applyId, replacementReason, transferDetail } this
let params {wtId: transferDetail.wtId,newPic: type 01 ? selectPersonId : , // type是方法入参oldPic: transferDetail.picOld,recordId: applyId, // 转派记录replacementPicStatus: 02, // 转派状态 01-转派中 02-转派结束replacementReason
}解构知识点链接地址https://es6.ruanyifeng.com/#docs/destructuring
1.12 多条件判断简写
常规
function typeHandle(type) {if (type test1) { test1(); } else if (type test2) { test2(); } else if (type test3) { test3(); } else if (type test4) { test4(); } else { throw new Error(Invalid value type); }
}
typeHandle(test3)简写
function typeHandle(type) { const types { test1: test1, test2: test2, test3: test3, test4: test4 }; let func types[type]; (!func) throw new Error(Invalid value type);func();
}
typeHandle(test3)1.13 多变量赋值简写
常规
let test1, test2, test3; test1 1;
test2 2;
test3 3; let name, age, sex;name zhangsan;
age 18;
sex 男简写(利用es6数组、对象的结构赋值实现多变量赋值简写)
let [test1, test2, test3] [1, 2, 3]; let {name, age, sex} { name: zhangsan, age: 18, sex: 男};1.14 解构时重命名
常规
const { ticketTypeName } this.tciketDetail
let params {wtName: ticketTypeName,...
}简写
const { ticketTypeName: wtName } this.tciketDetail
let params {wtName,...
}对象解构赋值的简写形式
let { foo: foo, bar: bar } { foo: aaa, bar: bbb };也就是说对象的解构赋值的内部机制是先找到同名属性然后再赋给对应的变量。真正被赋值的是后者而不是前者。
let { foo: baz } { foo: aaa, bar: bbb };
baz // aaa
foo // error: foo is not defined1.15 对象替代switch
常规
// 获取工作票名称
workTicketName(type) {switch (type) {case 01:return 变一breakcase 02:return 变二breakcase 03:return 配一breakcase 04:return 配二breakcase 05:return 低压breakcase 06:return 作业卡breakcase 07:return 派工单breakdefault:break}
}
workTicketName(01) // 变一简写
// 获取工作票名称
workTicketName(type) {const typeObj {01: 变一,02: 变二,03: 配一,04: 配二,05: 低压,06: 作业卡,07: 派工单}return typeObj[type]
}
workTicketName(01) // 变一1.16 链判断运算符
编程实务中如果读取对象内部的某个属性往往需要判断一下属性的上层对象是否存在。比如读取message.body.user.firstName这个属性安全的写法是写成下面这样。
// 错误的写法
const firstName message.body.user.firstName || default;// 正确的写法
const firstName (message message.body message.body.user message.body.user.firstName) || default;// 但是这种写法如果层级过长会显得冗长可读性也不强这是我们可以利用链判断运算符来解决
//链判断运算符写法
const firstName message?.body?.user?.firstName || default;链判断运算符?.有三种写法。
obj?.prop // 对象属性是否存在obj?.[expr] // 同上 也可以理解为 arr?.[index]func?.(...args) // 函数或对象方法是否存在
下面是?.运算符常见形式以及不使用该运算符时的等价形式。
a?.b
// 等同于
a null ? undefined : a.ba?.[x]
// 等同于
a null ? undefined : a[x]a?.b()
// 等同于
a null ? undefined : a.b()a?.()
// 等同于
a null ? undefined : a()常规
// 计划 id
let planId this.ticketDetail this.ticketDetail.planDetailVoList this.ticketDetail.planDetailVoList[0] this.ticketDetail.planDetailVoList[0].planId 简写
// 计划 id
let planId this.ticketDetail?.planDetailVoList?.[0]?.planId
// 如果任意一个问号前面的值为null或者undefined都将直接返回undefined不在向下取值注意链判断运算符只能在js代码块中使用不能在template里面的标签上使用否则会解析报错如果想要在template里达到上述简写效果可以在计算属性中使用。
// 错误的写法
template div classwrap span计划编号/span{{ticketDetail ticketDetail.planDetailVoList ticketDetail.planDetailVoList[0] ticketDetail.planDetailVoList[0].planNo || }} /div
/template
script export default { data() { return {ticketDetail: {planDetailVoList: [{planNo: T202304200940 // 计划编号}]} } }}
/script//正确的写法
template div classwrap span计划编号/span{{ planNo }} /div
/template
script export default { data() { return {ticketDetail: {planDetailVoList: [{planNo: T202304200940 // 计划编号}]} } },// 计算属性computed: {// 计划编号planNo({ticketDetail}) {return ticketDetail?.planDetailVoList?.[0]?.planNo || }}}
/script链判断运算符知识点链接https://es6.ruanyifeng.com/#docs/operator
1.17 Null 判断运算符
读取对象属性的时候如果某个属性的值是null或undefined有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。
const headerText response.settings.headerText || Hello, world!;
const animationDuration response.settings.animationDuration || 300;
const showSplashScreen response.settings.showSplashScreen || true;上面的三行代码都通过||运算符指定默认值但是这样写是错的。开发者的原意是只要属性的值为null或undefined默认值就会生效但是属性的值如果为空字符串或false或0默认值也会生效。
为了避免这种情况ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||但是只有运算符左侧的值为null或undefined时才会返回右侧的值。
const headerText response.settings.headerText ?? Hello, world!;
const animationDuration response.settings.animationDuration ?? 300;
const showSplashScreen response.settings.showSplashScreen ?? true;上面代码中默认值只有在左侧属性值为null或undefined时才会生效。
1.18 逻辑运算符
使用逻辑运算符!! 快速进行布尔转换。
常规
// 判断是否是ios环境
const isIOS Boolean(window.navigator.userAgent.match(/\(i[^;];( U;)? CPU.Mac OS X/))简写
// 判断是否是ios环境
const isIOS !!window.navigator.userAgent.match(/\(i[^;];( U;)? CPU.Mac OS X/)1.19 扩展运算符
使用扩展运算符进行数组、对象合并。
常规
let obj { name: zhangsan, sex: 男 }
obj Object.assign(obj, {age: 18})let arr [zhangsan, lisi]
arr arr.concat([wangwu, zhaoliu])简写
let obj { name: zhangsan, sex: 男 }
obj {...obj, ...{age: 18}}
obj {...obj, age: 18}let arr [zhangsan, lisi]
arr [...arr, ...[wangwu, zhaoliu]]1.20 运算符隐式转换
使用运算符不仅可以将字符串转为数字还可以将时间转为时间戳。
常规
let type 1
type Number(type)let sjc new Date().getTime()简写
let type 1
type typelet sjc new Date()1.21 v-for中使用解构
常规
li v-foritem in users :keyitem.id {{ item.name }} /li 简写
li v-for{ name, id } in users :keyid {{ name }} /li 1.22 使用Set给数组去重
常规
Array.prototype.distinct function(){let arr this,result [],i,j,len arr.length;for(i 0; i len; i){for(j i 1; j len; j){if(arr[i] arr[j]){j i;}}result.push(arr[i]);}return result;
}
const arra [1,2,3,4,4,1,1,2,1,1,1];
arra.distinct(); // [3,4,2,1]简写
const arr [1, 1, 2, 3, 4, 4];
const uniqueArr [...new Set(arr)];
const uniqueArr1 Array.from(new Set(arr)); // [1,2,34]1.23 Object[key] 重用代码块
常规
function validate(values) {if(!values.first) {uni.showToast({title: 第一项不可为空,icon: none})return false}if(!values.last) {uni.showToast({title: 最后一项不可为空,icon: none})return false}return true
}
let isCheckPass validate({first:, last: })
console.log(是否检验通过, isCheckPass)简写
// 对象校验规则
const schema {first: {required: true,failedTip: 第一项不可为空},last: {required: true,failedTip: 最后一项不可为空}
}
// 通用校验函数
const validate (schema, values) {for(field in schema) {if(schema[field].required) {if(!values[field]) {uni.showToast({title: schema[field].failedTip,icon: none})return false}}}return true
}
let isCheckPass validate(schema, {first: 1, last: 3})
console.log(是否检验通过, isCheckPass)1.24 禁用不必要的嵌套块
常规
function submitTicket(){const { ticketCode, isDoubleSign } thisif(ticketCode 05) {if(isDoubleSign) {this.signType 02}}
}简写
function submitTicket(){const { ticketCode, isDoubleSign } thisif(ticketCode 05 isDoubleSign) {this.signType 02}
}1.25 具有默认值的函数参数应该放到最后
默认值放到最后可以让函数少传实参还能正常执行收获预期结果。
常规
求和函数把具有默认值的参数放在参数列表「左边」
function sum(a 10, b) {return a b
}/*第1个实参 总是对应 第1个形参所以3 赋值给 a, 替换掉默认值 10参数b没有传值最终函数返回NaN
*/
sum(3) // returns NaN as b is undefined简写
求和函数把具有默认值的参数放在参数列表「右边」
function sum(ba 10) {return a b
}/*3 赋值给 ba 没有传值使用默认值 10
*/
sum(3) // 131.26 组件模板应该只包含简单的表达式复杂的表达式则应该重构为计算属性或方法
Bad Code
template div classwrap span工作负责人/span{{planDetail.personInfo.name (planDetail.personInfo.phone? - planDetail.personInfo.phone : planDetail.personInfo.phone)}} /div
/templatescript export default { data() { return {planDetail: {personInfo: {name: zhangsan,phone: 15236616216} } } }}
/scriptGood Code
template div classwrap span工作负责人/span{{WorkLeader}} /div
/templatescript export default { data() { return {planDetail: {personInfo: {name: zhangsan,phone: 15236616216} }} },conputed: {// 复杂的表达式则应该重构为计算属性WorkLeader({planDetail}) {return planDetail.personInfo.name (planDetail.personInfo.phone? - planDetail.personInfo.phone : planDetail.personInfo.phone)}}}
/script注意计算属性中必须包含return字段且不能产生副作用如修改外部变量调用外部无关方法操作了dom等等
1.27 计算属性使用解构赋值减少this滥用提升性能
Bad Code
script
export default { data() { return { ticketDetail: {wtType: 03},sendFlag: false,provPermitType: } }, computed: { // 票类型wtType() {return this.ticketDetail.wtType},// 应采取安全措施是否可编辑measureEdit() { // 配一工作票 非远程许可 非发送 可编辑if (this.ticketDetail.wtType 03 !this.sendFlag ![01, 02].includes(this.provPermitType)) {return true}return false}}
}
/script上述计算属性中存在滥用this去读取data数据的问题 使用this去读取data中数据时会去收集依赖如果滥用this去读取data中数据会多次重复地收集依赖从而产生性能问题。
解决办法
计算属性的值是一个函数其参数是Vue的实例化this对象在上述计算属性中滥用this的例子中可以这样优化。
Good Code
script
export default { data() { return { ticketDetail: {wtType: 03},sendFlag: false,provPermitType: } }, computed: { // 票类型wtType({ticketDetail}) {return ticketDetail.wtType},// 应采取安全措施是否可编辑measureEdit({wtType, sendFlag, provPermitType}) { // 配一工作票 非远程许可 非发送 可编辑if (wtType 03 !sendFlag ![01,02].includes(provPermitType)) {return true}return false}}
}
/script二、开发技巧推荐
2.1 vue开发技巧
2.1.1 使用v-bindobj将所有属性传给子组件
应用场景1
将自身接收到的所有props传递给它的子组件子组件需要在其props:{} 中定义要接受的参数名常用于对组件的二次封装中如对el-table、el-form利用json配置项来控制渲染加载等。 1、v-bind“$props” p r o p s 当前组件接收到的 p r o p s 对象。可以通过 v − b i n d props当前组件接收到的 props 对象。可以通过 v-bind props当前组件接收到的props对象。可以通过v−bindprops 传 入 内 部 组 件 也 可 以 通 过 v m . props 传入内部组件还可以通过vm.props[name]的形式去获取。
应用场景2
针对组件有过多的属性传参时可以不用再一个一个的写可以把较多的参数传递简化减少组件本身的参数定义。 2、v-bind“obj” 如
Children :namename :ageage :sexsex :jobjob/Children改写
Children v-bind{name,age,sex,job}/Children2.1.2 使用$on(‘hook:’) o n ( ‘ h o o k : ’ ) 能够注册 / 监听任意一个钩子函数。使用 on(‘hook:’)能够注册/监听任意一个钩子函数。使用 on(‘hook:’)能够注册/监听任意一个钩子函数。使用on(‘hook:’)方法可以仅使用一种生命周期方法而不是两种来定义/删除事件 。 实现一个定时器的调用与销毁大家很可能会用以下方式实现
export default{data(){timer:null // 需要创建实例},mounted(){this.timer setInterval((){//具体执行内容console.log(1);},1000);},beforeDestory(){clearInterval(this.timer);this.timer null;}
}这种方法存在的问题是
1、vue实例中需要有这个定时器的实例感觉有点多余。
2、创建的定时器代码和销毁定时器的代码没有放在一起不容易维护通常很容易忘记去清理这个定时器。
使用 o n ( ‘ h o o k : ’ ) 监听 b e f o r e D e s t o r y 生命周期可以避免该问题并且因为只需要监听一次所以可以使用 on(‘hook:’)监听beforeDestory生命周期可以避免该问题并且因为只需要监听一次所以可以使用 on(‘hook:’)监听beforeDestory生命周期可以避免该问题并且因为只需要监听一次所以可以使用once进行注册监听。
export default{mounted(){const timer setInterval((){console.log(1);},1000);this.$once(hook:beforeDestory,(){ // 监听一次即可clearInterval(timer);timer null;})}
}2.1.3 如何为Vue组件添加非响应式数据
在vue组件中内data内函数返回的对象默认是响应式的vue的observe函数会遍历此对象所有的属性和子孙属性并转化为getter/setter 使Vue能够追踪依赖在属性被访问和修改时通知变更。这种响应式被用在模板更新、watch变更、computed依赖中非常有用。
为什么要设置非响应式数据
如果我们的数据不会改变或者只会整体改变或者本身就不需要响应式那么为深度响应式做的转化、依赖以及产生的闭包、watcher空间其实是多余的白白浪费了时间和性能。
平时我们自己写的对象不会太复杂这种性能消耗并不明显但当在引用第三方工具库比如图表、地图、模型等如果把多个不需要深度响应式的第三方实例或数据直接挂载到data属性上又或者遇到大数据量列表性能的影响就会比较明显。
利用Vue无法检测到对象属性的添加来实现
官方文档中有介绍 受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃)Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如 let vm new Vue({data:{a:1}
})
// vm.a 是响应式的
vm.b 2
// vm.b 是非响应式的因此我们可以待实例完成初始化后即created中加入
export default {data() {this.version // 这样定义初始值也行return {}},created() {// 直接在created及后续的生命周期函数里定义赋值this.bigData {···}}···
}2.1.5 适时使用$options
vue实例属性$options是一个对象,可以调用vue的各个组件下的方法和数据 。
应用场景
1、获取、调用data外定义的属性
script
export default {data() {return {};},//在data外面定义的属性和方法通过$options可以获取和调用name: options_test,age: 18,testOptionsMethod() {console.log(hello options);},created() { console.log(this.$options.name); // options_testconsole.log(this.$options.age); // 18this.$options.testOptionsMethod(); // hello options},
/script2、复用过滤器filters中的方法
假设在时间选择控件里可以获取到一个返回的时间戳需要转换为日期时间来显示这个时候就可以用到一个filterTime函数来格式化处理时间戳返回想要显示的时间格式。如果这个时候后台要求我们在离线场景提交时增加一个同样时间格式的提交时间字段这个时候我们就可以利用this.$options.filters来拿到这个函数进行转换而不是重新在定义一个方法来处理。
template div{{ planStartSjc | filterText }}/div
/template export default {data() {return {planStartSjc: 1681960059227 // 计划开始时间的时间戳} },filters: {// 时间戳转换filterTime: function (sjc) {let date new Date(sjc)const year date.getFullYear()let month date.getMonth() 1let day date.getDate()let hour date.getHours()let minute date.getMinutes()let second date.getSeconds()month String(month).padStart(2, 0)day String(day).padStart(2, 0)hour String(hour).padStart(2, 0)minute String(minute).padStart(2, 0)second String(second).padStart(2, 0)let time ${year}-${month}-${day} ${hour}:${minute}:${second}return time}},methods:{// 计划提交submitPlan(){let filterTime this.$options.filters.filterTimelet nowSjc new Date() // 此时的时间戳let planCreateTime filterTime(nowSjc) // 转换后的时间let planStartTime filterTime(this.planStartSjc) // 转换后的时间let params {planCreateTime, // 计划创建时间planStartTime, // 计划开始时间}}, },
}3、一键搞定之重置data中的某个数据
scriptexport default {data() {return {// 表单searchForm: {input: ,name: ,isSelected: false}}},methods: {retset() {//重置某一个表单数据this.searchForm this.$options.data().searchForm;}}}
/script4、重置整个data的数据
scriptexport default {data() {return {// 表单searchForm: {input: ,name: ,isSelected: false},dataList: [],planId: }},methods: {// 更新测试testUpdata() {this.searchForm {input: 输入,name: 张三,isSelected: true}this.dataList [1,2]this.planId 123456},retset() {// 重置前先更新一些数据this.testUpdata()console.log(重置前的数据, this.$data)// 直接赋值重置即会导致报错产生最新的Vue已经不允许这样直接对根实例$data进行赋值// this.$data this.$options.data(); // 改用以下写法重置Object.assign(this.$data, this.$options.data()); console.log(重置后的数据, this.$data)}}}
/script2.1.6 如何解决Vue在main.js全局引入scss文件组件里使用scss变量报错问题
报错 Syntax Error: SassError: Undefined variable.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TSFtggSk-1682384948777)(前端代码整洁与开发技巧.assets/20210909143917135-1681975189904.png)]
我一开始的引入方式是这样的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c5PhE89y-1682384948778)(前端代码整洁与开发技巧.assets/20210909145443693-1681975204155.png)]
以为在main.js全局引入了就一劳永逸写着常规css样式时还好好的用到scss变量时就报错了。
解决方法一
在需要用到的 .vue 文件里单独引用 variable.scss 变量文件但是达不到我们想要的“一劳永逸“效果可能还会有奇奇怪怪的潜在问题建议使用方法二。
解决方法二
通过配置使得全局都能使用scss变量而不用每个文件单独引入 。
1、vue-cli2创建的项目
修改build中的utils.js文件将 scss: generateLoaders(‘sass’),修改为如下
scss: generateLoaders(sass).concat({loader: sass-resources-loader,options: {//你自己的scss全局文件的路径resources: path.resolve(__dirname, ../src/common/index.scss)}
}),2.vue-cli3创建的项目
module.exports {css: {loaderOptions: {// 不同 sass-loader 版本对应关键字 // v8-: data v8: prependData v10: additionalDatasass: {additionalData: import /assets/scss/index.scss;},scss: {additionalData: import /assets/scss/index.scss;},}}
}注意scss 配置后面需要加 分号 ‘;’否则会报错 Syntax Error: SassError: media query expression must begin with ‘(’
参考文档https://cli.vuejs.org/zh/guide/css.html#css-modules
2.2 javascript开发技巧
2.2.1 数组操作
1、使用 includes 简化 if 判断
Bad Code
if(ticketCode 01 || ticketCode 02 || ticketCode 03) { }Good Code
if([01, 02, 03].includes(ticketCode)) { }
// 下面这种也可以
if(01,02,03.includes(ticketCode)) { }2、使用every判断是否所有项都满足条件
Bad Code
let arr [{checked: true}, {checked: false}, {checked: true}, {checked: false}]
let isAllchecked true // 是否全部选中
arr.forEach(item {if(!item.checked) {isAllchecked false}
})Good Code
数组的every方法只要有一项不满足条件就会返回false不会再继续遍历
let arr [{checked: true}, {checked: false}, {checked: true}, {checked: false}]
let isAllchecked arr.every(item {console.log(item, item)return !!item.checked
})3、使用some判断是否有一项满足条件
Bad Code
let arr [{checked: false}, {checked: true}, {checked: false}, {checked: true}]
let isHaveChecked false // 是否有选中的
arr.forEach(item {if(item.checked) {isHaveChecked true}
})Good Code
数组的some方法只要有一项满足条件就会返回true不会再继续遍历
let arr [{checked: false}, {checked: true}, {checked: false}, {checked: true}]
let isHaveChecked arr.some(item {console.log(item, item)return !!item.checked
})4、使用reduce遍历数组处理求和等复杂逻辑
reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数每一次运行 reducer 会将先前元素的计算结果作为参数传入最后将其结果汇总为单个返回值 。
第一次执行回调函数时不存在“上一次的计算结果”。如果需要回调函数从数组索引为 0 的元素开始执行则需要传递初始值。否则数组索引为 0 的元素将被作为初始值 initialValue迭代器将从第二个元素开始执行索引为 1 而不是 0。
语法
reduce(callbackFn)
reduce(callbackFn, initialValue)参数 callbackFn 一个“reducer”函数包含四个参数 previousValue上一次调用 callbackFn 时的返回值。在第一次调用时若指定了初始值 initialValue其值则为 initialValue否则为数组索引为 0 的元素 array[0]。 currentValue数组中正在处理的元素。在第一次调用时若指定了初始值 initialValue其值则为数组索引为 0 的元素 array[0]否则为 array[1]。currentIndex数组中正在处理的元素的索引。若指定了初始值 initialValue则起始索引号为 0否则从索引 1 起始。array用于遍历的数组。 initialValue 可选 作为第一次调用 callback 函数时参数 previousValue 的值。若指定了初始值 initialValue则 currentValue 则将使用数组第一个元素否则 previousValue 将使用数组第一个元素而 currentValue 将使用数组第二个元素。
返回值
使用“reducer”回调函数遍历整个数组后的结果。
示例
1计算数组中每个元素出现的次数
let names [Alice, Bob, Tiff, Bruce, Alice]let nameNum names.reduce((pre,cur){if(cur in pre) {pre[cur]} else {pre[cur] 1 }return pre
}, {})
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}2数组去重
let arr [1,2,3,4,4,1]
let newArr arr.reduce((pre,cur){if(!pre.includes(cur)){return pre.concat(cur)}else{return pre}
},[])
console.log(newArr);// [1, 2, 3, 4]// 简便写法
let newArr [...new Set(arr)]
let newArr Array.from(new Set(arr))3将二维数组转化为一维
let arr [[0, 1], [2, 3], [4, 5]]
let newArr arr.reduce((pre,cur) {return pre.concat(cur)
}, [])
console.log(newArr); // [0, 1, 2, 3, 4, 5]// 简便写法
let newArr arr.flat() // 拉平二维数组4将多维数组转化为一维
let arr [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr function(arr){return arr.reduce((pre,cur)pre.concat(Array.isArray(cur)?newArr(cur):cur),[])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]2.2.2 对象操作
1、向对象有条件的添加属性
使用场景
表单提交时针对个性化开关控制的必填参数可以通过该方式来添加到固定参数集合中。
const condition true;
const person {id: 1,name: John Doe,...(condition { age: 16 }),
};// 如果 condition 为 falseJavaScript 会做这样的事情:
const person {id: 1,name: 前端小智,...(false),
};
// 展开 false 对对象没有影响
console.log(person); // { id: 1, name: John Doe }2、检查属性是否存在对象中
const example {};
example.prop exists;// hasOwn 只会对直接属性返回 true::
Object.hasOwn(example, prop); // returns true
Object.hasOwn(example, toString); // returns false
Object.hasOwn(example, hasOwnProperty); // returns false// in 会对直接或继承的属性返回 true:
prop in example; // returns true
toString in example; // returns true
hasOwnProperty in example; // returns true3、使用Object.entries() 返回一个给定对象自身可枚举属性的键值对数组
const object1 {a: somestring,b: 42
};const arr Object.entries(object1) //arr:[[a,somestring],[b,42]]for (const [key, value] of arr) {console.log(${key}: ${value});
}// Expected output:
// a: somestring
// b: 424 、使用Object.fromEntries( ) 方法把键值对列表转换为一个对象
const entries new Map([[foo, bar],[baz, 42]
]);const obj Object.fromEntries(entries);console.log(obj);
// Expected output: Object { foo: bar, baz: 42 }5、使用Object.freeze冻结对象可在vue data定义非响应式变量使用
const obj {prop: 42
};Object.freeze(obj);obj.prop 33;
// Throws an error in strict modeconsole.log(obj.prop);
// Expected output: 422.2.3 字符串操作
1、字符串不满两位补零
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度会在头部或尾部补全。padStart()用于头部补全padEnd()用于尾部补全。
padStart()和padEnd()一共接受两个参数第一个参数是字符串补全生效的最大长度第二个参数是用来补全的字符串。
x.padStart(5, ab) // ababx
x.padStart(4, ab) // abaxx.padEnd(5, ab) // xabab
x.padEnd(4, ab) // xaba如果原字符串的长度等于或大于最大长度则字符串补全不生效返回原字符串。
xxx.padStart(2, ab) // xxx
xxx.padEnd(2, ab) // xxx如果用来补全的字符串与原字符串两者的长度之和超过了最大长度则会截去超出位数的补全字符串。
abc.padStart(10, 0123456789)
// 0123456abc如果省略第二个参数默认使用空格补全长度。
x.padStart(4) // x
x.padEnd(4) // x 常用场景
// 获取当前月份小于10前补0
let month new Date().getMonth() 1
// 常规写法利用三木运算符处理
month month 10 ? 0 month : month// 也可以利用pdaStart填充实现
month String(month).padStart(2, 0)
// 还可以按照以下方式实现
month (0 month).slice(-2)console.log(month);
// expected output: 01// 银行卡号只显示后4位
const fullNumber 2034399002125581;
const last4Digits fullNumber.slice(-4);
const maskedNumber last4Digits.padStart(fullNumber.length, *);console.log(maskedNumber);
// expected output: ************55812、判断字符串前缀、后缀
判断字符串前缀、后缀不要一言不合就使用正则表达式
const url https://bili98.cn;
const isHTTPS /^https:\/\//.test(url); // trueconst fileName main.py;
const isPythonCode /\.py$/.test(fileName); // true推荐使用 String.prototype.startsWith 和 String.prototype.endsWith语义性更好
const url https://bili98.cn;
const isHTTPS url.startsWith(https://) // trueconst fileName main.py;
const isPythonCode fileName.endsWith(.py); // true3、使用replaceAll 方法替换所有匹配到的字符
ES2021新特性-替换一个字符串中的所有指定字符 replaceAll()方法的使用
在 ES2021 之前要替换掉一个字符串中的所有指定字符我们可以这么做
const str 2-4-6-8-10
const newStr str.replace(/\-/g, )
console.log(newStr) // 246810ES2021 则提出了 replaceAll 方法并将其挂载在 String 的原型上可以这么用现在可以用String.prototype.replaceAll()替换全部字符串而不需要使用正则。
const str 2-4-6-8-10
const newStr str.replaceAll(-, )
console.log(newStr) // 2468102.3 css开发技巧
2.3.1 巧妙使用伪类生成表单必填标识
::befor {content: *;color: red;margin-right: 4px;
}示例element ui里面的表单项必填标识
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dGYZMG1q-1682384948779)(前端代码整洁与开发技巧.assets/1673255318557.png)]
2.3.2 增强用户体验使用伪元素实现增大点击热区
在移动端按钮通常都很小但是有时由于设计稿限制我们不能直接去改变按钮元素的高宽。那么这个时候有什么办法在不改变按钮原本大小的情况下去增加他的点击热区呢
这里伪元素也是可以代表其宿主元素来响应的鼠标交互事件的。借助伪元素可以轻松帮我们实现如
.btn::befoer{ content: ; position: absolute; top: -10px; right: -10px; bottom: -10px; left: -10px;
} 2.3.3 利用伪类实现鼠标移入时下划线向两边展开的效果
html langen
headmeta charsetUTF-8title鼠标移入下划线展开/titlestyle typetext/css#underline{width: 200px;height: 50px;background: #ddd;margin: 20px;position: relative;}#underline:after{content: ;width: 0;height: 5px;background: blue;position: absolute;top: 100%;left: 50%;transition: all .8s;}#underline:hover:after{left: 0%;width: 100%;}/style
/head
bodydiv idunderline/div
/body
/html 效果展示
1、在浏览器上随便找个网页打开f12;
2、点击元素栏选中html跟标签右键以html格式修改
3、复制上述代码覆盖掉网页上的html代码完成修改
4、鼠标移入显示元素即可看到效果。
三、其它技巧
3.1.1 适时使用provide和inject
provide 和 inject 主要为高阶插件/组件库提供用例并不推荐直接用于应用程序代码中并且这对选项需要一起使用以允许一个祖先组件向其所有子孙后代注入一个依赖不论组件层次有多深在上下游关系成立的时间里始终生效。
provideObject | () ObjectinjectArray | { [key: string]: string | Symbol | Object }
//父组件:
provide: { // provide 提供一个属性和方法foo: 这是 foo,fooMethod: () {console.log(父组件 fooMethod 被调用)}
},// 子或者孙子组件
inject: [foo, fooMethod], //数组或者对象,注入到子组件
mounted() {this.fooMethod()console.log(this.foo)
}
//在父组件下面所有的子组件都可以利用injectprovide 和 inject 绑定并不是可响应的。这是官方刻意为之的。 然而如果你传入了一个可监听的对象那么其对象的属性还是可响应的因为对象是引用类型
//父组件:
provide: { foo: 这是 foo
},
mounted(){this.foo 这是新的 foo
}// 子或者孙子组件
inject: [foo],
mounted() {console.log(this.foo) //子组件打印的还是这是 foo
}//父组件:
provide() { return {foo: this.foo,nameList: this.nameList,personalInfo: this.personalInfo}
},
data() {return {foo: 这是foo,nameList: [张三, 李四],personalInfo: { age: 18 }}
},
mounted(){this.foo 这是新的 foothis.nameList.push(王五) // 改变堆内存中的值// this.nameList [王五, 赵六] // 放开后子组件仍打印 [张三, 李四, 王五]this.personalInfo.age 16 // 改变堆内存中的值this.personalInfo {age: 12} // 将当前对象的指针指向新的堆内存地址
}// 子或者孙子组件
templatediv{{foo}}/divdiv{{nameList}}/divdiv{{personalInfo personalInfo.age}}/div
/template
scriptexport default{inject: [foo, nameList, personalInfo], mounted() {setTimeout(() {console.log(this.foo) // 这是foo -视图不会更新console.log(this.nameList) // [张三, 李四, 王五] -视图会更新console.log(this.personalInfo.age) // 16 foo -视图会更新}, 1000)}}
/script3.1.2 利用key值解决vue就地复用策略的问题
已知throttle是封装过的节流指令在规定时间仅第一次执行后续点击不再执行目前设置的时间是2秒即每2秒内的重复点击只执行第一次。
// 节流指令
Vue.directive(throttle, {inserted: (el, binding) {const throttleTime binding.value || 2000 // 节流时间el.addEventListener(click, event {if (el.nodeName BUTTON || el.type button) {if (!el.disabled) {el.disabled truesetTimeout(() {el.disabled false}, throttleTime)}} else {// 合理使用样式穿透使元素本身及其子元素鼠标事件失效 el.style.pointerEvents noneel.style.color redel.setAttribute(data-flag, 0) // 只是为了打印setTimeout(() {if (el el.style) {el.style.pointerEvents autoel.style.color #000el.setAttribute(data-flag, 1) // 只是为了打印}}, throttleTime)}}, false)}
})通过以下代码可以发现点击完接受工作票按钮后不到2秒的时间内由于vue的就地复用策略导致工作许可按钮复用了原来的元素继承了之前的样式和自定义属性从而导致工作许可按钮在上述2s内的点击不会生效且颜色也发生了短暂的改变。利用项目加浏览器调试来演示说明
templateview!-- 待接收 --button v-ifstateNo 10 classbtn-test clickticketReceive v-throttle接收工作票/button!-- 待许可 --button v-ifstateNo 12 classbtn-test clickpermitTicket v-throttle工作许可/button /view
/template
script
export default{data() {return {stateNo: 10}},methods: {// 接收工作票ticketReceive($event) {console.log($event, $event)this.stateNo 12this.$nextTick(() {console.log(视图已完成更新, 工作许可按钮已显示)setTimeout(() {let dom document.querySelector(.btn-test)console.log(data-flag, dom.getAttribute(data-flag)) // 0}, 1000)setTimeout(() {let dom document.querySelector(.btn-test)console.log(data-flag, dom.getAttribute(data-flag)) // 1}, 2600)})},permitTicket() {console.log(点击了工作许可)},}
}
/script解决办法给两个使用v-if的标签添加不同的key值如
templateview!-- 待接受 --button v-ifstateNo 10 classbtn-test clickticketReceive v-throttle key1接受工作票/button!-- 待许可 --button v-ifstateNo 12 classbtn-test clickpermitTicket v-throttle key2工作许可/button /view
/template‘0’) // 只是为了打印 setTimeout(() { if (el el.style) { el.style.pointerEvents ‘auto’ el.style.color ‘#000’ el.setAttribute(‘data-flag’, ‘1’) // 只是为了打印 } }, throttleTime) } }, false) } }) 通过以下代码可以发现点击完接受工作票按钮后不到2秒的时间内由于vue的就地复用策略导致工作许可按钮复用了原来的元素继承了之前的样式和自定义属性从而导致工作许可按钮在上述2s内的点击不会生效且颜色也发生了短暂的改变。利用项目加浏览器调试来演示说明jsx
templateview!-- 待接收 --button v-ifstateNo 10 classbtn-test clickticketReceive v-throttle接收工作票/button!-- 待许可 --button v-ifstateNo 12 classbtn-test clickpermitTicket v-throttle工作许可/button /view
/template
script
export default{data() {return {stateNo: 10}},methods: {// 接收工作票ticketReceive($event) {console.log($event, $event)this.stateNo 12this.$nextTick(() {console.log(视图已完成更新, 工作许可按钮已显示)setTimeout(() {let dom document.querySelector(.btn-test)console.log(data-flag, dom.getAttribute(data-flag)) // 0}, 1000)setTimeout(() {let dom document.querySelector(.btn-test)console.log(data-flag, dom.getAttribute(data-flag)) // 1}, 2600)})},permitTicket() {console.log(点击了工作许可)},}
}
/script解决办法给两个使用v-if的标签添加不同的key值如
templateview!-- 待接受 --button v-ifstateNo 10 classbtn-test clickticketReceive v-throttle key1接受工作票/button!-- 待许可 --button v-ifstateNo 12 classbtn-test clickpermitTicket v-throttle key2工作许可/button /view
/template