String.prototype.padStart -- es2017

把指定字符串填充到字符串头部,返回新字符串。

语法

str.padStart(targetLength [, padString]

  • targetLength

当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。

  • padString 可选

填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断。此参数的默认值为 " "

示例

'abc'.padStart(10);         // "       abc"
'abc'.padStart(10, "foo");  // "foofoofabc"
'abc'.padStart(6,"123465"); // "123abc"
'abc'.padStart(8, "0");     // "00000abc"
'abc'.padStart(1);          // "abc"

应用场景

日期格式化

const now = new Date()
const year = now.getFullYear()
// 月份和日期 如果是一位前面给它填充一个0
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = (now.getDate()).toString().padStart(2, '0')
console.log(year, month, day)
console.log( `${year}-${month}-${day}` ) //输出今天的日期 2022-03-23

数字替换(手机号,银行卡号等)

const tel = '18781268679'
const newTel = tel.slice(-4).padStart(tel.length, '*')
console.log(newTel) // *******5678

String.prototype.padEnd -- es2017

语法

同上

示例

'abc'.padEnd(10);          // "abc       "
'abc'.padEnd(10, "foo");   // "abcfoofoof"
'abc'.padEnd(6, "123456"); // "abc123"
'abc'.padEnd(1);           // "abc"

应用场景

时间戳补零

在JS前端我们处理时间戳的时候单位是ms毫秒,但是后端返回的时间戳则不一定是毫秒,可能只有10位,以s秒为单位。所以,我们在前端处理这个时间戳的时候,保险起见,要先做一个13位的补全,保证单位是毫秒。

for await of -- es2017

异步迭代器(for-await-of):循环等待每个Promise对象变为resolved状态才进入下一步。

for of 同步运行

function TimeOut(time){
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(time)
        }, time)
    })
}

async function test() {
    let arr = [TimeOut(2000), TimeOut(1000), TimeOut(3000)]
    for (let item of arr) {  
     console.log(Date.now(),item.then(console.log))
    }
}

test()

image-20220323094244180

发现输出结果并不是我们想要的,接着我们使用 for await of

function TimeOut(time) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(time)
        }, time)
    })
}

async function test() {
    let arr = [TimeOut(2000), TimeOut(1000), TimeOut(3000)]
    for await (let item of arr) {
        console.log(Date.now(), item)
    }
}
test()
// 1647999860945 2000
// 1647999860945 1000
// 1647999861945 3000

Promise.prototype.finally() -- es2018

在promise执行结束时,无论结果是fulfilled或者是rejected,在执行then()和catch()后,都会执行finally指定的回调函数。这为指定执行完promise后,无论结果是fulfilled还是rejected都需要执行的代码提供了一种方式,避免同样的语句需要在then()和catch()中各写一次的情况。

应用场景

loading 关闭

Object.fromEntries() -- es2019

方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。

应用场景

过滤

// course表示所有课程,想请求课程分数大于80的课程组成的对象:
const course = {
    math: 70,
    english: 85,
    chinese: 90
}
const res = Object.entries(course).filter(([key, val]) => val > 80)
console.log(res) // [ [ 'english', 85 ], [ 'chinese', 90 ] ]
console.log(Object.fromEntries(res)) // { english: 85, chinese: 90 }

url的search参数转换

// let url = "https://www.baidu.com?name=jimmy&age=18&height=1.88"
// queryString 为 window.location.search
const queryString = "?name=jimmy&age=18&height=1.88";
const queryParams = new URLSearchParams(queryString);
const paramObj = Object.fromEntries(queryParams);
console.log(paramObj); // { name: 'jimmy', age: '18', height: '1.88' }

Array.prototype.flat() -- es2019

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

语法

var newArray = arr.flat([depth])

depth 可选

指定要提取嵌套数组的结构深度,默认值为 1。

示例

扁平化嵌套数组(以往我们扁平化数组都是通过 reduce实现)

// Before
function flatten(arr) {
  return arr.reduce((prev, curr) => prev.concat(Array.isArray(curr) ? flatten(curr) : curr), []);
}

const arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

const arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

