JSON.stringify 一个 ES6 Map

对于没有嵌套的一维Map可以使用解构来实现:

JSON.stringify([...new Map()]);
let map = new Map(JSON.parse(map));

其实JSON.stringifyJSON.parse 各自都支持第二个参数 replacer 和 reviver 。可以用下面的 replacer 和 reviver 来支持原生 Map 对象,包括嵌套的值。

function replacer(key, value) {
  const originalObject = this[key];
  if(originalObject instanceof Map) {
    return {
      dataType: 'Map',
      value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject]
    };
  } else {
    return value;
  }
}
function reviver(key, value) {
  if(typeof value === 'object' && value !== null) {
    if (value.dataType === 'Map') {
      return new Map(value.value);
    }
  }
  return value;
}

使用:

const originalValue = new Map([['a', 1]]);
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, newValue);
Deep nesting with combination of Arrays, Objects and Maps

const originalValue = [
  new Map([['a', {
    b: {
      c: new Map([['d', 'text']])
    }
  }]])
];
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, newValue);

参考文章:

https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map/56150320#56150320

使用 CSS clip-path

images.jpg

CSS 中的 clip-path 属性允许我们指定展示一个元素的特殊区域。注意 clip属性已经被废弃。

使用剪切路径定义基本形状

使用clip-path可以轻松使用 CSS exclusion 模块中的polygon,ellipse,circle或inset关键字剪切基本形状。

多边形

多边形是所有可用形状中最灵活的,因为它允许您指定任意数量的点,有点像SVG路径。提供的点是可以任意单位(例如:基于像素或基于百分比)的X和Y坐标对。因为它是最灵活的,所以它也是最复杂的,你可能需要使用一些工具来定义这些点。

下面是一个简单的例子, clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)

你可以使用Clippy在线生成这些坐标。

自定义SVG形状

你还可以定义任何任意的SVG形状以用作剪切路径值。可以使用诸如Sketch之类的工具创建形状,然后将SVG标记复制到文本编辑器中。在SVG标记中,只需将形状包装在clipPath元素中,然后将clipPath包装在defs块中。

<svg width="0" height="0">
  <defs>
    <clipPath id="my-shape">
      <path d="M89.6342913,129 C86.6318679,137.611315 85,146.865086 85,156.5 C85,200.767808 119.448105,236.989829 163,239.821749 L163,300 L300,300 L300,163 L251.750745,163 C251.915896,160.855015 252,158.687329 252,156.5 C252,110.384223 214.615777,73 168.5,73 C146.712501,73 126.873981,81.3445721 112.006052,95.0121046 L64.5,0 L0,129 L89.6342881,129 Z">
      </path>
    </clipPath>
  </defs>
</svg>

现在,就可以使用url关键字和SVG形状的ID将定义的形状应用为剪切路径值:

