3.2.4、项目中的使用场景
1、对this进行解构,减少this的滥用
//在computed中对this进行解构 computed: { btnsArr({isShowDispatch, isPaperWorkTicket, wtPaperValidOrg}) { // 派工单或者是四川个性化纸质票不展示保存按钮 return isShowDispatch || (isPaperWorkTicket && wtPaperValidOrg) ? ['提交'] :['保 存', '提交'] } } //在methods中对this进行解构 methods: { getData(){ let { getIssuerArr, ticketCodeValue } = this /* *getIssuerArr.forEach(item=>{ * ...//处理数据的逻辑 *}) */ } }
2、在通过接口拿到后端获取的数据,进行处理的时候,常用对象的解构赋值代替普通的赋值操作来优化代码。
//获取接口返回的数据res const res = await workTicket.getSignerList(params) if (res.RT_F === 1) { //对返回值进行解构,赋值给对应的变量 let { signerList, defaultSigner, defaultSignerVo } = res.DTS || {} ...//后续的逻辑处理 }
3.3、字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。(解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。)
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello'; len // 5
3.4、函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x, y]){ return x + y; } add([1, 2]); // 3
上面代码中,函数add
的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x
和y
。对于函数内部的代码来说,它们能感受到的参数就是x
和y
。
函数参数的解构也可以使用默认值。
function move({x = 0, y = 0} = {}) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0]
上面代码中,函数move
的参数是一个对象,通过对这个对象进行解构,得到变量x
和y
的值。如果解构失败,x
和y
等于默认值。
注意,下面的写法会得到不一样的结果。
function move({x, y} = { x: 0, y: 0 }) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]
上面代码是为函数move
的参数指定默认值,而不是为变量x
和y
指定默认值,所以会得到与前一种写法不同的结果。
4、运算符的扩展
4.1、扩展运算符和剩余参数
扩展运算符(Spread Operator)和剩余参数(Rest Parameter)的写法相同,都是在变量或字面量之前加三个点(…)。虽然两者之间有诸多类似,但它们的功能和应用场景却完全不同。扩展运算符能把整体展开成个体,常用于函数调用、数组或字符串处理等;而剩余参数正好相反,把个体合并成整体,常用于函数声明、解构参数等。此处的整体可能是数组、字符串或类数组对象等,个体可能是字符、数组的元素或函数的参数等。
4.1.1、扩展运算符
扩展运算符的用途简单概括,可以分为以下三种。
(1)替代函数的apply()方法。
(2)简化函数调用时传递实参的方式。
(3)处理数组和字符串。
1)apply()
函数的apply()方法能够间接调用其它对象的方法,往往能收获奇效,例如用Math对象的min()方法获取数组中的最小值,min()方法本来只接收一组参数,利用apply()方法后就能直接传递一个数组,如下所示。
let arr = [1, 0, 2], min; min = Math.min(1, 0, 2); //一组参数的调用方式 min = Math.min.apply(null, arr); //利用apply()间接调用
虽然apply()方法很便捷,但每次都必须设置this的指向(即定义第一个参数),并且迂回的写法可能会为理解代码意图设置障碍。而使用扩展运算符后,既能以简单的语法形式完成相同的功能,还能更清晰的表明代码的意图。下面用扩展运算符查找数组中的最小值。
min = Math.min(...arr); console.log(min); //0
2)传参
函数在被调用时,实参通常都是以逗号分隔的序列形式传递到函数体内。如果实参的值被保存在数组中,那么就要一个一个的读取数组中指定位置的元素,例如创建一个日期对象(调用它的构造函数),把年月日的信息保存在数组中,如下代码所示。注释中的日期并不是默认的显示格式,只是为了更容易阅读而这么写的。
let date = [2018, 7, 6]; new Date(date[0], date[1], date[2]); //2018-7-6
换成扩展运算符的写法后,实参的传递就变得非常的简洁,如下所示。
new Date(...date); //2018-7-6
不仅如此,在调用函数的时候,还可以使用多个扩展运算符,并能和普通的实参混合使用,如下所示。
let time = [10, 28]; new Date(...date, ...time, 45); //2018-7-6 10:28:45
3)数组和字符串
在扩展运算符出现之前,要执行数组的复制、合并等操作,需要调用数组的slice()、concat()、unshift()等方法。下面是一个数组复制与合并的简单示例。
let arr1 = [1, 2, 3], arr2, arr3; arr2 = arr1.slice(); //复制数组 arr3 = arr2.concat(arr1); //合并数组 console.log(arr1); //[1, 2, 3] console.log(arr2); //[1, 2, 3] console.log(arr3); //[1, 2, 3, 1, 2, 3]
接下来用扩展运算符来完成同样的功能,如下代码所示。
arr2 = [...arr1]; //复制数组 arr3 = [...arr1, ...arr2]; //合并数组
在实际项目中,肯定会碰到各式各样的数组操作,合理利用扩展运算符,不但可以节省大量的代码,还能提升代码的可读性。
扩展运算符不仅能处理数组,还能处理字符串。在JavaScript中,字符串的行为类似于数组,但它不能直接调用数组的方法,需要先执行自己的split()方法转换成数组。而使用扩展运算符后,就能省去这步操作,具体如下所示,注意,包裹的方括号不能省略。
let str = "strick"; str.split(""); //["s", "t", "r", "i", "c", "k"] [...str]; //["s", "t", "r", "i", "c", "k"]
4.1.2、剩余参数
在JavaScript的函数中,声明时定义的形参个数可以和传入的实参个数不同。当实参个数大于形参个数时,ES6新增的剩余参数能把没有对应形参的实参收集到一个数组中。下面是一个简单的示例。
function func(name, ...args) { console.log(name); console.log(args[0]); } args = [29,'88'] func("strick"); //首先输出"strick",然后输出undefined func("freedom", 29,'88'); //首先输出"freedom",然后输出29
第一次调用func()函数只传入了一个实参,对应的形参就是name。第二次调用func()函数传入了两个实参,第一个有对应的形参,而第二个并没有对应的形参。此时,该实参就会被放到数组args(就是剩余参数)中,变为该数组的一个元素,在函数体内就能通过数组的索引读取该实参。有一点要注意,剩余参数不会影响函数的length属性,该属性的值表示形参个数。以上面的func()函数为例,… args并不是一个形参,因此,func()函数的length属性值为1。
console.log(func.length); //1
剩余参数 之前,我们在接触多个参数的时候,通常使用arguments对象去代替接收多个参数。但是,arguments是类数组对象,不能使用数组的很多便捷操作, 箭头函数当中没有arguments对象 。
下面是一个 rest 参数代替arguments
变量的例子。
// arguments变量的写法 function sortNumbers() { return Array.from(arguments).sort(); } // rest参数的写法 const sortNumbers = (...numbers) => numbers.sort();
1) 两点限制
剩余参数有两点限制,在使用时需要引起注意。第一点是在函数中声明时必须放在最后,下面是一种错误的写法。
function restrict1(...args, name) { //抛出语法错误 }
第二点是不能在对象字面量的setter方法中声明,因为该方法只接收一个参数,而剩余参数不会限制参数的数量。注意,setter方法在定义时会用set替代function关键字,下面是一个会抛出语法错误的例子。
var obj = { set age(...value) { this._age = value; } };
4.2、链判断运算符
编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取message.body.user.firstName
这个属性,安全的写法是写成下面这样。
// 错误的写法 const firstName = message.body.user.firstName || 'default'; // 正确的写法 const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default';
上面例子中,firstName
属性在对象的第四层,所以需要判断四次,每一层是否有值。
这样的层层判断非常麻烦,因此 ES2020 引入了“链判断运算符”(optional chaining operator)?.
,简化上面的写法。
const firstName = message?.body?.user?.firstName || 'default';
上面代码使用了?.
运算符,直接在链式调用的时候判断,左侧的对象是否为null
或undefined
。如果是的,就不再往下运算,而是返回undefined
。
4.3、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
时,才会生效。
这个运算符的一个目的,就是跟链判断运算符?.
配合使用,为null
或undefined
的值设置默认值。
const animationDuration = response.settings?.animationDuration ?? 300;
4.4、项目中的使用过场景
读取层级很深的对象:
//在computed获取配置信息 computed: { isFillPreview({sysParasInfo}) { return sysParasInfo?.WT_PREVIEW_RULE?.includes('01') }, getPreviewPage({sysParasInfo}) { return sysParasInfo?.WT_TICKET_PRE_STYLE ?? '01' }, }
5、字符串的扩展
5.1、模板字符串
ES6 引入了模板字符串,用反引号表示,模板字符串中嵌入变量,需要将变量名写在${}
之中。
//用uni.redirectTo进行页面跳转时url使用模板字符串 uni.redirectTo({ url: `/pages/work-plan/work-week-detail?planId=${res.DTS}&openFrom=${this.openFrom}` })
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中
5.2、字符串的新增方法
(1) 实例方法:includes(), startsWith(), endsWith()
传统上,JavaScript 只有indexOf
方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false
上面代码表示,使用第二个参数n
时,endsWith
的行为与其他两个方法有所不同。它针对前n
个字符,而其他两个方法针对从第n
个位置直到字符串结束。
项目中的使用场景
//判断SYS_IGW_FUNC对应的值(字符串类型)中是否包含02 computed: { isShowIndexAndCloseBtn() { const sysParasInfo = this.$store.state.sysParasInfo return sysParasInfo?.SYS_IGW_FUNC?.includes('02') }, }
(2) 实例方法:padStart(),padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()
用于头部补全,padEnd()
用于尾部补全。
'x'.padStart(5, 'ab') // 'ababax' 'x'.padEnd(5, 'ab') // 'xabab'
上面代码中,padStart()
和padEnd()
一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx' 'xxx'.padEnd(2, 'ab') // 'xxx'
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
'abc'.padStart(10, '0123456789') // '0123456abc'
如果省略第二个参数,默认使用空格补全长度。
'x'.padStart(4) // ' x' 'x'.padEnd(4) // 'x '
padStart()
的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
'1'.padStart(10, '0') // "0000000001" '12'.padStart(10, '0') // "0000000012" '123456'.padStart(10, '0') // "0000123456"
另一个用途是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
(3) 实例方法:trimStart(),trimEnd()
ES2019 对字符串实例新增了trimStart()
和trimEnd()
这两个方法。它们的行为与trim()
一致,trimStart()
消除字符串头部的空格,trimEnd()
消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc '; s.trim() // "abc" s.trimStart() // "abc " s.trimEnd() // " abc"
上面代码中,trimStart()
只消除头部的空格,保留尾部的空格。trimEnd()
也是类似行为。
除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
(4) 实例方法:replaceAll()
历史上,字符串的实例方法replace()
只能替换第一个匹配。
'aabbcc'.replace('b', '_') // 'aa_bcc'
上面例子中,replace()
只将第一个b
替换成了下划线。
如果要替换所有的匹配,不得不使用正则表达式的g
修饰符。
'aabbcc'.replace(/b/g, '_') // 'aa__cc'
正则表达式毕竟不是那么方便和直观,ES2021 引入了replaceAll()
方法,可以一次性替换所有匹配。
'aabbcc'.replaceAll('b', '_') // 'aa__cc'
它的用法与replace()
相同,返回一个新字符串,不会改变原字符串。