//使用 Infinity,可展开任意深度的嵌套数组
const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

reduce代替map和filter

const _arr = [0, 1, 2];

// map
const arr = _arr.map(v => v * 2);
const arr = _arr.reduce((t, v) => [...t, v * 2], []);
// arr => [0, 2, 4]

// filter
const arr = _arr.filter(v => v > 0);
const arr = _arr.reduce((t, v) => v > 0 ? [...t, v] : t, []);
// arr => [1, 2]

// map和filter
const arr = _arr.map(v => v * 2).filter(v => v > 2);
const arr = _arr.reduce((t, v) => {
    v = v * 2;
    v > 2 && t.push(v);
    return t;
}, []);
// arr => [4]

空值合并运算符(Nullish coalescing Operator)-- es2020

空值合并操作符?? )是一个逻辑操作符,当左侧的操作数为 null或者undefined时,返回其右侧操作数,否则返回左侧操作数。

注意点

?? 直接与 AND(&&)和 OR(||)操作符组合使用是不可取的。

null || undefined ?? "foo"; // 抛出 SyntaxError
true || undefined ?? "foo"; // 抛出 SyntaxError

示例

const foo = undefined ?? "foo"
const bar = null ?? "bar"
console.log(foo) // foo
console.log(bar) // bar

const foo = "" ?? 'default string';
const foo2 = "" || 'default string';
console.log(foo); // ""
console.log(foo2); // "default string"

const baz = 0 ?? 42;
const baz2 = 0 || 42;
console.log(baz); // 0
console.log(baz2); // 42

可选链 Optional chaining -- es2020

可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为 null 或者 undefined 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined

示例

const user = {
    address: {
        street: 'xx街道',
        getNum() {
            return '80号'
        }
    }
}

// 在之前的语法中,想获取到深层属性或方法,不得不做前置校验,否则很容易命中 Uncaught TypeError: Cannot read property... 这种错误,这极有可能让整个应用挂掉。
const street = user && user.address && user.address.street
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(street, num)

// 用了 Optional Chaining ,上面代码会变成
const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)

与空值合并操作符一起使用

let customer = {
  name: "jimmy",
  details: { age: 18 }
};
let customerCity = customer?.city ?? "成都";
console.log(customerCity); // "成都"

注意点

可选链不能用于赋值

let object = {};
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment

globalThis -- es2020

在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 windowself 取到全局对象,在 Node.js 中,它们都无法获取,必须使用 global

现在globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。

为便于记忆,你只需要记住,全局作用域中的 this 就是globalThis。以后就用globalThis就行了。

pc_platformglobalThis 兼容处理

image-20220323112308658

Promise.allSettled() -- es2020

我们都知道 Promise.all() 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入reject 状态。

场景:现在页面上有三个请求,分别请求不同的数据,如果一个接口服务异常,整个都是失败的,都无法渲染出数据

我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态,这就是Promise.allSettled的作用

const promise1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise1");
      //   reject("error promise1 ");
    }, 3000);
  });
};
const promise2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise2");
      //   reject("error promise2 ");
    }, 1000);
  });
};
const promise3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      //   resolve("promise3");
      reject("error promise3 ");
    }, 2000);
  });
};

//  Promise.all 会走到catch里面
Promise.all([promise1(), promise2(), promise3()])
  .then((res) => {
    console.log(res); 
  })
  .catch((error) => {
    console.log("error", error); // error promise3 
  });
  
// Promise.allSettled 不管有没有错误,三个的状态都会返回
Promise.allSettled([promise1(), promise2(), promise3()])
  .then((res) => {
    console.log(res);  
    // 打印结果 
    // [
    //    {status: 'fulfilled', value: 'promise1'}, 
    //    {status: 'fulfilled',value: 'promise2'},
    //    {status: 'rejected', reason: 'error promise3 '}
    // ]
  })
  .catch((error) => {
    console.log("error", error); 
  });

Promise.any -- (实验性)

方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态

