本文汇总了 JavaScript 开发中的实用技巧,涵盖 && 和 || 短路运算、函数默认参数、类数组转数组、解构赋值及数组去重扁平化等常见场景。重点解析 ES5 与 ES6+ 的实现差异,提供代码示例与注意事项,帮助开发者编写更简洁高效的代码。
JavaScript 实用技巧与 ES6+ 语法速查
前言
本文整理自日常实践、博客阅读及项目源码中的 JavaScript 技巧。内容既包含一些巧妙的编程手法,也涉及 ES6+ 标准中具备高实用价值的新语法特性。
&& 和 || 的妙用
&& 和 || 的妙用
在某些场景下,我们需要判断函数或变量是否为真,若为真则执行后续操作。例如:
const task1 = () => {
console.log('执行 task1');
return Math.random() >= 0.5;
}
const task2 = () => console.log('task1 执行成功后执行 task2');
if (task1()) task2();
上述 if 语句逻辑可以通过 && 运算符简化为:
task1() && task2();
若需在 task1 执行失败(返回 false)时执行 task3,可结合使用 && 和 ||:
const task3 = () => console.log('task1 执行失败后执行 task3');
task1() && task2() || task3();
这本质上利用了 && 和 || 的短路求值特性。
此外,使用三元条件运算符也能实现相同逻辑:
task1() ? task2() : task3();
以下展示一个在 React Hooks 项目中实际使用的代码片段:
const ProfileItem = (props) => {
const { name, value, render } = props;
return (
<div className="profile-item">
<span className="item-name">{name}</span>
<form action="">
{/* 根据是否有 render 这个 props 来返回不同的内容 */}
{render && render(props) || <SimpleProfileItemContent value={value}/>}
</form>
</div>
)
}
函数默认值
函数默认值
ES5 版本
利用短路或运算符设置函数默认值是常见做法,但存在潜在陷阱。如下代码所示,当默认值参数为数字时,若传入 0,仍会回退使用默认值,因此必须对 y 为 0 的情况进行单独判断。
const pow = (x, y) => {
y = y || 2;
let result = 1;
for (let i = 0, max = y; i < max; i++) {
result *= x;
}
return result;
}
console.log(pow(2)); // => 4
console.log(pow(2, 3)); // => 8
// 当 y 传值为 0 时, y 取值 2
console.log(pow(2, 0)); // => 4
ES6 版本
ES6 在语法层面提供的默认参数功能更为可靠:
const pow = (x, y=2) => {
let result = 1;
for (let i = 0, max = y; i < max; i++) {
result *= x;
}
return result;
}
console.log(pow(2)); // => 4
console.log(pow(2, 3)) // => 8
console.log(pow(2, 0)); // => 1
类数组转数组
类数组转数组
类数组对象(如 arguments 或 jQuery 对象)具备 length 属性且可通过下标访问,外观与数组相似,但并非真正的数组实例。
由于类数组缺少 slice、map 等集合方法,有时需将其转换为数组以使用这些功能。
function func() {
for (let i = 0, max = arguments.length; i < max; i++) {
console.log(arguments[i]);
}
console.log(Array.isArray(arguments)); // => false
// 类数组没有 slice, forEach, map 等集合函数
console.log(arguments.slice === undefined); // => true
}
func('Google', 'facebook', 'Microsoft');
// =>
// Google
// facebook
// Microsoft
ES5 中的转换方法
将 Array 原型的 slice 方法绑定到 arguments 对象上调用,且不传参数,从而返回所有元素。
function func() {
const array = Array.prototype.slice.call(arguments);
console.log(array.slice(0, 1));
}
func('Google', 'facebook', 'Microsoft'); // => [ 'Google' ]
ES6 中的转换方法
ES6 提供了更多将类数组转换为数组的途径。
使用扩展运算符
function func() {
console.log([...arguments])
}
func('Google', 'facebook', 'Microsoft'); // [ 'Google', 'facebook', 'Microsoft' ]
使用 Array.from
function func() {
console.log(Array.from(arguments))
}
func('Google', 'facebook', 'Microsoft'); // [ 'Google', 'facebook', 'Microsoft' ]
构造连续整数数组
构造一个连续整数的数组
这里直接展示我认为较为简洁的方法:
// 输出 2 开始连续的8个整数
const array = Array.from({ length: 8}).map((ele, index) => index + 2);
console.log(array); // => [ 2, 3, 4, 5, 6, 7, 8, 9 ]
// 评论区指出有更简洁的版本, Array.from 自带的映射函数
const array = Array.from({ length: 8}, (ele, index) => index + 2);
console.log(array); // => [ 2, 3, 4, 5, 6, 7, 8, 9 ]
函数参数解构赋值
函数参数使用解构赋值
当函数参数较多时,通常接受一个配置对象。但直接使用对象参数无法便捷地设置默认值,且在函数体内通过对象属性访问参数较为繁琐,尤其是嵌套较深或访问次数较多时。在函数参数中使用解构赋值可有效解决上述问题。
// 必须给对象参数设置默认值, 不然传参数时因为没有解构对象会报错
const getUsers = ({
offset=0,
limit=1,
orderBy="salary"
}={}) => {
// 根据条件查询数据库返回用户数据
console.log({ offset, limit, orderBy });
}
getUsers({ offset: 10, limit: 20,orderBy: 'age' }); // => { offset: 10, limit: 20, orderBy: 'age' }
getUsers();// => { offset: 0, limit: 1, orderBy: 'salary' }
类型转换与对象处理
使用 !! 将其它类型转换成 bool 型
console.log(!!{}); // true
console.log(!!0); // false
console.log(!![]); // true
console.log(!!undefined); // false
const httpGet = (url, retry) => {
if (!!retry) {
// 超时重发
}
}
JSON.stringify
深度克隆
通过先序列化再反序列化的方式克隆对象通常很方便,但其缺点是无法克隆函数及继承的属性。若需克隆函数属性,建议使用 lodash 的 cloneDeep 方法。
const me = {
name: 'lyreal666',
age: 23,
speak() {
console.log(`Hello, I'm ly!`);
}
}
const clonedMe = JSON.parse(JSON.stringify(me));
console.log(clonedMe); // => { name: 'lyreal666', age: 23 }
console.log(clonedMe.speak === undefined); // => true
使用第二个和第三参数
JSON.stringify 的第二个参数用于处理属性值,第三个参数指定输出 JSON 字符串的缩进长度,可传入数字或字符串。
const me = {
name: 'lyreal666',
age: 23,
speak() {
console.log(`Hello, I'm ly!`);
}
}
const jsonStr = JSON.stringify(me, (key, value) => key === 'name' ? '老余' : value, 2);
console.log(jsonStr);
/* =>
{
"name": "老余",
"age": 23
}
*/
优雅的遍历对像
结合解构赋值与 Object.entries 进行遍历。
const me = {
name: 'lyreal666',
age: 23,
speak() {
console.log(`Hello, I'm ly!`);
}
}
for (const [key, value] of Object.entries(me)) {
console.log(`${key}: ${value}`);
}
/* =>
name: lyreal666
age: 23
speak: speak() {
console.log(`Hello, I'm ly!`);
}
*/
数组操作技巧
清空数组的最快方法
评论区指出直接修改 length 的做法可能存在争议,此前我也关注过相关讨论。但在一般应用场景下,这种方法简单且无需为新数组重新分配内存,通常是可以接受的。
const array = [1, 2, 3, 4];
array.length = 0;
console.log(array); // => []
// 网友指出可以更好的方式是直接赋值空数组
let array = [1, 2, 3, 4];
array = [];
其他实用技巧
判断一个整数是否是 -1
// ~ 操作符的运算规律可以简单记作将加一的结果取反
console.log(~1); // => -2
console.log(~0); // => -1
console.log(~(-3)); // => 2
console.log(~(-1)); // => 0
const number = -2;
// 判断一个数是否为 -1
if (!~number) {
// 当 number 是 -1 的操作...
}
立即执行函数
立即执行函数(IIFE)可使变量不污染外部作用域,常见用法如下:
// 使用括号将函数括起来调用
(function(window, $) {
// 内部代码
}) (window, jQuery)
更优雅的方式如下,事实上,其他算术运算符如 +、-、*、~ 等也可实现类似效果:
! function(window, $) {
// 内部代码
} (window, jQuery)
// 还可以使用 +, -, * 等
+ function(window, $) {
// 内部代码
} (window, jQuery)
// 更神奇的是还可以用 new, typeof 等操作符
new function(window, $) {
// 内部代码
} (window, jQuery);
使用 set 来对数组去重复
console.log([...new Set([1, 3, 1, 2, 2, 1])]); // => [ 1, 3, 2 ]
使用 reduce 连乘或连加
const array = [ 1, 2, 3, 4];
// 连加
console.log(array.reduce((p, c) => p + c)); // => 10
// 连乘
console.log(array.reduce((p, c) => p * c)); // => 24
取整
Math 对象提供了多种取整函数,此处主要介绍一些巧妙的取整技巧。
console.log(~~3.14); // => 3
console.log(~~(-2.5)); // => -2
console.log(6.18 | 0); // => 6
console.log(-3.6 | 0); // => -3
console.log(9.9 >> 0); // => 9
console.log(-2.1 >> 0); // => -2
// superagent 是一个很实用的发送 http 请求的 node 模块,它对返回码的处理就用到了 |
var type = status / 100 | 0;
// status / class
res.status = status;
res.statusType = type;
// basics
res.info = 1 == type;
res.ok = 2 == type;
res.clientError = 4 == type;
res.serverError = 5 == type;
res.error = 4 == type || 5 == type;
使用 + 将其它类型转换成 number 类型
console.log(+'3.14'); // => 3.14
console.log(typeof +'3.14') // => number
const sleep = (milliseconds) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), milliseconds);
});
}
// 当然这里可以考虑使用 console.time 来测试
! async function main() {
const start = +new Date();
await sleep(3000);
const end = +new Date();
console.log(`执行了${end - start}`); // 执行了 3002
}();
使用科学计数法表示大数字
const str1 = 'hello';
const str2 = ' world'
console.time('测试 + 拼接字符串');
for (let i = 0; i < 200000000; i++) {
const joinedStr = str1 + str2;
}
console.timeEnd('测试 + 拼接字符串');
console.time('测试模板字符串拼接字符串');
// 使用科学计数法比打 8 个 0 方便不少
for (let i = 0; i < 2E8; i++) {
const joinedStr =`${str1}${str2}`;
}
console.timeEnd('测试模板字符串拼接字符串')
/* =>
测试 + 拼接字符串: 3238.037ms
测试模板字符串拼接字符串: 3680.225ms
*/
交换变量值
直接利用解构赋值
let a = 666;
let b = 999;
[a, b] = [b, a];
console.log({ a, b }); // => { a: 999, b: 666 }
获取随机字符串
截取子串从索引 2 开始是为了去除 Math.random() 生成的小数部分中的“0.”字符。使用 36 进制可生成字符种类更丰富的随机字符串。
console.log(Math.random().toString(16).substring(2)); // 13位 => 45d9d0bb10b31
console.log(Math.random().toString(36).substring(2)); // 11位 => zwcx1yewjvj
扁平化数组
ES 2019 新增了 Array.prototype.flat,Chrome 73.0.3683.103 及 Node 11.13.0 已支持,但 Node LTS 10.15.3 尚不支持。此处引用自某面经的较 hack 方法,需注意根据情况处理类型转换。
const array = [1, [2, [3, 4], 5], 6, 7];
console.log(array.toString().split(',').map(ele => Number.parseInt(ele))); // => [ 1, 2, 3, 4, 5, 6, 7 ]
近期在腾讯、阿里前端实习岗位的面试经历颇具代表性,后续计划另文详述。
总结
总结
以上为本文全部内容,希望对您的学习或工作有所参考,感谢支持。