前端开发中常用的10个小本领,助你写出高逼格代码

手机游戏开发者 2024-9-17 16:24:56 23 0 来自 中国
1. 有条件地向对象、数组添加属性

1) 向对象添加属性

可以使用展开运算符来有条件地向对象中添加属性:
const condition = true;const person = {  id: 1,  name: "dby",  ...(condition && { age: 12 }),};
假如 condition 为 true ,则 { age: 16 } 会被添加到对象中;假如 condition 为 false ,相当于展开 false ,不会对对象产生任何影响
2) 向数组添加属性

这是 CRA 中 Webpack 设置的源码:
module.exports = {  plugins: [    new HtmlWebpackPlugin(),    isEnvProduction &&      new MiniCssExtractPlugin(),    useTypeScript &&      new ForkTsCheckerWebpackPlugin(),  ].filter(Boolean),}
上面代码中,只有 isEnvProduction 为 true 才会添加 MiniCssExtractPlugin 插件;当 isEnvProduction 为 false 则会添加 false 到数组中,以是末了使用了 filter 过滤
上面如许写,逻辑上是没有问题,但是在 TS 项目中使用的时间,范例推断会有点问题。由于存在一种布尔值添加到数组的中心状态,而 TS 只能静态分析,不能实行代码。
试问下面 arr 数组的范例:
const arr = ['111', true && '222'].filter(Boolean); // string[]const arr = ['111', false && '222'].filter(Boolean); // (string | boolean)[]下面是一种范例安全的做法:
module.exports = {  plugins: [    new HtmlWebpackPlugin(),    ...(isEnvProduction ? [new MiniCssExtractPlugin()] : []),    ...(useTypeScript ? [new ForkTsCheckerWebpackPlugin()] : []),  ],}3. 数组小本领

1) 访问数组末了一个元素

在开发中我们常常须要访问数组末了一个元素,通例的做法是如许:
const rollbackUserList = [1, 2, 3, 4];const lastUser = rollbackUserList[rollbackUserList.length - 1]; // 4上面如许写逻辑上没问题,但是我们发现当数组的变量名很长的时间,整个表达式会变得非常长,影响代码可读性。一种方法是把上述逻辑封装一下:
// 将获取数组末了一个元素的逻辑封装为一个函数const getLastEle = (arr) => arr[arr.length - 1];const lastUser = getLastEle(rollbackUserList); // 4可以看到如许语义性就比力好了。尚有一种是使用 Array.prototype.slice 方法,通过通报负索引,从右往左定位到末了一个元素:
const lastUser = rollbackUserList.slice(-1)[0]; // 4// 大概使用解构赋值const [lastUser] = rollbackUserList.slice(-1); // 4注意 slice 方法返回的是一个 新数组 而不是元素本身,须要手动从数组中取出元素,看起来不是很优雅。现实上 ES2022 专门提供了一个 Array.prototype.at 方法,用于根据给定索引获取数组元素:
const lastUser = rollbackUserList.at(-1); // 4
at 方法通报索引为正时从左往右定位(这与直接通过下标访问的作用同等),索引为负时从右往左定位。在访问数组末了一个元素的场景中非常好用。从 Chrome 92 开始已经支持 at 方法,core-js 也提供了 polyfill。
developer.mozilla.org/en-US/docs/…
2) 异步函数组合(异步串行调用)

我们知道,在函数式编程中有一个函数组合 compose 的本领,可以把多个函数组合为一个函数,创建一个从右到左的数据流,右边函数实行的效果作为参数传入左边。比方:
/** * 给定参数:[A, B, C, D] * 调用次序:A(B(C(D()))) */const compose = (middlewares) => (initialValue) => middlewares.reduceRight(    (accu, cur) => cur(accu),    initialValue);从上面的代码可以看出 compose 是不能用于 Promise,由于 compose 须要将上一次调用的返回值,作为参数传入下一次调用,但 Promise 本身是一个包装过的数据布局,只有通过 then 方法才华拿到返回值。以是假如是异步的情况,我们通常会把 reduce 改成平常 FOR 循环。
假如碰到异步的情况,比方想做一系列串行的哀求,是否可以更优雅呢?本人在 Vite 源码中找到了答案,在 Vite 源码中有这么一段:
await postBuildHooks.reduce(  (queue, hook) => queue.then(() => hook(build as any)),  Promise.resolve();)
参考: github.com/vitejs/vite…
可以看出,这里对 reducer 函数举行了包装,将 hook 的实行放到 then 方法回调中,如许就可以保证调用次序。按照这个思绪,我们对上面 compose 方法稍微改动一下,就可以得到一个 asyncCompose:
/** * 给定参数:[A, B, C, D] * 调用次序:Promise.resolve().then(res => D(res)).then(res => C(res)).then(res => B(res)).then(res => A(res)) * 以上只是为了让各人看清楚,简化之后如下:Promise.resolve().then(D).then(C).then(B).then(A) */const asyncCompose = (middlewares) => (initialValue) => middlewares.reduceRight(    (queue, hook) => queue.then(res => hook(res)),    Promise.resolve(initialValue));4. 解构赋值

