总结js非常实用的技巧
js的实用技巧
收集整理一些js常用的技巧
不使用for循环和break终止循环遍历
随着es6规范的普及,现在很少使用for循环进行遍历了,我们习惯使用forEach、map来进行遍历操作;
但是当我们想提前结束循环,则使用forEach方法无法满足需求。
这时候如果想在遍历过程中终止循环,则可以通过find,some方法控制回调函数返回值来实现。
let arr = [
{index: 1, value: 111},
{index: 2, value: 222},
{index: 3, value: 333},
{index: 4, value: 444},
];
// find方式
arr.find(item => {
if (item.index > 2) return true; // 相当于for循环中的break;
// 循环内具体业务逻辑
console.log(item.index);
return false;
});
// some方式
arr.some(item => {
if (item.index > 2) return true; // 相当于for循环中的break;
// 循环内具体业务逻辑
console.log(item.index);
return false;
});
当找到符合条件的数据项时,返回true,则会停止遍历
快速创建一个m * n的二维数组
创建一个m行n列的二维数组有多种方式
- 原始方法:
function makeMatrix(m, n) {
let result = [];
for (let i = 0; i < m; i++) {
result.push(new Array(n).fill(0));
}
console.log(result);
}
- 使用es6的数组方法:
// 方式1:利用new Array和map方法
function makeMatrix1(m, n) {
return new Array(m).fill(0).map(() => new Array(n).fill(0));
}
// 方式2:Array.from可以在创建数组时传入一个控制函数,对每一项进行处理,其返回值即为每一项的值(相当于一个map方法)
function makeMatrix2(m, n) {
return Array.from(new Array(m), () => new Array(n).fill(0));
}
String.raw
String.raw`aaa\nbbb` 可以将模板字符串原样效果输出,一般用于编写含有代码的文档,这样就可以输出源代码而不被转义
对象属性链式get
一个obj对象嵌套了很多层, 给定一个字符串key = 'first.second.third' , 根据key得到最终的obj.first.second.third的值
let obj = {
first: {
second: {
third: 'message'
}
}
};
function getResultValue(obj, key) {
return key.split('.').reduce((o, i) => {
if (o) return o[i];
}, obj);
}
let key = 'first.second.third';
let result = getResultValue(obj, key);
console.log(result);
巧用位运算
1、奇偶性判断 —— 按位与(&)
- 按位与运算是把两个操作数转换成二进制再逐位比较,当前位都为1结果为1,否则为0。
- 而所有数字转化为二进制的奇偶性就只用看末尾,奇数尾数为1,偶数尾数为0。
if ( a & 1 ) {
alert('a是奇数!');
} else {
alert('a是偶数!');
}
2、两个整数的互换 —— 按位异或(^)
加减互为逆运算,异或和异或互为逆运算.
- 利用加减:
var a = 1;
var b = 2;
a = a + b;
b = a - b;
a = a - b;
console.log(a, b);
- 按位异或:
var a = 1;
var b = 2;
a = a ^ b;
b = a ^ b;
a = a ^ b;
console.log(a, b);
两个整数互换可以申明第三个中间变量进行临时存储实现,但是声明变量会占用内存,不友好。而加减又没有位运算效率高。
3、 数字-1的判断 —— 按位取反(~)
判断一个数是否为-1是我们经常遇到的,indexOf()
在查找字符串的时候没有找到会返回-1,很多程序,插件,框架错误状态值默认返回-1,在位运算中,~-1===0
的。
// 常规
if ( str.indexOf('a') != -1 ) {
alert('a在字符串str中');
}
// ~取反
if ( ~str.indexOf('a') ) {
alert('a在字符串str中');
}
4、数字取整—— ~~
很多时候我们需要得到数字整数部分(非四舍五入),有以下方法:
let num = 13.14;
// parseInt()
console.log(parseInt(num)); // 参数如果为NaN、null、undefined等等会得到NaN
// Math.floor()
console.log(Math.floor(num)); // 参数如果为字符串、NaN、undefined等等会得到NaN,参数为null结果为0
// ~~
console.log(~~num); // 非数字都会转化为0
5、二进制数去掉最后的1 —— n & (n - 1)
现有二进制数 10101100
,最后的1在倒数第三位,想要将最后的1变为0,则可以使用 n & (n - 1)
,结果为10101000
n & (n - 1)
的 运用:
问题:编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。比如求 1001 1100 1010 1100 0101 0011 0110 1000
中1的个数
var hammingWeight = function(n) {
let count = 0;
while (n != 0) {
count++;
n = n & (n - 1);
}
return count;
};
6、位移运算 —— >> 和 >>>
>>
是带符号的右移运算符,将运算符左边的对象向右移动指定的位数。如果是正数,高位补0;如果是负数,则在高位补1。>>>
是不带符号的右移运算符,将运算符左边的对象向右移动指定的位数,并且在高位补0>>>0
对负数执行>>>0
操作可以去掉负号(原来负数 + 2的32次方,负数变回无符号整数)
可以得出:
- 当对正数移位运算时,
>>
和>>>
操作结果是一样的; - 当对负数移位运算时,
>>
和>>>
的操作结果是不一样的,>>
将二进制高位用1补上,而>>>
将二进制高位用0补上,这就导致了>>>
将负数的移位操作结果变成了正数,因为高位用0补上了
运用示例:
问题:编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。比如求 1001 1100 1010 1100 0101 0011 0110 1000
中1的个数
这个题目使用 n & (n - 1)非常方便,也可以使用 >>
和 >>>
实现
// >> 实现
var hammingWeight = function(n) {
let count = 0;
// 由于‘>>’运算是高位补1操作,所以不能使用while循环判断
for (let i = 0; i < 32; i++) {
if ((n >> i) & 1) count++;
}
return count;
};
// >>> 实现
var hammingWeight = function(n) {
let count = 0;
// `>>>`是高位补零操作,不影响二进制数中1个数的增加,每当右移一次,n == 0时,表示1已经不存在了,计数完毕
while (n) {
if (n & 1) count++;
n = n >>> 1; // n >> 1不适用于负数,会导致while死循环,需要通过 n >>> 1 来进行零补位的移位操作
}
return count;
};
给对象动态设置属性/值
1、根据条件判断给哪个属性设置值
var obj = { top: 60 };
// 当元素在盒子左侧,则obj['left'] = 20, 当元素在盒子右侧, 则obj['right'] = 20
obj[['left', 'right'][+(posLeft > containerWidth / 2)]] = 20;
2、根据条件判断是否添加某个属性和值
可以使用展开运算符来有条件地向对象中添加属性:
const edit = this.$route.query.edit; // 是否编辑状态
const params = {
id: editData.id,
title: editData.title,
...(edit == 1 && { type: 'edit' }), // 如果是编辑状态,则设置type属性为‘edit’
};
解析 如果 edit
值为1,则 会给params对象设置属性type值为'edit';如果 edit
值不为1,相当于展开 false
,不会对对象产生影响
有条件地向数组添加元素
这是 CRA 中 Webpack 配置的源码:
module.exports = {
plugins: [
new HtmlWebpackPlugin(),
isEnvProduction && new MiniCssExtractPlugin(),
useTypeScript && new ForkTsCheckerWebpackPlugin(),
].filter(Boolean),
}
解析 只有 isEnvProduction
为 true
才会添加 MiniCssExtractPlugin
插件;当 isEnvProduction
为 false
则会添加 false
到数组中,最后使用了 filter
过滤值为true的数组项
if/else优化
有这样一个业务处理函数, 根据参数type不同执行不同的方法得到相应的值
function getValue(type) {
if (type == 'a') {
return getValue_a();
} else if (type == 'b') {
return getValue_b();
} else if (type == 'c') {
return getValue_c();
} else if (type == 'd') {
return getValue_d();
} else {
return getValue_default();
}
}
这个方法推荐使用switch/case或者对象集合形式替代:
- switch/case方式:
function getValue(type) {
switch(type) {
case 'a':
return getValue_a();
case 'b':
return getValue_b();
case 'c':
return getValue_c();
case 'd':
return getValue_d();
default:
return getValue_default();
}
- 对象集合形式:
const handlers = {
'a': () => getValue_a(),
'b': () => getValue_b(),
'c': () => getValue_c(),
'd': () => getValue_d(),
'default': () => getValue_default(),
}
function getValue(type) {
return handlers[type || 'default']();
}
判断对象中是否存在某个属性
我们知道使用 in
关键字可以判断对象中是否存在某个属性:
const params = { id: 111, title: '编辑', type: 'edit' };
console.log('type' in params); // true
console.log('content' in person); // false
但是 in
关键字会获取原型上的属性,所以不能区分该属性是当前对象中定义的还是原型上定义的,例如:
"hasOwnProperty" in {}; // true
"toString" in {}; // true
很多情况我们并不关心原型上是否存在某个属性,所以可以通过Object.prototype.hasOwnProperty
方式来判断,比如:
Object.prototype.hasOwnProperty.call(params, 'type'); // true
Object.prototype.hasOwnProperty.call(params, 'content'); // false
上面代码每次判断都得写一大长串代码,可以定义一个全局方法,按需使用即可。
const hasOwnProperty = Object.prototype.hasOwnProperty
function hasOwn(target, key) {
return hasOwnProperty.call(target, key);
}
console.log(hasOwn(params, 'type')); // true
async/await异常处理封装
正常情况async/await使用起来确实方便、简洁,也避免了出现回调地狱的情况
function getUserInfoApi(params) {
return new Promise((resolve, reject) => {
axios.get(url, { params }).then(res => {
if (res.status == 200) {
resolve(res.data.data);
} else {
reject(null);
}
}).catch(err => {
reject(null);
});
});
}
async getUserInfo() {
const userInfo = await getUserInfoApi({ userId: 123 }); // { name: 'zhangsan', age: 30 }
console.log(userInfo.name);
}
但是,如果getUserInfoApi
执行过程中出现异常,则会报错影响后续的代码执行;
所以我们需要一个异常处理解决方案:
方案1
直接在getUserInfoApi
方法中进行异常捕获后,resolve一个空对象,不会影响后续 userInfo 的操作
但是实际开发过程中会有非常多的接口请求,如果每个接口都这么处理一遍,那就费时费力还影响效率。
function getUserInfoApi(params) {
return new Promise((resolve, reject) => {
axios.get(url, { params }).then(res => {
if (res.status == 200) {
resolve(res.data.data);
} else {
resolve({});
}
}).catch(err => {
resolve({});
});
});
}
方案2
封装一个公共的异常处理函数,对返回值进行包装处理。
function getUserInfoApi(params) {
return new Promise((resolve, reject) => {
axios.get(url, { params }).then(res => {
if (res.status == 200) {
resolve(res.data.data);
} else {
reject(null);
}
}).catch(err => {
reject(null);
});
});
}
/**
* @description: 对awaited异步进行包装,异常处理 (使用async/await时避免try/catch进行处理)
* @param {*} awaited 需要进行异常处理的请求 (一般情况下是一个Promise)
* @return {Promise} 返回一个Promise,在调用的地方通过async/await 解构
* 使用:let [err, result] = await this.wrappedAwait(awaited), 正常请求时result为接口返回的数据,异常时err为错误信息
*/
wrappedAwait(awaited) {
let p = Promise.resolve(awaited); // 非Promise则转为Promise
return p.then(
res => {
return [null, res];
},
err => {
return [err, null];
}
);
}
async function test() {
const [err, res] = await wrappedAwait(getUserInfoApi({ userId: 123 }));
console.log('__err__', err);
console.log('__res__', res);
}
test();
wrappedAwait函数的返回值是一个数组类型,第一项是异常情况时的错误信息,第二项是正常情况返回的接口数据,通过判断返回值进行相应的逻辑处理
JS随机打乱数组
function shuffle(arr) {
let i = arr.length;
while (i) {
let j = Math.floor(Math.random() * i--);
[arr[j], arr[i]] = [arr[i], arr[j]];
}
return arr;
}
利用sort方法实现:
const list = [-34567, 5, 8, 110, -321, 567, 200, 234, -654, -2, 1, 654321];
list.sort(() => Math.random() - 0.5);
console.log(list);
数组扁平化
数组扁平化的几种实现方式:
-
1、递归实现(常用)
const arr = [1, [2, [3, 4], [5, [6, 7]]]]; function flatten(arr) { let result = []; arr.forEach(item => { if (Array.isArray(item) { result = result.concat(flatten(item)); } else { result.push(item); } }); return result; } console.log(flatten(arr));
-
2、reduce实现(常用)
const arr = [1, [2, [3, 4], [5, [6, 7]]]]; function flatten(arr) { return arr.reduce((prev, curr) => { return prev.concat(Array.isArray(curr) ? flatten(curr) : curr); }, []); } console.log(flatten(arr));
-
3、es10-flat()函数 (推荐)
es10中新增了flat()方法,专门用来对数组扁平化(“拉平”)操作,该方法返回一个新数组。
flat()方法默认只会“拉平”一层,它接收一个参数,表示要“拉平的”层数,不传参数则默认为1。
如果不管有多少层嵌套,都要转成一维数组,可以用
Infinity
关键字作为参数。const arr = [1, [2, [3, 4], [5, [6, 7]]]]; console.log(arr.flat(Infinity));
关于
flat()
相关的介绍,可以看看阮一峰
大佬的 ECMAScript 6 入门 -
4、replace替换
let arr = [1, [2, [3, 4], [5, [6, 7]]]]; let str = JSON.stringify(arr); console.log(str); let result = str.replace(/(\[|\])/g, '').split(','); console.log(result);
既然es6已经给我们提供了专用方法,岂有不用之理?
日期格式化
下面是一个常见的日期格式化方法
// 根据自定义format格式进行格式化 1 (只能替换一次)
function formatDateAndTime(value, fmt) {
let date = value ? new Date(value) : new Date();
let o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'H+': date.getHours(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'S+': date.getMilliseconds()
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (('00' + o[k]).substr(String(o[k]).length)));
}
}
return fmt;
}
上面 formatDateAndTime 方法只能替换一次年月日等信息,一般情况下满足使用要求, 但如果 fmt参数值是类似 'yyyy/MM/dd hh:mm:ss---yyyy年MM月dd日' 这种格式时, 由于yyyy, MM, dd有重复,上面方法中正则只会替换第一次出现的yyyy, 而第二次出现的yyyy则不会转换,
可以对正则替换进行优化,使用全局替换
// 根据自定义format格式进行格式化 2 (全局替换)
function formatDateAndTime(value, fmt) {
let date = value ? new Date(value) : new Date();
let o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'H+': date.getHours(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'S+': date.getMilliseconds()
};
let reg = new RegExp('(y+)', 'g');
if (reg.test(fmt)) {
fmt = fmt.replace(reg, (_, curr) => (date.getFullYear() + '').substring(4 - curr.length));
}
for (var k in o) {
let reg_k = new RegExp(`(${k})`, 'g');
if (reg_k.test(fmt)) {
fmt = fmt.replace(reg_k, (_, curr) => {
return curr.length == 1 ? (o[k]) : (`00${o[k]}`).substring(String(o[k]).length)
});
}
}
return fmt;
}
补零操作
方式1:循环补零直到指定长度
function zerofill(num, n) {
let str = num.toString();
let len = str.length;
while (len < n) {
str = '0' + str;
len++;
}
return str;
}
方式2:先补齐足够多的0,然后从末尾截取指定长度字符串
// 第一种方法
function zerofill(num, n) {
return (Array(n).fill(0) + num).slice(-n);
}
方式3:ES2017 引入了字符串补全长度的功能 (padStart, padEnd)
'1'.padStart(4, '0') // '0001'
'1'.padStart(4) // ' 1' (如果省略第二个参数,则默认使用空格填充)
生成随机字符串
生成随机字符串是一个很常见的需求,可以根据具体需求定制不同的输出结果。
方式1
利用Math.random
方法每次从目标数组中随机抽取一个字符
/**
* @description: 生成随机字符串
* @param {Number} len 想要生成的随机字符串长度
* @param {Boolean} isPlainNumber 随机字符串是否纯数字
* @return {String} 要输出的字符串
*/
function randomHandler(len = 16, isPlainNumber = false) {
let chars = isPlainNumber ? '1234567890' : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
let result = '';
for (let i = 0; i < len; i++) {
let currIndex = ~~(Math.random() * chars.length);
result += chars[currIndex];
}
return result;
}
console.log(randomHandler(20));
方式2
利用Math.random()
和toString()
方法巧妙生成随机字符串
Math.random()
可以生成一个[0, 1)区间的随机数toString(radix)
方法可以将数字转化为radix
进制的字符串
console.log(Math.random()) // 0.1852958957954327
console.log(Math.random()) // 0.19654440821013286
console.log(Math.random()) // 0.862253082562344
console.log(Math.random().toString()) // 0.8898541967454725
console.log(Math.random().toString()) // 0.21295312937219646
console.log(Math.random().toString(36).substring(2)) // xkji8078cgr
console.log(Math.random().toString(36).substring(2)) // 0e8jhas9zxft
console.log(Math.random().toString(36).substring(2)) // yxnudwel5iq
棒棒哒,一行代码就实现了需求。
哎,等一下,好像发现了一点小瑕疵:Math.random()生成的小数点后面的数字长度是不固定的
那就稍微优化一下:如果一次random达不到所需的目标长度,那就random多次后拼接起来呗
接下来看看具体实现:
/**
* @description: 生成随机字符串
* @param {Number} len 想要生成的随机字符串长度
* @param {Boolean} isPlainNumber 随机字符串是否纯数字
* @return {String} 要输出的字符串
*/
function randomHandler(len = 16, isPlainNumber = false) {
let result = '';
// 如果要求纯数字,则转化为10进制,否则转化为36进制 (26个字母+10个数字)
let radix = isPlainNumber ? 10 : 36;
// 使用substring(2) 保留小数位,去掉整数和小数点
let getOnce = () => Math.random().toString(radix).substring(2);
while(result.length < len) {
result += getOnce();
}
return result.substring(0, len);
}
console.log(randomHandler(20));
最后你发现没,利用Math.random().toString(16)
貌似直接就可以实现一个常见的小功能:生成随机颜色
function getRandomColor() {
return `#${Math.random().toString(16).substr(2, 6)}`
}
console.log(getRandomColor())
条件判断简化
有这么一个业务判断,用户角色值role有[1,2,3,4,5,6,7,8,9,10],总共十种角色,当role 为 1,2,5,8,10时,则显示 删除 按钮
常规的判断方法可能是这样的:
if (role == 1 || role == 2 || role == 5 || role == 8 || role == 10) {
this.showDelBtn = true;
}
可以这样优化一下:
if ([1, 2, 5, 8, 10].includes(role)) {
this.showDelBtn = true;
}
转载自:https://juejin.cn/post/7046708805500928036