JavaScript的一些优雅写法

【本文参与征文活动】

“阅前必阅”

  JavaScript博大精深,本人只是在工作中,发现了一些js的各种写法,下面介绍的是一些能简化代码量的写法,但是有一些我是不太推荐的,详细原因也有论述。抛的砖是砸到头了还是引出了玉,就看各位了。

切换变量0和1

// if判断可以这么写
let flag = 0;
if (flag === 0) {
    flag = 1;
} else {
    flag = 0;
}
// 也可以用三目运算符
flag = flag === 0 ? 1 : 0;
// 也可以使用位异或(^)
flag ^= 1;

  一般我要求自己的团队在js上是尽量少用位运算,为什么呢?因为位运算其实二进制数执行运算,包括与&、或|、异或^、非~、左移<<、右移>>都是整数的逐位运算。然而,不幸的是在js内部所有数字都是双精度浮点数,所以这些运算js会先转为整数再运算,而且代码阅读性也会降低。
其实说到位运算,还不得不提一个月更贴,为什么

console.log(0.1 + 0.2 === 0.3); // false

  总结一句就是十进制转二进制的精度丢失,如果想深入了解,推荐一篇文章0.1 + 0.2不等于0.3?为什么JavaScript有这种“骚”操作?

!!

  继续说位运算中的非,我在项目中看到越来越多的人用!!来判空等操作,针对一般场景也没什么问题,但是毫无节制的使用!!真的有必要吗?比如已知表达式就是一个Boolean值,还需要这样判断吗?
  非运算字面比较简单——真就是假,假就是真,不过js的类型特点,还是详细看下:

console.log(!null); // true
let u = void 0; // undefined
console.log(!u); // true
console.log(!0); // true
console.log(!1); // false
console.log(!''); // true
console.log(!'a'); // false
console.log(!{}); // false
console.log(!{ a: 'a' }); // false

  这里基本涵盖了所有类型(boolean就没必要参与了),可以看到null、undefined、0、’'是true,其它为false,所以我们就明白为什么很多人用这个来判断空字符串了吧,问题却来了0不是空啊,比如常见场景我们Http请求时,要剔除空参数,但如果参数有个0,那就尴尬了吧~即使你真的要将0纳入false范畴,我也更推荐下面的做法:

console.log(Boolean('null'));
let u = void 0; // undefined
console.log(Boolean(u));
console.log(Boolean(0));
console.log(Boolean(1));
console.log(Boolean(''));
console.log(Boolean('a'));
console.log(Boolean({}));
console.log(Boolean({ a: 'a' }));

  如果你只是单纯的想判断空,可以使用下面的方法

function isEmpty(str) {
    return null == str || '' == str;
}
console.log(isEmpty(0));
console.log(isEmpty(void 0));
console.log(isEmpty(null));

交换值

  如果用位运算,则

let a = 1;
let b = 2;
a ^= b;
b ^= a;
a ^= b;
console.log(a); // 2
console.log(b); // 1

但与借助中间值,似乎差距不大

let a = 1;
let b = 2;
let c = a;
a = b;
b = c;
console.log(a); // 2
console.log(b); // 1

而现在基本都支持ES6的情况下,其实可以用解构来操作

let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1
  • 解构一般用来如本例的交换值,或者从对象取值,如
// 从对象中取值
let obj = { a: 1, b: 2, c: 3 };
let { a, b, c } = obj;
console.log(a, b, c); // 1,2,3

// 从数组中取值
let arr = [1, 2, 3, 4, 5];
let [a, , , b, , c] = arr;
console.log(a, b, c); // 1 4 undefined

但解构的作用可不仅仅是如此,还有很多妙处,比如:从数组中取一个值,如果该索引不存在则赋一个默认值

let arr = [1];
let value = 'a';
if (arr.length > 1) {
    value = arr[1];
}
console.log(value); // a

而用解构,则仅需

let arr = [1];
let [, value = 'a'] = arr;
console.log(value); // a

用户签到

  在开发中,我用位运算较多的一个场景——签到,比如下面的做法来实现判断用户一个月的签到情况:

  1. 每天对应一个数
dayNum = Math.pow(2, day); // day是日期(几号)
  1. 如果该天签到,则签到数值加上该数值
daySum += dayNum;
  1. 判断某天是否签到
(dayNum & daySum) === dayNum

比如

// 假设用户1 3 5号三天签到了
let daySum = Math.pow(2, 1) + Math.pow(2, 3) + Math.pow(2, 5);
// 判断2号是否签到
let day2 = Math.pow(2, 2);
console.log((day2 & daySum) === day2); // false
// 判断3号是否签到
let day3 = Math.pow(2, 3);
console.log((day3 & daySum) === day3); // true

  关于原理,转成二进制去运算,就一目了然了。