解构赋值很方便,项目中常常会用到,可以分为以下两个场景:

  • 对象/数组的解构;
  • 函数参数解构;
这里先容四种常用本领。
1) 深度解构

大部门时间我们只会解构一层,但现实上解构赋值是可以深度解构的:
let obj = {  name: "dby",  a: {    b: 1  }}const { a: { b } } = obj;console.log(b); // 12) 解构时使用别名

假如后端返回的对象键名不是我们想要的,可以使用别名:
const obj = {  // 这个键名太长了,我们渴望把它换掉  aaa_bbb_ccc: {    name: "dby",    age: 12,    sex: true  }}const { aaa_bbb_ccc: user } = obj;console.log(user); // { name: "dby", age: 12, sex: true }3) 解构时使用默认值

对象的解构可以使用默认值,默认值见效的条件是,对象的属性值严格即是 undefined :
fetchUserInfo()  .then(({ aaa_bbb_ccc: user = {} }) => {    // ...  })
以上三个特性可以结合使用
4) 使用短路克制报错

解构赋值固然好用,但是要注意解构的对象不能为 undefined 、null ,否则会报错,故要给被解构的对象一个默认值:
const {a,b,c,d,e} = obj || {};5. 展开运算符

使用展开运算符归并两个数组大概两个对象:
const a = [1,2,3];const b = [1,5,6];// badconst c = a.concat(b);// goodconst c = [...new Set([...a,...b])];const obj1 = { a:1 };const obj2 = { b:1 };// badconst obj = Object.assign({}, obj1, obj2);// goodconst obj = { ...obj1, ...obj2 };这里要注意一个问题,对象和数组归并固然看上去都是 ... ,但是现实上是有区别的。
ES2015 扩展运算符只规定了在数组函数参数中使用,但并没有规定可以在对象中使用,而且是基于 for...of 的,因此被展开的只能是数组、字符串、Set、Map 等可迭代对象,假如将平常对象展开到数组就会报错。
对象中的 ... 现实上是 ES2018 中的对象展开语法,相当于 Object.assign :
babeljs.io/docs/en/bab…
6. 查抄属性是否存在对象中

可以使用 in 关键字查抄对象中是否存在某个属性:
const person = { name: "dby", salary: 1000 };console.log('salary' in person); // trueconsole.log('age' in person); // false但是 in 关键字实在并不安全,会把原型上的属性也包罗进去,比方:
"hasOwnProperty" in {}; // true"toString" in {}; // true以是保举使用下面的方法举行判断:
Object.prototype.hasOwnProperty.call(person, "salary"); // true不外上面如许的问题就是太长了,每次使用都要如许写很贫困。ECMA 有一个新的提案 Object.hasOwn() ,相当于 Object.prototype.hasOwnProperty.call() 的别名:
Object.hasOwn(person, "salary"); // true
developer.mozilla.org/en-US/docs/…
须要注意这个语法存在兼容性问题(Chrome > 92),不外只要精确设置 polyfill 就可以放心使用。
7. 对象的遍历

项目中很多同事都会如许写:
for (let key in obj) {  if (Object.prototype.hasOwnProperty.call(obj, key)) {    // ...  }}吐槽:用 Object.keys 大概 Object.entries 转成数组就可以用数组方法遍历了,而且遍历的是自身属性,不会遍历到原型上。
Object.keys(obj).forEach(key => {  // ...})Object.entries(obj).forEach(([key, val]) => {  // ...})举个例子,将对象的 key、value 拼接为查询字符串:
const _stringify = (obj) => Object.entries(obj).map(([key, val]) => `${key}=${val}`).join("&");_stringify({    a: 1,    b: 2,    c: "2333"});// 'a=1&b=2&c=2333'反驳:偶然候不想遍历整个对象,数组方法不能用 break 停止循环呀。
吐槽:看来你对数组方法把握还是不够彻底,使用 find 方法找到符合条件的项就不会继承遍历。
Object.keys(obj).find(key => key === "name");
总结:在开发中只管不要写 for 循环,无论数组和对象。对象就通过 Object.keys 、Object.values 、Object.entries 转为数组举行遍历。如许可以写成 JS 表达式,充实使用函数式编程。
8. 使用 includes 简化 if 判断

在项目中常常看到如许的代码:
if (a === 1 || a === 2 || a === 3 || a === 4) {    // ...}可以使用 includes 简化代码:
if ([1, 2, 3, 4].includes(a)) {    // ...}9. Promise 三点总结

1) async/await 优雅非常处置惩罚

在 async 函数中,只要此中某个 Promise 报错,整个 async 函数的实行就停止了,因此非常处置惩罚非常紧张。但现实上 async 函数的非常处置惩罚非常贫困,很多同事都不乐意写。有没有一种简单的方法呢?看到一个 await-to-js 的 npm 包,可以优雅处置惩罚 async 函数的非常,不须要手动添加 try...catch 捕捉非常:
import to from 'await-to-js';async function asyncFunctionWithThrow() {  const [err, user] = await to(UserModel.findById(1));  if (!user) throw new Error('User not found');}
www.npmjs.com/package/awa…
现实上就是在 await 前面返回的 Promise 封装了一层,提前处置惩罚非常。源码非常简单,本人本身也实现了下:
function to(awaited) {  // 不管是不是 Promise 划一转为 Promise  const p = Promise.resolve(awaited);  // await-to-js 接纳 then...catch 的用法  // 但现实上 then 方法第一个回调函数内里并不包罗会抛出非常的代码  // 因此使用 then 方法第二个回调函数捕捉非常,不须要额外的 catch  return p.then(    res => {      return [null, res];    },    err => {      return [err, undefined];    }  )}2) Promise 作为状态机

看到有同事写过如许的代码:
function validateUserInfo(user) {  if (!userList.find(({ id }) => id === user.id)) {    return {      code: -1,      message: "用户未注册"    }  }  if (    !userList.find(      ({ username, password }) =>        username === user.username &&        password === user.password    )  ) {    return {      code: -1,      message: "用户名或暗码错误"    }  }  return {    code: 0,    message: "登录乐成"  }}观察发现这边实在就是两个状态,然后还须要一个字段提示操纵效果。这种情况下我们可以使用 Promise 。有人说为啥咧,显着没有异步逻辑啊。我们知道,Promise 实在就是一个状态机,即使不须要处置惩罚异步逻辑,我们也可以使用状态机的特性:
function validateUserInfo(user) {  if (!userList.find(({ id }) => id === user.id)) {    return Promise.reject("用户未注册");  }  if (    !userList.find(      ({ username, password }) =>        username === user.username &&        password === user.password    )  ) {    return Promise.reject("用户名或暗码错误");  }  return Promise.resolve("登录乐成");}// 使用如下validateUserInfo(userInfo)  .then(res => {    message.success(res);  })  .catch(err => {    message.error(err);  })显着如许代码就变得非常优雅了,但实在还可以更优雅。我们知道 async 函数返回值是一个 Promise 实例,因此下面两个函数是等价的:
// 平常函数返回一个 Promsie.resolve 包裹的值const request = (x) => Promise.resolve(x);// async 函数返回一个值const request = async (x) => x;既然末了返回一个 Promise ,为何不直接在函数前面加 async 修饰符呢。如许乐成的效果只要直接返回就行,不消 Promise.resolve 包裹:
async function validateUserInfo(user) {  if (!userList.find(({ id }) => id === user.id)) {    return Promise.reject("用户未注册");  }  if (    !userList.find(      ({ username, password }) =>        username === user.username &&        password === user.password    )  ) {    return Promise.reject("用户名或暗码错误");  }  return "登录乐成";}
对 async 函数不认识的同砚,可以参考 阮一峰 ES6 教程
更进一步,由于在 Promise 内部抛出非常等同于被 reject ,因此我们可以使用 throw 语句更换 Promise.reject() :
async function validateUserInfo(user) {  if (!userList.find(({ id }) => id === user.id)) {    throw "用户未注册";  }  if (    !userList.find(      ({ username, password }) =>        username === user.username &&        password === user.password    )  ) {    throw "用户名或暗码错误";  }  return "登录乐成";}
throw 语句的用法可以参考 MDN 文档
3) Promise 两点使用误区

不发起在 Promise 内里使用 try...catch,如许即使 Promise 内部报错,状态仍然是 fullfilled,会进入 then 方法回调,不会进入 catch 方法回调。
function request() {  return new Promise((resolve, reject) => {    try {      // ...      resolve("ok");    } catch(e) {      console.log(e);    }  })}request()  .then(res => {    console.log("哀求效果:", res);  })  .catch(err => {    // 由于在 Promise 中使用了 try...catch    // 因此即使 Promise 内部报错,也不会被 catch 捕捉到    console.log(err);  })
Promise 内部的非常,老诚实实往外抛就行,让 catch 方法来处置惩罚,符合单一职责原则
不发起在 async 函数中,既倒霉用 await,也倒霉用 return,如许就算内部的 Promise reject 也无法捕捉到:
async function handleFetchUser(userList) {  // 这里既没有使用 await,也没有使用 return  Promise.all(userList.map(u => request(u)));}handleFetchUser(userList)  .then(res => {    // 由于没有返回值,这里拿到的是 undefined    console.log(res);  })  .catch(err => {    // 即使 handleFetchUser 内部的 Promise reject    // async 函数返回的 Promise 仍然是 fullfilled    // 此时仍然会进入 then 方法回调,无法被 catch 捕捉到    console.log(err);  })
假如确实有这种需求,发起不要使用 async 函数,直接改用平常函数即可
10. 字符串小本领

1) 字符串不满两位补零

这个需求在开发中挺常见。比方,调用 Date api 获取到日期大概只有一位:
let date = new Date().getDate(); // 3通例做法:
if (data.toString().length == 1) {    date = `0${date}`;}使用 String.prototype.slice :
// 不管几位,都在前面拼接一个 0 ,然后截取末了两位date = `0${date}`.slice(-2);使用 String.prototype.padStart :
// 当字符串长度小于第一个参数值,就在前面补第二个参数date = `${date}`.padStart(2, 0);2) 千分位分隔符

实现如下的需求:

  • 从后往前每三个数字前加一个逗号
  • 开头不能加逗号
如许看起来非常符合 (?=p) 的规律,p 可以表现每三个数字,要添加逗号所处的位置正好是 (?=p) 匹配出来的位置。
第一步,先实行把末了一个逗号弄出来:
"300000000".replace(/(?=\d{3}$)/, ",")// '300000,000'第二步,把全部逗号都弄出来:
"300000000".replace(/(?=(\d{3})+$)/g, ",")// ',300,000,000'
使用括号把一个 p 模式变成一个团体
第三步,去掉首位的逗号:
"300000000".replace(/(?!^)(?=(\d{3})+$)/g, ",")// '300,000,000'3) 借用数组拼接字符串

很多同砚都知道 模板字符串 可以很方便地举行字符串拼接,但是须要拼接较多参数的时间,如许就显得比力贫困:
// HH -> 23// mm -> 58// ss -> 32const timeString = `${HH}{mm}{ss}`;// scheme -> https://// host -> 10.3.71.108// port -> :8080const URLString = `${scheme}${host}${port}`现实上,拼接字符串,除了使用模板字符串的方式,还可以使用数组:
const timeString = [HH, mm, ss].join(":");const URLString = [scheme, host, port].join("");趁便一提,本人之前维护过一个 jQuery 项目,就使用这种方式拼接 html 模板:
const dataSource = ["dby", "dm", "233"];const template = dataSource.map(name => `<div>${name}</div>`).join("");4) 判断字符串前缀、后缀

判断字符串前缀、后缀不要一言不合就使用正则表达式:
const url = "https://bili98.cn";const isHTTPS = /^https:\/\//.test(url); // trueconst fileName = "main.py";const isPythonCode = /\.py$/.test(fileName); // true保举使用 String.prototype.startsWith 和 String.prototype.endsWith,语义性更好:
const url = "https://bili98.cn";const isHTTPS = url.startsWith("https://") // trueconst fileName = "main.py";const isPythonCode = fileName.endsWith(".py"); // true转载:https://juejin.cn/post/7021047385337888775
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 22:36, Processed in 0.163368 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表