前端实战开发技巧【持续更新】

505 阅读1分钟

input正则校验

let regexp = '^1[3456789]\d{9}$'
<input pattern="regexp" required>

动态存在某个值

有些时候,后端返回的数据里动态存在某个值,也就意味着,有时候有这个数据,有时候没有,然后甩你一句话,“有就显示,没有就不显示”,作为前端的我们自然很严谨

// 模拟后端数据
let result = {
    status:200,
    codeMsg:'success',
    data:{
        userInfo:{
            age:18,
            hobby:['敲代码','打篮球']
        }
    }
}
// 前端严谨写法
if(result.data){
    if(result.data.userInfo){
        if(result.data.userInfo.hobby){
            if(Array.isArray(result.data.userInfo.hobby)){
                if(result.data.userInfo.hobby.length){
                    //遍历 result.data.userInfo.hobby 进行渲染显示
                }
            }
        }
    }
}
// 第一种用 `&&` 进行优化
if ( result.data && result.data.userInfo && 
    result.data.userInfo.hobby && 
    Array.isArray(result.data.userInfo.hobby) && 
    result.data.userInfo.hobby.length ) 
{
    //遍历 result.data.userInfo.hobby 进行渲染显示
}
// 第二种,`try catch `策略,严谨但又懒的前端
try {
   if(result.data.userInfo.hobby.length){
       //遍历 result.data.userInfo.hobby 进行渲染显示
   }
} catch (error) {  }
// 第三种可选项 (`?`)
if(result?.data?.userInfo?.hobby?.length){
    //遍历 result.data.userInfo.hobby 进行渲染显示
}


封装一个获取位置的方法

function getPosition(direction){
    return ({
        left:"左",
        right:"右",
        top:"上",
        bottom:"下"
    })[direction] || "未知"
}

判空并赋值

// 例子
let first = 'abc';
if (first !== null || first !== undefined || first !== '') {}
// 优化
let second = first || '空值';
console.log(second, 'second');

给变量默认值

let toto 
console.log(toto) //undefined 
toto = toto ?? 'default value' 
console.log(toto) //default value

条件简写

// 例子
if(condition){ toto() }
// 优化
condition && toto()

可选项 (?)

检查对象的某些属性是否存在,然后才能再处理它,不然会报错

const toto = { a: { b: { c: 5 } } }
console.log(toto?.a?.b?.c) // 5
console.log(toto?.a?.b?.d) // 5undefined
// 例子
if (!!toto.a && !!toto.a.b && !!toto.a.b.c) { ... } // toto.a.b.c exist;
// 优化
if (toto.a?.b?.c?.d) { ... }

!操作符将任何变量转换为布尔值

const toto = null;
console.log(!!toto,'!!toto') // false

避免if过长

if (value === 'a' || value === 'b' || value === 'c') { ... }
// 优化
if (['a', 'b', 'c'].includes(value)) { ... }

移除type="number"尾部的箭头

<input type="number" class="no-arrow" />
/* 关键css */ 
.no-arrow::-webkit-inner-spin-button {
    -webkit-appearance: none; 
}

巧用not选择器

有些情况下所有的元素都需要某些样式,唯独最后一个不需要,这时候使用not选择器将会特别方便