const promise1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise1");
      //  reject("error promise1 ");
    }, 3000);
  });
};
const promise2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise2");
      // reject("error promise2 ");
    }, 1000);
  });
};
const promise3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("promise3");
      // reject("error promise3 ");
    }, 2000);
  });
};
Promise.any([promise1(), promise2(), promise3()])
  .then((first) => {
    // 只要有一个请求成功 就会返回第一个请求成功的
    console.log(first); // 会返回promise2
  })
  .catch((error) => {
    // 所有三个全部请求失败 才会来到这里
    console.log("error", error);
  });

优雅处理 async/await 异常

正常情况async/await使用起来确实方便、简洁,也避免了出现回调地狱的情况

但我们使用 async/await时候往往需要使用try catch包裹避免 Promise.reject()中断后续代码执行

/**
 * @description: 对awaited异步进行包装,异常处理 (使用async/await时避免try/catch进行处理)
 * @param {*} awaited 需要进行异常处理的请求 (一般情况下是一个Promise)
 * @return {Promise} 返回一个Promise,在调用的地方通过async/await 解构
 * 使用:let [err, result] = await this.wrappedAwait(awaited), 正常请求时result为接口返回的数据,异常时err为错误信息
 */
function wrappedAwait(awaited) {
  let p = Promise.resolve(awaited); // 非Promise则转为Promise
  return p.then(
    res => [null, res],
    err => [err, null]
  );
}

async function test() {
  const [err, res] = await wrappedAwait({ userId: 123 });
  console.log('__err__', err);
  // __err__ null
  console.log('__res__', res);
  // __res__ {userId: 123}
}
async function test2() {
  const [err, res] = await wrappedAwait(Promise.reject('a error'));
  console.log('__err__', err);
  // __res__ a error
  console.log('__res__', res);
  // __res__ null
}

test()
test2()

逻辑运算符和赋值表达式(&&=,||=,??=)-- es2020

&&=

逻辑与赋值 x &&= y等效于:x && (x = y);

let a = 1;
let b = 0;

a &&= 2;
console.log(a); // 2

b &&= 2;
console.log(b);  // 0

||=

逻辑或赋值(x ||= y)运算仅在 x 为false时赋值。x ||= y 等同于:x || (x = y);

const a = { duration: 50, title: '' };

a.duration ||= 10;
console.log(a.duration); // 50

a.title ||= 'title is empty';
console.log(a.title); // "title is empty"

??=

逻辑空赋值运算符 (x ??= y) 仅在 xnullundefined 时对其赋值。

x ??= y 等价于:x ?? (x = y);

function config(options) {
  options.duration ??= 100;
  options.speed ??= 25;
  return options;
}

config({ duration: 125 }); // { duration: 125, speed: 25 }
config({}); // { duration: 100, speed: 25 }

Array.prototype.at -- es2022

Array.prototype.at()接收一个正整数或者负整数作为参数,表示获取指定位置的成员

参数正数就表示顺数第几个,负数表示倒数第几个,这可以很方便的某个数组末尾的元素

var arr = [1, 2, 3, 4, 5]
// 以前获取最后一位
console.log(arr[arr.length-1]) //5
// 简化后
console.log(arr.at(-1)) // 5

forEach如何跳出循环?

forEach的的回调函数形成了一个作用域,在里面使用return并不会跳出,只会被当做continue

利用 try catch

function getItemById(arr, id) {
  let item = null;
  try {
    arr.forEach(function (curItem, i) {
      if (curItem.id === id) {
        item = curItem;
        throw Error();
      }
    })
  } catch (e) {
  }
  return item;
}

数组映射

我们最常用的数组映射方法是 Array.map,另一种方法就是Array.from

const cities = [
    { name: 'Paris', visited: 'no' },
    { name: 'Lyon', visited: 'no' },
    { name: 'Marseille', visited: 'yes' },
    { name: 'Rome', visited: 'yes' },
    { name: 'Milan', visited: 'no' },
    { name: 'Palermo', visited: 'yes' },
    { name: 'Genoa', visited: 'yes' },
    { name: 'Berlin', visited: 'no' },
    { name: 'Hamburg', visited: 'yes' },
    { name: 'New York', visited: 'yes' }
];

const cityNames = Array.from(cities, ({ name}) => name);
console.log(cityNames);
// 输出 ["Paris", "Lyon", "Marseille", "Rome", "Milan", "Palermo", "Genoa", "Berlin", "Hamburg", "New York"]

解构原始数据

const rawUser = {
   name: 'Jona',
   surname: 'Doe',
   email: 'john@doe.com',
   displayName: 'SuperCoolJohn',
   joined: '2021-03-31',
   image: 'path-to-the-image',
   followers: 45
}

let user = {}, userDetails = {};
({ name: user.name, surname: user.surname, ...userDetails } = rawUser);

console.log(user); 
// 输出 { name: "John", surname: "Doe" }
console.log(userDetails); 
// 输出 { email: "john@doe.com", displayName: "SuperCoolJohn", joined: "2016-05-05", image: "path-to-the-image", followers: 45 }

快速创建一个m * n的二维数组

// 方式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));
}

对象属性链式get

场景

一个obj对象嵌套了很多层, 给定一个字符串key = 'first.second.third' , 根据key得到最终的obj.first.second.third的值

const obj = {
  first: {
    second: {
      third: 'message'
    } 
  }
};
const getResultValue = (obj, key) => key.split('.').reduce((o, i) => {
  if (o) return o[i];
}, obj);

const key = 'first.second.third';
const result = getResultValue(obj, key);
console.log(result);
// message

奇偶性判断 -- 按位与(&)

  • 按位与运算是把两个操作数转换成二进制再逐位比较,当前位都为1结果为1,否则为0。
  • 而所有数字转化为二进制的奇偶性就只用看末尾,奇数尾数为1,偶数尾数为0
const a = 2
a & 1 ? alert('a是奇数!') : alert('a是偶数!')

数字 -1 的判断 -- 按位取反(~)

判断一个数是否为-1是我们经常遇到的,String.indexOf()在查找字符串的时候没有找到会返回-1,Arroy.findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1,很多程序,插件,框架错误状态值默认返回-1,在位运算中,~-1===0 的。

const str = 'sjoeai'
// 常规
if ( str.indexOf('a') != -1 ) {
  alert('a在字符串str中');
}
// Better ~取反
if ( ~str.indexOf('a') ) {
  alert('a在字符串str中');
}

数字取整 -- (~~)

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

生成随机字符串

方式1 (wxt-utils里的随机生成字符串就是类似于此方法)

/**
 * @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.4219865731472241
console.log(Math.random()) // 0.6486352330721274
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)) // ni6869kv5
console.log(Math.random().toString(36).substring(2)) // 1cs693i4bd1
console.log(Math.random().toString(36).substring(2)) // bo740ginr0w

发现了一点小瑕疵: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())
// #a8119b

交换两个变量

let x = 1;
let y = 2;

let temp = x;
x = y;
y = temp;

// Better
[x, y] = [y, x];

处理多个条件

const conditions = ["Condition 2","Condition String2"];
someFunction(str){
  return str.includes("someValue1") || str.includes("someValue2")
}

// Better
someFunction(str){
   const conditions = ["someValue1","someValue2"];
   return conditions.some(condition=>str.includes(condition));
}

动态键值对 替换 switch 或 if else

const UserRole = {
  ADMIN: "Admin",
  GENERAL_USER: "GeneralUser",
  SUPER_ADMIN: "SuperAdmin",
};
function getRoute(userRole = "default role"){
  switch(userRole){
    case UserRole.ADMIN:
      return "/admin"
    case UserRole.GENERAL_USER:
      return "/GENERAL_USER"
    case UserRole.SUPER_ADMIN:
      return "/superadmin"
    default:
      return "/Default path" 
  }
}
console.log(getRoute(UserRole.ADMIN)) // return "/admin"
console.log(getRoute("Anything")) // return Default path
console.log(getRoute()) // return Default path
console.log(getRoute(null)) // return Default path

// Better
const appRoute = {
  [UserRole.ADMIN]: "/admin",
  [UserRole.GENERAL_USER]: "/user",
  [UserRole.SUPER_ADMIN]: "/superadmin"
};
function getRoute(userRole = "default role"){
 return appRoute[userRole] || "Default path";
}