创建重复的字符串

let repeat1 = '';
for (let i = 0; i < 7; i++) {
    repeat1 += 'a';
}
console.log('repeat1=', repeat1); // aaaaaaa
let repeat2 = Array(7).join('a');
console.log('repeat2=', repeat2); // aaaaaa
let repeat3 = 'a'.repeat(7); // ES6写法
console.log('repeat3=', repeat3); // aaaaaaa
  • 第三种写法是ES6的api,将字符串复制指定次数。
  • 第二种写法是借助join的插入函数实现需求功能,这里其实不是很推荐,因为你打印repeat2会发现,其实只有6个a,因为join是在元素中插入,所以这里要实现复制7次就得创建一个8个元素的数组,一个字符串还要先创建一个数组,个人觉得没必要。

清空数组的操作

let arr = [1,2,3];
// 第一种写法
arr = [];
// 第二种写法
arr.length = 0;

  两种写法有什么区别呢?
  第一种写法其实是赋值一个新数组给变量aar,第二种写法是直接操作原数组。看下面例子

let arr = [1, 2, 3];
let arr2 = arr;
arr = [];
console.log('arr=', arr); // []
console.log('arr2=', arr2); // [ 1, 2, 3 ]
arr = arr2;
arr2.length = 0;
console.log('arr=', arr); // []
console.log('arr2=', arr2); // []
  • 两种写法,很明显不能一棍子说优劣,但如果你的数组不需要使用了,自然应该使用Array.length=0的写法。

END

  最后附赠JS社区玩坏的梗~

console.log(([][[]] + [])[+!![]] + ([] + {})[!+[] + !![]]);
console.log((!(~+[]) + {})[--[~+""][+[]] * [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] * ~+[]]);

以上所有知识点皆来源于网络,各位有兴趣可以再去深入了解,如果发现本文有什么错误,可以给我留言。
顺便推荐各位关注我的公众号:

15赞

签到算法 挺不错了。

get到了

签到 牛逼

大佬牛逼,受教了

签到的很骚气。。

第一个应该不是js的问题 所有用了那个库的都有问题 java c# 0.1+0.2都不等于0.3:sweat:

签到本质上还是二进制的或运算


// 假设用户1 3 5号三天签到了
let daySum =  1 << 1 | 1 << 3  | 1 << 5;

// 判断2号是否签到
let day2 = 1 << 2;
console.log((day2 & daySum) === day2); // false
// 判断3号是否签到
let day3 = 1 << 3;
console.log((day3 & daySum) === day3); // true
2赞

因为我是从有道复制过来的,可能有些空格被清空了,但是md文档,最后两个空格没了,也只是不换行,不会出现吞文字啊,@jare 这是什么情况?

因为我是先在有道云笔记复制过来的,有些文字都丢失了~

感谢大佬分享!

交换两个数这么写就好了
let a = 1;
let b = 2;
[a,b] = [b,a];

签到算法没带年份月份,不能直接挪用。

后面介绍了这种用法啊

知道原理才是最重要的,不要被需求遮住了想法

// 也可以使用位异或(^)
flag ^= 1;

这种写法直接开除。

原因很简单,大部分人看到这句,都会停顿一下,想想这是个啥?被看过几次,就浪费几次的时间,简直是浪费大家的生命!

我的原则很简单,尽可能写傻瓜都能看懂的代码。比如遍历数组,我只写 for (let i = 0; i < arr.length; i++)。我从不写 arr.forEach(e => {…}); 原因就是第1种写法没有人看不懂,并且也没有性能问题。

曾经收到一份简历,上面写:“炫技一般地实现了XXX”,结果:没有给他面试机会。

正常的成长线是:新手:只会写看起来很简单的代码;中级:会一些看起来很牛逼的写法,慢慢用上;老手:重新回归简单。。。

(个人看法,不喜勿喷)

1赞

嗯,我也是要求自己的团队的代码可读性强,不过我们不能撇开场景去谈好坏的问题。关于老手的看法就是,不是说要重新回归简单,而是他知道用哪种是最优的。

1赞
// if判断可以这么写
let flag = 0;
if (flag === 0) {
    flag = 1;
} else {
    flag = 0;
}
// 也可以用三目运算符
flag = flag === 0 ? 1 : 0;
// 也可以使用位异或(^)
flag ^= 1;

确实是,这三种写法,我也不会同意后两种的写法。在不影响整体性能的情况下,建议用易懂的方式来书写代码。

第2种也可以用的,也属于最简单的

基本操作:test: