记得看https://www.30secondsofcode.org/js/p/1 (opens in a new tab)

void 0?

起源于 div.js (opens in a new tab) 这个库的源码(能用 <div is="p"> 来全用 div 写 html 哈哈哈。

里面看到了熟悉又陌生的 void 0 用法,于是谷歌了一下 why

参考博客 (opens in a new tab)

起因:undefined 不是 JS 的关键字/保留字,所以是可以对他进行赋值的(null 就不能作为左值)!不同浏览器(低版本 IE)可能会对 undefined 变量的取值行为不同

所以,void 操作符就是 ecma 提出获得纯正 undefined 的。

void 行为

  • 返回纯正的 undefined
  • 并且对右边的 expression 进行 eval,如果变量有 getter 会执行,有可能会产生副作用

其他纯正 undefined

  • 未定义的变量/形参

    const getUndefined = (u) => u;
  • 没有返回值的函数

  • 未定义的属性

说实话不去谷歌一下,真看不懂 void 0。。

对象属性的懒加载计算

来自这篇文章 (opens in a new tab)

对于一些属性值需要复杂计算,可以在第一次需要访问这个属性的时候再进行计算并进行缓存,是一个比较经典的懒加载模型

比如下面的 data 属性需要一些复杂计算才能得到

class MyClass {
  constructor() {
    this.data = someExpensiveComputation();
  }
}

首先可以用 getter 去解决第一次获取才计算。然后问题是如何缓存,当然可以用另一个类属性去记录和判断(当然我第一直觉是这么想的,但是不够优雅)。

文中提出的方法是用 Object.defineProperty,又是这个神器,能够对原本的 getter 进行重新定义为属性,缓存计算之后的值。

直接看代码吧

Class 的,为什么在构造函数就先 define 呢,因为 getter 在 class 是不可枚举的,不是实例的 property

class MyClass {
  constructor() {
    Object.defineProperty(this, "data", {
      get() {
        const actualData = someExpensiveComputation();
 
        Object.defineProperty(this, "data", {
          value: actualData,
          writable: false,
          configurable: false,
        });
 
        return actualData;
      },
      configurable: true,
      enumerable: true,
    });
  }
}

Object 的,比较简单,getter 是可枚举的

const object = {
  get data() {
    const actualData = someExpensiveComputation();
 
    Object.defineProperty(this, "data", {
      value: actualData,
      writable: false,
      configurable: false,
      enumerable: false,
    });
 
    return actualData;
  },
};

复习一下,属性的 configurable:不可被删除

P.S. 到这里其实感觉有个 bug,如果这个复杂计算的函数是个动态的类方法,懒加载和缓存应该就没那么简单了,还需要对依赖进行收集和监听,好吧,那就是 vue 干的事情了。。只要给对应的 setter 函数中加上对需要计算的属性的 defineProperty 操作就行了!

取整

向下取整

n >>> 0;
~~n;

生成 1-10 的数组

一定要 fill,不然都是空 item ,不会被高阶函数遍历到的(红宝书写过)

const a = Array(10)
  .fill("any")
  .map((v, i) => i + 1);

生成 0-n 的数组

let n = 10;
const a = Array.from(Array(n).keys());

wow,很炫

const aa = [...Array(n).keys()];

这个更加简洁!

range()

const range = (start, end) =>
  Array.from({ length: end - start }, (_, i) => start + i);

当然也可以直接 map 成你想要的东西

逆序字符串

split 成数组Array.from 转换为数组, reverse 之后再 join 起来

const reverseStr = (value) => Array.from(value).reverse().join("");

数组添加尾部元素的方法

  • arr[arr.length] = 'tail',长度重新计算,最后 index+1

Array.fill() 共享内存

想构造二维数组,结果用 fill 发现共享内存...踩坑(这里仅是引用类型会共享)

const dp = Array(lenB).fill(Array(lenA));

正确的方法可以

TODO dp 算法那篇

Math.floor() 用 >> 0

右移 0 可以实现向下取整

JSON.stringify()

乞丐版深拷贝

let a = { m: 3, k: { jj: 123, oo: 321 } };
let b = JSON.parse(JSON.stringify(a));

当然对于这样仅包含对象的情况来说足够了

对于拷贝其他引用类型、拷贝函数、循环引用等情况,就需要手动递归

判断两个对象是否相等(有同样的属性)

let a = [1, 2, 3];
let b = [1, 2, 3];
JSON.stringify(a) === JSON.stringify(b);

判读一个对象的属性是否在另一个对象中

都 stringify 成字符串之后用 indexOf 看下标即可

webStorage 存储对象

stringify 之后存在 localStorage/sessionStorage

按照指定 key 的顺序

参考 (opens in a new tab)

默认的顺序就是对象定义的时候的顺序?

JSON.stringify(sortMyObj, Object.keys(sortMyObj).sort());

在第二个参数指定 key 的数组就行

类型判断

Object.prototype.toString.call(element).slice(8, -1) === "Array";

回忆:

  • typeof:对基本类型的判断
  • instanceof:检查原型链上的原型是否是目标构造函数的原型

flat

最简单版本

let a = [1, 2, 3, [4, 5, 6], [4, 5, 6, [4, 5, 6], [4, 5, 6]]];
 
const flatten = (arr) => {
  const res = [];
  arr.forEach((v) => {
    if (Array.isArray(v)) {
      res.push(...flatten(v));
    } else {
      res.push(v);
    }
  });
  return res;
};
 
console.log(flatten(a));

polyfill 版本

Array.prototype.myFlat = function (num = 1) {
  if (!Array.isArray(this)) {
    return;
  }
  const result = [];
  this.forEach((item) => {
    if (Array.isArray(item)) {
      if (num <= 0) {
        result.push(item);
        return result; // 后面不走了 进入下一次循环
      }
      result.push(...item.myFlat(num - 1));
    } else {
      result.push(item);
    }
  });
  return result;
};

网上找的,我记得我也写过,但找不到了。。。

console

console.table

当遇到 array of objects 的时候很好用(node 命令行也支持)

const foo = { name: "Suibin", age: 30, coder: true };
const bar = { name: "Borja", age: 40, coder: true };
const baz = { name: "Paul", age: 50, coder: false };
console.table([foo, bar, baz]);

console.log with ‘%’ sign 定制 CSS 样式

浏览器中有效。。node 没有 CSS

%c

console.log("%cfirst: ", "color: MidnightBlue; background: Aquamarine;", first);
 
console.log("%cStyled log", "color: orange; font-weight: bold;");
console.log("Normal log");

多个对象可以放到一个{}里 log

const foo = { name: "Suibin", age: 30, coder: true };
const bar = { name: "Borja", age: 40, coder: true };
const baz = { name: "Paul", age: 50, coder: false };
console.log({ foo, bar, baz });

这样可以看的更加清楚变量了...

解构赋值剩余属性

...X 来获取没有被解构的对象

let a = { k: 12, e: 3, rr: 33, es: 13 };
let { k, ...restData } = a;
// > k
// 12
// > restData
// { e: 3, rr: 33, es: 13 }