li:not(:last-child){ border-bottom: 1px solid #ebedf0; }

节流函数

//节流函数
throttle() {
        //保持this的指向始终指向vue实例
        var that = this;
        if (!that.statu) {
                return;
        }
        that.statu = false;
        setTimeout(function() {
                console.log(new Date());
                that.search();
                that.statu = true;
        }, 3000);
},

v-if和v-for不建议同时使用

问题

  1. v-for的优先级比v-if高,每次v-for都会执行v-if,造成不必要的计算
<li v-for="(item,index) in list" v-if="item.isActive" :key="item.id">
    {{ item.name }}
</li>

如上例子即使用100个item中只有一个需要展示 使用了v-if也是需要循环整个数组的、这在性能上是极大的浪费

解决

1、如何实在循环外部可以在外层嵌套已成template(页面不生成dom节点)、在外层v-if判断、然后在进行使用v-for循环

<template v-if="isShow">
    <div v-for="(item,index) in list" v-if="item.isActive" :key="item.id">
        {{ item.name }}
    </div>
</template>

2、如果条件出现在循环内部、可以通过计算属性computed提前过滤掉那些不需要展示的数据

computed: {
    listData: function() {
        return this.list.filter(function (item) {
            return item.isShow
        })
    }
}

v-for key作用

1.给每个节点做唯一标识 2.高效的更新虚拟DOM

冒泡排序

numbersSort1(arr = []) {
    var len = arr.length;
    for (var i = 0; i < len - 1; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                // 相邻元素两两对比,元素交换的时候用第三者变量来暂存数据,最后用这个变量去替换原数据
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return arr;
},

等待多个异步函数

async fun1() {
    const res = await http(url1, query2)
}

async fun2() {
    const res = await http(url2, query2)
}
async fun() {
    await promise.all([fun1,fun2])
}
// Promise.all 实际上是一个函数,它接受一个 promises 数组并返回一个 Promise。
然后当所有的 promises 都完成时会得到 resolve 或者当其中一个被拒绝时会得到 rejected。

获取对象某个值

let obj = {a: '1',b: '2'}
console.log('获取对象值',(obj)['a']);

switch优化

// 1.对象优化
 previewWeek(i){
    return ({
        1:'星期一',
        2:'星期二',
        3:'星期三',
        4:'星期四',
        5:'星期五',
        6:'星期六',
        7:'星期日',
    })[i] || ''
}
// 2.数组优化
previewWeek(i) {
        return i > 0 && i < 8 ? '星期' + ['一', '二', '三', '四', '五', '六', '日'][i - 1] : '';
}
// 3.map优化
function previewWeek(i){
    let weeksMap = new Map([
        [1, '一'],
        [2, '二'],
        [3, '三'],
        [4, '四'],
        [5, '五'],
        [6, '六'],
        [7, '日']
    ]);
    return weeksMap.get(i)?'星期'+weeksMap.get(i):''
}
// 4.## includes 优化代码
function verifyIdentity(identityId){
    if([1,2,3,4].includes(identityId)){
        return '你的身份合法,请通行!'
    }else{
        return '你的身份未知,警告!'
    }
}
// 最终优化实现:includes + 三元
function verifyIdentity(identityId){
    return [1,2,3,4].includes(identityId)?'你的身份合法,请通行!':'你的身份未知,警告!'
}

清空数组方式

let arr = [1,2,3,4];
arr.length = 0;

获取用户设备

// 获取用户设备
function getUserMedia(constraints, success, error) {
        if (navigator.mediaDevices.getUserMedia) {
                //最新的标准API
                navigator.mediaDevices
                        .getUserMedia(constraints)
                        .then(success)
                        .catch(error);
        } else if (navigator.webkitGetUserMedia) {
                //webkit核心浏览器
                navigator.webkitGetUserMedia(constraints, success, error);
        } else if (navigator.mozGetUserMedia) {
                //firfox浏览器
                navigator.mozGetUserMedia(constraints, success, error);
        } else if (navigator.getUserMedia) {
                //旧版API
                navigator.getUserMedia(constraints, success, error);
        }
}

watch监听

watch: {
        // 第一种方式:监听整个对象,每个属性值的变化都会执行handler
        // 注意:属性值发生变化后,handler执行后获取的 newVal 值和 oldVal 值是一样的
    food: {
        // 每个属性值发生变化就会调用这个函数
        handler(newVal, oldVal) {
            console.log('oldVal:', oldVal)
            console.log('newVal:', newVal)
        },
        // 立即处理 进入页面就触发
        immediate: true,
        // 深度监听 属性的变化
        deep: true
    },
    // 第二种方式:监听对象的某个属性,被监听的属性值发生变化就会执行函数
    // 函数执行后,获取的 newVal 值和 oldVal 值不一样
    'food.name'(newVal, oldVal) {
        console.log('oldVal:', oldVal)   // 冰激凌
        console.log('newVal:', newVal)   // 棒棒糖
    }
}

截取字符串

// substring(beginIndex,endIndex) 方法返回字符串的子字符串。
// beginIndex -- 起始索引(包括), 索引从 0 开始。
// endIndex -- 结束索引(不包括)。
names = ',某某分包单位'
names = names.substring(1);
// 输出:某某设计单位

检索数组

  // currDepartId.includes(item.key)
  // currDepartId.includes(item.key)
  for(let item of this.dataList){
    // console.log(currDepartId.includes(item.key))
    if(currDepartId.includes(item.key)){
      names+=","+item.title
    }
  }

递归遍历

  reWriterWithSlot(arr){
    for(let item of arr){
      if(item.children && item.children.length>0){
        this.reWriterWithSlot(item.children)
        let temp = Object.assign({},item)
        temp.children = {}
        this.dataList.push(temp)
      }else{
        this.dataList.push(item)
        item.scopedSlots={ title: 'title' }
      }
    }
  },

判断数组是否存在

item.children && item.children.length>0

文本溢出处理

//单行 
.single { 
    overflow: hidden; 
    white-space: nowrap; 
    text-overflow: ellipsis; 
} 
//多行 
.more { 
    display: -webkit-box !important;
    overflow: hidden; 
    text-overflow: ellipsis; 
    work-break: break-all; 
    -webkit-box-orient: vertical; 
    -webkit-line-clamp: 2; //指定行数 
}

判断系统

created(){
    //判断系统
    let u = navigator.userAgent;
    let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //g
    let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
    if (isAndroid) {
        this.system = 'Android';
    } else if (isIOS) {
        this.system = 'IOS';
    } else {
        this.system = 'pc';
    }
},

获取初始化数据

方法一

//初始化时获取基本数据
async initData(){
    if (!this.latitude) {
        //获取位置信息
        let res = await msiteAddress(this.geohash);
        // 记录当前经度纬度进入vuex
        this.RECORD_ADDRESS(res);
    }
    //获取商铺信息
    this.shopDetailData = await shopDetails(this.shopId, this.latitude, this.longitude);
    //隐藏加载动画
    this.hideLoading();
},

方法二

async initData(){
    try{
        this.city = await cityGuess();
        const countData = await getResturantsCount();
        if (countData.status == 1) {
            this.count = countData.count;
        }else{
            throw new Error('获取数据失败');
        }
        this.getResturants();
    }catch(err){
        console.log('获取数据失败', err);
    }
},

vuex提交

methods: {
    ...mapMutations([
        'RECORD_ADDRESS','ADD_CART','REDUCE_CART','INIT_BUYCART','CLEAR_CART','RECORD_SHOPDETAIL'
    ]),
        //初始化时获取基本数据
    async initData(){
        if (!this.latitude) {
            //获取位置信息
            let res = await msiteAddress(this.geohash);
            // 记录当前经度纬度进入vuex
            this.RECORD_ADDRESS(res);
        }
    },
}

计算属性

//购物车中总共商品的数量
totalNum: function (){
    let num = 0;
    this.cartFoodList.forEach(item => {
        num += item.num
    })
    return num
},

更新数据

async updateShop(){
    this.dialogFormVisible = false;
    try{
        Object.assign(this.selectTable, this.address);
        this.selectTable.category = this.selectedCategory.join('/');
        const res = await updateResturant(this.selectTable)
        if (res.status == 1) {
            this.$message({
                type: 'success',
                message: '更新店铺信息成功'
            });
            this.getResturants();
        }else{
            this.$message({
                type: 'error',
                message: res.message
            });
        }
    }catch(err){
        console.log('更新餐馆信息失败', err);
    }
},

自定义参数

const params = {
	...this.foodForm,
	category_id: this.selectValue.id,
	restaurant_id: this.restaurant_id,
}

使用less的mixin方法

@import '../style/mixin';
span{
	.sc(14px, #999);
	transition: all 400ms;
}

去除滚动条

/* 去掉滚动条 */
&::-webkit-scrollbar {
	display: none;
}

获取初始数据

loadData() {
this.dataSource = []
getPermissionList().then((res) => {
  if (res.success) {
    console.log(res.result)
    this.dataSource = res.result
  }
})
},

接口赋值

let { result } = data;
let list = [];
if (result) {
  if (Array.isArray(result)) {
    list = result
  } else if (Array.isArray(result.records)) {
    list = result.records
  }
}

变量方法

@click="handleAdd(1)"
handleAdd(num) {
if (num == 1) {
  this.$refs.departModal.add()
  this.$refs.departModal.title = '新增'
} else {
  this.$refs.departModal.add(this.rightClickSelectedKey)
  this.$refs.departModal.title = '新增'
}
},

获取路由参数

created() {
  this.currFlowId = this.$route.params.id
  this.currFlowName = this.$route.params.name
  // this.loadTree()
},

导入样式

<style scoped>
  @import '~@assets/less/common.less';
</style>

删除方法

handleDelete() {
var that = this
this.$confirm({
  title: '确认删除',
  content: '确定要删除此部门以及子节点数据吗?',
  onOk: function () {
    deleteByDepartId({id: that.rightClickSelectedKey}).then((resp) => {
      if (resp.success) {
        //删除成功后,去除已选中中的数据
        that.checkedKeys.splice(that.checkedKeys.findIndex(key => key === that.rightClickSelectedKey), 1);
        that.$message.success('删除成功!')
        that.loadTree()
        //删除后同步清空右侧基本信息内容
        let orgCode=that.model.orgCode;
        if(orgCode && orgCode === that.rightClickSelectedOrgCode){
          that.onClearSelected()
        }
      } else {
        that.$message.warning('删除失败!')
      }
    })
  }
})
},