.svg-shape {
  -webkit-clip-path: url(#my-shape);
  clip-path: url(#my-shape);
}

其实 clipPath元素可以包含任何数量的基本形状(如<rect><circle>等),<path><text>

clipPathUnits 属性

clipPathUnits 属性设置SVG坐标系统,值可以是 userSpaceOnUse 或 objectBoundingBox。

  • userSpaceOnUse 表示当前用户坐标系
  • objectBoundingBox 坐标系的原点位于剪切路径所应用于的元素的边界框的左上角,并且该边界框的宽度和高度相同。我们一般使用这个坐标系统,有点类似于百分比。

一般我们从矢量绘制工具里导出的都是用户坐标系统,我们可以在 https://yoksel.github.io/relative-clip-path/ 这里将坐标转换成 objectBoundingBox。

剪切路径的参考框

除了剪切路径本身之外,当所应用的剪切路径为时,还可以在属性中定义引用框。也就是说,使用基本形状函数之一创建的剪切路径。因此,仅为用作剪切路径的CSS形状指定参考框,而不为SVG指定。对于SVG ,引用框是HTML元素的边框。clip-path<basic-shape><clipPath><clipPath>

因此,为<basic-shape>剪辑路径指定了参考框。如果被修剪的元件是一个HTML元素,所述引用盒可以是四个基本盒模型框之一:margin-box,border-box,padding-box,或content-box。这些都是不言自明的。

如果将<basic-shape>剪辑路径应用于SVG元素,则可以将参考框设置为三个关键字值之一:

  • fill-box –使用对象边界框作为参考。
  • stroke-box –使用笔划边界框作为参考。
  • view-box –如果未viewBox指定,则使用最近的SVG视口作为参考框。如果viewBox确实指定了,则坐标系统将由所指定的原点和尺寸建立viewBox。

给裁剪加阴影

box-shadows肯定是不行的,clip-path将其截断。这里我们需要使用CSS filter 的 drop-shadow 函数实现。但是您不能直接在元素上使用它,因为clip-path也会将其切断,所以将其放在父元素上。只是 drop-shadow 的 spread-radius 大部分浏览器都不支持,但也基本满足了一般需求。
filter: drop-shadow(0 0 0.75rem crimson)


参考文章

https://alligator.io/css/clipping-with-clip-path/
https://www.sarasoueidan.com/blog/css-svg-clipping/
https://css-tricks.com/using-box-shadows-and-clip-path-together/

【译】Javascript 中的独立 UUID 生成器

这是一种在 Javascript 中生成UUID的简便方法(无外部依赖项)。

// Author: Abhishek Dutta, 12 June 2020
// License: CC0 (https://creativecommons.org/choose/zero/)
function uuid() {
  var temp_url = URL.createObjectURL(new Blob());
  var uuid = temp_url.toString();
  URL.revokeObjectURL(temp_url);
  return uuid.substr(uuid.lastIndexOf('/') + 1); // remove prefix (e.g. blob:null/, blob:www.test.com/, ...)
}

URL.createObjectURL() 静态方法会创建一个唯一的URL,以表示作为参数传递给它的对象。该uuid()方法使用此属性URL.createObjectURL()来在空对象上生成唯一的URL(这是我到目前为止测试过的所有浏览器中的随机UUID)。以下是此方法生成的一些示例UUID:

for(var i=0; i<10; ++i) { console.log(uuid()); }

f6ca05c0-fad5-46fc-a237-a8e930e7cb49
6a88664e-51e1-48c3-a85e-7bf00467e9e6
e6050f4c-e86d-4081-9376-099bfbef2c30
bde3da3c-b318-4498-8a03-9a773afa84bd
ba0fda03-f806-4c2f-b6f5-1e74a299e603
62b2edc3-b09f-4bf9-8dbf-c4d599479a29
e70c0609-22ad-4493-abcc-0e3445291397
920255b2-1838-497d-bc33-56550842b378
45559c64-971c-4236-9cfc-706048b60e70
4bc4bbb9-1e90-432b-99e8-277b40af92cd

注意:URL.createObjectURL()目标不是生成随机UUID。因此,上述生成UUID的方法可能会导致我尚未意识到的副作用。

为什么可以这样做,是因为blob URL生成UUID是规范的一部分。

原文地址:

https://abhishekdutta.org/blog/standalone_uuid_generator_in_javascript.html

【译】当你调用 console.log 时发生了什么

原文地址 https://keleshev.com/standard-io-under-the-hood

以V8为例,首先调用一个通用方法WriteToFile:

void D8Console::Log(const debug::ConsoleCallArguments& args,
                    const v8::debug::ConsoleContext&) {
  WriteToFile(nullptr, stdout, isolate_, args);
}

https://github.com/v8/v8/blob/4b9b23521e/src/d8-console.cc#L52-L55

在计算出Javascript的值之后,最终会调用fwrite。

void WriteToFile(const char* prefix, FILE* file, Isolate* isolate,
                 const debug::ConsoleCallArguments& args) {
  if (prefix) fprintf(file, "%s: "⁠, prefix);
  for (int i = 0; i < args.Length(); i++) {
    HandleScope handle_scope(isolate);
    if (i > 0) fprintf(file, " ");

    Local arg = args[i];
    Local str_obj;

    if (arg->IsSymbol()) arg = Local::Cast(arg)->Name();
    if (!arg->ToString(isolate->GetCurrentContext()).ToLocal(&str_obj)) return;

    v8::String::Utf8Value str(isolate, str_obj);
      int n = static_cast<int>(fwrite(*str, sizeof(**str), str.length(), file));

    if (n != str.length()) {
      printf("Error in fwrite\n");
      base::OS::ExitProcess(1);
    }
  }
  fprintf(file, "\n");
}

https://github.com/v8/v8/blob/4b9b23521e/src/d8-console.cc#L26

函数fwrite是C标准库(也称为libc)的一部分。在不同平台上有几种libc实现。在Linux上,流行的是glibc和musl。让我们用 musl。在那里,fwrite用C实现如下:

size_t fwrite(const void *restrict src, size_t size, size_t nmemb, FILE *restrict f)
{
    size_t k, l = size*nmemb;
    if (!size) nmemb = 0;
    FLOCK(f);
    k = __fwritex(src, l, f);
    FUNLOCK(f);
    return k==l ? nmemb : k/size;
}

https://github.com/bminor/musl/blob/05ac345f89/src/stdio/fwrite.c#L28-L36

进行一些间接操作后,这将调用一个实用程序函数__stdio_write,然后将进行(操作系统)系统调用 writev。

size_t __stdio_write(FILE *f, const unsigned char *buf, size_t len)
{
    struct iovec iovs[2] = {
        { .iov_base = f->wbase, .iov_len = f->wpos-f->wbase },
        { .iov_base = (void *)buf, .iov_len = len }
    };
    struct iovec *iov = iovs;
    size_t rem = iov[0].iov_len + iov[1].iov_len;
    int iovcnt = 2;
    ssize_t cnt;
    for (;;) {
          cnt = syscall(SYS_writev, f->fd, iov, iovcnt);

        /* … */
    }
}

https://github.com/bminor/musl/blob/05ac345f89/src/stdio/__stdio_write.c#L15

syscall这里的符号是一个宏,经过一些预处理程序后,它将扩展为__syscall3。

操作系统之间的系统调用不同,并且处理器体系结构之间的执行方式也不同。它通常需要编写(或生成)一些汇编。在x86-64上,musl定义__syscall3如下:

static __inline long __syscall3(long n, long a1, long a2, long a3)
{
    unsigned long ret;
    __asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
                          "d"(a3) : "rcx"⁠, "r11"⁠, "memory");
    return ret;
}

https://github.com/bminor/musl/blob/593caa4563/arch/x86_64/syscall_arch.h#L26-L32

这将设置系统调用号和参数。在x86-64上,调用进行系统调用的指令syscall。

进行系统调用后,控制将转移到(在这种情况下为Linux)内核。这就是另一码事了...

回顾 is-promise 库迁移 ESM 事故

is-promise 只做一件事,判断 JavaScript 对象是否为 Promise,但这个包被近百万个项目所依赖。作者在 4月25日发布的新版本并未遵循正确的 ES 模块标准,从而导致更新完成后,所有在构建时使用 is-promise 库的项目几乎全部报错。

这里犯了几个个错误 https://github.com/then/is-promise/blob/8e51d62bf158eb0685cd6109f0137472e8c3cb91/package.json#L7-L10

  • 以为 exports 和 main 一样,但实际上需要 ./ 前缀。
  • exports 不仅限制你能导入什么,还得限制你怎么导入

exports 字段

我们都知道 node 模块导出有 main ,所有版本的Node.js都支持,但是能力有限:它仅定义包的主要入口点。

"exports"扩展了 main,而且二者同时存在时,优先使用 exports。

Node.js支持以下条件:

  • "import" 通过import或 加载包时匹配import()。可以同时引用ES模块或CommonJS文件, import并且import()可以加载ES模块或CommonJS源。
  • "require"通过加载包时匹配require()。由于require()仅支持CommonJS,因此引用的文件必须为CommonJS。
  • "node"适用于任何Node.js环境。可以是CommonJS或ES模块文件。这种情况应该总是在"import"或 之后出现"require"。
  • "default"是CommonJS或ES模块文件,应始终排在最后。

条件导出

一个package.json文件可以直接定义单独的CommonJS和ES模块入口点:
条件匹配的规则是从上至下,所以应按对象顺序使用从最具体到最不具体的条件。

 "exports": {
    ".": [
      {
        "import": "./index.mjs",
        "require": "./index.js",
        "default": "./index.js"
      },
      "./index.js"
    ]
  },

嵌套条件

"exports": {
    "browser": "./feature-browser.mjs",
    "node": {
      "import": "./feature-node.mjs",
      "require": "./feature-node.cjs"
    }
  }

参考文章:
https://github.com/then/is-promise/issues/13
https://medium.com/javascript-in-plain-english/is-promise-post-mortem-cab807f18dcc