[目录]
“+ -”符号的特技
比较大小使用 a - b > 0的方式更好,用a > b有时候会出现错误
If,for…in,for…of和的使用
if多条件判断
switch语句优化
字符串拼接:join()
链式取值a[0].b不存在的undefined 问题
- 类型强制转换
1.1 string强制转换为数字
1.2 使用filter过滤数组中的所有假值
1.4 双位运算符 ~~实现向下取整
1.5 短路运算符&&、||
1.6 取整 | 0
1.7 判断奇偶数 & 1
- 函数
2.1 函数默认值
2.2 强制参数,缺失报错
2.3 箭头函数隐式返回值
2.4 惰性载入函数
2.5 一次性函数
- 字符串
3.1 字符串比较时间先后(时间形式注意补0)
- 数字
4.1 不同进制表示法
4.2 精确到指定位数的小数
4.3 数字补0操作
- 数组
5.1 reduce方法同时实现map和filter
5.2 统计数组中相同项的个数
5.3 交换参数数值
5.4 批量接收多个请求返回结果
5.5 将数组平铺到指定深度
5.6 数组的对象解构
5.7 对象数组按照某个属性查询最大值 是不是比for循环方便了很多。
- 对象
6.1 使用解构删除不必要属性
6.2 在函数参数中解构嵌套对象
- 代码复用
7.1 Object [key]
Object [key] 实现表单验证
----------------------------正文-----------------------------
#“+ -”符号的特技
用+可以将数值型的字符串转为数字,但是必须结合 isNaN(), isNaN() 函数用于检查其参数是否是非数字,不是数字则为true
基础知识
let a = "2" ,b= '20',c=34 ,d='2'
a-b = -18
c-d = 32
a+b = "220"
a+c = "234"
isNaN('ad') // true
isNaN('22') // false
骚操作
+('ab') // NaN
+('22') // 22
+(22) // 22
let a = 1 , b ='2'
let c = a + +(b) //3
示例
// 选择最小值的函数如下(源于《vuejs权威指南》里看到表单验证validator.js源码)
export default min (val, areg){
return !isNaN(val) && !isNaN(areg) && +(val)>= +(areg)
}
复制代码
###比较大小使用 a - b > 0的方式更好,用a > b有时候会出现错误
错误结果
'20' > '100' // true
预期结果
'20' - '100' > 0 // false
//数组排序算法
arr.sort((a, b ) =>{
return a-b
})
复制代码
#If,for…in,for…of和的使用
1.能用三元运算符就用,减少if的嵌套
2.减少多余条件判断,查询结束立即返回(早返回,多返回),如果是函数返回if里面和外面返回相同的数据类型
3.If…else if…else多个条件时以else结尾,因为符合防御性编程规则
4.NaN不应该用于比较,应该是判断是否是数字
5.Switch…case使用在至少有三个判断值,case不可省,每次case必须用break跳出
6.for…of遍历数组和字符串
可以使用break,return(一个循环一个),continue不使用,降低代码可读性
7.for…in遍历对象
For…in遍历对象包括所有继承的属性,所以如果只是想使用对象本身的属性需要做一个判断
8.在循环内部声明函数慎用,因为是循环执行完成函数调用才会执行
9.Return后面不要写代码
#if多条件判断
“早点返回,经常返回“(return early,return often),If语句用来处理主路径上的异常 比如我们在之前文章中提过的find 函数。
function find(predicate, arr) {
for (let item of arr) {
if (predicate(item)) {
return item;
}
}
}
复制代码
在这个find函数内,一旦我们找到想查找的对象就立刻返回该对象并且退出循环。这使得我们的代码更加高效。
也可以结合三元运算符实现高效简洁的代码实现
#switch语句优化
低级版:太容易忘记写break
let notificationPtrn;
switch (notification.type) {
case 'citation':
notificationPtrn = 'You received a citation from {{actingUser}}.';
break;
case 'follow':
notificationPtrn = '{{actingUser}} started following your work';
break;
case 'mention':
notificationPtrn = '{{actingUser}} mentioned you in a post.';
break;
default:
// Well, this should never happen
}
复制代码
优化一: 用return
function getnotificationPtrn(n) {
switch (n.type) {
case 'citation':
return 'You received a citation from {{actingUser}}.';
case 'follow':
return '{{actingUser}} started following your work';
case 'mention':
return '{{actingUser}} mentioned you in a post.';
default:
throw new Error('You’ve received some sort of notification we don’t know about.';
}
}
let notificationPtrn = getNotificationPtrn(notification);
复制代码
优化二:采用对象字典
function getNotificationPtrn(n) {
const textOptions = {
citation: 'You received a citation from {{actingUser}}.',
follow: '{{actingUser}} started following your work',
mention: '{{actingUser}} mentioned you in a post.',
}
return textOptions[n.type];
}
复制代码
优化三(推荐) 类似模式匹配方法
const textOptions = {
citation: 'You received a citation from {{actingUser}}.',
follow: '{{actingUser}} started following your work',
mention: '{{actingUser}} mentioned you in a post.',
}
function getNotificationPtrn(textOptions, n) {
return textOptions[n.type];
}
const notificationPtrn = getNotificationPtrn(textOptions, notification);
复制代码
若包含未知处理,我们也可以把默认的信息作为参数
constdefaultTxt= 'You’ve received some sort of notification we don’t know about.'; // 默认值
function getNotificationPtrn(defaultTxt, txtOptions, n) {
return txtOptions[n.type] || defaultTxt;
}
const notificationPtrn = getNotificationPtrn(defaultTxt, txtOptions, notification.type);
复制代码
现在textOptions是一个变量,并且该变量不用再被硬编码了。我们可以把它移动到一个JSON配置文件中,或者从服务器获取该变量。现在我们想怎么改textOptions就怎么改。我们可以增加新的选项,或者移除选项。我们也可以把多个地方不同的选项合并到一起。
#字符串拼接:join()
应使用数组保存字符串片段,使用时调用join方法。避免使用+或+=的方式拼接较长的字符串,每个字符串都会使用一个小的内存片段,过多的内存片段会影响性能
#链式取值a[0].b不存在的undefined 问题
开发中,链式取值是非常正常的操作,如:
res.data.goods.list[0].price
但是对于这种操作报出类似于Uncaught TypeError: Cannot read property 'goods' of undefined 这种错误也是再正常不过了,如果说是res数据是自己定义,那么可控性会大一些,但是如果这些数据来自于不同端(如前后端),那么这种数据对于我们来说我们都是不可控的,因此为了保证程序能够正常运行下去,我们需要对此校验:
if (res.data.goods.list[0] && res.data.goods.list[0].price) {
// your code
}
如果再精细一点,对于所有都进行校验的话,就会像这样:
if (res && res.data && res.data.goods && res.data.goods.list && res.data.goods.list[0] && res.data.goods.list[0].price){
// your code
}
复制代码
不敢想象,如果数据的层级再深一点会怎样,这种实现实在是非常不优雅,那么如果优雅地来实现链式取值呢?
方法一.通过函数解析字符串(lodash的 _.get 方法)
我们可以通过函数解析字符串来解决这个问题,这种实现就是lodash的 _.get 方法(www.lodashjs.com/docs/4.17.5…)
// _.get(object, path, [defaultValue])
var object = { a: [{ b: { c: 3 } }] };
var result = _.get(object, 'a[0].b.c', 1);
console.log(result);
// output: 3
该方法源码实现起来也非常简单,只是简单的字符串解析而已:
function get (obj, props, def) {
if((obj == null) || obj == null || typeof props !== 'string') return def;
const temp = props.split('.');
const fieldArr = [].concat(temp);
temp.forEach((e, i) => {
if(/^(\w+)\[(\w+)\]$/.test(e)) {
const matchs = e.match(/^(\w+)\[(\w+)\]$/);
const field1 = matchs[1];
const field2 = matchs[2];
const index = fieldArr.indexOf(e);
fieldArr.splice(index, 1, field1, field2);
}
})
return fieldArr.reduce((pre, cur) => {
const target = pre[cur] || def;
if(target instanceof Array) {
return [].concat(target);
}
if(target instanceof Object) {
return Object.assign({}, target)
}
return target;
}, obj)
}
// 使用
var c = {
a: {
b : [1,2,3]
}
}
_get(c ,'a.b') // [1,2,3]
_get(c, 'a.b[1]') // 2
_get(c, 'a.d', 12) // 12
复制代码
方法二,使用解构赋值
这个思路是来自github上 You-Dont-Need-Lodash-Underscore 这个仓库,看到这个的时候真的佩服
缺点是:可读性不太好
const obj = {
a:{
b: [1,2,3,4]
},
a1: 121,
a2: 'name'
}
let {a: result} = obj // result : {b: [1,2,3,4]}
let {a1: result} = obj // result: 121
let {b: result} = obj // result: undefined
当然,这个时候为了保证不报undefined,我们仍然需要定义默认值,
let {b: result = 'default'} = obj // result: 'default'
复制代码
方法三,使用Proxy
function pointer(obj, path = []) {
return new Proxy(() => {}, {
get (target, property) {
return pointer(obj, path.concat(property))
},
apply (target, self, args) {
let val = obj;
let parent;
for(let i = 0; i < path.length; i++) {
if(val === null || val === undefined) break;
parent = val;
val = val[path[i]]
}
if(val === null || val === undefined) {
val = args[0]
}
return val;
}
})
}
使用方法:
var c = {
a: {
b : [1,2,3]
}
}
pointer(c).a(); // {b: [1,2,3]}
pointer(c).a.b(); // [1,2,3]
pointer(d).a.b.d('default value'); // default value
复制代码
问题:为啥new Proxy()第一个参数要是一个箭头函数,我看大部分都是一个对象
解析:Proxy工作的原理,如果是对象的话,下面的apply函数内的内容是不会执行到的 因为proxy要代理函数执行的行为才会去执行apply里面的函数,所以必须要传一个箭头函数
#1. 类型强制转换
##1.1 string强制转换为数字
可以用*1(乘以1)来转化为数字(实际上是调用.valueOf方法) 然后使用Number.isNaN来判断是否为NaN,或者使用 a !== a 来判断是否为NaN,因为 NaN !== NaN
'32' * 1 // 32
'ds' * 1 // NaN
null * 1 // 0
undefined * 1 // NaN
1 * { valueOf: ()=>'3' } // 3
复制代码
常用: 也可以使用+来转化字符串为数字
+ '123' // 123
+ 'ds' // NaN
+ '' // 0
+ null // 0
+ undefined // NaN
+ { valueOf: ()=>'3' } // 3
复制代码
##1.2 使用filter过滤数组中的所有假值
我们知道JS中有一些假值:false,null,0,"",undefined,NaN,怎样把数组中的假值快速过滤呢,可以使用Boolean构造函数来进行一次转换
const compact = arr => arr.filter(Boolean)
compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34]) // [ 1, 2, 3, 'a', 's', 34 ]
复制代码
##1.4 双位运算符 ~~实现向下取整
可以使用双位操作符来替代 Math.floor( )。双否定位操作符的优势在于它执行相同的操作运行速度更快。
math.floor(x)返回小于参数x的最大整数,即对浮点数向下取整
Math.floor(4.9) === 4 //true
// 简写为:
~~4.9 === 4 //true
复制代码
不过要注意,对负数来说 ~~ 运算结果与 Math.floor( ) 运算结果不相同:
~~4.5 // 4
Math.floor(4.5) // 4
~~-4.5 // -4
Math.floor(-4.5) // -5
复制代码
##1.5 短路运算符&&、||
我们知道逻辑与&&与逻辑或||是短路运算符,短路运算符就是从左到右的运算中前者满足要求,就不再执行后者了; 可以理解为:
&&为取假运算,从左到右依次判断,如果遇到一个假值,就返回假值,以后不再执行,否则返回最后一个真值 ||为取真运算,从左到右依次判断,如果遇到一个真值,就返回真值,以后不再执行,否则返回最后一个假值
let param1 = expr1 && expr2
let param2 = expr1 || expr2
复制代码
&& :expr1&&expr2 如果expr1 能转换成false则返回expr1,否则返回expr2. 因此, 在Boolean环境中使用时, 两个操作结果都为true时返回true,否则返回false.
|| :expr1||expr2 如果expr1能转换成true则返回expr1,否则返回expr2. 因此,在boolean环境(在if的条件判断中)中使用时,二者操作结果中只要有一个为true,返回true;二者操作结果都为false时返回false.
! :!expr 如果单个表达式能转换为true的话返回false,否则返回true. 因此可以用来做很多有意思的事,比如给变量赋初值:
let variable1
let variable2 = variable1 || 'foo'
复制代码
如果variable1是真值就直接返回了,后面短路就不会被返回了,如果为假值,则会返回后面的foo。
也可以用来进行简单的判断,取代冗长的if语句:
let variable = param && param.prop
复制代码
如果param如果为真值则返回param.prop属性,否则返回param这个假值,这样在某些地方防止param为undefined的时候还取其属性造成报错。
但是要注意以下反例如果在数据为0(或者空字符串的时候也要返回数据),此时用||运算符就会搞错了,返回的是默认值了:
// 返回值为
let res = {
data: 0, // 后台返回状态码(0表示不存在,1表示存在)
code: 200
}
let val = res.data || '无数据'
console.log(val) // '无数据',其实我们要的是data的值
复制代码
##1.6 取整 | 0
对一个数字| 0可以取整,负数也同样适用,num | 0
1.3 | 0 // 1
-1.9 | 0 // -1
复制代码
##1.7 判断奇偶数 & 1
对一个数字& 1可以判断奇偶数,负数也同样适用,num & 1
const num=3;
!!(num & 1) // true
!!(num % 2) // true
复制代码
#2. 函数
##2.1 函数默认值
func = (x, m = 3, n = 4 ) => (x * m * n);
func(2) //output: 24
func(null) //output: 12 因为1*null = 0
复制代码
注意,传入参数为undefined或者不传入的时候会使用默认参数,
但是传入null还是会覆盖默认参数。
##2.2 强制参数,缺失报错
默认情况下,如果不向函数参数传值,那么JS 会将函数参数设置为undefined。其它一些语言则会发出警告或错误。要执行参数分配,可以使用if语句抛出未定义的错误,或者可以利用强制参数。
logError = ( ) => {
throw new Error('Missing parameter!');
}
foo = (bar = logError( ) ) => { // 这里如果不传入参数,就会执行logError 函数报出错误(此处也可以添加日志打印等操作)
return bar;
}
复制代码
##2.3 箭头函数隐式返回值
返回值是我们通常用来返回函数最终结果的关键字。只有一个语句的箭头函数,可以隐式返回结果(函数必须省略大括号{ },以便省略返回关键字)。
要返回多行语句(例如对象文本),需要使用( )而不是{ }来包裹函数体。这样可以确保代码以单个语句的形式进行求值。
function calcCircumference(diameter) {
return Math.PI * diameter
}
// 简写为:
calcCircumference = diameter => (
Math.PI * diameter;
)
复制代码
##2.4 惰性载入函数
在某个场景下我们的函数中有判断语句,这个判断依据在整个项目运行期间一般不会变化,所以判断分支在整个项目运行期间只会运行某个特定分支,那么就可以考虑惰性载入函数
// 低级版
function foo(){
if(a !== b){
console.log('aaa')
}else{
console.log('bbb')
}
}
// 优化后
function foo(){
if(a != b){
foo = function(){
console.log('aaa')
}
}else{
foo = function(){
console.log('bbb')
}
}
return foo();
}
复制代码
那么第一次运行之后就会覆写这个方法,下一次再运行的时候就不会执行判断了。当然现在只有一个判断,如果判断很多,分支比较复杂,那么节约的资源还是可观的。
##2.5 一次性函数
跟上面的惰性载入函数同理,可以在函数体里覆写当前函数,那么可以创建一个一次性的函数,重新赋值之前的代码相当于只运行了一次,适用于运行一些只需要执行一次的初始化代码
var sca = function() {
console.log('msg')
sca = function() {
console.log('foo')
}
}
sca() // msg
sca() // foo
sca() // foo
复制代码
#3. 字符串
##3.1 字符串比较时间先后(时间形式注意补0)
比较时间先后顺序可以使用字符串:
var a = "2014-08-08";
var b = "2014-09-09";
console.log(a>b, a<b); // false true
console.log("21:00"<"09:10"); // false
console.log("21:00"<"9:10"); // true 时间形式注意补0
复制代码
因为字符串比较大小是按照字符串从左到右每个字符的charCode来的,但所以特别要注意时间形式注意补0
附送一个时间格式处理函数
export default{
// 将时间格式处理为两位数,例如1时1分输出01:01
twoNumFormat(num){
return (num < 10)? '0'+ num: num
},
// 时间格式化,输出2018-01-02 01:10
dateFormat: function(date = new Date()){
let now = new Date(date)
let year = now.getFullYear()
let month = (now.getMonth()+1)
month = this.twoNumFormat(month)
let day = now.getDate()
day = this.twoNumFormat(day)
let hour = now.getHours()
hour = this.twoNumFormat(hour)
let minus = now.getMinutes()
minus = this.twoNumFormat(minus)
let second = now.getSeconds()
second = this.twoNumFormat(second )
return `${year}-${month}-${day} ${hour}:${minus}:${second }`
}
}
复制代码
#4. 数字
##4.1 不同进制表示法
ES6中新增了不同进制的书写格式,在后台传参的时候要注意这一点。
29 // 10进制
035 // 8进制29 原来的方式
0o35 // 8进制29 ES6的方式
0x1d // 16进制29
0b11101 // 2进制29
复制代码
##4.2 精确到指定位数的小数
将数字四舍五入到指定的小数位数。使用 Math.round() 和模板字面量将数字四舍五入为指定的小数位数。 省略第二个参数 decimals ,数字将被四舍五入到一个整数。
const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`)
round(1.345, 2) // 1.35 Number(1.345e2e-2)
round(1.345, 1) // 1.3
// 此处e2表示乘以10的2次方
1.23e1 //12.3
1.23e2 // 123
123.45e-2 // 1.2345
复制代码
##4.3 数字补0操作
感谢网友 @JserWang @vczhan 提供 这个小技巧 有时候比如显示时间的时候有时候会需要把一位数字显示成两位,这时候就需要补0操作,可以使用slice和string的padStart方法
const addZero1 = (num, len = 2) => (`0${num}`).slice(-len) // 从字符串倒数第len处开始截取到最后
const addZero2 = (num, len = 2) => (`${num}`).padStart( len , '0') // padStart为ES6的字符串拼接方法
addZero1(3) // 03
addZero2(32,4) // 0032
复制代码
此处可以采用另外一个思路;可以将得到的字符串先拼接len个0,然后再截取len长的字符串 #5. 数组
##5.1 reduce方法同时实现map和filter
假设现在有一个数列,你希望更新它的每一项(map的功能)然后筛选出一部分(filter的功能)。如果是先使用map然后filter的话,你需要遍历这个数组两次。 在下面的代码中,我们将数列中的值翻倍,然后挑选出那些大于50的数。
const numbers = [10, 20, 30, 40];
const doubledOver50 = numbers.reduce((finalList, num) => {
num = num * 2;
if (num > 50) {
finalList.push(num);
}
return finalList;
}, []);
doubledOver50; // [60, 80]
复制代码
##5.2 统计数组中相同项的个数
很多时候,你希望统计数组中重复出现项的个数然后用一个对象表示。那么你可以使用reduce方法处理这个数组。
下面的代码将统计每一种车的数目然后把总数用一个对象表示。
var cars = ['BMW','Benz', 'Benz', 'Tesla', 'BMW', 'Toyota'];
var carsObj = cars.reduce(function (obj, name) {
obj[name] = obj[name] ? ++obj[name] : 1; // obj[name]存在就加一,不存在就为1
return obj;
}, {});
carsObj; // => { BMW: 2, Benz: 2, Tesla: 1, Toyota: 1 }
复制代码
##5.3 交换参数数值
有时候你会将函数返回的多个值放在一个数组里。我们可以使用数组解构来获取其中每一个值。
let param1 = 1;
let param2 = 2;
[param1, param2] = [param2, param1];
console.log(param1) // 2
console.log(param2) // 1
当然我们关于交换数值有不少其他办法:
var temp = a; a = b; b = temp
b = [a, a = b][0]
a = a + b; b = a - b; a = a - b
复制代码
##5.4 批量接收多个请求返回结果
在下面的代码中,我们从/post中获取一个帖子,然后在/comments中获取相关评论。由于我们使用的是async/await,函数把返回值放在一个数组中。而我们使用数组解构后就可以把返回值直接赋给相应的变量。
async function getFullPost(){
return await Promise.all([
fetch('/post'),
fetch('/comments')
]);
}
const [post, comments] = getFullPost();
复制代码
##5.5 将数组平铺到指定深度
使用递归,为每个深度级别 depth 递减 1 。
使用 Array.reduce() 和 Array.concat() 来合并元素或数组。 基本情况下,depth 等于 1 停止递归。 省略第二个参数,depth 只能平铺到 1 (单层平铺) 的深度。
const flatten = (arr, depth = 1) =>
depth != 1
? arr.reduce((a, v) => a.concat(Array.isArray(v) ? flatten(v, depth - 1) : v), [])
: arr.reduce((a, v) => a.concat(v), []);
flatten([1, [2], 3, 4]); // [1, 2, 3, 4]
flatten([1, [2, [3, [4, 5], 6], 7], 8], 2); // [1, 2, 3, [4, 5], 6, 7, 8]
复制代码
##5.6 数组的对象解构
数组也可以对象解构,可以方便的获取数组的第n个值
const csvFileLine = '1997,John Doe,US,john@doe.com,New York';
const { 2: country, 4: state } = csvFileLine.split(',');
country // US
state // New Yourk
复制代码
##5.7 对象数组按照某个属性查询最大值
var array=[
{
"index_id": 119,
"area_id": "18335623",
"name": "满意度",
"value": "100"
},
{
"index_id": 119,
"area_id": "18335624",
"name": "满意度",
"value": "20"
},
{
"index_id": 119,
"area_id": "18335625",
"name": "满意度",
"value": "80"
}];
复制代码
一行代码搞定 Math.max.apply(Math, array.map(function(o) {return o.value})) 执行以上一行代码可返还所要查询的array数组中对象value属性的最大值100。
同理,要查找最小值如下即可: Math.min.apply(Math, array.map(function(o) {return o.value})) 是不是比for循环方便了很多。
作者:遇见小美好 来源:CSDN 原文:blog.csdn.net/qq_37268201…
#6. 对象
##6.1 使用解构删除不必要属性
有时候你不希望保留某些对象属性,也许是因为它们包含敏感信息或仅仅是太大了(just too big)。你可能会枚举整个对象然后删除它们,但实际上只需要简单的将这些无用属性赋值给变量,然后把想要保留的有用部分作为剩余参数就可以了。
下面的代码里,我们希望删除_internal和tooBig参数。我们可以把它们赋值给internal和tooBig变量,然后在cleanObject中存储剩下的属性以备后用。
let {_internal, tooBig, ...cleanObject} = {el1: '1', _internal:"secret", tooBig:{}, el2: '2', el3: '3'};
console.log(cleanObject); // {el1: '1', el2: '2', el3: '3'}
复制代码
##6.2 在函数参数中解构嵌套对象
在下面的代码中,engine是对象car中嵌套的一个对象。如果我们对engine的vin属性感兴趣,使用解构赋值可以很轻松地得到它。
var car = {
model: 'bmw 2018',
engine: {
v6: true,
turbo: true,
vin: 12345
}
}
const modelAndVIN = ({model, engine: {vin}}) => {
console.log(`model: ${model} vin: ${vin}`);
}
modelAndVIN(car); // => model: bmw 2018 vin: 12345
复制代码
#7. 代码复用
##7.1 Object [key]
虽然将 foo.bar 写成 foo ['bar'] 是一种常见的做法,但是这种做法构成了编写可重用代码的基础。许多框架使用了这种方法,比如element的表单验证。
请考虑下面这个验证函数的简化示例:
function validate(values) {
if(!values.first)
return false;
if(!values.last)
return false;
return true;
}
console.log(validate({first:'Bruce',last:'Wayne'})); // true
复制代码
上面的函数完美的完成验证工作。但是当有很多表单,则需要应用验证,此时会有不同的字段和规则。如果可以构建一个在运行时配置的通用验证函数,会是一个好选择。
###Object [key] 实现表单验证
// object validation rules
const schema = {
first: {
required:true
},
last: {
required:true
}
}
// universal validation function
const validate = (schema, values) => {
for(field in schema) {
if(schema[field].required) {
if(!values[field]) {
return false;
}
}
}
return true;
}
console.log(validate(schema, {first:'Bruce'})); // false
console.log(validate(schema, {first:'Bruce',last:'Wayne'})); // true
复制代码