diff --git a/404.html b/404.html new file mode 100644 index 0000000..f1435fd --- /dev/null +++ b/404.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +

Oops!

404 - PAGE NOT FOUND

The page you are looking for might have been removed had its name changed or is temporarily unavailable.

Return home
+ +
+ + + diff --git a/Bundler/Webpack/advance.html b/Bundler/Webpack/advance.html new file mode 100644 index 0000000..5a6da54 --- /dev/null +++ b/Bundler/Webpack/advance.html @@ -0,0 +1,453 @@ + + + + + + + 进阶用法 | LOFT + + + + +

进阶用法

自动清理构建目录

避免构建前每次都需要手动删除 dist

  • 使用 npm scripts
{
+  "scripts": {
+    "build": "rm -rf ./dist && webpack",
+    "build2": "rimraf ./dist && webpack",
+  },
+}
+
  • 使用 CleanWebpackPlugin 插件。默认 删除 output 指定的输出目录
module.exports = {
+  entry: './src/index.js',
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: 'bundle.js',
+  },
+  plugins: [
+    new CleanWebpackPlugin()
+  ]
+};
+

PostCSS 插件 autoprefixer 自动补齐 CSS3 前缀

CSS3 属性为什么需要前缀?

  • 各个浏览器实现有差别,需要进行区分
    • Trident(-ms)
    • Geko(-moz)
    • Webkit(-webkit)
    • Presto(-o)

根据 Can I Useopen in new window 规则

module.exports = {
+  module: {
+    rules: [
+      {
+        test: /\.(le|c)ss$/,
+        use: [
+          'style-loader',
+          'css-loader',
+          'less-loader',
+          {
+            loader: 'postcss-loader',
+            options: {
+              plugins: () => [
+                require('autoprefixer')({
+                  browsers: ['last 2 version', '>1%', 'ios 7']
+                })
+              ]
+            }
+          }
+        ]
+      },
+    ]
+  }
+};
+

移动端 CSS px 自动转换 rem

移动设备浏览器分辨率不同。 早期使用 CSS 媒体查询实现响应式布局,缺点是需要写多套适配样式代码。

rem 是什么?

  • W3C 对 rem 的定义为 font-size of the root element(根元素的字体大小)

    • rem 是相对单位
    • px 是绝对单位

px2rem-loader 和 页面渲染时根元素的 font-size 值大小(可以使用lib-flexibleopen in new window)结合使用

module.exports = {
+  module: {
+    rules: [
+      {
+        test: /\.(le|c)ss$/,
+        use: [
+          'style-loader',
+          'css-loader',
+          'less-loader',
+          {
+            loader: 'postcss-loader',
+            options: {
+              plugins: () => [
+                require('autoprefixer')({
+                  browsers: ['last 2 version', '>1%', 'ios 7']
+                })
+              ]
+            }
+          },
+          {
+            loader: 'px2rem-loader',
+            options: {
+              remUnit: 16,
+              remPrecesion: 8,
+            }
+          }
+        ]
+      },
+    ]
+  }
+};
+
<!DOCTYPE html>
+<html lang="zh">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>search</title>
+    <script type="text/javascript">
+      // lib-flexible 库代码
+    </script>
+  </head>
+  <body>
+    <div id="root"></div>
+  </body>
+</html>
+
+

静态资源内联

  • 代码
    • 页面框架的初始化脚本
    • 上报相关打点
    • CSS 内联避免页面闪动
  • 请求
    • 减少 HTTP 网络请求数
    • 小图片或字体内联(url-loader)

意义

  • 工程维护 常见场景:
    1. 对于多页面应用(MPA)中的许多重复内容如 meta 信息等,借助 html-webpack-plugin 将 meta 信息内联进去;
    2. 小图片、字体等文件转换为 base64 并内联源代码中。
  • 页面加载性能 减少 HTTP 网络请求数
  • 页面加载体验 我们都知道浏览器解析 HTML 源码是从上到下解析,因此我们会把 CSS 放到头部,JS 放置到底部。以 SSR 场景为例,如果不将打包出来的 CSS 内联进 HTML 里面,HTML 出来的时候页面的结构已经有了,但是还需要发送一次请求去请求 css,这个时候就会出现页面闪烁,网络情况差的时候更加明显。

HTML / JS 内联引入

  • 使用 ejs 语法结合 raw-loader 实现内联

    <script>${require('raw-loader!babel-loader!./meta.html')}</script>
    +<script>${require('raw-loader!babel-loader!../node_modules/lib-flexible')}</script>
    +
  • 实现自定义 loader

    
    +const fs = require('fs');
    +const path = require('path');
    +
    +const getContent = (matched, reg, resourcePath) => {
    +    const result = matched.match(reg);
    +    const relativePath = result && result[1];
    +    const absolutePath = path.join(path.dirname(resourcePath), relativePath);
    +    return fs.readFileSync(absolutePath, 'utf-8');
    +};
    +
    +module.exports = function(content) {
    +  const htmlReg = /<link.*?href=".*?\__inline">/gmi;
    +  const jsReg = /<script.*?src=".*?\?__inline".*?>.*?<\/script>/gmi;
    +
    +  content = content.replace(jsReg, (matched) => {
    +    const jsContent = getContent(matched, /src="(.*)\?__inline/, this.resourcePath);
    +    return `<script type="text/javascript">${jsContent}</script>`;
    +  }).replace(htmlReg, (matched) => {
    +    const htmlContent = getContent(matched, /href="(.*)\?__inline/, this.resourcePath);
    +    return htmlContent;
    +  });
    +
    +  return `module.exports = ${JSON.stringify(content)}`;
    +}
    +
    +

    模板文件中使用

    <link href="./meta.html?__inline" />
    +<script type="text/javascript" src="../node_modules//lib-flexible/flexible.js?__inline"></script>
    +

CSS 内联引入

核心思路: 将页面打包过程的产生的所有 CSS 提取成一个独立的文件,然后将这个 CSS 文件内联进 HTML head 标签内,需要使用 mini-css-extract-plugin 和 html-inline-css-webpack-plugin 实现。

  • style-loader

    module.exports = {
    +  module: {
    +    rules: [
    +      {
    +        test: /\.(le|c)ss$/,
    +        use: [
    +          {
    +            loader: 'style-loader',
    +            options: {
    +              insertAt: 'top', // 样式插入到<head>中
    +              singleton: true, // 将所有的style标签合并成一个
    +            }
    +          },
    +          'css-loader',
    +          'less-loader',
    +          {
    +            loader: 'postcss-loader',
    +            options: {
    +              plugins: () => [
    +                require('autoprefixer')({
    +                  browsers: ['last 2 version', '>1%', 'ios 7']
    +                })
    +              ]
    +            }
    +          },
    +          {
    +            loader: 'px2rem-loader',
    +            options: {
    +              remUnit: 16,
    +              remPrecesion: 8,
    +            }
    +          }
    +        ]
    +      },
    +    ]
    +  }
    +};
    +
  • html-inline-css-webpack-plugin 需要放在 html-webpack-plugin 后面

      module.exports = {
    +    plugins: [
    +      new MiniCssExtractPlugin({
    +        filename: '[name]__[contenthash:8].css',
    +      }),
    +      new HtmlWebpackPlugin(),
    +      new HTMLInlineCSSWebpackPlugin()
    +    ]
    +  };
    +

图片、字体内联

使用 url-loader

  module.exports = {
+    module: {
+      rules: [
+        {
+          test: /.(png|jpg|gif|jpeg)$/,
+          use: [
+            {
+              loader: 'url-loader',
+              options: {
+                name: '[name]_[hash:8].[ext]',
+                limit: 10240
+              }
+            }
+          ]
+        },
+        {
+          test: /.(woff|woff2|eot|ttf|otf)$/,
+          use: [
+            {
+              loader: 'url-loader',
+              options: {
+                name: '[name]_[hash:8][ext]',
+                limit: 10240
+              }
+            }
+          ]
+        }
+      ]
+    }
+  };
+

缺点:不能个性化设置某张图片自动编码

实现自定义 loader ,对使用?__inline语法进行自动编码。 核心代码如下:

export default function loader(content) {
+  const options = loaderUtils.getOptions(this) || {};
+
+  validateOptions(schema, options, {
+    name: 'File Loader',
+    baseDataPath: 'options',
+  });
+
+  const hasInlineFlag = /\?__inline$/.test(this.resource);
+
+  if (hasInlineFlag) {
+    const file = this.resourcePath;
+    // Get MIME type
+    const mimetype = options.mimetype || mime.getType(file);
+
+    if (typeof content === 'string') {
+      content = Buffer.from(content);
+    }
+
+    return `module.exports = ${JSON.stringify(
+      `data:${mimetype || ''};base64,${content.toString('base64')}`
+    )}`;
+  }
+}
+

source map

通过 source map 定位到源代码

开发环境开启,线上环境关闭

线上排查问题时可将 source map 上传到错误监控系统,用于排查问题。

类型描述
eval使用 eval 包裹模块代码
source map生成.map 文件
cheap不包含列信息
inline将.map 作为 DataURI 嵌入,不单独生成.map 文件
module包含 loader 的 source map
devtool首次构建二次构建是否适合生产环境定位的代码
(none)++++++yes最终输出的代码
eval++++++nowebpack 生成的代码(单独模块)
cheap-eval-source-map+++no经过 loader 转换后的代码(只能看到行)
cheap-module-eval-source-mapO++no源代码(只能看到行)
eval-source-map--+no源代码
cheap-source-map+Oyes经过 loader 转化后的代码(只能看到行)
cheap-module-source-mapO-yes源代码(只能看到行)
inline-cheap-source-map+Ono经过 loader 转换后的代码(只能看到行)
inline-cheap-module-source-mapO-no源代码(只能看到行)
source-map----yes源代码
inline-source-map----no源代码
hidden-source-map----yes源代码

提取页面公共资源

基础库分离。

  • 使用 html-webpack-externals-plugin 例如:将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中。

    const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
    +
    +module.exports = {
    +  plugins: [
    +    new HtmlWebpackExternalsPlugin({
    +      externals: [
    +        {
    +          module: 'react',
    +          entry: '//11.url.cn/now/lib/15.1.0/react-with-addons.min.js?_bid=3123',
    +          global: 'React'
    +        },
    +        {
    +          module: 'react-dom',
    +          entry: '//11.url.cn/now/lib/15.1.0/react-dom.min.js?_bid=3123',
    +          global: 'ReactDOM'
    +        }
    +      ]
    +    })
    +  ]
    +}
    +
  • 利用 SplitChunksPlugin 进行公共脚本分离 Webpack 4 内置,替代 CommonsChunkPlugin 插件

    • async 异步引入的库进行分离(默认)
    • initial 同步引入的库进行分离
    • all 所有引入的库进行分离(推荐)
    module.exports = {
    + optimization: {
    +  splitChunks: {
    +    chunks: 'async',
    +    minSize: 30000,
    +    maxSize: 0,
    +    minChunks: 1,
    +    maxAsyncRequests: 5,
    +    maxInitialRequests: 3,
    +    automaticNameDelimiter: '~',
    +    name: true,
    +    cacheGroups: {
    +      vendors: {
    +        test: /[\\/]node_modules[\\/]/,
    +        priority: -10
    +      }
    +    }
    +  }
    + }
    +}
    +
    • 分离基础包
    module.exports = {
    +  optimization: {
    +    splitChunks: {
    +      cacheGroups: {
    +        commons: {
    +          test: /(react|react-dom)/,
    +          name: 'vendors',
    +          chunks: 'all',
    +        }
    +      }
    +    }
    +  }
    +}
    +
    • 提取页面公共文件

    minChunks:设置最小引用次数 minSize:分离包体积的大小

    module.exports = {
    +  optimization: {
    +    splitChunks: {
    +      minSize: 0,
    +      cacheGroups: {
    +        commons: {
    +          name: 'commons',
    +          chunks: 'all',
    +          minChunks: 2
    +        }
    +      }
    +    }
    +  }
    +}
    +

优化构建时命令行的显示日志

统计信息 stats

预设值可选项描述
errors-onlynone只在发生错误时输出
minimalnone只在发生错误或有新的编译时输出
nonefalse没有输出
normaltrue标准输出
verbosenone全部输出

插件优化 friendly-errors-webpack-plugin

  • success
  • warning
  • error
module.exports = {
+  plugins: [
+    new FriendlyErrorsWebpackPlugin(),
+  ],
+  stats: 'errors-only'
+}
+

构建异常和中断处理

  • 如何判断构建是否成功? 在 CI/CD 的 pipeline 或者发布系统需要知道当前构建状态

    每次构建完成后输入 echo $? 获取错误码

    webpack4 之前的版本构建失败不会抛出错误码(error code)

    Node.js 中的 process.exit 规范

    • 0 表示成功完成,回调函数中,err 为 null
    • 非 0 表示执行失败,回调函数中,err 不为 null,err.code 为传给 exit 的数字
  • 如何主动捕获并处理构建错误?

    compiler 在每次构建结束后触发 done hook

    module.exports = {
    +  plugins: [
    +    // webpack 4
    +    function() {
    +      this.hooks.done.tap('done', stats => {
    +        const { errors } = stats.compilation;
    +        if (errors && errors.length
    +        && process.argv.indexOf('--watch') === -1) {
    +          console.log('Build error.');
    +          process.exit(1);
    +        }
    +      });
    +    }
    +  ],
    +}
    +

    process.exit 主动处理构建报错

Tree Shaking 原理分析及使用

DCE(Dead Code Elimination)

死码消除(Dead code elimination)是一种编译最优化技术,它的用途是移除对程序运行结果没有任何影响的代码。

  • 可以减少程序的大小;
  • 可以避免程序在运行中进行不相关的运算行为,减少它运行的时间。

不会被运行到的代码(unreachable code)以及只会影响到无关程序运行结果的变量(Dead Variables),都是死码(Dead code)的范畴。

  • 代码不会被执行,不可到达
  • 代码执行的结果不会被用到
  • 代码只会影响死变量(只写不读)

Tree Shaking 原理

一个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打包到 bundle 中去,tree shaking 就是只把用到的方法打包到 bundle 中,没有使用到的方法会在 uglify 阶段被擦除掉。

webpack 默认支持,produciton 模式下默认开启,在 .babelrc 中设置 modules: false 即可

必须是 ES6 语法,不支持 CJS

利用 ES6 模块的特点

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串变量
  • import binding 是 immutable 的

Scope Hoisting 原理分析和使用

现象:构建后的代码存在大量的闭包代码

  • 大量函数闭包包裹代码,导致代码体积增大(模块越多越明显)
  • 运行代码时创建的函数作用域变多,内存开销变大

模块转化分析

  • 被 webpack 转换后的模块会带上一层包裹
  • import 会被转换成 __webpack_require__
// 模块代码
+import { helloworld } from './helloworld';
+import '../../common';
+
+document.write(helloworld());
+
+// 模块初始化函数
+/* 0 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _common_WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var _helloworld_WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
+
+document.write(Object(_helloworld__WEBPACK_IMPORTED_MODULE_1__["helloworld"])());
+/***/ })
+

webpack 模块机制

  • 打包出来的是一个 IIFE (匿名闭包)
  • modules 是一个数组,每一项是一个模块初始化函数
  • __webpack_require__ 用来加载模块,返回 module.exports
  • 通过 WEBPACK_REQUIRE_METHOD(0) 启动程序
(funciton(modules) {
+  var installedModules = {};
+
+  function __webpack_require__(moduleId) {
+    if (installedModules[moduleId])
+      return installedModules[moduleId].exports;
+    var module = installedModules[moduleId] = {
+      i: moduleId,
+      l: false,
+      exports: {}
+    };
+    modules[moduleId].call(module.exports, module, module.exports, __webpack_requrie__);
+    module.l = true;
+    return module.exports;
+  }
+  __webpack_require__(0);
+})([
+  /* 0 module */
+  (function (module, __webpack_exports__, __webpack_require__) {
+    // ...
+  }),
+  /* 1 module */
+  (function (module, __webpack_exports__, __webpack_require__) {
+    // ...
+  }),
+  /* ... */
+  /* n module */
+  (function (module, __webpack_exports__, __webpack_require__) {
+    // ...
+  }),
+]);
+

Scope Hoisting 原理分析

原理:将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突

对比:通过 scope hoisting 可以减少函数声明代码和内存开销

webpack 默认支持,produciton 模式下默认开启

必须是 ES6 语法,不支持 CJS

module.exports = {
+  plugins: [
+    new webpack.optimize.ModuleConcatenationPlugin()
+  ]
+};
+

代码分割

对于大的 Web 应用来说,将所有代码都放在一个文件中显然是不够有效的,特别当你的某些代码是在某些特殊时候才会被使用到,webpack 可以将你的代码库分割成 chunks(代码块),当代码运行到需要它们的时候才会进行加载。

适用于

  • 抽离相同代码到一个共享块
  • 脚本懒加载,使得初始下载的代码更小
    • CommonJS: require.ensure
    • ES6: 动态 import (可能需要 babel 转换)
      • @babel/plugin-syntax-dynamic-import

ESLint 必要性

提前暴露一些潜在问题

Airbnb: eslint-config-airbnb / eslint-config-airbnb-base 等

制定团队的 ESLint 规范

  • 不重复造轮子,基于 eslint:recommend 配置并改进
  • 能够帮助发现代码错误的规则,全部开区
  • 帮助保持团队的代码风格统一,而不是限制开发体验

ESLint Rulesopen in new window

Vue Rulesopen in new window

TypeScript Rulesopen in new window

ESLint 如何落地?

  • 与 CI/CD 系统集成

    CI/CD

    本地开发阶段阶段增加 precommit 钩子

    安装 husky npm install --save-dev husky

    增加 npm script 通过 lint-staged 增量检查修改的文件

    // package.json
    +{
    +  "scripts": {
    +    "precommit": "lint-staged"
    +  },
    +  "lint-staged": {
    +    "linters": {
    +      "*.{js,scss}": ["eslint --fix", "git add"]
    +    }
    +  }
    +}
    +
  • 和 webpack 集成

    使用 eslint-loader,构建时检查 JS 规范

    module.exports = {
    +  module: {
    +    rules: [
    +      {
    +        test: /\.js$/,
    +        exclude: /node_modules/,
    +        use: [
    +          "babel-loader",
    +          "eslint-loader"
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

webpack 打包库和组件

  • 打包压缩版和非压缩版本

  • 支持 AMD / CJS / ESM 模块引入,script 直接引入

    // AMD
    +require(['large-number'], function(largeNumebr) {
    +  largeNumber.add('999', 1);
    +});
    +// CJS
    +const largeNumber = require('large-number');
    +largeNumber.add('999', 1);
    +// ESM
    +import * as largeNumber from 'large-number';
    +largeNumber.add('999', 1);
    +// HTML script
    +<script src="path/to/large-number.js"></script>
    +

打包输出库的名称

  • 未压缩版 large-number.js
  • 压缩版 large-number.min.js
|-/dist
+  |-large-number.js
+  |-large-number.min.js
+|-/src
+  |-index.js
+|-index.js
+|-webpack.config.js
+|-package.json
+

暴露库

配置示例

module.exports = {
+  mode: 'none',
+  entry: {
+    'large-number': './src/index.js',
+    'large-number.min': './src/index.js',
+  },
+  output: {
+    filename: '[name].js',
+    libraryExport: 'default',
+    library: 'largeNumber', // 指定库的全局变量
+    libraryTarget: 'umd', // 支持库引入的方式
+  },
+  optimization: {
+    minimize: true,
+    minimizer: [
+      new TerserPlugin({
+        include: /\.min\.js$/,
+      })
+    ]
+  }
+};
+

设置入口文件

// package.json
+{
+  "version": "0.0.1",
+  "description": "description",
+  "main": "index.js",
+}
+
+// index.js
+if (process.env.NODE_ENV === 'production') {
+  module.exports = require('./dist/large-number.min.js');
+} else {
+  module.exports = require('./dist/large-number.js');
+}
+
+ +
+ + + diff --git a/Bundler/Webpack/compress.html b/Bundler/Webpack/compress.html new file mode 100644 index 0000000..c77501b --- /dev/null +++ b/Bundler/Webpack/compress.html @@ -0,0 +1,53 @@ + + + + + + + Webpack 代码压缩 | LOFT + + + + +

Webpack 代码压缩

HTML、CSS、JS 压缩,页面代码资源压缩后,文件所占字节数更少,访问速度更快。

JS 文件压缩

内置 UglifyjsWebpackPlugin 插件

CSS 文件压缩

使用 OptimizeCssAssetsWebpackPlugin 插件 配合 cssnano

module.exports = {
+  // ...
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: '[name]_[chunkhash:8].js',
+  },
+  plugins: [
+    new OptimizeCssAssetsWebpackPlugin({
+      assetNameRegExp: /\.css$/g,
+      cssProcessor: require('cssnano')
+    })
+  ],
+};
+

HTML 文件压缩

使用 HtmlWebpackPlugin 插件,设置压缩参数。

module.exports = {
+  // ...
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: '[name]_[chunkhash:8].js',
+  },
+  plugins: [
+    new HtmlWebpackPlugin({
+      template: path.join(__dirname, 'src/search.html'),
+      filename: 'search.html',
+      chunks: ['search'],
+      inject: true,
+      minify: {
+        html5: true,
+        collapseWhitespace: true,
+        preserveLineBreaks: false,
+        minifyCSS: true,
+        minifyJS: true,
+        removeComments: false,
+      }
+    })
+  ],
+};
+
+ +
+ + + diff --git a/Bundler/Webpack/concept.html b/Bundler/Webpack/concept.html new file mode 100644 index 0000000..a894ae7 --- /dev/null +++ b/Bundler/Webpack/concept.html @@ -0,0 +1,80 @@ + + + + + + + Webpack 核心概念 | LOFT + + + + +

Webpack 核心概念

Entry

webpack 打包的入口。

webpack 是一个模块打包器,webpack 会把一切的资源(代码资源(js、css 等)与非代码资源(图片、字体等))统一视为模块,所有模块之间有依赖关系。webpack 根据入口文件遍历整个项目找出依赖关系树。根据依赖关系进行打包。

Webpack 原理图

// 单入口
+module.exports = {
+  entry: './src/index.js',
+};
+// 多入口
+module.exports = {
+  entry: {
+    app: './src/app.js',
+    admin: './src/admin.js'
+  }
+};
+

Output

webpack 打包的输出。

指定 webpack 如何将编译后的文件输出到磁盘。

// 单入口
+module.exports = {
+  entry: './src/index.js',
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: 'bundle.js',
+  }
+};
+// 多入口
+module.exports = {
+  entry: {
+    app: './src/app.js',
+    admin: './src/admin.js'
+  },
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: '[name].js', // ----------- 通过占位符确保文件名称的唯一
+  }
+};
+

Webpack output 占位符open in new window

Loaders

webpack 开箱即用,但只支持 JS 和 JSON 两种文件类型,通过 Loaders 可以支持其他文件类型并将它们转换为有效的模块,从而可以添加到依赖图中。

Loaders 本身是一个函数,接收源文件作为参数,返回转换的结果。

常用的 Loaders 如下:

名称描述
文件
val-loader将代码作为模块执行,并将其导出为 JS 代码
ref-loader用于手动建立文件之间的依赖关系
file-loader进行图片、字体等的打包
raw-loader将文件以字符串的形式导入
JSON
cson-loader加载并转换 CSONopen in new window 文件
语法转化
babel-loader使用 Babelopen in new window 加载 ES2015+代码并将其转换为 ES5
esbuild-loader加载 ES2015+代码并使用 esbuildopen in new window 转移到 ES6+
buble-loader使用 Bubléopen in new window 加载 ES2015+ 代码并将其转换为 ES5
traceur-loader使用 Traceuropen in new window 加载 ES2015+代码并将其转换为 ES5
ts-loader像加载 JavaScript 一样加载 Typescriptopen in new window 2.0+
coffee-loader像加载 JavaScript 一样加载 CoffeeScriptopen in new window
fengari-loader使用 fengariopen in new window 加载 Lua 代码
elm-webpack-loader像加载 JavaScript 一样记载 Elmopen in new window
thread-loader多进程打包 JS 和 CSS
模板
html-loader将 HTML 导出为字符串,需要传入静态资源的引用路径
pug-loader加载 Pug 和 Jade 模板并返回一个函数
markdown-loader将 Markdown 编译为 HTML
react-markdown-loader使用 markdown-parse 解析器将 Markdown 编译为 React 组件
posthtml-loader使用 PostHTMLopen in new window 加载并转换 HTML 文件
handlebars-loader将 Handlebars 文件编译为 HTML
markup-inline-loader将 SVG、MathML 文件内嵌到 HTML 中。在将图标字体或 CSS 动画应用于 SVG 时,此功能非常实用。
twig-loader编译 Twig 模板并返回一个函数
remark-laoder通过 remark 加载 markdown,且支持解析内容中的图片
样式
style-loader将模块导出的内容作为样式并添加到 DOM 中
css-loader加载 CSS 文件并解析 import 的 CSS 文件,最终返回 CSS 代码
less-loader加载并编译 LESS 文件
sass-loader加载并编译 SASS、SCSS 文件
postcss-loader使用 PostCSSopen in new window 加载并转换 CSS、SSS 文件
stylus-loader加载并编译 Stylus 文件
框架
vue-loader加载并编译 Vueopen in new window 组件
angular2-template-loader加载并编译 Angularopen in new window 组件
更多第三方 loaderopen in new window
module.exports = {
+  entry: './src/index.js',
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: 'bundle.js',
+  },
+  module: {
+    rules: [
+      { test: /\.txt$/, use: 'raw-loader' },
+      // test 指定匹配规则,use 指定使用的 loader 名称
+      { test: /\.css$/, use: ['style-loader', 'css-loader'] }
+      // loader 调用为链式调用,执行顺序是从右往左加载
+    ]
+  }
+};
+

Plugins

用于增强 webpack,bundle 文件的优化,资源管理和环境变量的注。

作用于整个构建过程

名称描述
BannerPluginopen in new window在每个生成的 chunk 顶部添加横幅
CommonsChunkPluginopen in new window将 chunks 相同的模块代码提取出公共 JS
CleanWebpackPluginopen in new window清理构建目录
CompressionWebpackPluginopen in new window通过压缩算法,将前端打包好的资源文件进一步压缩,生成指定的、体积更小的压缩文件,让浏览器能够更快的加载资源
ContextReplacementPluginopen in new windowOverride the inferred context of a require expression
CopyWebpackPluginopen in new window将文件或者文件夹拷贝到构建的输出目录
DefinePluginopen in new window在编译时期创建全局变量
DllPluginopen in new window主要用于将第三方依赖库单独打包,以避免每次构建时都重新编译这些依赖库,从而加快构建速度
EnvironmentPluginopen in new windowShorthand for using the DefinePlugin on process.env keys
EslintWebpackPluginopen in new windowA ESLint plugin for webpack
ExtractTextWebpackPluginopen in new window将 CSS 从 bundle 文件里提取成一个独立的 CSS 文件
HotModuleReplacementPluginopen in new windowEnable Hot Module Replacement (HMR)
HtmlWebpackPluginopen in new window轻松创建 HTML 文件去承载输出的 bundle
IgnorePluginopen in new windowExclude certain modules from bundles
LimitChunkCountPluginopen in new windowSet min/max limits for chunking to better control chunking
MinChunkSizePluginopen in new windowKeep chunk size above the specified limit
MiniCssExtractPluginopen in new windowcreates a CSS file per JS file which requires CSS
NoEmitOnErrorsPluginopen in new windowSkip the emitting phase when there are compilation errors
NormalModuleReplacementPluginopen in new windowReplace resource(s) that matches a regexp
NpmInstallWebpackPluginopen in new windowAuto-install missing dependencies during development
ProgressPluginopen in new windowReport compilation progress
ProvidePluginopen in new windowUse modules without having to use import/require
SourceMapDevToolPluginopen in new windowEnables a more fine grained control of source maps
EvalSourceMapDevToolPluginopen in new windowEnables a more fine grained control of eval source maps
TerserPluginopen in new windowUses Terser to minify the JS in your project
UglifyjsWebpackPluginopen in new window压缩 JS
ZipWebpackPluginopen in new window将打包出的资源生成一个 zip 包
module.exports = {
+  entry: './src/index.js',
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: 'bundle.js',
+  },
+  module: {
+    rules: [
+      { test: /\.txt$/, use: 'raw-loader' },
+      // test 指定匹配规则,use 指定使用的 loader 名称
+    ]
+  },
+  plugins: [ // -------------- plugins
+    new HtmlWebpackPlugin({
+      template: './src/index.html'
+    }),
+  ]
+};
+

Mode

指定 webpkac 当前的构建环境: production \ development \ none,默认值为 production。可以使用 webpack 内置函数设置 mode。

提示

如果 mode 未通过配置或 CLI 赋值,CLI 将使用可能有效的 NODE_ENV 值作为 mode。

选项描述
development会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。
none不使用任何默认优化选项
+ +
+ + + diff --git a/Bundler/Webpack/fingerprint.html b/Bundler/Webpack/fingerprint.html new file mode 100644 index 0000000..0c0e145 --- /dev/null +++ b/Bundler/Webpack/fingerprint.html @@ -0,0 +1,45 @@ + + + + + + + Webpack 文件指纹 | LOFT + + + + +

Webpack 文件指纹

打包后输出的文件名的后缀。

  • 通常用来做版本的管理
  • 浏览器缓存不变的文件,加速网站访问速度。
<script src="path/to/js/index_df23fe?d=234212312312"></script>
+

文件指纹的生成

描述
Hash和整个项目的构建有关,只要项目文件有修改,整个项目构建的 hash 值就会更改
Chunkhash和 webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值
Contenthash根据文件内容来定义 hash,文件内容不变,则 contenthash 不变

占位符

占位符名称描述
[ext]资源后缀名
[name]文件名称
[path]文件的相对路径
[folder]文件所在的文件夹
[contenthash]文件的内容 hash,默认为 md5 生成
[hash]文件内容的 hash,默认为 md5 生成
[emoji]一个随机的指代文件内容的 emoji

文件指纹的设置

module.exports = {
+  // ...
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: '[name]_[chunkhash:8].js',
+  },
+  plugins: [
+    new MiniCssExtractPlugin({
+      filename: '[name]_[contenthash:8].css',
+    })
+  ],
+  module: {
+    rules: [
+      {
+        test: /\.(png|svg|jpg|gif)$/,
+        use: [
+          {
+            loader: 'file-loader',
+            options: {
+              name: 'img/[name][hash:8].[ext]',
+            }
+          }
+        ]
+      }
+    ]
+  }
+};
+
+ +
+ + + diff --git a/Bundler/Webpack/index.html b/Bundler/Webpack/index.html new file mode 100644 index 0000000..011cfe0 --- /dev/null +++ b/Bundler/Webpack/index.html @@ -0,0 +1,113 @@ + + + + + + + Webpack | LOFT + + + + +

Webpack

默认配置文件名为 webpack.config.js;

亦可通过 webpack --config [path/to/filename] 指定配置文件。

Webpack 环境搭建

安装 Node.js 和 NPM

提示

安装 nvmopen in new window 允许你通过命令行快速安装和使用不同版本的 node。

nvm use 16
+Now using node v16.9.1 (npm v7.21.1)
+node -v
+v16.9.1
+nvm use 14
+Now using node v14.18.0 (npm v6.14.15)
+node -v
+v14.18.0
+nvm install 12
+Now using node v12.22.6 (npm v6.14.5)
+node -v
+v12.22.6
+

使用下面的 cURL 或 Wget 命令进行安装:


+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
+wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
+

安装 Webpack 和 Webpack-cli


# 创建空文件夹Webpack,并在当前目录下执行以下命令初始化 package.json
+npm init -y
+
+# 安装 webpack 和 webpack-cli
+npm install webpack webpack-cli --save-dev
+
+# 执行以下命令检查是否安装成功
+./node_modules/.bin/webpack -v
+

Webpack 配置组成


module.exports = {
+  entry: './src/index.js',// ---------------- 打包的入口文件,Webpack4+默认值
+  output: './dist/main.js',// --------------- 打包的输出,Webpack4+默认值
+  mode: 'development',// -------------------- 环境
+  module: {
+    rules: [// ------------------------------ Loader 配置
+      { test: /\.txt$/, use: 'raw-loader' }
+    ]
+  },
+  plugins: [// ------------------------------ 插件 配置
+    new HtmlWebpackPlugin({
+      template: './src/index.html'
+    })
+  ]
+};
+

示例

在创建好的 Webpack 根目录下创建 webpack.config.js 文件:

// ./Webpack/webpack.config.js
+const path = require('path');
+
+module.exports = {
+  entry: './src/index.js',
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: 'bundle.js',
+  },
+  mode: 'production',
+};
+

在创建好的 Webpack 根目录下创建 src 目录,并在 src 目录中创建 test.jsindex.js

// ./Webpack/src/test.js
+export function printHello() {
+  return 'Hello world!';
+}
+
+export function add(a, b) {
+  return a + b;
+}
+// ./Webpack/src/index.js
+import { printHello } from './test';
+
+document.write(printHello());
+

在终端中运行命令行进行打包:

./node_modules/.bin/webpack
+

在进行模块局部安装时会在 node_modules/.bin 目录下创建软连接,而 package.json 默认可以读取到该目录下的命令,因此可以通过 npm script 运行 webpack

{
+  "name": "webpack",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "dev": "webpack-dev-server --config webpack.dev.js --open",
+    "build": "webpack --config webpack.prod.js",
+    "watch": "webpack --watch",
+
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "webpack": "^4.31.0",
+    "webpack-cli": "^3.3.2"
+  }
+}
+

接着在终端中运行:

npm run build
+
+> webpack@1.0.0 build
+> webpack
+
+Hash: dc04ce51eb3dc2716844
+Version: webpack 4.31.0
+Time: 75ms
+Built at: 2021/03/10 12:40:11
+     Asset       Size  Chunks             Chunk Names
+bundler.js  982 bytes       0  [emitted]  main
+Entrypoint main = bundler.js
+[0] ./src/index.js + 1 modules 171 bytes {0} [built]
+    | ./src/index.js 67 bytes [built]
+    | ./src/test.js 104 bytes [built]
+

可看到 根目录下生成 dist/bundler.js 打包文件。

+ +
+ + + diff --git a/Bundler/Webpack/miniWebpack.html b/Bundler/Webpack/miniWebpack.html new file mode 100644 index 0000000..5072ab3 --- /dev/null +++ b/Bundler/Webpack/miniWebpack.html @@ -0,0 +1,17 @@ + + + + + + + 实现 Webpack | LOFT + + + + +

实现 Webpack

+ +
+ + + diff --git a/Bundler/Webpack/mpa.html b/Bundler/Webpack/mpa.html new file mode 100644 index 0000000..49b53ad --- /dev/null +++ b/Bundler/Webpack/mpa.html @@ -0,0 +1,66 @@ + + + + + + + 多页面应用 | LOFT + + + + +

多页面应用(MPA)

每次页面跳转的时候,后台服务器都会返回一个新的 html 文档,这种类型的网站就是多页网站,也叫做多页面应用。

  • 优点
    • 页面直接解耦
    • 对 SEO 友好
  • 缺点
    • 每次新增或删除页面需要修改 webpack 配置

打包通用方案

动态获取 entry 和设置 html-webpack-plugin 数量

利用 glob.sync

entry: glob.sync(path.join(__dirname, './src/*/index.js'))

const setMPA = () => {
+  const entry = {};
+  const htmlWebpackPlugins = [];
+
+  const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));
+
+  entryFiles.forEach(entryFile => {
+    const matched = entryFile.match(/Webpack\/src\/(.*)\/index\.js$/);
+    const pageName = matched && matched[1];
+    entry[pageName] = entryFile;
+    htmlWebpackPlugins.push(new HtmlWebpackPlugin({
+      template: path.join(__dirname, `src/${pageName}/index.html`),
+      filename: `${pageName}.html`,
+      chunks: [pageName],
+      inject: true,
+      minify: {
+        html5: true,
+        collapseWhitespace: true,
+        preserveLineBreaks: false,
+        minifyCSS: true,
+        minifyJS: true,
+        removeComments: false,
+      }
+    }));
+  });
+  return {
+    entry,
+    htmlWebpackPlugins,
+  };
+}
+
+const { entry, htmlWebpackPlugins } = setMPA();
+
+module.exports = {
+  entry,
+  // ...,
+  plugins: [
+    new MiniCssExtractPlugin({
+      filename: '[name]_[contenthash:8].css',
+    }),
+    new OptimizeCssAssetsWebpackPlugin({
+      assetNameRegExp: /\.css$/g,
+      cssProcessor: require('cssnano')
+    }),
+    new CleanWebpackPlugin(),
+    ...htmlWebpackPlugins,
+    new HTMLInlineCssWebpackPlugin()
+  ]
+}
+
+ +
+ + + diff --git a/Bundler/Webpack/package.html b/Bundler/Webpack/package.html new file mode 100644 index 0000000..42f79e2 --- /dev/null +++ b/Bundler/Webpack/package.html @@ -0,0 +1,91 @@ + + + + + + + 编写可维护的Webpack构建配置 | LOFT + + + + +

编写可维护的 Webpack 构建配置

意义

  • 通用性
    • 业务开发者无需关注构建配置
    • 统一团队构架脚本
  • 可维护性
    • 构建配置合理的拆分
    • README 文档、ChangeLog 文档等
  • 质量
    • 冒烟测试、单元测试、测试覆盖率
    • 持续集成

可选方案

团队规模大选择方案 3,团队规模小选择方案 1,2

  1. 通过多个配置文件管理不同环境的构建,webpack --config 参数进行控制
  2. 将构建配置设计成一个库,如:hjs-webpackopen in new windowNeutrinoopen in new windowwebpack-blocksopen in new window
  3. 抽成一个工具进行管理,如:
  1. 将所有的配置放在一个文件中,通过 --env 参数控制分支选择

构建配置包设计

通过多个配置文件管理不同环境的 webpack 配置

  • 基础配置 webpack.base.js
  • 开发环境 webpack.dev.js
  • 生产环境 webpack.prod.js
  • SSR 环境 webpack.ssr.js
  • PWA 环境 webpack.pwa.js
  • ...

配置拆分后组合

通过 webpack-mergeopen in new window 组合配置

const merge = require("webpack-merge");
+
+merge(
+  { a: [1], b: 5, c: 20 },
+  { a: [2], b: 10, d: 421 }
+);
+
+// { a: [ 1, 2 ], b: 10, c: 20, d: 421 }
+

合并配置 module.exports = merge(baseConfig, devConfig);

抽离成一个 npm 包统一管理

  • 规范: Git commit 日志、README、ESLint 规范、Semver 规范
  • 质量:冒烟测试、单元测试、测试覆盖率、CI

构建包功能设计

构建包功能设计

目录结构设计


|-/test
+|-/lib
+  |-webpack.base.js
+  |-webpack.dev.js
+  |-webpack.prod.js
+  |-webpack.ssr.js
+|-index.js
+|-package.json
+|-.babelrc
+|-.eslintrc
+|-CHANGELOG.md
+|-README.md
+

使用 ESLint 规范构建脚本

使用 eslint-config-airbnb-base 基础包规范

{
+  "parser": "babel-eslint",
+  "env": {
+    "browser": true,
+    "node": false
+  },
+  "extends": "airbnb-base",
+  "rules": {
+    "indent": ["error", 2]
+  },
+  "ignorePatterns": ["dist", "node_modules", "packages"]
+}
+

提示

eslint --fix 可以自动处理空格、间距等格式报错

冒烟测试(Smoke Testing)

冒烟测试是指对提交测试的软件在进行详细深入的测试之前而进行的预测试,这种预测试的主要目的是暴漏导致软件需重新发布的基本功能失效等严重问题。

  • 构建是否成功
  • 每次构建成功 build 目录是否有内容输出,判断基本功能是否正常,如编写 mocha 测试用例
    • 是否有 JS、CSS 等静态资源文件
    • 是否有 HTML 文件

单元测试(Unit Testing)

单元测试与测试覆盖率

编写单元测试用例

技术选型:Mocha + chai

测试代码:describe,it,except

测试命令:mocha add.test.js

示例

add.test.js

const expect = require('chai').expect;
+const add = require('../src/add');
+
+describe('use expect: src/add.js', () => {
+  it('add(1, 2) === 3', () => {
+    expect(add(1, 2)).to.equal(3);
+  });
+});
+

单元测试接入

  1. 安装 mochachai
  2. 新建 test 目录,并增加 xxx.test.js 测试文件;
  3. 在 package.json 中的 scripts 字段增加 test 命令, "test": "node_modules/mocha/bin/_mocha"
  4. 执行测试命令。

持续集成

  • 作用
    • 快速发现错误
    • 防止分支大幅偏离主干
  • 核心措施:代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。

github 最流行的 CI

接入 Travis CI

  1. Travis CIopen in new window使用 github 账号登录
  2. Travis CI Repositoriesopen in new window为项目开启
  3. 项目根目录下新增 .travis.yml
language: node_js
+
+sudo: false
+
+cache:
+  apt: true
+  directories:
+    - node_modules
+
+node_js: stable # 设置相应的node版本
+
+install:
+  - npm install -D # 安装构建器版本
+  - cd ./test/template-project
+  - npm install -D # 安装模板项目依赖
+
+script:
+  - npm test
+

发布构建包到 npm 社区

  • 清除非必要文件,如 dist 目录等
  • 登录 npm login
    • 输入 Username, Password, Email
  • 添加用户 npm adduser
  • 升级版本
    • 升级补丁版本号 npm version patch
    • 升级小版本号 npm version minor
    • 升级大版本号 npm version major
  • 发布版本 npm publish

Git commit 规范和 Changelog 生成

优点

  • 加快 Code review 的流程
  • 根据 Git commit 的元数据生成 changelog
  • 后续维护者可以知道 Feature 被修改的原因

技术方案

Git commit规范

提交格式要求

<type>(<scrope>): <subject>
+<BLANK_LINE>
+<body>
+<BLANK_LINE>
+<footer>
+
  • type 代表某次提交的类型,具体类型如下:
    • feat 新增 feature
    • fix 修复 bug
    • docs 只修改了文档,如 README,CHANGELOG,CONTRIBUTE 等
    • style 只修改了空格、格式缩进,逗号等,不改变代码逻辑
    • refactor 代码重构,没有加新功能或者修复 bug
    • perf 优化相关,如提升性能,体验等
    • test 测试用例,如单元测试、集成测试等
    • chore 改变构建流程、增加依赖库、工具等
    • revert 回退到上一个版本

本地开发阶段增加 precommit 钩子

安装 husky,通过 commitmsg 钩子校验信息

{
+  "scripts": {
+    "commitmsg": "validate-commit-msg",
+    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
+  },
+  "devDependencies": {
+    "validate-commit-msg": "^2.11.1",
+    "conventional-changelog-cli": "^1.2.0",
+    "husky": "^0.13.1"
+  }
+}
+

语义化版本(Semantic Versioning)

开源项目版本信息案例

  • 软件的版本通常由三位组成,形如:X.Y.Z
  • 版本是严格递增的,16.2.0 -> 16.3.0 -> 16.3.1
  • 在发布重要版本时,可以发布 alpharc(release candidate) 等先行版本
  • alpharc 等修饰版本的关键字后面可以带上次数和 meta 信息

规范格式

  • 主版本号:当做了不兼容的 API 修改时
  • 次版本号:当做了向下兼容的功能性新增
  • 修订号:当做了向下兼容的问题修正
    • 先行版本号:作为发布正式版之前的版本,格式是在修订版本号后面加上一个连接号(-),再加上一连串以点(.)分割的标识符,标识符可以由英文、数字和连接号([0-9A-Za-z-])组成。
      • alpha:内部测试版,一般不向外部发布,会有很多 bug,一般只有测试人员使用
      • beta:测试版,这个阶段版本会一直加入新的功能,在 alpha 版之后推出
      • rc:release candidate。系统平台上就是发行候选版本,rc 版不会加入新的功能,主要着重于排错

遵守 semver 规范的优势

  • 避免出现循环依赖
  • 依赖冲突减少
+ +
+ + + diff --git a/Bundler/Webpack/perfermance.html b/Bundler/Webpack/perfermance.html new file mode 100644 index 0000000..248ec1b --- /dev/null +++ b/Bundler/Webpack/perfermance.html @@ -0,0 +1,367 @@ + + + + + + + Webpack 构建优化 | LOFT + + + + +

Webpack 构建优化

初级分析

使用 webpack 内置的 stats 展示构建的统计信息

  • package.json 中使用

    {
    +  "scripts": {
    +    "build:stats": "webpack --env production --json > stats.json",
    +  }
    +}
    +
  • Node.js 中使用

    颗粒度太粗,看不出问题所在

    const webpack = require('webpack');
    +const config = require('./webpack.config.js')('production');
    +
    +webpack(config, (err, stats) => {
    +  if (err) {
    +    return console.error(err);
    +  }
    +  if (stats.hasErrors()) {
    +    return console.error(stats.toString('errors-only'));
    +  }
    +  console.log(stats);
    +});
    +

速度分析

  • 分析整个打包总耗时
  • 每个 loader 和插件的执行耗时情况

使用 speed-measure-webpack-plugin,可以看到每个 loader 和插件的执行耗时

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
+
+const smp = new SpeedMeasureWebpackPlugin();
+
+const webpackConfig = smp.wrap({
+  module: {
+    rules: [
+      // ...
+    ]
+  },
+  plugins: [
+    new MyPlugin(),
+    new OtherPlugin(),
+  ]
+});
+

体积分析

  • 分析依赖的第三方模块文件大小
  • 分析业务里面的组件代码大小

使用 webpack-bundle-analyzer 分析体积,构建完成后会在 8888 端口展示大小

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
+
+module.exports = {
+  plugins: [
+    new BundleAnalyzerPlugin();
+  ]
+};
+

高版本 webpack 和 Node.js

webpack 4 优化原因

  • V8 引擎优化(for of 替代 forEach,Map 和 Set 替代 Object,includes 替代 indexOf)
  • 默认使用更快的 md4 hash 算法
  • webpack 的 AST 可以直接从 loader 传递给 AST,减少解析时间
  • 使用字符串方法替代正则表达式

动态 Polyfill 服务

方案优点缺点是否采用
babel-polyfillReact16 官方推荐1.包体积 200k+,难以单独抽离 Map,Set
2. 项目里 React 是单独引用的 cdn,如果要用它,需要单独构建一份放在 React 前加载
x
babel-plugin-transform-runtime能只 polyfill 用到的类和方法,相对体积较小不能 polyfill 原型上的方法,不适用于业务项目的复杂开发环境x
customize polyfill, e.g es6-shim定制化高,体积小1. 重复造轮子,容易在日后年久失修成为坑
2. 即使体积小,依然所有用户都要加载
x
polyfill-service只给用户返回需要的 polyfill,社区维护部分国内奇葩浏览器 UA 可能无法识别(但可以降级返回所需全部 polyfill)O

polyfill-service 原理

识别 User Agent,下发不同的 Polyfill

  • 使用 polyfill.io 官方提供的 cdn 服务
  • 基于官方自建的 polyfill 服务

多进程/多实例

构建

资源并行解析可选方案

  • thread-loader 每次 webpack 解析一个模块,thread-loader 会将它及它的依赖分配给 worker 线程中

    module.exports = {
    +  module: {
    +    rules: [
    +      {
    +        test: /.js$/,
    +        use: [
    +          {
    +            loader: 'thread-loader',
    +            options: {
    +              workers: 3,
    +            }
    +          },
    +          'babel-loader'
    +        ]
    +      }
    +    ]
    +  }
    +};
    +
  • parallel-webpack

  • HappyPack 每次 webpack 解析一个模块,HappyPack 会将它及它的依赖分配给 worker 线程中 HappyPack

    exports.plugins = [
    +  new HappyPack({
    +    id: 'jsx',
    +    threads: 4,
    +    loaders: ['babel-loader']
    +  }),
    +  new HappyPack({
    +    id: 'styles',
    +    threads: 2,
    +    loaders: ['style-loader', 'css-loader', 'less-loader']
    +  })
    +]
    +

并行压缩

  • parallel-uglify-plugin

    const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
    +
    +module.exports = {
    +  plugins: [
    +    new ParallelUglifyPlugin({
    +      uglifyJS: {
    +        output: {
    +          beautify: false,
    +          comments: false,
    +        },
    +        compress: {
    +          warnings: false,
    +          drop_console: true,
    +          collapse_vars: true,
    +          reduce_vars: true
    +        }
    +      }
    +    })
    +  ]
    +};
    +
  • uglifyjs-webpack-plugin 开启 parallel 参数

    const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
    +
    +module.exports = {
    +  plugins: [
    +    new UglifyJSPlugin({
    +      uglifyOptions: {
    +        warnings: false,
    +        parse: {},
    +        compress: {},
    +        mangle: true,
    +        output: null,
    +        toplevel: false,
    +        nameCache: null,
    +        ie8: false,
    +        keep_fnames: false,
    +      },
    +      parallel: true
    +    })
    +  ]
    +};
    +
  • terser-webpack-plugin 开启 parallel 参数

    const TerserPlugin = require('terser-webpack-plugin');
    +
    +module.exports = {
    +  optimization: {
    +    minimizer: [
    +      new TerserPlugin({
    +        parallel: 4
    +      })
    +    ]
    +  }
    +};
    +

分包

设置 Externals

  • 使用 html-webpack-externals-plugin,将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中

  • 自定义 plguin

class InjectExternalPlugin {
+  constructor(options) {
+    this.options = options;
+  }
+
+  apply(compiler) {
+    console.log('InjectExternalPlugin is starting' /* , this.options */);
+    const { externals = [] } = this.options;
+    const externalScripts = externals.map(item => ({
+      tagName: 'script',
+      attributes: {
+        defer: false,
+        type: 'text/javascript',
+        src: item.entry,
+      },
+      voidTag: false,
+    }));
+    compiler.hooks.compilation.tap('InjectExternalPlugin', (compilation, compilationParams) => {
+      HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('InjectExternalPlugin', (data, cb) => {
+        const { headTags } = data;
+        headTags.unshift(...externalScripts);
+        console.log(headTags);
+        cb(null, data);
+      });
+    });
+  }
+}
+module.exports = {
+  externals: {
+    react: 'React',
+    'react-dom': 'ReactDOM'
+  },
+  plugins: [
+    new InjectExternalPlugin({
+      externals: [
+        {
+          module: 'react',
+          global: 'React',
+          entry: 'https://cdn.bootcdn.net/ajax/libs/react/16.8.6/umd/react.production.min.js',
+        },
+        {
+          module: 'react-dom',
+          global: 'ReactDOM',
+          entry: 'https://cdn.bootcdn.net/ajax/libs/react-dom/16.8.2/umd/react-dom.production.min.js',
+        },
+      ]
+    }),
+  ]
+};
+

预编译资源模块

使用 DLLPlguin 进行分包,DllReferencePlugin 对 manifest.json 引用,将 react、react-dom、redux、react-redux 基础包和业务基础包打包成一个文件。

const path = require('path');
+const webpack = require('webpack');
+
+module.exports = {
+  context: process.cwd(),
+  resolve: {
+    extensions: ['.js', '.jsx', '.json', '.less', '.css'],
+    modules: [__dirname, 'node_modules']
+  },
+  entry: {
+    library: ['react', 'react-dom', 'redux', 'react-redux']
+  },
+  output: {
+    filename: '[name].dll.js',
+    path: path.resolve(__dirname, './build/library'),
+    library: '[name]' // 与下面name的值必须一致
+  },
+  plugins: [
+    new webpack.DllPlguin({
+      name: '[name]', // 与上面library的值必须一致
+      path: './build/library/[name].json',
+    })
+  ]
+};
+// 在 webpack.prod.js中引入
+module.exports = {
+  plugins: [
+    new webpack.DllReferencePlugin({
+      manifest: require('./build/library/manifest.json')
+    })
+  ]
+};
+

注意

这里通过 DllReferencePlugin 引入 manifest.json 文件后,还需要在 html 中引入对应的 [name].dll.js 文件

<head>
+  <script src="../build/library/library.dll.js"></script>
+</head>
+

提示

对于多个要分包的资源文件,更推荐使用 add-asset-html-webpack-plugin 插件自动引入分包后分离出来的 dll.js 文件。

// config.js 示例分开为两个dll文件
+module.exports = {
+  library: {
+    react: ['react'],
+    reactBucket: ['react-dom'],
+  }
+};
+// webpack.dll.js
+const path = require('path');
+const webpack = require('webpack');
+const config = require('./config.js');
+
+module.exports = {
+  entry: config.library,
+  output: {
+    filename: '[name].dll.js',
+    path: path.join(__dirname, 'build/library'),
+    library: '[name]_dll',
+  },
+  plugins: [
+    new webpack.DllPlugin({
+      context: __dirname,
+      name: '[name]_dll',
+      path: path.join(__dirname, 'build/library/[name]-manifest.json'),
+    })
+  ]
+};
+// webpack.prod.js
+const path = require('path');
+const webpack = require('webpack');
+const config = require('./config.js');
+const libraryKeys = Object.keys(config.library);
+const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
+
+module.exports = {
+  plugins: [
+    ...libraryKeys.map(key => new webpack.DllReferencePlugin({
+      context: __dirname,
+      manifest: require(`./build/library/${key}-manifest.json`)
+    })),
+    new AddAssetHtmlPlugin(libraryKeys.map(key => ({
+      filepath: path.resolve(__dirname, `build/library/${key}.dll.js`),
+      publicPath: '../build/library'
+    })))
+  ]
+};
+

缓存提升二次构建速度

  • babel-loader 开启缓存

    ['babel-loader?cacheDirectory=true']

  • terser-webpack-plugin 开启缓存

    module.exports = {
    +  optimization: {
    +    minimizer: [
    +      new TerserWebpackPlugin({
    +        parallel: true,
    +        cache: true,
    +      })
    +    ]
    +  }
    +};
    +
  • 使用 cache-loader 或者 hard-source-webpack-plugin

    module.exports = {
    +  plugins: [
    +    new HardSourceWebpackPlugin(),
    +  ]
    +};
    +

缩小构建目标

  • 尽可能的少构建模块,如:babel-loader 不解析 node_modules

    module.exports = {
    +  module: {
    +    rules: [
    +      {
    +        test: /\.js$/,
    +        loader: 'happypack/loader',
    +        exclude: 'node_modules',
    +      }
    +    ]
    +  }
    +};
    +
  • 减少文件搜索范围

    • 优化 resolve.modules 配置(减少模块搜索层级)
    • 优化 resolve.mainFields 配置
    • 优化 resolve.extensions 配置
    • 合理使用 alias
    module.exports = {
    + resolve: {
    +   modules: [path.resolve(__dirname, 'node_modules')],
    +   mainFields: ['main'],
    +   extensions: ['.js'],
    +   alias: {
    +     react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
    +     '@utils': path.resolve(__dirname, './utils/utils.js')
    +   }
    + }
    +};
    +

Tree Shaking 擦除无用的 JS 和 CSS

一个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打包到 bundle 中去,tree shaking 就是只把用到的方法打包到 bundle 中,没有使用到的方法会在 uglify 阶段被擦除掉。

webpack 默认支持,produciton 模式下默认开启,在 .babelrc 中设置 modules: false 即可

必须是 ES6 语法,不支持 CJS

利用 ES6 模块的特点

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串变量
  • import binding 是 immutable 的

擦除无用 CSS

  • PurifyCSS 遍历代码,识别已经用到的 CSS class

    purgecss-webpack-plugin 和 mini-css-extract-plugin 配合使用

    const PATHS = {
    +  src: path.join(__dirname, 'src')
    +};
    +module.exports = {
    +  module: {
    +    rules: [
    +      {
    +        test: /\.css$/,
    +        use: [
    +          MiniCssExtractPlugin.loader,
    +          'css-loader'
    +        ]
    +      }
    +    ]
    +  },
    +  plugins: [
    +    new MiniCssExtractPlugin({
    +      filename: '[name].css',
    +    }),
    +    new PurgeCssPlugin({
    +      paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true })
    +    })
    +  ]
    +};
    +
  • uncss HTML 需要通过 jsdom 加载,所有的样式通过 PostCSS 解析,通过document.querySelector 来识别在 html 文件里面不存在的选择器

图片压缩

要求:基于 Node 库的 imagemin 或者 tinypng API

  • 有很多定制选项
  • 可以引入更多第三方优化插件,如 pngquant
  • 可以处理多种图片格式

imagemin 压缩原理:

  • pngquant 通过将图像转换为具有 alpha 通道(通常比 24/32 为 png 文件小 60%-80%)的更高效的 8 为 png 格式,可显著减小文件大小
  • pngcrush 通过尝试不同的压缩级别和 png 过滤方法来降低 png IDAT 数据流的大小
  • optipng 设计灵感来自于 pngcrush,可将图像文件重新压缩为更小尺寸,而不会丢失任何信息
  • tinypng 将 24 为 png 文件转化为更小有索引的 8 位图片,同时所有非必要的 metadata 也会被剥离

使用:配置 image-webpack-loader

module.exports = {
+  module: {
+    rules: [
+      {
+        test: /\.(png|svg|jpg|gif|blob)$/,
+        use: [
+          {
+            loader: 'file-loader',
+            options: {
+              name: `${filename}img/[name]${hash}.[ext]}`
+            },
+          },
+          {
+            loader: 'image-webpack-loader',
+            options: {
+              mozjpeg: {
+                progressive: true,
+                quality: 65
+              },
+              optipng: {
+                enabled: false,
+              },
+              pngquant: {
+                quality: '65-90',
+                speed: 4
+              },
+              gifsicle: {
+                interlaced: false,
+              },
+              webp: {
+                quality: 75
+              }
+            }
+          }
+        ]
+      }
+    ]
+  }
+};
+
+ +
+ + + diff --git a/Bundler/Webpack/resolve.html b/Bundler/Webpack/resolve.html new file mode 100644 index 0000000..6fd4ae3 --- /dev/null +++ b/Bundler/Webpack/resolve.html @@ -0,0 +1,93 @@ + + + + + + + Webpack 资源解析 | LOFT + + + + +

Webpack 资源解析

解析 ES6

使用 babel-loader 进行解析。

module.exports = {
+  // ...
+  module: {
+    rules: [
+      { test: /\.js$/, use: 'babel-loader' },
+    ]
+  }
+};
+

增加 ES6 babel preset 配置。

// ./Webpack/.babelrc 文件
+{
+  "presets": ["@babel/preset-env"],
+  "plugins": ["@babel/proposal-class-properties"]
+}
+

解析 React JSX

增加 React babel preset 配置。

// ./Webpack/.babelrc 文件
+{
+  "presets": [
+    "@babel/preset-env",
+    "@babel/preset-react"
+  ],
+  "plugins": ["@babel/proposal-class-properties"]
+}
+

解析 CSS

css-loader 用于加载 .css 文件,并转换成 CommonJS 对象

style-loader 用于将样式通过 <style> 标签插入到 <head> 中

module.exports = {
+  // ...
+  module: {
+    rules: [
+      { test: /\.css$/, use: ['style-loader', 'css-loader'] }
+      // use 数组加载顺序是从右往左加载
+    ]
+  }
+};
+

解析 LESS 和 SASS

less-loader 用于将 less 转换成 css

module.exports = {
+  // ...
+  module: {
+    rules: [
+      {
+        test: /\.css$/,
+        use: [
+          'style-loader',
+          'css-loader',
+          'less-loader'
+        ]
+        // use 数组加载顺序是从右往左加载
+      }
+    ]
+  }
+};
+

解析图片、字体等文件

file-loader 用于处理文件

module.exports = {
+  // ...
+  module: {
+    rules: [
+      {
+        test: /\.(png|svg|jpg|gif)$/,
+        use: 'file-loader'
+      }
+      // use 数组加载顺序是从右往左加载
+    ]
+  }
+};
+

url-loader 也可以处理图片和字体,设置较小资源的 base64 自动转换

module.exports = {
+  // ...
+  module: {
+    rules: [
+      {
+        test: /\.(png|svg|jpg|gif)$/,
+        use: [
+          {
+            loader: 'url-loader',
+            options: {
+              limit: 10240
+            }
+          }
+        ]
+      }
+    ]
+  }
+};
+
+ +
+ + + diff --git a/Bundler/Webpack/sourcecode.html b/Bundler/Webpack/sourcecode.html new file mode 100644 index 0000000..420b6ff --- /dev/null +++ b/Bundler/Webpack/sourcecode.html @@ -0,0 +1,274 @@ + + + + + + + Webpack 源码分析 | LOFT + + + + +

Webpack 源码分析

可将其理解为一种基于事件流的编程范例,一系列的插件运行。

webpack 命令

  • 通过 npm scripts 运行 webpack

npm run dev npm run build

  • 通过 webpack 直接运行

webpack entry.js bundle.js

命令行运行以上命令后,npm 会 让命令行工具进入 node_modules\.bin 目录 查找是否存在 webpack.sh 或者 webpack.cmd 或者 webpack.ps1 文件,如果存在,就执行,不存在,抛出错误。

实际入口文件是 node_modules\webpack\bin\webpack.js

// 1. 正常执行返回
+process.exitCode = 0;
+// 2. 运行某个命令
+const runCommand = (command, args) => {};
+// 3. 判断某个包是否安装
+const isInstalled = packageName = {};
+// 4. webpack可用的CLI:webpack-cli和webpack-command
+const CLIs = [];
+// 5. 过滤安装过的CLI
+const installedClis = CLIs.filter(cli => cli.installed);
+// 6. 根据安装数量进行处理
+if (installedClis.length === 0) {
+} else if (installedClis.length === 1) {
+} else {}
+// 7. 中途报错退出
+process.exitCode = 1;
+

webpack 最终找到 webpack-cli / webpack-command 包,并执行 CLI。

webpack-cli 命令

  • 引入 yargs,对命令行进行定制
  • 分析命令行参数,对各个参数进行转换,组成编译配置项
  • 引入 webpack,根据配置项进行编译和构建

从 NON_COMPILATION_CMD 分析出不需要编译的命令

webpack-cli 处理不需要经过编译的命令

/* const NON_COMPILATION_ARGS = [
+	"init",              // 创建一份 webpack 配置文件
+	"migrate",           // 进行webpack版本迁移
+	"add",               // 向webpack配置文件中增加属性
+	"remove",            // 向webpack配置文件中删除属性
+	"serve",             // 运行webpack-serve
+	"generate-loader",   // 生成webpack-loader代码
+	"generate-plugin",   // 生成webpack-plugin代码
+	"info"               // 返回与本地环境相关的一些信息
+]; */
+const { NON_COMPILATION_ARGS } = require("./utils/constants");
+
+const NON_COMPILATION_CMD = process.argv.find(arg => {
+  if (arg === "serve") {
+    global.process.argv = global.process.argv.filter(a => a !== "serve");
+    process.argv = global.process.argv;
+  }
+  return NON_COMPILATION_ARGS.find(a => a === arg);
+});
+
+if (NON_COMPILATION_CMD) {
+  return require("./utils/prompt-command")(NON_COMPILATION_CMD, ...process.argv);
+}
+
+

webpack-cli 对配置文件和命令行参数进行转换最终生成配置选项参数 options,最终会根据配置参数实例化 webpack 对象,然后执行构建流程

yargs 工具包

提供命令和分组参数,动态生成 help 帮助信息

webpack-cli 使用 args 分析 参数数组(config/config-args.js),将命令划分为 9 类:

  • Config options:配置相关参数(文件名称、运行环境等)
  • Basic options:基础参数(entry 设置、debug 模式设置、watch 监听设置、devtool 设置)
  • Module options:模块参数,给 loader 设置扩展
  • Output options:输出参数(输出路径、输出文件名称)
  • Advanced options:高级用法(记录设置、缓存设置、监听频率、bail 等)
  • Resolving options:解析参数(alias 和解析的文件后缀设置)
  • Optimizing options:优化参数
  • Stats options:统计参数
  • options:通用参数(帮助命令、版本信息等)

Tapable

类似于 Node.js 的 EventEmitter 的库,主要是控制钩子函数的发布与订阅,控制着 webpack 的插件系统。

Tapable 库暴露了很多 Hook 类,为插件提供挂载的钩子

const {
+  SyncHook,                 // 同步钩子
+  SyncBailHook,             // 同步熔断钩子
+  SyncWaterfallHook,        // 同步流水钩子
+  SyncLoopHook,             // 同步循环钩子
+  AsyncParallelHook,        // 异步并发钩子
+  AsyncParallelBailHook,    // 异步并发熔断钩子
+  AsyncSeriesHook,          // 异步串行钩子
+  AsyncSeriesBailHook,      // 异步串行熔断钩子
+  AsyncSeriesWaterfallHook  // 异步串行流水钩子
+} = require('tapable');
+

Tapable Hooks 类型

类型功能
Hook所有钩子的后缀
Waterfall同步方法,但是会传值给下一个函数
Bail熔断:当函数有任何返回值,就会在当前执行函数停止
Loop监听函数返回 true 表示继续循环,返回 undefined 表示结束循环
Sync同步方法
AsyncSeries异步串行钩子
AsyncParallel异步并行执行钩子

new Hook 新建钩子

Tapable 暴露出来的都是类方法,new 一个类方法获得我们需要的钩子。

class 接收数组参数 options,非必传,类方法会根据传参,接收同样数量的参数。

const hook1 = new SyncHook(['arg1', 'arg2', 'arg3']);

钩子的绑定与执行

Tapable 提供了同步和异步绑定钩子的方法,并且它们都有绑定事件和执行事件对应的方法。

Sync*Async*
绑定:tap绑定:tapAsync / tapPromise / tap
执行:call执行:callAsync / promise

示例

示例 1:

const hook1 = new SyncHook(['arg1', 'arg2', 'arg3']);
+
+// 绑定实践到 webpack 事件流
+hook1.tap('hook1', (arg1, arg2, arg3) => console.log(arg1, arg2, arg3));
+
+// 执行绑定事件
+hook1.call(1, 2, 3);
+

示例 2:

定义一个 Car 方法,在内部 hooks 上新建钩子。分别是同步钩子 accelerate,brake(accelerate 接收一个参数),异步钩子 calculateRoutes;

使用钩子对应的绑定和执行方法

calculateRoutes 使用 tapPromise 可以返回一个 promise 对象

const { SyncHook, AsyncSeriesHook } = require('tapable');
+
+class Car {
+  constructor() {
+    this.hooks = {
+      accelerate: new SyncHook(['newspeed']),
+      brake: new SyncHook(),
+      calculateRoutes: new AsyncSeriesHook(['source', 'target', 'routesList'])
+    };
+  }
+}
+
+const myCar = new Car();
+
+myCar.hooks.brake.tap('WarningLampPlugin', () => console.log('WarningLampPlugin'));
+
+myCar.hooks.accelerate.tap('LoggerPlugin', newSpeed => console.log(`Accelerating to ${newSpeed}`));
+
+myCar.hooks.calculateRoutes.tapPromise('calcuateRoutes tapPromise', (source, target, routesList, callback) => {
+  console.log('source', source);
+  return new Promise((resolve, reject) => {
+    setTimeout(() => {
+      console.log(`tapPromise to ${source}${target}${routesList}`);
+      resolve();
+    }, 1000);
+  });
+});
+
+myCar.hooks.brake.call();
+myCar.hooks.accelerate.call(10);
+
+console.time('cost');
+
+myCar.hooks.calculateRoutes.promise('Async', 'Hook', 'Demo').then(() => {
+  console.timeEnd('cost');
+}, err => {
+  console.error(err);
+  console.timeEnd('cost');
+});
+

Tapable 与 Webpack 的联系


let compiler;
+if (Array.isArray(options)) {
+  compiler = new MultiCompiler(
+    Array.from(options).map(options => webpack(options))
+  );
+} else if (typeof options === "object") {
+  options = new WebpackOptionsDefaulter().process(options);
+
+  compiler = new Compiler(options.context);
+  compiler.options = options;
+  new NodeEnvironmentPlugin({
+    infrastructureLogging: options.infrastructureLogging
+  }).apply(compiler);
+  if (options.plugins && Array.isArray(options.plugins)) {
+    for (const plugin of options.plugins) {
+      if (typeof plugin === "function") {
+        plugin.call(compiler, compiler);
+      } else {
+        plugin.apply(compiler);
+      }
+    }
+  }
+  compiler.hooks.environment.call();
+  compiler.hooks.afterEnvironment.call();
+  compiler.options = new WebpackOptionsApply().process(options, compiler);
+} else {
+  throw new Error("Invalid argument: options");
+}
+

Webpack 流程

webpack 的编译都按照下面的钩子调用顺序执行:

webpack 流程

初始化阶段

WebpackOptionsApply.js 将所有的配置 options 参数转换成 webpack 内部插件 使用默认插件列表,如:

  • output.library -> LibraryTemplatePlugin
  • externals -> ExternalsPlugin
  • devtool -> EvalDevtoolModulePlugin, SourceMapDevToolPlugin
  • AMDPlugin, CommonJsPlugin
  • RemoveEmptyChunksPlugin

模块构建和 chunk 生成阶段

Compiler Hooks

  • 流程相关
    • (before-)run
    • (before-/after-)compile
    • make
    • (after-)emit
    • done
  • 监听相关
    • watch-run
    • watch-close

Compilation

Compiler 调用 Compilation 生命周期方法

  • addEntry -> addModuleChain
  • finish(上报模块错误)
  • seal

Compilation Hooks

  • 模块相关
    • build-module
    • failed-module
    • succeed-module
  • 资源生成相关
    • module-asset
    • chunk-asset
  • 优化和 seal 相关
    • (after-)seal
    • optimize
    • optimize-modules(-basic/advanced)
    • after-optimize-(modules/chunks/tree)
    • optimize-chunk-modules(-basic/advanced)
    • after-optimize-chunk-modules
    • optimize-module/chunk-order
    • before-module/chunk-ids
    • (after-)optimize-module/chunk-ids
    • before/after-hash

ModuleFactory

ModuleFactory

Module

Module

NormalModule

Build 方法

  • 使用 loader-runner 运行 loaders
  • 通过 Parser 解析(内部使用 acron)
  • ParserPlugins 添加依赖

Chunk 生成算法

  1. webpack 先将 entry 中对应的 module 都生成一个新的 chunk;
  2. 遍历 module 的依赖列表,将依赖的 module 也加入到 chunk 中;
  3. 如果一个依赖 module 是动态引入的模块,则会根据这个 module 创建一个新的 chunk,继续遍历依赖;
  4. 重复上面的过程,直到得到所有的 chunks。

emit 文件生成阶段

addEntry -> _addModuleChain -> buildModule -> module.build -> runLoader(内容转化) -> Parser 依赖分析 -> compile -> compilation.seal(资源优化) -> createHash/modifyHash -> createModuleAssets -> emitAsset 资源对象生成 -> compiler emitAssets -> hooks.emit -> outputFileSystem.mkdirp

实现简易 Webpack

增强代码的可读性和维护性

  • 传统网页开发转变成 Web Apps 开发
  • 代码复杂度在逐步提高
  • 分离的 JS 文件/模块,便于后续代码的维护性
  • 部署时希望把代码优化成几个 HTTP 请求

常见的几种模块化方式

  • ES module
  • CJS
  • AMD

AST(Abstract syntax tree) 抽象语法树,是源代码的抽象语法结构的树状表现形式,树上的每个节点都表示源代码中的一种结构。例如 esprimaopen in new window

提示

  • 可以将 ES6 语法转换成 ES5 语法
    • 通过 babylon 生成 AST
    • 通过 babel-core 将 AST 重新生成源码
  • 可以分析模块之间的依赖关系
    • 通过 babel-traverse 的 ImportDeclaration 方法获取依赖属性
  • 生成的 JS 文件可以在浏览器中运行
  • 基本组成
    • config.js
      • options 提供 options,相当于 webpack 中的 webpack.config.js
        • entry 配置
        • output 配置
    • index.js
      • new Compiler(options).run()
    • compiler.js
      • class Compiler
        • run()
        • buildModule(filename, isEntry) 构建 modules
        • emitFiles() 输出编译后文件
    • parser.js
      • getAST(path) 解析 AST 树
      • getDependencies(ast) 从 entry 解析依赖
      • transform(ast) 将 AST 重新生成 ES5 源码

loader 的链式调用和执行顺序

loader 只是一个导出为函数的 JS 模块。

多 loader 执行顺序

从后到前,串行执行

module.exports = {
+  module: {
+    rules: [
+      {
+        test: /\.less$/,
+        use: ['style-loader', 'css-loader', 'less-loader']
+        // 执行顺序: 3 <- 2 <- 1
+      }
+    ]
+  }
+};
+

提示

函数组合的两种情况

  • Unix 中的 pipeline

  • Compose(Webpack 采用)

    const compose = (f, g) => (...args) => f(g(...args));

loader-runner

允许你在不安装 webpack 的条件下运行 loaders

作用:

  • 作为 webpack 的依赖,webpack 中使用它来执行 loader
  • 进行 loader 的开发和调试
import { runLoaders } from 'loader-runner';
+
+runLoaders({
+  // String 资源的绝对路径(可以增加查询字符串)
+  resource: '/abs/path/to/file.txt?query',
+  // String[] loader的绝对路径(可以增加查询字符串)
+  loaders: '/abs/path/to/loader.js?query',
+  // 基础上下文之外的额外loader上下文
+  context: { minimize: true },
+  // 读取资源的函数
+  readResource: fs.readFile.bind(fs),
+}, function(err, result) {
+  // err: Error?
+  // result.result: Buffer | String
+});
+

loader 参数获取

通过 loader-utils 的 getOptions 方法获取参数

loader 异常处理

  • loader 内直接通过 throw 抛出

  • 通过 this.callback 传递错误

    • this.callback(
      +  err: Error | null,
      +  content: string | Buffer,
      +  sourceMap?: SourceMap,
      +  meta?: any
      +);
      +

loader 异步处理

通过 this.async 返回一个异步函数

const callback = this.async();
+
+if (callback) {
+  // ...
+}
+callback(null, result)
+

loader 缓存

webpack 默认开启 loader 缓存,使用 this.cacheable(false) 关闭。

缓存条件:loader 的结果在相同的输入下有确定的输出。有依赖的 loader 无法使用缓存。

loader 文件输出

通过 this.emitFile 进行文件写入

自动合成雪碧图 loader

background: url('a.png?__sprite');background: url('b.png?__sprite'); 转换为 background: url('sprite.png');

使用spritesmithopen in new window将多张图片合成为一张图片

关键处理逻辑

通过匹配获取对应后缀的路径进行路径解析,再传递给 Spritesmith 处理

const fs = require('fs');
+const path = require('path');
+const Spritesmith = require('spritesmith');
+
+module.exports = function (source) {
+  const callback = this.async();
+  const imgs = source.match(/url\((\S*)\?__sprite/g);
+  const matchedImgs = [];
+
+  for (let i = 0; i < imgs.length; i += 1) {
+    const img = imgs[i].match(/url\((\S*)\?__sprite/)[1];
+    matchedImgs.push(path.join(__dirname, img));
+  }
+
+  Spritesmith.run({ src: matchedImgs }, (err, result) => {
+    fs.writeFileSync(path.join(process.cwd(), 'dist/sprite.jpg'), result.image);
+    console.log(result.coordinates, result.properties);
+    source = source.replace(/url\((\S*)\?__sprite\)/g, (match, $0) => {
+      const filePath = path.join(__dirname, $0);
+      const { x, y, width, height } = result.coordinates[filePath];
+      return `url("dist/sprite.jpg");\n  background-position: ${x}px ${y}px;\n  background-size: ${width}px ${height}px;\n`;
+    });
+    callback(null, source);
+  });
+};
+

plugin 插件开发

插件没有像 loader 那样的独立运行环境,只能在 webpack 里面运行。

// plugin.js
+module.exports = class Plugin {
+  apply(compiler) {
+    compiler.hooks.done.tap('Plugin', (stats) => {
+      console.log(stats);
+    });
+  }
+}
+// webpack.config.js
+module.exports = {
+  plugins: [new Plugin()]
+};
+

plugin 参数获取

通过插件的构造函数进行获取。

plugin 异常处理

  • 参数校验阶段可以直接 throw 的方式抛出

  • 通过 compilation 对象的 warnings 和 errors 接收

    • compilation.warnings.push('warning');
      +compilation.errors.push('error');
      +

plugin 文件输出

compilation 上的 assets 可用于文件输出

  • 可以将 zip 资源包设置到 compilation.assets 对象上

  • 需要使用 webpack-sourceopen in new window

    • const { RawSource } = require('webpack-source');
      +compiler.hooks.emit.tap('Plugin', (compilation, cb) => {
      +  compilation.assets[name] = new RawSource('test');
      +  cb();
      +});
      +

插件扩展(插件的插件)

插件自身也可以通过暴露 hooks 的方式进行自身扩展,如 html-webpack-plugin:

  • html-webpack-plugin-alter-chunks (Sync)
  • html-webpack-plugin-before-html-generation (Async)
  • html-webpack-plugin-alter-asset-tags (Async)
  • html-webpack-plugin-after-html-processing (Async)
  • html-webpack-plugin-after-emit (Async)

压缩构建资源位 zip 包插件

  • 生成的 zip 包文件名称可以通过插件传入

  • 需要使用 compiler 对象上的特定 hooks 进行资源生成

  • 使用 jszipopen in new window 压缩 zip 包

    • const zip = new JSZip();
      +
      +zip.file("Hello.txt", "Hello World\n");
      +
      +const img = zip.folder("images");
      +img.file("smile.gif", imgData, {base64: true});
      +
      +zip.generateAsync({type:"blob"}).then(function(content) {
      +    // see FileSaver.js
      +    saveAs(content, "example.zip");
      +});
      +
      +/*
      +Results in a zip containing
      +Hello.txt
      +images/
      +    smile.gif
      +*/
      +
  • emit (AsyncSeriesHook) 生成文件阶段, 读取 compilation.assets对象的值,故 将 zip 资源包设置到 compilation.assets 对象上

关键处理逻辑

const path = require('path');
+const JSZip = require('jszip');
+const { RawSource } = require('webpack-sources');
+
+const zip = new JSZip();
+
+module.exports = class ZipPlugin {
+  constructor(options) {
+    this.options = options;
+  }
+
+  apply(compiler) {
+    compiler.hooks.emit.tapAsync('ZipPlugin', (compilation, callback) => {
+      const folder = zip.folder(this.options.filename);
+      Object.keys(compilation.assets).forEach(filename => {
+        const source = compilation.assets[filename].source();
+        folder.file(filename, source);
+      });
+
+      zip.generateAsync({ type: 'nodebuffer' }).then(content => {
+        const outputPath = path.join(compilation.options.output.path, this.options.filename + '.zip');
+        const outputRelativePath = path.relative(
+          compilation.options.output.path,
+          outputPath
+        );
+        compilation.assets[outputRelativePath] = new RawSource(content);
+
+        callback();
+      });
+    });
+  }
+};
+
+ +
+ + + diff --git a/Bundler/Webpack/ssr.html b/Bundler/Webpack/ssr.html new file mode 100644 index 0000000..c96fc82 --- /dev/null +++ b/Bundler/Webpack/ssr.html @@ -0,0 +1,17 @@ + + + + + + + 服务端渲染(SSR) | LOFT + + + + +

服务端渲染(Server Side Render)

  • 客户端渲染:请求 HTML,CSS,JS 并发生数据接口请求,渲染 HTML 页面
  • 服务端渲染:
    • 核心:减少请求,减少白屏数据,对 SEO 友好
    • 所有模板等资源都存储在服务端
    • 内网机器拉取数据更快
    • 一个 HTML 返回所有数据

浏览器与服务端的交互流程

浏览器与服务端的交互流程

客户端渲染服务端渲染
请求多个请求(HTML,数据等)一个请求
加载过程HTML 和数据串行加载一个请求返回 HTML 和数据
渲染前端渲染服务端渲染
可交互图片等静态资源加载完成,JS 逻辑执行完成可交互

SSR 实现思路

  • 服务端

    • 使用 react-dom/server 的 renderToString 方法将 React 组件渲染出字符串
    • 服务端路由返回对应的模板
  • 客户端

    • 打包出针对服务端的组件

SSR 问题

  • 浏览器全局变量(Node.js 中没有 document,window)
    • 组件适配:将不兼容的组件根据打包环境进行适配
    • 请求适配:将 fetch 或者 ajax 发送请求的写法改成 isomorphic-fetch 或者 axios
  • 样式问题(Node.js 无法解析 CSS)
    • 服务端打包通过 ignore-loader 忽略掉 CSS 解析
    • 将 style-loader 替换成 isomorphic-style-loader
    • 不显示
      • 使用打包出来的浏览器端 html 文件
      • 设置占位符,动态插入组件
+ +
+ + + diff --git a/Bundler/Webpack/watch.html b/Bundler/Webpack/watch.html new file mode 100644 index 0000000..7ce0b42 --- /dev/null +++ b/Bundler/Webpack/watch.html @@ -0,0 +1,45 @@ + + + + + + + Webpack 文件监听与热更新 | LOFT + + + + +

Webpack 文件监听

文件监听: 在监听到源码发生变化时,自动重新构建出新的输出文件。

webpack 开启监听模式的两种方式:

  • webpack --watch
  • webpack.config.js 配置文件中设置 watch: true

注意

缺点是:每次编译完成需要手动刷新浏览器

原理

轮询判断文件的最后编辑时间是否发生变化,当某个文件发生变化,并不会立刻告诉监听者,而是先缓存起来,等待 aggregateTimeout,等待时间内有其他文件发生变化,则继续将其加入变化列表最后在等待时间结束后统一触发。

module.exports = {
+  // ...
+  watch: true, // 默认 false,即不开启
+  watchOptions: { // 只有开启监听模式时,配置才生效
+    // 默认为空,忽略监听文件或文件夹,支持正则匹配
+    ignored: /node_modules/,
+    // 监听到变化发生后会等 300ms 再去执行,默认 300ms
+    aggregateTimeout: 300,
+    // 判断文件是否发生变化是通过不停轮询系统指定文件有无变化实现的,默认 每秒询问1000次
+    poll: 1000
+  }
+};
+

Webpack 热更新

原理

功能
Webpack Compile将 JS 编译成 Bundle
HMR Server将热更新的文件输出给 HMR Runtime
Bundle Server提供文件在浏览器的访问
HMR Runtime会被注入到浏览器,更新文件的变化
bundle.js构建输出的文件

webpack-hmr

webpack-dev-server

WDS 无需手动刷新浏览器,不输出文件,而是放在内存中。无磁盘 IO 问题,构建速度更快。

配合 HotModuleReplacementPlugin 插件

webpack-dev-server(WDS)的功能提供 bundle server 的能力,就是生成的 bundle.js 文件可以通过 localhost://xxx 的方式去访问,另外 WDS 也提供 livereload(浏览器的自动刷新)。

HotModuleReplacementPlugin 的作用是提供 HMR 的 runtime,并且将 runtime 注入到 bundle.js 代码里面去。一旦磁盘里面的文件修改,那么 HMR server 会将有修改的 js module 信息发送给 HMR runtime,然后 HMR runtime 去局部更新页面的代码。因此这种方式可以不用刷新浏览器。

单独写两个包也是出于功能的解耦来考虑的。简单来说就是:HotModuleReplacementPlugin 包给 webpack-dev-server 提供了热更新的能力。

webpack-dev-middleware

WDM 将 webpack 输出的文件传输给服务器,适用于灵活的定制场景

const express = require('express');
+const webpack = require('webpack');
+const webpackDevMiddleware = requrie('webpack-dev-middleware');
+
+const app = express();
+const config = require('./webpack.config.js');
+const compiler = webpack(config);
+
+app.use(webpackDevMiddleware(compiler, {
+  publicPath: config.output.publicPath,
+}));
+
+app.listen(3000, function() {
+  console.log('App is running at http://localhost:3000\n');
+});
+``
+
+ +
+ + + diff --git a/Bundler/index.html b/Bundler/index.html new file mode 100644 index 0000000..91ecdef --- /dev/null +++ b/Bundler/index.html @@ -0,0 +1,17 @@ + + + + + + + Bunder | LOFT + + + + +

为什么需要构建工具?

  • 转换 ES6 语法
  • 转换 JSX
  • CSS 前缀补全/预处理器(LESS/SASS)
  • 压缩混淆
  • 图片压缩

构建演变发展

从早期的 HTML+JS+切图+CSS 在线压缩资源文件等,再到 YUI Compressor 进行压缩;requirejs 等模块化概念提出后逐渐演变到 grant,打包过程中会将打包结果暂时存储在本地磁盘中,因为磁盘 IO 的问题,打包速度较慢;后来产生 gulp,将结果存放在内存中,不经过磁盘 IO,加速打包速度,同期百度推出的 fis3 打包工具;现阶段使用最为广泛的为 webpack、rollup、parcel、vite 等。

+ +
+ + + diff --git a/JSBooks/ECMAScript/ES10.html b/JSBooks/ECMAScript/ES10.html new file mode 100644 index 0000000..e41e021 --- /dev/null +++ b/JSBooks/ECMAScript/ES10.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +

trimStart()/trimEnd() ES10

trimStart()消除头部空格。 返回新字符串,不修改原始字符串。 trimEnd()消除尾部空格。 返回新字符串,不修改原始字符串。

+ +
+ + + diff --git a/JSBooks/ECMAScript/ES11.html b/JSBooks/ECMAScript/ES11.html new file mode 100644 index 0000000..0e9c2be --- /dev/null +++ b/JSBooks/ECMAScript/ES11.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +

matchAll()

+ +
+ + + diff --git a/JSBooks/ECMAScript/ES12.html b/JSBooks/ECMAScript/ES12.html new file mode 100644 index 0000000..a990340 --- /dev/null +++ b/JSBooks/ECMAScript/ES12.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +
FLOOR
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES13.html b/JSBooks/ECMAScript/ES13.html new file mode 100644 index 0000000..95033c4 --- /dev/null +++ b/JSBooks/ECMAScript/ES13.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +

at() ES13

接收一个整数作为参数,返回参数指定位置的字符,支持负索引

FLOOR
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/array.html b/JSBooks/ECMAScript/ES6/array.html new file mode 100644 index 0000000..dc78d78 --- /dev/null +++ b/JSBooks/ECMAScript/ES6/array.html @@ -0,0 +1,144 @@ + + + + + + + LOFT + + + + +

Array 扩展

扩展运算符(spread) ...

用于将一个数组转为逗号分隔的参数序列。其底层调用的是[Symbol.iterator]遍历方法。

Run
console.log([...['a', 'b', 'c']]); // ["a", "b", "c"]
+
+function foo(a, ...rest) {
+  console.log([a, ...rest]);
+}
+foo('a', 'b', 'c', 'd', 'e'); // ["a", "b", "c", "d", "e"]
+

Array.from()


Array.from<T>(iterable: Iterable<T> | ArrayLike<T>): T[];
+Array.from<T, U>(
+  iterable: Iterable<T> | ArrayLike<T>,
+  mapFn: (v: T, k: number) => U,
+  thisArg?: any
+): U[];
+

用于将 类数组对象(具有length属性)可遍历对象(具有[Symbol.iterator]接口) 转换为真数组。

Run
const arrayLike1 = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
+console.log(Array.from(arrayLike1)); // ['a', 'b', 'c']
+
+console.log(Array.from({ length: 2 })); // [undefined, undefined]
+
+console.log(Array.from('What?')); // ['W', 'h', 'a', 't', '?']
+
+console.log(Array.from({ length: 3 }, (_, i) => i + 1)); // [1, 2, 3]
+

map()


map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
+

在数组的每个元素上调用已定义的回调函数,并返回包含结果的新数组。

Run
const arr = [1, 2, 3, 4, 5, 6];
+const arr2 = arr.map((value, index) => value * index);
+console.log(arr2); // [0, 2, 6, 12, 20, 30]
+

forEach()


forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
+

为数组中的每个元素执行指定的操作。

Run
const arr = [1, 2, 3, 4, 5, 6];
+const arr2 = [];
+arr.forEach((value, index) => {
+  arr2.push(value * index);
+});
+console.log(arr2); // [0, 2, 6, 12, 20, 30]
+

filter()


filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
+filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
+

返回数组中满足回调函数中指定条件的元素。

Run
const arr = [1, 2, 3, 4, 5, 6];
+const arr2 = arr.filter((value, index) => value % 2 === 0);
+console.log(arr2); // [2, 4, 6]
+

reduce()


reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
+reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
+reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
+

为数组中的所有元素调用指定的回调函数。回调函数的返回值是累积的结果,并在下次调用回调函数时作为第一个参数提供。

Run
const arr = [1, 2, 3, 4, 5, 6];
+const sum = arr.reduce((prev, curr) => {
+  console.log([prev, curr]);
+  prev += curr;
+  return prev;
+}, 0);
+// [0, 1]
+// [1, 2]
+// [3, 3]
+// [6, 4]
+// [10, 5]
+// [15, 6]
+console.log(sum); // 21
+

实现 compose 函数

Run
const add = (x, y) => x + y;
+const add10 = x => x + 10;
+const multiply10 = x => x * 10;
+
+const compose = (...fns) => fns.reduce((f, g) => {
+  return (...args) => f(g(...args));
+});
+
+const f = compose(add10, multiply10, add); // 从右往左求值
+console.log(f(5, 5));
+

reduceRight()


reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
+reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
+reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
+

降序为数组中的所有元素调用指定的回调函数。回调函数的返回值是累积的结果,并在下次调用回调函数时作为第一个参数提供。

Run
const arr = [1, 2, 3, 4, 5, 6];
+const sum = arr.reduceRight((prev, curr) => {
+  console.log([prev, curr]);
+  prev += curr;
+  return prev;
+}, 0);
+// [0, 6]
+// [6, 5]
+// [11, 4]
+// [15, 3]
+// [18, 2]
+// [20, 1]
+console.log(sum); // 21
+

实现 compose 函数

Run
const add = (x, y) => x + y;
+const add10 = x => x + 10;
+const multiply10 = x => x * 10;
+
+const compose = (...fns) => {
+  return (...args) => fns.reduceRight((pre, cur) => {
+    return Array.isArray(pre) ? cur(...pre) : cur(pre);
+  }, args);
+}
+
+const f = compose(add10, multiply10, add); // 从右往左求值
+console.log(f(5, 5));
+

find()


find<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
+find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
+

返回数组中第一个符合条件的元素,否则返回undefined

Run
const arr = [1, 2, 3, 4, 5, 6];
+const target = arr.find((value, index) => value % 2 === 0);
+console.log(target); // 2
+

findIndex()


findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number;
+

返回数组中第一个符合条件的元素索引,否则返回-1

Run
const arr = [1, 2, 3, 4, 5, 6];
+const target = arr.findIndex((value, index) => value % 2 === 0);
+console.log(target); // 1
+

fill()


fill(value: T, start?: number, end?: number): this;
+

将所有数组元素从开始到结束索引(左闭右开[start, end))更改为静态值,并返回修改后的数组。

Run
const arr = [1, 2, 3, 4, 5, 6];
+const arr2 = arr.fill('a', 2, 4);
+console.log(arr2); // [1, 2, 'a', 'a', 5, 6]
+

some()


some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
+

数组中只要有一个元素符合条件就返回 true,都不符合返回 false

Run
const arr = [1, 2, 3, 4, 5, 6];
+console.log(arr.some((value, index) => value % 2 === 0)); // true
+

every()


every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[];
+every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
+

数组中所有元素均符合条件就返回 true,有一个元素不符合返回 false

Run
const arr = [1, 2, 3, 4, 5, 6];
+console.log(arr.every((value, index) => value % 2 === 0)); // false
+

copyWithin()


/**
+ * @param {target} 从该位置开始替换数据。如果为负值,则从后往前进行。
+ * @param {start} 从该位置开始读取数据,默认为0。如果为负值,则从后往前进行。
+ * @param {end} 到该位置前停止读取数据,默认为数组长度。如果为负值,则从后往前进行。
+ */
+copyWithin(target: number, start?: number, end?: number): this;
+

在当前数组内部,将指定位置(左闭右开[start, end))的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。

Run
const arr = [1, 2, 3, 4, 5, 6];
+arr.copyWithin(0, 3);
+console.log(arr); // [4, 5, 6, 4, 5, 6]
+
+const arr2 = [1, 2, 3, 4, 5, 6];
+arr.copyWithin(0, -3, -1);
+console.log(arr2); // [4, 5, 3, 4, 5, 6]
+
+const arrayLike = { 4: 'foo', length: 5 };
+[].copyWithin.call(arrayLike, 0, 4);
+console.log(arrayLike); // { 0: 'foo', 4: 'foo', length: 5 }
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/class.html b/JSBooks/ECMAScript/ES6/class.html new file mode 100644 index 0000000..62c3800 --- /dev/null +++ b/JSBooks/ECMAScript/ES6/class.html @@ -0,0 +1,120 @@ + + + + + + + LOFT + + + + +

Class 类

Class 创建

JavaScript 中生成实例对象的传统方法如下:

Run
function Dog(name) {
+  this.name = name;
+}
+
+Dog.prototype.bark = function () {
+  console.log(`${this.name} is barking.`);
+};
+
+const dog = new Dog('Tom');
+dog.bark(); // 'Tom is barking.'
+

ES6 中引入了 Class 类的概念。通过class关键字来定义类,如下示例。类必须使用new命令调用。其中的constructor()方法,即为构造方法,在调用new命令时自动调用该方法。this代表实例对象。

Run
class Dog {
+  constructor(name) {
+    this.name = name;
+  }
+
+  bark() {
+    console.log(`${this.name} is barking.`);
+  }
+}
+
+const dog = new Dog('Tom');
+dog.bark(); // 'Tom is barking.'
+

Class 静态方法

直接通过类来调用的方法,该方法不会被类的实例继承。父类的静态方法可以被子类继承。

Run
class Animal {
+  static getInstance() {
+    return 'instance';
+  }
+}
+
+console.log(Animal.getInstance()); // 'instance'
+const animal = new Animal();
+try {
+  animal.getInstance(); // TypeError: animal.getInstance is not a function
+} catch (e) {
+  console.log(e);
+}
+
+class Dog extends Animal {}
+
+console.log(Dog.getInstance()); // 'instance'
+

new.target属性

用来确定构造函数是如何调用的。若构造函数不是通过new命令或者Reflect.construct()调用的,返回undefined,否则返回new命令作用的那个构造函数。

Run
function Dog(name) {
+  if (new.target !== Dog)
+    throw new Error("Class constructor Dog cannot be invoked without 'new'");
+  this.name = name;
+}
+
+let dog = new Dog('Tom'); // Ok
+dog = Dog.call(dog, 'Snoopy');
+// Error: Class constructor Dog cannot be invoked without 'new'
+

提示

当子类继承父类时,new.target返回子类。利用该特点,可以写出必须继承后才能使用的类。

Run
class Animal {
+  constructor() {
+    console.log(new.target);
+  }
+}
+class Dog extends Animal {}
+
+const dog = new Dog(); // class Dog extends Animal {}
+

Class 继承

ES5

ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面。


Run
function Parent() {}
+
+function Child() {}
+Child.prototype = new Parent();
+Child.prototype.constructor = Child;
+
+const child = new Child();
+
+console.log(Child.prototype.constructor === Child);
+console.log(child.constructor === Child);
+console.log(child.__proto__ === Child.prototype);
+console.log(Child.prototype.__proto__ === Parent.prototype);
+// true
+// true
+// true
+// true
+

ES6

ES6 的继承机制,是先将父类的属性和方法,添加到一个空对象上面,然后再将该对象作为子类的实例。这就表示新建子类实例时,父类的构造函数必定先运行一次。由上面new.target的示例代码可看出:如果子类没有定义constructor()方法,则会默认添加并执行super()方法。


Run
class Parent {}
+
+class Child extends Parent {}
+
+const child = new Child();
+
+console.log(Child.prototype.constructor === Child);
+console.log(child.constructor === Child);
+console.log(child.__proto__ === Child.prototype);
+console.log(Child.__proto__ === Parent);
+console.log(Child.prototype.__proto__ === Parent.prototype);
+// true
+// true
+// true
+// true
+// true
+

Run
class Animal {
+  constructor() {
+    console.log('Animal');
+  }
+}
+class Dog extends Animal {
+  constructor() {
+    super();
+    console.log('Dog');
+  }
+}
+
+const dog = new Dog();
+// Animal
+// Dog
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/destructuring.html b/JSBooks/ECMAScript/ES6/destructuring.html new file mode 100644 index 0000000..0c56395 --- /dev/null +++ b/JSBooks/ECMAScript/ES6/destructuring.html @@ -0,0 +1,118 @@ + + + + + + + LOFT + + + + +

变量的解构赋值

解构赋值是对赋值运算符的扩展。它是对数组或者对象进行模式匹配,只要等号两边的模式相同,左边的变量就会被赋予对应的值。 解构赋值时,只要等号右边的值不是对象或者数组,就先转为对象(undefinednull除外)。

数组解构

数组解构示例:

Run
const [foo, [[bar], baz]] = [1, [[2], 3]];
+console.log(foo); // 1
+console.log(bar); // 2
+console.log(baz); // 3
+
+const [, , third] = [1, 2, 3];
+console.log(third); // 3
+
+const [x, , y, , z] = [1, 2, 3, 4];
+console.log(x); // 1
+console.log(y); // 3
+console.log(z); // undefined
+
+const [head, ...tail] = [1, 2, 3, 4];
+console.log(head); // 1
+console.log(tail); // [2, 3, 4]
+
+const [qux] = new Set([1, 2, 3]);
+console.log(qux); // 1
+

实际上,使用数组形式的解构赋值,等号的右边必须是可迭代的对象结构(具备 [Symbol.iterator] 方法)

Run
// const [x] = 1; // TypeError: 1 is not iterable.
+
+// ****** 字符串也可解构赋值,字符串被转换成类数组对象
+const [y] = '1';
+console.log(y); // '1'
+
+const count = {
+  nums: [1, 2, 3, 4, 5],
+  [Symbol.iterator]() {
+    let i = 0;
+    const length = this.nums.length;
+    const self = this;
+    return {
+      next() {
+        if (i < length) {
+          return { done: false, value: self.nums[i++] };
+        }
+        return { done: true, value: undefined };
+      },
+    };
+  },
+};
+
+const [a, b, c, d, e] = count;
+console.log(a); // 1
+console.log(b); // 2
+console.log(c); // 3
+console.log(d); // 4
+console.log(e); // 5
+

对象解构

对象解构时,变量必须与属性同名,才能取到正确的值,但可以为变量取别名。

Run
const { foo, bar, baz, qux = 3 } = { foo: 1, bar: 2 };
+console.log(foo); // 1
+console.log(bar); // 2
+console.log(baz); // undefined
+console.log(qux); // 3
+
+const {
+  foo: foo2,
+  bar: [bar2, { baz: baz2 }],
+} = { foo: 1, bar: ['Hello', { baz: 'World' }] };
+console.log(foo2); // 1
+console.log(bar2); // 'Hello'
+console.log(baz2); // 'World'
+
+let fo;
+// { fo } = { fo: 'fo' }; // SyntaxError: Unexpected token '='
+
+({ fo } = { fo: 'fo' }); // Ok
+console.log(fo); // 'fo'
+

函数参数解构

函数参数亦可解构赋值。

Run
const sum = ([a, b]) => console.log(a + b);
+sum([1, 2]); // 3
+

默认值

解构赋值允许指定默认值。但只有对应位置成员的值严格等于undefined,默认值才会生效。

Run
const [x = 'a'] = [];
+console.log(x); // 'a'
+
+const [y = 1] = [undefined];
+console.log(y); // 1
+
+const [z = 2] = [null];
+console.log(z); // null
+
+const { foo = 1 } = {};
+console.log(foo); // 1
+

如果默认值是一个表达式,则这个表达式是惰性求值的,只有在需要用到时才会求值。

Run
function foo() {
+  console.log('123');
+}
+let [x = foo()] = ['a'];
+
+// ==
+/*
+  let x;
+  if (['a'][0] === undefined) {
+    x = f();
+  } else {
+    x = ['a'][0];
+  }
+*/
+

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

Run
let [x = 1, y = x] = [];
+console.log(`x=${x}; y=${y}`); // x=1; y=1
+let [x1 = 1, y1 = x1] = [2];
+console.log(`x1=${x1}; y1=${y1}`); // x1=2; y1=2
+let [x2 = 1, y2 = x2] = [1, 2];
+console.log(`x2=${x2}; y2=${y2}`); // x2=1; y2=2
+let [x3 = y3, y3 = 1] = []; // ReferenceError: Cannot access 'y3' before initialization
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/function.html b/JSBooks/ECMAScript/ES6/function.html new file mode 100644 index 0000000..c9eab6d --- /dev/null +++ b/JSBooks/ECMAScript/ES6/function.html @@ -0,0 +1,98 @@ + + + + + + + LOFT + + + + +

Function 扩展

函数参数默认值

在函数参数后接function ([参数] = [默认值]) {}的形式。参数默认值是惰性求值

Run
function Foo(x = 0, y = 0) {
+  this.x = x;
+  this.y = y;
+}
+
+const foo = new Foo();
+console.log(foo); // { x: 0, y: 0 }
+
+function Bar({ x = 4, y = 3 }) {
+  this.x = x;
+  this.y = y;
+}
+
+const bar = new Bar({});
+console.log(bar); // { x: 4, y: 3 }
+const bar1 = new Bar({ x: 1 });
+console.log(bar1); // { x: 1, y: 3 }
+const bar2 = new Bar();
+// TypeError: nnot read properties of undefined (reading 'x')
+

rest 参数

使用function (...[变量]) {}的形式,用于获取函数的剩余参数,替代原来的arguments对象 。

Run
function add(...params) {
+  let sum = 0;
+  params.forEach(function (param) {
+    sum += param;
+  });
+  console.log(sum);
+}
+add(1, 2, 3, 4, 5); // 15
+

注意

rest 参数后不可再有其他参数。

Run
function log(a, ...rest, z) {}
+// SntaxError: Rest parameter must be last formal parameter
+

提示

函数的length属性表示函数的参数个数。但 rest 参数不包含在内。

Run
function foo(a) {}
+console.log(foo.length); // 1
+function foo1(...rest) {}
+console.log(foo1.length); // 0
+function foo2(a, ...rest) {}
+console.log(foo2.length); // 1
+

name 属性

返回函数的函数名。

Run
function foo() {}
+console.log(foo.name); // 'foo'
+
+console.log((function () {}).name); // ''
+
+const bar = function () {};
+// ES5
+console.log(bar.name); // ''
+// ES6
+console.log(bar.name); // 'bar'
+
+const baz = new Function();
+console.log(baz.name); // 'anonymous'
+
+function qux() {}
+console.log(qux.bind({}).name); // 'bound qux'
+

箭头函数

使用() => {}定义函数。直接返回对象时,必须用括号包裹对象。

const foo = () => 1 + 2;
+// Desugars into
+const foo2 = function () {
+  return 1 + 2;
+};
+const getObject = () => ({ foo: 'foo' });
+
  • 箭头函数的 this 指向; 对于普通函数,函数内部的this指向当前函数的调用对象。 但对于箭头函数,函数内部的this指向定义时上层作用域中的this。简单来说就是,箭头函数的this指向是固定的,指向定义时上层作用域中的this。普通函数的this指向会因为调用该函数的对象的改变而改变。

    Run
    function foo() {
    +  setTimeout(function () {
    +    console.log('id:', this.id);
    +  });
    +  setTimeout(() => {
    +    console.log('id:', this.id);
    +  });
    +}
    +
    +var id = 21;
    +
    +foo.call({ id: 42 });
    +// id: 21
    +// id: 42
    +
  • 没有argumentssupernew.target变量;arguments应使用 rest 参数。

    Run
    const foo = () => {
    +  console.log(arguments);
    +};
    +// foo(1, '2'); // ReferenceError: arguments is not defined
    +const bar = (...rest) => {
    +  console.log(rest);
    +};
    +bar(1, '2'); // [1, '2']
    +
  • 不能用作构造函数;不能使用new命令。

    Run
    const Foo = () => {};
    +new Foo(); // TypeError: Foo is not a constructor
    +
  • 不能使用yield命令;即箭头函数不能用作 Generator 函数。

+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/let-const.html b/JSBooks/ECMAScript/ES6/let-const.html new file mode 100644 index 0000000..8172b15 --- /dev/null +++ b/JSBooks/ECMAScript/ES6/let-const.html @@ -0,0 +1,122 @@ + + + + + + + LOFT + + + + +

let 声明

用法类似var,最明显的区别是,let声明的范围是块级作用域,而var声明的范围是函数作用域。

Run
if (true) {
+  var name = 'Moriarty';
+  console.log(name); // Moriarty
+}
+console.log(name); // Moriarty
+
+if (true) {
+  let age = '18';
+  console.log(age); // 18
+}
+console.log(age); // ReferenceError: age is not defined
+

let不允许同一个块级作用域中出现冗余声明。混用letvar亦会声明冗余报错。

Run
var name;
+var name;
+
+let age;
+let age; // SyntaxError: Identifier 'age' has already been declared
+
Run
  var gender;
+  let gender; // SyntaxError: Identifier 'gender' has already been declared
+

暂时性死区(Temporal dead zone)

letvar的另一个重要区别是,let声明的变量不会在作用域中被提升。

Run
console.log(name); // undefined
+var name = 'Moriarty';
+// name被提升
+// Desugars into
+var _name;
+console.log(_name); // undefined
+_name = 'Moriarty';
+
+// age不会被提升
+console.log(age); // ReferenceError: age is not defined
+let age = 18;
+

全局声明

var声明不同,let在全局作用域中声明的变量 不会 成为window对象的属性。

Run
var name = 'Moriarty';
+console.log(window.name); // Moriarty
+
+let age = 18;
+console.log(window.age); // undefined
+

块级作用域与函数声明

明确允许在块级作用域之中声明函数。块级作用域之中,函数声明语句的行为类似于 let,在块级作用域之外不可引用。

function foo() {
+  console.log('foo');
+}
+
+(function () {
+  if (false) {
+    function foo() {
+      console.log('foo in');
+    }
+  }
+  foo(); // 'foo in'
+})();
+// ==等价于 if内声明的函数foo被提升到函数头部
+function foo() {
+  console.log('foo');
+}
+
+(function () {
+  function foo() {
+    console.log('foo in');
+  }
+  if (false) {
+  }
+  foo(); // 'foo in'
+})();
+
Run
function bar() {
+  console.log('bar');
+}
+
+(function () {
+  if (false) {
+    function bar() {
+      console.log('bar in');
+    }
+  }
+  bar(); // TypeError:bar is not a function
+})();
+

提示

不能使用 let 进行条件式声明是件好事,因为条件声明是一种反模式,它会让程序变得更难理解

for 循环中使用 let

for循环中的计数器非常适合使用let命令。

Run
for (var i = 0; i < 5; i++) {
+  setTimeout(() => {
+    console.log(i);
+  });
+}
+// 输出为:5,5,5,5,5
+// 所有i均指向同一个i变量,因此最终输出值为结束条件时的最终值5
+
+for (let i = 0; i < 5; i++) {
+  setTimeout(() => {
+    console.log(i);
+  });
+}
+// 输出为:0,1,2,3,4
+// 用let声明迭代变量,每次迭代循环声明一个新的迭代变量,
+// 每个setTimeout引用的都是不同的变量实例,因此输出值也为不同值。
+(async () => {
+  for (let i = 0; i < 5; i++) {
+    await new Promise(resolve => {
+      setTimeout(() => {
+        console.log(i);
+        resolve();
+      }, 1000);
+    });
+  }
+})();
+

const 声明

const 行为与 let 基本相同。唯一一个重要的区别是用它声明变量时必须同时初始化变量。 const 声明限制只适用于它指向的变量的引用。

const a = 1;
+a = 2; // TypeError: Assignment to constant variable.
+
+const b = 1;
+const b = 2; // SyntaxError: Identifier 'b' has already been declared.
+
+const person = {};
+person.name = 'Moriarty';
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/module.html b/JSBooks/ECMAScript/ES6/module.html new file mode 100644 index 0000000..2c05ab9 --- /dev/null +++ b/JSBooks/ECMAScript/ES6/module.html @@ -0,0 +1,168 @@ + + + + + + + LOFT + + + + +

Module

ES6 之前的一些模块加载方案有 CommonJS(用于服务器)和 AMD(用于浏览器)两种。ES6 在语言标准层面实现了模块功能,成为服务器和浏览器的通用模块解决方案。

静态分析

Module 的设计思想是尽量静态化,在编译时就确定模块的依赖关系及输入输出变量。但一些模块需要进行动态加载,这会导致静态分析变得困难。在浏览器中加载模块必须在所有依赖都加载后执行代码。如果没有依赖模块的代码,则必须发送请求并等待网络返回。收到模块代码之后,浏览器必须确定刚收到的模块是否也含有依赖,并递归加载所有依赖,直到所有依赖模块都加载完成。只有所有依赖都加载完成,才会执行入口模块。

循环依赖

一个 JavaScript 应用几乎不可能不包含循环依赖,这时模块的加载顺序可能会导致一些副作用。

require('./moduleD');
+require('./moduleB');
+console.log('moduleA');
+
require('./moduleA');
+require('./moduleC');
+console.log('moduleB');
+
require('./moduleB');
+require('./moduleD');
+console.log('moduleC');
+
require('./moduleA');
+require('./moduleC');
+console.log('moduleD');
+

当 moduleA 最先加载,打印输出为

"moduleB"

"moduleC"

"moduleD"

"moduleA"

当 moduleC 最先加载,打印输出为

"moduleD"

"moduleA"

"moduleB"

"moduleC"

从上面两图可以看出加载器会进行深度优先的依赖加载。

CommonJS

CommonJS 规范描述同步声明依赖的模块定义。主要用于服务端实现模块化代码,不能直接在浏览器中运行。要想在浏览器中使用 CommonJS,常用的解决方案是提前把模块文件打包好,把全局属性转换为原生 JavaScript 结构,将模块代码封装在函数闭包中,再通过全面生成的依赖图按顺序进行模块打包,最终形成一个文件。

CommonJS 模块定义示例

// moduleA.js
+const { stat, readFileSync } = require('fs');
+// Desugar into
+const fs = requrie('fs');
+const stat = fs.stat;
+const readFileSync = fs.readFileSync;
+
+module.exports = {
+  readFile: readFileSync,
+};
+

一个模块不管通过require()引用多少次,永远都是单例。模块在第一次加载后会被缓存,后续加载会直接获取缓存的模块。

module.exports是一个对象,用于定义模块的公共接口。

module.exports 示例

// moduleA.js
+module.exports = 'This is module A.';
+// moduleB.js
+const moduleB = requrie('./moduleA.js');
+console.log(moduleB); // "This is module A."
+
// moduleA.js
+module.exports = {
+  foo: 'foo',
+  bar() {
+    return 'bar';
+  },
+};
+// 或者
+module.exports.foo = 'foo';
+module.exports.bar = function () {
+  return 'bar';
+};
+// moduleB.js
+const moduleB = requrie('./moduleA.js');
+console.log(moduleB.bar()); // "bar"
+
// moduleA.js
+class Baz {}
+module.exports = Baz;
+// moduleB.js
+const Baz = requrie('./moduleA.js');
+const baz = new Baz();
+console.log(baz); // Baz {}
+
// moduleA.js
+class Qux {}
+module.exports = new Qux();
+// moduleB.js
+const qux = requrie('./moduleA.js');
+console.log(qux); // Qux {}
+

AMD

由于 CommonJS 以服务端作为目标环境,不需要考虑网络延迟问题,可一次性把所有模块都加载到内存中。而当以浏览器作为目标环境时,需要考虑网络延迟的问题,故有了 AMD 即异步模块定义(Asynchronous Module Definition)。 AMD 一般是让模块声明自己的依赖,在浏览器中按需获取依赖,并在依赖加载完成时立即执行依赖模块。

AMD 模块的实现核心是用函数包装模块定义。这样做的好处有:

  • 可防止声明全局变量;
  • 允许加载器库控制何时加载模块;
  • 便于模块代码的移植(包装函数内部模块代码都是原生 JavaScript 结构);

其中包装函数作为全局 define(加载器库定义)的参数。同时 AMD 也支持 require 和 exports 对象,通过使用它们可以在 AMD 模块工厂函数内部定义 CommonJS 风格的模块,AMD 加载器会将它们识别为原生 AMD 结构,而非模块。

AMD 模块定义示例

define('moduleA', ['moduleB', 'require', 'exports'], function (
+  moduleB,
+  require,
+  exports
+) {
+  const moduleC = requrie('moduleC');
+  exports.doThings = moduleC.doThings;
+});
+

UMD

UMD 即通用模块定义(Universal Module Definition)。用于统一 CommonJS 和 AMD 生态方案。实际上,UMD 定义的模块会在启动时检测要使用哪个模块系统,然后进行适当配置,并把所有逻辑包装在一个立即执行函数表达式(IIFE)中。

UMD 模块定义示例

(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD, register as anonymous module
+    define(['Foo'], factory);
+  } else if (typeof module === 'object' && module.exports) {
+    // Node, no support for strict CommonJS
+    // used in env Such as Node which support CommonJS with module.exports
+    module.exports = factory(require('Foo'));
+  } else {
+    // Browser context (root should be window)
+    root.returnExports = factory(root.Foo);
+  }
+})(this, function (Foo) {
+  return {};
+  // return () => {}
+});
+

ES6 模块 (ESM)

script module 标签

带有 type="module" 属性的 <script> 标签会告知浏览器其相关代码应作为模块执行,而非传统脚本执行。

script module 标签示例

<script type="module">
+  // some module code
+</script>
+
+<script type="module" src="path/to/module.js"></script>
+

浏览器在解析到 <script type="module"></script> 标签后会立即下载模块文件,但执行时机会延迟到文档解析完成。其在页面中的出现顺序即为它的执行顺序。与 <script defer> 一样,修改标签的位置,无论是在 <head> 还是 <body> 中,都只会影响文件的加载时机,不会影响模块的加载时机。

Run
<script type="module">
+  console.log('a');
+  var a = 'local variable';
+  export const foo = { foo: 'foo' };
+</script>
+<script type="module">
+  console.log('b');
+  export const bar = { bar: 'bar' };
+</script>
+<script>
+  console.log('c');
+  try {
+    a;
+  } catch (e) {
+    console.log(e);
+  }
+</script>
+

运行代码可以看到打印顺序为'c', 'a', 'b'

模块的加载是异步的,从顶级模块异步递归加载,直到整个依赖图都解析完成。

模块行为

ES6 模块行为
1模块是单例,只能加载一次
2模块可以定义公共接口,其他模块基于该公共接口进行观察和交互
3模块可以请求加载(通过 CORS)其他模块
4模块支持循环依赖
5模块是异步加载并执行的
6模块代码只在加载后执行
7默认在严格模式下执行
8不共享全局命名空间,每个 ESM 都是单独的私有作用域
9模块顶级this的值为undefined
10模块中var声明不会添加到window对象上

模块导出

ES6 模块导出有两种:命名导出默认导出

1.命名导出

将模块作为容器,可以在一个模块中声明多个命名导出。

const foo = 'foo';
+export { foo };
+
+export const bar = 'bar';
+
+export function () {}
+
+export const qux = () => {}
+
+export class Dog {}
+
+// 导出别名
+const baz = 'baz';
+export { baz as Baz };
+

2.默认导出

每个模块只能存在一个默认导出。默认导出和命名导出不冲突,可以在一个模块中同时定义两种导出。

const foo = 'foo';
+export default foo;
+// OR
+const foo = 'foo';
+export { foo as default };
+// ES6模块会识别作为别名提供的default关键字,虽然使用命名导出的形式导出,但实际会成为默认导出。
+

模块导入

不同的导出方式对应不同的导入方式。模块标识符可以是相对于当前模块的相对路径,也可以是模块文件的绝对路径,必须是纯字符串,不可以动态拼接(因为 import 是静态执行的,这些只有在运行时才能得到的结果不能使用)。导入对模块而言是只读的,不能直接修改导出的值,但可以修改导出对象的属性。

import foo from './foo.js';
+// OR
+import { default as foo } from './foo.js';
+
+// 不需要模块特定的导出,加载和执行模块并利用其副作用
+import './foo.js';
+
+/* bar.js
+  const foo = 'foo';
+  const bar = 'bar';
+  export { foo, bar }
+*/
+// 命名导出使用*批量获取并赋值给保存导出集合的别名
+import * as Bar from './bar.js';
+console.log(Bar.foo); // 'foo'
+console.log(Bar.bar); // 'bar'
+
+import { foo, bar as Bar } from './bar.js';
+console.log(foo); // 'foo';
+console.log(Bar); // 'bar';
+

模块转移导出

模块导入的值可以直接通过管道转移到导出。

// 外部模块的所有命名导出作为当前模块的命名导出
+export * from './bar.js';
+
+export { foo, bar as Bar } from './bar.js';
+
+// 外部模块的默认导出可直接作为当前模块的默认导出
+export { default } from './bar.js';
+
+// 外部模块的命名导出也可指定为当前模块的默认导出
+export { foo as default } from './bar.js';
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/object.html b/JSBooks/ECMAScript/ES6/object.html new file mode 100644 index 0000000..baf7ebe --- /dev/null +++ b/JSBooks/ECMAScript/ES6/object.html @@ -0,0 +1,81 @@ + + + + + + + LOFT + + + + +

Object 扩展

扩展运算符(spread) ...

拷贝参数对象的所有可枚举属性到当前对象之中。扩展运算符后面不是对象的则会自动转为对象。

Run
const foo = { a: 1, b: 2, c: 3 };
+const bar = { ...foo };
+console.log(bar); // { a: 1, b: 2, c: 3 }
+
+console.log(({ ...true })); // {}
+// ===
+console.log(({ ...Object(true) }));
+

声明简写


const foo = 'foo';
+const bar = 'bar';
+const baz = { foo, bar };
+

Object.assign()

用于将所有可枚举属性的值从一个或多个源对象分配到目标对象,并返回目标对象。

Run
const foo = { a: 1 };
+const bar = { b: 2 };
+const baz = { b: 3, c: 4 };
+Object.assign(foo, bar, baz);
+console.log(foo); // { a: 1, b: 3, c: 4 }
+

Object.is()

用来比较两个值是否严格相等,与严格比较运算符===行为基本一致,差异在于对+0-0NaN自身的比较行为不同。

Run
console.log(+0 === -0);// true
+console.log(NaN === NaN);// false
+console.log(Object.is(+0, -0)); // false
+console.log(Object.is(NaN, NaN)); // true
+
+// Desugars into
+
+Object.defineProperty(Object, 'is', {
+  configurable: true,
+  enumerable: false,
+  writable: true,
+  value: function (x, y) {
+    if (x === y) {
+      return x !== 0 || 1 / x === 1 / y; // +0,-0
+    }
+    return x !== x && y !== y; // NaN
+  },
+});
+

Object.keys()

返回对象自身的所有可枚举属性的键名。

Run
const foo = { a: 1, b: 2, c: 3 };
+Object.defineProperty(foo, 'd', {
+  configurable: true,
+  writable: true,
+  enumerable: false,
+  value: 4,
+});
+console.log(Object.keys(foo)); // ['a', 'b', 'c']
+

__proto__属性

__proto__属性是为了兼容浏览器环境才规定的。在新的代码环境中不应该使用该属性。 使用Object.getPrototypeOf()Object.setPrototypeOf()来进行读写操作。

Object.getPrototypeOf()

返回对象的原型对象(prototype)。如果参数不是对象(除去undefinednull),会先转为对象。

Run
function Dog() {}
+
+const dog = new Dog();
+console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true
+
+console.log(Object.getPrototypeOf('foo')); // String { length: 0, [[PrimitiveValue]]: "" }
+

Object.setPrototypeOf()

设置对象的原型对象(prototype)。返回第一个参数对象自身。如果第一个参数不是对象(除去undefinednull),会先转为对象。

Run
// ==
+function setPrototypeOf(obj, proto) {
+  obj.__proto__ = proto;
+  return obj;
+}
+
+const Dog = {};
+const dog = { name: 'foo' };
+Object.setPrototypeOf(dog, Dog);
+Dog.gender = 'male';
+Dog.eat = function () {
+  console.log([this.name, 'is eating.'].join(' '));
+};
+
+console.log(dog.name); // 'foo'
+console.log(dog.gender); // 'male'
+dog.eat(); // 'foo is eating'
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/promise.html b/JSBooks/ECMAScript/ES6/promise.html new file mode 100644 index 0000000..45df129 --- /dev/null +++ b/JSBooks/ECMAScript/ES6/promise.html @@ -0,0 +1,272 @@ + + + + + + + LOFT + + + + +

Promise

Promise 是一种异步编程的解决方案,相比传统依靠回调函数和事件的解决方案更加合理。

自己实现一个Promise以便更好理解Promise的流程。

  1. Promise有三种状态: PENDING, FULFILLED, REJECTED。初始状态为PENDING

状态的改变只有两种可能:

  • PENDING -> FULFILLED
  • PENDING -> REJECTED

状态改变后不得过渡到任何其他状态。

  1. Promise构造函数接收一个函数executor作为参数,该函数有两个参数resolvereject,这两个参数为两个函数,供Promise内部调用;

  2. Promise实例提供then方法获取其 value 或者 reasonthen方法接收两个函数onFulfilledonRejected作为可选参数;

promise.then(onFulfilled, onRejected);

  • onFulfilled: 必须在promise fulfilled 之后调用且只可调用一次,同时将promisevalue作为其第一个参数。

  • onRejected : 必须再promise rejected 之后调用且只可调用一次,同时将promisereason作为其第一个参数。

  • 需要确保 onFulfilledonRejected异步执行, 在事件循环(Event Loop)中被调用之后,并使用一个新的堆栈。可以使用宏任务(setTimeoutsetImmediate)或者微任务(MutationObserverprocess.nextTick)实现。

  • onFulfilledonRejected必须被作为函数调用,且无this值。严格模式下 this 值为 undefined,非严格模式下 this 值为全局对象。

  • then方法可能会被同一promise调用多次,当promise fulfilled 或 rejected 之后,所有对应的onFulfilledonRejected方法必须按照then的原始调用顺序进行调用。

  • then方法必须返回一个 promise, promise2 = promise1.then(onFulfilled, onRejected);

  1. Promise 关键解析过程

Promise 关键解析过程 是以一个 promise 和一个值做为参数的抽象过程,可表示为[[Resolve]](promise, x)

  • promise === x 时,抛出 TypeError

  • x为一个promise时,promise的状态跟随x状态改变;

  • x为一个对象或者函数时:

    • x.then 是一个函数,调用该函数,第一个参数为 resolve, 第二个参数为 reject
    • x.then 不是函数,则使用x fulfill promise
  • x不是对象也不是函数时,则使用x fulfill promise

Promise 实现


const PENDING = 'pending';
+const FULFILLED = 'fulfilled';
+const REJECTED = 'rejected';
+
+class IPromise {
+  state = PENDING;
+  result = undefined;
+  #onFulfilledCallbacks = [];
+  #onRejectedCallbacks = [];
+  constructor(executor) {
+    const resolve = (value) => {
+      if (this.state === PENDING) {
+        this.state = FULFILLED;
+        this.result = value;
+        this.#onFulfilledCallbacks.forEach((fn) => fn());
+      }
+    };
+    const reject = (reason) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = reason;
+        this.#onRejectedCallbacks.forEach((fn) => fn());
+      }
+    };
+
+    try {
+      executor(resolve, reject);
+    } catch (e) {
+      reject(e);
+    }
+  }
+
+  then(onFulfilled, onRejected) {
+    onFulfilled =
+      typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
+    onRejected =
+      typeof onRejected === 'function'
+        ? onRejected
+        : (reason) => {
+            throw reason;
+          };
+    let promise2 = new IPromise((resolve, reject) => {
+      if (this.state === FULFILLED) {
+        setTimeout(() => {
+          try {
+            const x = onFulfilled(this.result);
+            this.#resolvePromise(promise2, x, resolve, reject);
+          } catch (e) {
+            reject(e);
+          }
+        });
+      }
+      if (this.state === REJECTED) {
+        setTimeout(() => {
+          try {
+            const x = onRejected(this.result);
+            this.#resolvePromise(promise2, x, resolve, reject);
+          } catch (e) {
+            reject(e);
+          }
+        });
+      }
+      if (this.state === PENDING) {
+        this.#onFulfilledCallbacks.push(() => {
+          setTimeout(() => {
+            try {
+              const x = onFulfilled(this.result);
+              this.#resolvePromise(promise2, x, resolve, reject);
+            } catch (e) {
+              reject(e);
+            }
+          });
+        });
+        this.#onRejectedCallbacks.push(() => {
+          setTimeout(() => {
+            try {
+              const x = onRejected(this.result);
+              this.#resolvePromise(promise2, x, resolve, reject);
+            } catch (e) {
+              reject(e);
+            }
+          });
+        });
+      }
+    });
+    return promise2;
+  }
+
+  #resolvePromise(promise2, x, resolve, reject) {
+    if (promise2 === x) {
+      return reject(new TypeError('Chaining cycle detected for promise.'));
+    }
+    let isCalled = false;
+    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
+      try {
+        let then = x.then;
+        if (typeof then === 'function') {
+          then.call(
+            x,
+            (y) => {
+              if (isCalled) return;
+              isCalled = true;
+              this.#resolvePromise(promise2, y, resolve, reject);
+            },
+            (reason) => {
+              if (isCalled) return;
+              isCalled = true;
+              reject(reason);
+            }
+          );
+        } else {
+          resolve(x);
+        }
+      } catch (e) {
+        if (isCalled) return;
+        isCalled = true;
+        reject(e);
+      }
+    } else {
+      resolve(x);
+    }
+  }
+
+  catch(onRejected) {
+    return this.then(null, onRejected);
+  }
+
+  finally(cb) {
+    return this.then(
+      (data) => {
+        cb(data);
+        return data;
+      },
+      (err) => {
+        cb(err);
+        return err;
+      }
+    );
+  }
+
+  static resolve(value) {
+    return new IPromise((resolve) => resolve(value));
+  }
+
+  static reject(value) {
+    return new IPromise((resolve, reject) => reject(value));
+  }
+
+  static all(promises) {
+    return new IPromise((resolve, reject) => {
+      const len = promises.length;
+      const arr = [];
+      let i = 0;
+      const processData = (index, data) => {
+        arr[index] = data;
+        if (++i === len) {
+          resolve(arr);
+        }
+      };
+      for (let i = 0; i < len; i++) {
+        const promise = promises[i];
+        if (typeof promise.then === 'function') {
+          promise.then((data) => processData(i, data), reject);
+        } else {
+          processData(i, promise);
+        }
+      }
+    });
+  }
+
+  static race(promises) {
+    return new IPromise((resolve, reject) => {
+      promises.forEach((promise) => {
+        promise.then(resolve, reject);
+      });
+    });
+  }
+}
+

Promise 测试

我们可以使用工具包测试我们的 Promise 实现。

首先要在 Promise 上添加静态方法 deferred,如下:

class IPromise {
+  // ...省略
+
+  static deferred() {
+    const dfd = {};
+    dfd.promise = new IPromise((resolve, reject) => {
+      dfd.resolve = resolve;
+      dfd.reject = reject;
+    });
+    return dfd;
+  }
+}
+

接下来在终端中安装工具包 promises-aplus-tests,可全局安装。

npm install -g promises-aplus-tests
+
+// 或
+
+yarn add -g promises-aplus-tests
+

接着使用命令测试 Promise 实现即可。

promises-aplus-tests path/to/your-promise.js
+

基本功能


Run
const p = new IPromise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(123);
+  }, 2000);
+});
+p.then((res) => {
+  console.log(`then1: ${res}`);
+})
+  .then((res) => {
+    console.log(`then2: ${res}`);
+    throw '1';
+  })
+  .catch((error) => {
+    console.log(`error: ${error}`);
+  });
+
+console.log(p);
+// IPromise {state: "pending", result: undefined} -> IPromise {state: "fulfilled", result: 123}
+// "then1: 123"
+// "then2: undefined"
+// "error: 1"
+

Promise 循环引用


Run
const p = new IPromise((resolve) => {
+  setTimeout(() => {
+    resolve(456);
+  }, 2000);
+}).then(() => {
+  return p;
+});
+p.finally((res) => {
+  console.log(`finally: ${res}`);
+});
+// "finally: TypeError: Chaining cycle detected fro promise."
+

Promise 静态方法 resolve, reject


Run
const p = IPromise.resolve(1);
+console.log(p);
+
+const p2 = IPromise.reject(0);
+console.log(p2);
+// IPromise {state: "fulfilled", result: 1}
+// IPromise {state: "rejected", result: 0}
+

Promise 静态方法 all


Run
const p = IPromise.all([
+  new IPromise((resolve) => setTimeout(() => resolve(123), 3000)),
+  new IPromise((resolve) => setTimeout(() => resolve(456), 1000)),
+  new IPromise((resolve) => setTimeout(() => resolve(789), 2000)),
+]).then((res) => {
+  console.log(`all: [${res}]`);
+});
+console.log(p);
+// IPromise {state: "fulfilled", result: undefined}
+// "all: [123,456,789]"
+

Promise 静态方法 race


Run
const p = IPromise.race([
+  new IPromise((resolve) => setTimeout(() => resolve(123), 3000)),
+  new IPromise((resolve) => setTimeout(() => resolve(456), 1000)),
+  new IPromise((resolve) => setTimeout(() => resolve(789), 2000)),
+]).then((res) => {
+  console.log(`race: [${res}]`);
+});
+console.log(p);
+// IPromise {state: "fulfilled", result: undefined}
+// "race: [456]"
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/proxy.html b/JSBooks/ECMAScript/ES6/proxy.html new file mode 100644 index 0000000..939936f --- /dev/null +++ b/JSBooks/ECMAScript/ES6/proxy.html @@ -0,0 +1,423 @@ + + + + + + + LOFT + + + + +

Proxy

用于创建一个 Proxy 对象,可理解为代理对象。这个 Proxy 对象允许创建一个可以用来代替原始对象的新对象,但这个新对象可能会重新定义基本的对象操作,例如获取、设置和定义属性值等。代理对象通常用于记录属性访问、验证、格式化或者清除输入等操作。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {ProxyHandler<T>} handler 代理配置对象,每个被代理的操作,都需要提供一个对应的处理函数,用于拦截对应的操作。
+ * @returns {T}
+ */
+new Proxy<T extends object>(target: T, handler: ProxyHandler<T>): T;
+
Run
const obj = {};
+const proxyObj = new Proxy(obj, {
+  get(target, prop, receiver) {
+    console.log(`get ${prop}`);
+    return Reflect.get(target, prop, receiver);
+  },
+  set(target, prop, receiver) {
+    console.log(`set ${prop}`);
+    return Reflect.set(target, prop, receiver);
+  },
+});
+proxyObj.value = 1; // set value
+console.log(++proxyObj.value);
+// get value
+// set value
+// 2
+

提示

Reflect用于获取操作的默认行为。

13 种捕捉器(拦截操作)

操作详情
get(target, prop, receiver)拦截对象属性的读取
set(target, prop, value, receiver)拦截对象属性的设置
has(target, prop)拦截 prop in proxy 操作,返回布尔值
defineProperty(target, prop, descriptor)拦截Object.defineProperty(proxy, prop, descriptor)Object.defineProperties(proxy, descriptors),返回布尔值
deleteProperty(target, prop)拦截delete proxy[prop]操作,返回布尔值
getPrototypeOf(target)拦截Object.getPrototypeOf(proxy),返回一个对象
setPrototypeOf(target, proto)拦截Object.setPrototypeOf(proxy, proto),返回布尔值
getOwnPropertyDescriptor(target, prop)拦截getOwnPropertyDescriptor(proxy, prop),返回属性的描述对象
ownKeys(target)拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in,返回一个数组
preventExtensions(target)拦截Object.preventExtensions(proxy),返回布尔值
isExtensible(target)拦截Object.isExtensible(proxy),返回布尔值
target 为函数时,有下面两种额外操作可拦截
apply(target, object, args)拦截 Proxy 实例作为函数调用的操作,如proxy(..args)proxy.call(object, ...args)proxy.apply(...args)
construct(target, args)拦截 Proxy 实例作为构造函数调用的操作,如new proxy(...args)

get(target, prop, receiver)

get 捕捉器用于拦截属性的读取操作。该方法可以被继承。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {string | symbol} prop 目标属性名称
+ * @param {any} receiver  如果目标属性是一个 getter 访问器属性,则 receiver 就是本次读取属性所在的 this 对象。通常,这就是 proxy 对象本身(或者,如果我们从 proxy 继承,则是从该 proxy 继承的对象)
+ */
+get(target: T, prop: string | symbol, receiver: any): any;
+
Run
const proxy = new Proxy(
+  { name: 'Foo' },
+  {
+    get(target, prop) {
+      if (prop in target) return target[prop];
+      return 'Not exist';
+    },
+  }
+);
+
+console.log(proxy.name); // 'Foo'
+console.log(proxy.age); // 'Not exist'
+console.log(proxy.gender); // 'Not exist'
+

如果拦截的属性描述为不可配置(configurable: false)并且不可写(writable: false),则 Proxy 不能代理该属性。

Run
const obj = {};
+Object.defineProperty(obj, 'name', {
+  value: 'Foo',
+  writable: false,
+  configurable: false,
+});
+
+const proxy = new Proxy(obj, {
+  get(target, prop) {
+    return 'Bar';
+  },
+});
+console.log(proxy.name);
+// TypeError: 'get' on proxy: property 'name' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'Foo' but got 'Bar')
+

示例:实现数组读取负数索引

Run
const proxy = new Proxy([], {
+  get(target, prop, receiver) {
+    if (typeof prop === 'string') {
+      let index = Number(prop);
+      if (!isNaN(index)) {
+        if (index < 0) {
+          prop = String(target.length + index);
+        }
+      } else if (prop === 'push') {
+        return function (...rest) {
+          target.push(...rest);
+        };
+      }
+    }
+    return Reflect.get(target, prop, receiver);
+  },
+});
+proxy.push(1, 2, 3, 4);
+console.log(proxy[-1]); // 4
+console.log(proxy[-2]); // 3
+

set(target, prop, value, receiver)

set 捕捉器用于拦截属性的赋值操作。该方法可以被继承。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {string | symbol} prop 目标属性名称
+ * @param {any} newValue 目标属性的值
+ * @param {any} receiver 与get捕捉器类似,仅与setter访问器属性相关
+ * @returns 如果写入操作成功,应返回true,否则返回false(触发TypeError)
+ */
+set(target: T, prop: string | symbol, newValue: any, receiver: any): boolean;
+

使用 set 捕捉器可以实现数据验证或者数据绑定。

Run
const numbers = [];
+const proxy = new Proxy(numbers, {
+  set(target, prop, val) {
+    if (typeof val === 'number') {
+      target[prop] = val;
+      return true;
+    } else {
+      return false;
+    }
+  },
+});
+
+proxy.push(1);
+proxy.push(2);
+proxy.push('3');
+// TypeError: 'set' on proxy: trap returned falsish for property '2'
+

注意

对于 set 操作,必须在写入操作成功时返回 true

如果返回任何falsy值,则该操作将触发TypeError

has(target, prop)

has捕捉器用来拦截HasProperty操作。如in操作符(for...in循环除外)

/**
+ * @param {T} target 被代理的原始对象
+ * @param {string | symbol} prop 需检查的目标属性名称
+ * @returns {boolean} 返回布尔值
+ */
+has(target: T, prop: string | symbol): boolean;
+

注意

has()方法拦截HasProperty操作,而不是HasOwnProperty操作,即该方法不判断一个属性是对象自身属性还是继承属性。

目标对象不可扩展(Object.isExtensible(proxy)为false)或其属性不可配置(configurable: false),则has()报错。

Run
// 隐藏某些属性
+const proxy1 = new Proxy(
+  { __privateKey: 'private' },
+  {
+    has(target, prop) {
+      if (prop.startsWith('__')) {
+        return false;
+      }
+      return prop in target;
+    },
+  }
+);
+console.log('__privateKey' in proxy1); // false
+
+const nonExtensionObj = { foo: 'foo' };
+Object.preventExtensions(nonExtensionObj);
+
+const proxy2 = new Proxy(nonExtensionObj, {
+  has(target, prop) {
+    return false;
+  },
+});
+console.log('foo' in proxy2);
+// TypeError: 'has' on proxy: trap returned falsish for property 'foo' but the proxy target is not extensible
+

defineProperty(target, prop, descriptor)

defineProperty捕捉器用于拦截Object.defineProperty()操作。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {string | symbol} prop 目标属性名称
+ * @param {PropertyDescriptor} attributes 目标属性描述对象
+ * @returns {boolean} 返回布尔值
+ */
+defineProperty(target: T, prop: string | symbol, attributes: PropertyDescriptor): boolean;
+

注意

目标对象不可扩展(Object.isExtensible(proxy)为false),则defineProperty()不能增加目标对象上不存在的属性。

目标对象的属性不可写(writable: false)或不可配置(configurable: false),则defineProperty()不可改变该属性描述。

Run
const proxy = new Proxy(
+  {},
+  {
+    defineProperty(target, prop, attributes) {
+      return false;
+    },
+  }
+);
+
+proxy.prop = 'no'; // won't work!
+console.log(proxy.prop); // undefined
+

deleteProperty(target, prop)

deleteProperty 捕捉器用于拦截delete操作,当返回false或抛出错误时,delete命令将无法正确执行。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {string | symbol} prop 目标属性名称
+ * @returns {boolean} 返回布尔值
+ */
+deleteProperty(target: T, prop: string | symbol): boolean;
+

注意

目标对象自身的不可配置(configurable: false)属性,不能被deleteProperty方法删除。

Run
const proxy = new Proxy(
+  { __privateKey: 'private' },
+  {
+    deleteProperty(target, prop) {
+      if (prop.startsWith('__')) {
+        throw new Error(
+          `Invalid attempt to delete private property "${prop}".`
+        );
+      }
+      delete target[prop];
+      return true;
+    },
+  }
+);
+delete proxy.__privateKey;
+// Error: Invalid attempt to delete private property "__privateKey".
+

getPrototypeOf(target)

getPrototypeOf捕捉器用来拦截获取对象的原型。拦截以下操作:

  • Object.prototype.__proto__
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • instanceof
/**
+ * @param {T} target 被代理的原始对象
+ * @returns {object | null} 返回值必须是对象或null
+ */
+getPrototypeOf(target: T): object | null;
+

注意

目标对象不可拓展,则getPropertyOf()必须返回目标对象的原型对象。

setPrototypeOf(target, proto)

setPrototypeOf捕捉器用来拦截设置对象的原型。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {object | null} newPrototype 新的原型对象或null
+ * @returns {boolean} 返回布尔值
+ */
+setPrototypeOf(target: T, v: object | null): boolean;
+

注意

目标对象不可拓展,则setPropertyOf()不可改变目标对象的原型。

Run
const nonExtensionObj = { foo: 'foo' };
+Object.preventExtensions(nonExtensionObj);
+
+const proxy = new Proxy(nonExtensionObj, {
+  setPropertyOf(target, v) {
+    return true;
+  },
+});
+const proto = {};
+Object.setPrototypeOf(proxy, proto);
+// TypeError: #<Object> is not extensible
+

getOwnPropertyDescriptor(target, prop)

getOwnPropertyDescriptor 捕捉器用于拦截Object.getOwnPropertyDescriptor()操作。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {string | symbol} prop 目标属性名称
+ * @returns {PropertyDescriptor | undefined} 返回属性描述对象或undefined
+ */
+getOwnPropertyDescriptor(target: T, prop: string | symbol): PropertyDescriptor | undefined;
+
Run
const proxy = new Proxy(
+  { __privateKey: 'private', key: 'public' },
+  {
+    getOwnPropertyDescriptor(target, prop) {
+      if (prop.startsWith('__')) return;
+      return Object.getOwnPropertyDescriptor(target, prop);
+    },
+  }
+);
+
+console.log(Object.getOwnPropertyDescriptor(proxy, 'a')); // undefined
+console.log(Object.getOwnPropertyDescriptor(proxy, '__privateKey')); // undefined
+console.log(Object.getOwnPropertyDescriptor(proxy, 'key'));
+// { value: 'public', writable: true, enumerable: true, configurable: true }
+

ownKeys(target)

/**
+ * @param {T} target 被代理的原始对象
+ * @returns {ArrayLike<string | symbol>}
+ */
+ownKeys(target: T): ArrayLike<string | symbol>;
+

ownKeys捕捉器拦截对象自身属性的读取操作。拦截以下操作:

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in循环

注意

其返回的数组成员,只能是字符串或者Symbol值,否则报错。

目标对象包含不可配置(configurable: false)属性,则该属性必须被返回,否则报错。

目标对象不可扩展(Object.isExtensible(proxy)为false),则返回的数组成员必须包含原对象的所有属性,且不能包含多余的属性,否则报错。

Run
const proxy1 = new Proxy(
+  {},
+  {
+    ownKeys(target) {
+      return [true, undefined, null, {}, []];
+    },
+  }
+);
+
+Object.getOwnPropertyNames(proxy1);
+// TypeError: true is not a valid property name
+
Run
const obj1 = {};
+Object.defineProperty(obj1, 'a', {
+  configurable: false,
+  enumerable: true,
+  value: 10,
+});
+
+const proxy2 = new Proxy(obj1, {
+  ownKeys(target) {
+    return ['b'];
+  },
+});
+
+Object.getOwnPropertyNames(proxy2);
+// TypeError: 'ownKeys' on proxy: trap result did not include 'a'
+
Run
const obj2 = { a: 1 };
+
+Object.preventExtensions(obj2);
+
+const proxy3 = new Proxy(obj2, {
+  ownKeys(target) {
+    return ['a', 'b'];
+  },
+});
+
+Object.getOwnPropertyNames(proxy3);
+// TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible
+

preventExtensions(target)

preventExtensions捕捉器用于拦截Object.preventExtensions()操作。

/**
+ * @param {T} target 被代理的原始对象
+ * @returns {boolean} 返回布尔值
+ */
+preventExtensions(target: T): boolean;
+

注意

目标对象不可扩展(Object.isExtensible(proxy)为false),则preventExtensions才能返回true,否则报错。

Run
const proxy1 = new Proxy(
+  {},
+  {
+    preventExtensions(target) {
+      return true;
+    },
+  }
+);
+Object.preventExtensions(proxy1);
+// TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible
+
Run
const proxy2 = new Proxy(
+  {},
+  {
+    preventExtensions(target) {
+      Object.preventExtensions(target); // Need to invoke.
+      return true;
+    },
+  }
+);
+console.log(Object.preventExtensions(proxy2)); // Proxy {}
+

isExtensible(target)

isExtensible捕捉器用于拦截Object.isExtensible()操作。

/**
+ * @param {T} target 被代理的原始对象
+ * @returns {boolean} 返回布尔值
+ */
+isExtensible(target: T): boolean;
+

注意

返回值必须与目标对象的 isExtension 属性保持一致,否则报错。

Run
const proxy = new Proxy(
+  {},
+  {
+    isExtensible: function (target) {
+      return false;
+    },
+  }
+);
+
+Object.isExtensible(proxy);
+// TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
+

apply(target, object, args)

apply 捕捉器能使代理以函数的方式被调用。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {any} thisArg this的值
+ * @param {any[]} argArray 参数列表
+ * @returns {any}
+ */
+apply(target: T, thisArg: any, argArray: any[]): any;
+

当使用原始函数实现一个包装器时:

Run
function delay(fn, ms) {
+  return function (...rest) {
+    setTimeout(() => fn.apply(this, rest), ms);
+  };
+}
+
+function _sayHi(name) {
+  console.log(`Hello, ${name}!`);
+}
+
+console.log(_sayHi.length); // 1
+
+const sayHi = delay(_sayHi, 3000);
+
+console.log(sayHi.length); // 0
+
+sayHi('Moriarty'); // (3秒后输出), Hello, Moriarty!
+










 



 


但是包装函数不会转发属性读取/写入操作或者任何其他操作。包装后,失去了对原始函数属性的访问,如上段代码1115行。

Run
function delay(fn, ms) {
+  return new Proxy(fn, {
+    apply(target, thisArg, args) {
+      setTimeout(() => target.apply(thisArg, args), ms);
+    },
+  });
+}
+
+function _sayHi(name) {
+  console.log(`Hello, ${name}!`);
+}
+
+console.log(_sayHi.length); // 1
+
+const sayHi = delay(_sayHi, 3000);
+
+console.log(sayHi.length); // 1
+
+sayHi('Moriarty'); // (3秒后输出), Hello, Moriarty!
+












 

 
 
 


使用 Proxy 可以将所有内容都转发到目标对象。可以看到1315行的参数结果相同。不仅仅是调用,代理的所有操作都能被转发到原始函数。

construct(target, args)

construct 捕捉器用于拦截new命令。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {any[]} argArray 参数列表
+ * @param {Function} newTarget new调用的原始构造器
+ * @returns {object} 必须返回一个对象
+ */
+construct(target: T, argArray: any[], newTarget: Function): object;
+

construct()方法中的this指向的是handler,而不是实例对象。

Run
function Animal() {}
+const handler = {
+  construct(target, args) {
+    console.log(`this === handler : ${this === handler}`);
+    console.log(`called: ${args.join(', ')}`);
+    return { value: `${args[0]} has been proxied.` };
+  },
+};
+const AnimalProxy = new Proxy(Animal, handler);
+
+console.log(new AnimalProxy('Animal'));
+// this === handler : true
+// called: Animal
+// { value: 'Animal has been proxied.' }
+

Proxy.revocable()

一个 可撤销 的代理是可以被禁用的代理。

/**
+ * @param {T} target 被代理的原始对象
+ * @param {ProxyHandler<T>} handler 代理配置对象,每个被代理的操作,都需要提供一个对应的处理函数,用于拦截对应的操作。
+ * @returns {{ proxy: T, revoke: () => void }}
+ */
+Proxy.revocable<T extends object>(target: T, handler: ProxyHandler<T>): { proxy: T; revoke: () => void; };
+

假设我们有一个资源,并且想随时关闭对该资源的访问。我们可以做的是将它包装成可一个撤销的代理,没有任何捕捉器。这样的代理会将操作转发给对象,并且我们可以随时将其禁用。

Run
const obj = { data: 'foo' };
+const { proxy, revoke } = Proxy.revocable(obj, {});
+console.log(proxy.data); // foo
+revoke();
+console.log(proxy.data);
+// TypeError: Cannot perform 'get' on a proxy that has been revoked
+

Proxy 的局限性

内建对象 -> 内部插槽(Internal Slot)

许多内建对象,例如 MapSetDatePromise 等,都使用了所谓的“内部插槽(Internal Slot)”。类似于属性,但仅限于内部使用,仅用于规范目的。例如,Map将项目(item)存储在[[MapData]]中。内建方法可以直接访问它们,而不通过[[Get]]/[[Set]]内部方法。所以Proxy无法拦截它们。

Run
const proxy = new Proxy(new Map(), {});
+proxy.set('foo', 1);
+// TypeError: Method map.prototype.set called on incompatible receiver #<Map>
+

在内部,一个 Map 将所有数据存储在其 [[MapData]]内部插槽中。代理对象没有这样的插槽。内建方法 Map.prototype.set 方法试图访问内部属性 this.[[MapData]],但由于 this=proxy,在 proxy 中无法找到它,因此抛错。

但是,将代码如下修改:

Run
const proxy = new Proxy(new Map(), {
+  get(target, prop, receiver) {
+    const value = Reflect.get(...arguments);
+    return typeof value === 'function' ? value.bind(target) : value;
+  },
+});
+proxy.set('foo', 1);
+console.log(proxy.get('foo')); // 1
+

现在它正常工作了,因为 get 捕捉器将函数属性(例如 map.set)绑定到了目标对象(map)本身。

与前面的示例不同,proxy.set(...) 内部 this 的值并不是 proxy,而是原始的 map。因此,当 set 捕捉器的内部实现尝试访问 this.[[MapData]] 内部插槽时,成功执行。

提示

出于历史原因,内建 Array 没有使用内部插槽。所以,代理数组时没有上述问题。

类的私有字段

类的私有字段也是通过内部插槽实现的,不通过[[Get]]/[[Set]]内部方法访问。

Run
class User {
+  #name = 'Guest';
+
+  getName() {
+    return this.#name;
+  }
+}
+const user = new Proxy(new User(), {});
+user.getName();
+// TypeError: Cannot read private member #name from an object whose class did not declare it
+

上面代码在调用getName()是,this的值是代理后的user实例,它没有带有私有字段的内部插槽。

使用bind方案即可恢复正常。

Run
class User {
+  #name = 'Guest';
+
+  getName() {
+    return this.#name;
+  }
+}
+const user = new Proxy(new User(), {
+  get(target, prop, receiver) {
+    const value = Reflect.get(...arguments);
+    return typeof value === 'function' ? value.bind(target) : value;
+  },
+});
+console.log(user.getName()); // 'Guest'
+

注意

bind方案也有缺点: 它将原始对象暴露给该方法,可能使其进一步传递并破坏其他代理功能。

+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/reflect.html b/JSBooks/ECMAScript/ES6/reflect.html new file mode 100644 index 0000000..abb6bb1 --- /dev/null +++ b/JSBooks/ECMAScript/ES6/reflect.html @@ -0,0 +1,49 @@ + + + + + + + LOFT + + + + +

Reflect

Reflect 是一个内建对象,可简化 Proxy 的创建。内建对象的内部方法,例如 [[Get]][[Set]] 等,都是规范性的,不能直接调用。

  • Reflect 对象使调用这些内部方法成为了可能。它的方法是内部方法的最小包装。
  • 修改某些Object方法的返回结构,让其变得更合理。
try {
+  Object.defineProperty(target, property, attributes);
+} catch (e) {}
+
+if (Reflect.defineProperty(target, property, attributes)) {
+} else {
+}
+
  • Object操作都变成函数行为。一些命令式的操作,如name in objdelete obj[name]操作符。使用Reflect.has(obj, name)Reflect.deleteProperty(obj, name)将命令式行为变成函数行为。

  • Reflect对象的方法与Proxy对象的方法一一对应,不管Proxy怎么修改默认行为,总可以在Reflect上获取默认行为。

13 种静态方法与 Proxy 的 13 种捕捉器(拦截操作)

方法描述
Reflect.get(target, prop, receiver)获取对象某个属性值,类似于 target[name]
Reflect.set(target, prop, value, receiver)将值分配给对象属性,返回布尔值,成功返回true
Reflect.has(target, prop)判断对象是否存在某一属性,相当于 in操作符
Reflect.defineProperty(target, prop, attributes)Object.defineProperty()类似,设置成功返回true
Reflect.deleteProperty(target, prop)相当于delete操作符,即delete target[name]
Reflect.getPrototypeOf(target)获取对象的原型,类似于Object.getPrototypeOf()
Reflect.setPrototypeOf(target, prop)设置对象的原型,返回布尔值,设置成功返回true
Reflect.getOwnPropertyDescriptor(target, prop)类似于Object.getOwnPropertyDescriptor(),若存在属性,返回对应属性描述符,否则返回undefined
Reflect.ownKeys(target)返回包含所有自身属性(不含继承属性)的数组。类似于Object.keys(),但不受enumerable影响
Reflect.preventExtensions(target)类似于Object.preventExtensions(),返回布尔值
Reflect.isExtensible(target)类似于Object.isExtensible()
Reflect.apply(target, thisArg, args)对一个函数进行调用操作,类似于Function.prototype.apply()
Reflect.construct(target, args, newTarget)对构造函数进行new操作,相当于执行new target(...args)

使用 Proxy 与 Reflect 实现观察者模式


Run
const handlers = Symbol('handlers');
+
+function Observable(target) {
+  target[handlers] = [];
+
+  target.observe = function (handler) {
+    this[handlers].push(handler);
+  };
+
+  return new Proxy(target, {
+    set(target, prop, value, receiver) {
+      const success = Reflect.set(...arguments);
+      if (success) {
+        target[handlers].forEach((handler) => handler(prop, value));
+      }
+      return success;
+    },
+  });
+}
+
+const user = Observable({});
+user.observe((key, value) => {
+  console.log(`SET ${key}=${value}`);
+});
+user.name = 'Moriarty';
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/string.html b/JSBooks/ECMAScript/ES6/string.html new file mode 100644 index 0000000..381a86f --- /dev/null +++ b/JSBooks/ECMAScript/ES6/string.html @@ -0,0 +1,81 @@ + + + + + + + LOFT + + + + +

字符串扩展

模板字符串

字符串插值

字符串插值通过在${}中使用一个 JavaScript 表达式实现。所有插入的值都会使用toString()方法强制转换为字符串。

Run
const value = 5;
+const exponent = 'second';
+
+const res1 = value + ' to the ' + exponent + ' power is ' + value * value;
+const res2 = `${value} to the ${exponent} power is ${value * value}`;
+
+console.log(res1); // 5 to the second power is 25
+console.log(res2); // 5 to the second power is 25
+

标签函数

通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果。

Run
const a = 6;
+const b = 9;
+
+function tag(strings, aValExpression, bValExpression, sumExpression) {
+  console.log(strings);
+  console.log(aValExpression);
+  console.log(bValExpression);
+  console.log(sumExpression);
+  return 'foobar';
+}
+
+const unTaggedResult = `${a} + ${b} = ${a + b}`;
+const taggedResult = tag`${a} + ${b} = ${a + b}`;
+// ["", " + ", " = ", ""]
+// 6
+// 9
+// 15
+
+console.log(unTaggedResult); // "6 + 9 = 15"
+console.log(taggedResult); // "foobar"
+

原始字符串

使用默认的String.raw标签函数可以直接获取原始的模板字符串内容(如换行符或 Unicode 字符)。

Run
console.log(`\u00A9`); // ©
+console.log(String.raw`\u00A9`); // \u00A9
+
+function printRaw(strings) {
+  console.log('Actual characters:');
+  for (const string of strings) {
+    console.log(string);
+  }
+
+  console.log('Escaped characters:');
+  for (const rawString of strings.raw) {
+    console.log(rawString);
+  }
+}
+
+printRaw`\u00A9${'and'}\n`;
+// Actual characters:
+// ©
+// (换行符)
+// Escaped characters:
+// \u00A9
+// \n
+

字符串新方法

String.fromCodePoint()


String.fromCodePoint(...codePoints: number[]): string;
+

用于识别码点大于0xffff的字符,弥补String.fromCharCode()方法的不足。

String.raw()


String.raw(template: { raw: readonly string[] | ArrayLike<string>}, ...substitutions: any[]): string;
+

codePointAt()


codePointAt(pos: number): number | undefined;
+

实例方法,正确处理 4 个字节存储的字符,返回一个字符的码点。与String.fromCodePoint()方法相反。

normalize()


normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string;
+

用来将字符的不同表示方法统一为同样的形式,即 Unicode 正规化

Run
const res = '\u01D1'.normalize() === '\u004F\u030C'.normalize();
+console.log(res); // true
+

includes()


includes(searchString: string, position?: number): boolean;
+

判断字符串是否包含参数字符串,返回布尔值。第二个参数,表示开始搜索的位置,从第 n 个位置直到字符串结束。

startsWith()


startsWith(searchString: string, position?: number): boolean;
+

startsWith()判断参数字符串是否在原字符串的头部,返回布尔值。第二个参数,表示开始搜索的位置,从第 n 个位置直到字符串结束。

endsWith()


endsWith(searchString: string, endPosition?: number): boolean;
+

endsWith()判断参数字符串是否在原字符串的尾部,返回布尔值。第二个参数,表示开始搜索的位置,前 n 个字符。

repeat()


repeat(count: number): string;
+

返回一个新字符串,将原字符串重复 n 次。 n为小数会向下取整,为负数或Infinity会报错RangeError,为NaN0相同,为字符串会先转换成数字。

Run
const str = 'hello';
+console.log(str.at(1)); // 'e'
+console.log(str.at(-1)); // 'o'
+console.log(str.at(8)); // undefined
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES6/symbol.html b/JSBooks/ECMAScript/ES6/symbol.html new file mode 100644 index 0000000..fccb108 --- /dev/null +++ b/JSBooks/ECMAScript/ES6/symbol.html @@ -0,0 +1,242 @@ + + + + + + + LOFT + + + + +

Symbol 基本数据类型

Symbol 是 ES6 引入的新的基本数据类型,其实例是唯一,不可变的,用于确保对象属性使用唯一标识符,不会发生属性冲突的危险。

Run
const a = Symbol();
+console.log(a); // Symbol()
+console.log(typeof a); //  symbol
+
+const foo = Symbol('foo');
+const bar = Symbol('foo');
+console.log(foo === bar); // false
+
+const b = new Symbol(); // TypeError: Symbol is not a constructor
+

使用全局 Symbol 注册表

如果运行时的不同部分需要共享和重用 Symbol 实例,可以用一个字符串作为键,在全局 Symbol 注册表中创建并重用 Symbol

Run
const foo = Symbol.for('foo');
+console.log(typeof foo); //  symbol
+

Symbol.for(key: string)方法对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的 Symbol,则会生成一个新 Symbol 实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在于该字符串对应的 Symbol,则会返回该 Symbol 实例。全局注册表中的键必须使用字符串来创建,传入Symbol.for()方法的任何值都会被转为字符串,同时该字符串键也会被用作Symbol描述

Run
const local = Symbol('foo');
+const foo = Symbol.for('foo');
+const bar = Symbol.for('foo');
+
+console.log(local === foo); //  false
+console.log(foo === bar); //  true
+
+const empty = Symbol.for();
+console.log(empty); // Symbol(undefine)
+

Symbol.keyFor(sym: symbol)方法用来查询全局 Symbol 注册表。返回该全局 Symbol 对应的字符串键,如果不是全局 Symbol,则返回 undefined

Run
const localSymbol = Symbol('foo');
+const globalSymbol = Symbol.for('bar');
+
+console.log(Symbol.keyFor(localSymbol)); // undefined
+console.log(Symbol.keyFor(globalSymbol)); // 'bar'
+
+console.log(Symbol.keyFor(1)); // TypeError: 1 is not a symbol.
+

使用 Symbol 作为属性

凡是可以使用字符串或数值作为属性的地方都可以使用Symbol

Run
const a = { normal: 123, [Symbol('foo')]: 'foo' };
+console.log(a); // {normal: 123, Symbol(foo): 'foo'}
+a[Symbol('bar')] = 'bar';
+
+// Object.defineProperties
+Object.defineProperty(a, Symbol('baz'), { value: 'baz' });
+console.log(a);
+// {normal: 123, Symbol(foo): 'foo', Symbol(bar): 'bar', Symbol(baz): 'baz'}
+
+Object.getOwnPropertyNames; // 返回对象实例的常规属性数组
+console.log(Object.getOwnPropertyNames(a)); // ['normal']
+
+Object.getOwnPropertySymbols; // 返回对象实例的Symbol属性数组
+console.log(Object.getOwnPropertySymbols(a));
+// [Symbol(foo), Symbol(bar), Symbol(baz)]
+
+Object.getOwnPropertyDescriptors; // 返回同时包含常规和Symbol属性描述符的对象
+console.log(Object.getOwnPropertyDescriptors(a));
+/*
+  {
+    normal: {value: 1, writable: true, enumerable: true, configurable: true},
+    Symbol(foo): {value: 'foo', writable: true, enumerable: true, configurable: true},
+    Symbol(bar): {value: 'bar', writable: true, enumerable: true, configurable: true},
+    Symbol(baz): {value: 'baz', writable: true, enumerable: true, configurable: true},
+  }
+*/
+
+Reflect.ownKeys; // 返回同时包含常规和Symbol属性数组
+console.log(Reflect.ownKeys(a));
+// ['normal', Symbol(foo), Symbol(bar), Symbol(baz)]
+

如果作为对象属性时,没有显式保存这些 Symbol 属性的引用,那么必须遍历对象的所有 Symbol 属性才能找到相应的属性键。

Run
const sym = {
+  [Symbol('foo')]: 'foo',
+  [Symbol('bar')]: 'bar',
+};
+
+const barSymbol = Object.getOwnPropertySymbols(sym).find((symbol) =>
+  symbol.toString().match(/bar/)
+);
+
+console.log(barSymbol); // Symbol(bar)
+

内置 Symbol

用于暴露语言内部行为。开发者可以直接访问、重写或模拟这些行为。所有内置 Symbol 属性都是**不可写、不可枚举且不可配置**的。

Symbol.iterator

表示一个方法,该方法返回对象默认的迭代器。即实现迭代器 API 的函数

Run
// 简单实现
+const count = {
+  nums: [1, 2, 3, 4, 5],
+  [Symbol.iterator]() {
+    let i = 0;
+    const length = this.nums.length;
+    const self = this;
+    return {
+      next() {
+        if (i < length) {
+          return { done: false, value: self.nums[i++] };
+        }
+        return { done: true, value: undefined };
+      },
+    };
+  },
+};
+
+for (const i of count) {
+  console.log(i);
+}
+// 依次输出 1,2,3,4,5
+
+const b = count[Symbol.iterator]();
+console.log(b); // {next: f}
+console.log(b.next()); // {done: false, value: 1}
+console.log(b.next()); // {done: false, value: 2}
+console.log(b.next()); // {done: false, value: 3}
+console.log(b.next()); // {done: false, value: 4}
+console.log(b.next()); // {done: false, value: 5}
+console.log(b.next()); // {done: true, value: undefined}
+

数组的迭代器不能关闭,如下:

Run
const a = [1, 2, 3, 4, 5];
+const iterator = a[Symbol.iterator]();
+
+for (const i of iterator) {
+  console.log(i);
+  if (i > 2) {
+    break;
+  }
+}
+// 1
+// 2
+// 3
+console.log('continue to iterate.');
+
+for (const i of iterator) {
+  console.log(i);
+}
+
+// 4
+// 5
+

Symbol.asyncIterator ES9

Symbol.isConcatSpreadable

表示一个布尔值,如果为ture,则意味着对象应该用Array.prototype.concat()展平其数组元素

Run
const foo = ['foo'];
+const bar = ['bar'];
+console.log(bar[Symbol.isConcatSpreadable]); // undefined
+console.log(foo.concat(bar)); // ['foo', 'bar']
+bar[Symbol.isConcatSpreadable] = false;
+console.log(foo.concat(bar)); // ['foo', ['bar']]
+
+const arrayLikeObj = { 0: 'baz', length: 1 };
+console.log(arrayLikeObj[Symbol.isConcatSpreadable]); // undefined
+console.log(foo.concat(arrayLikeObj)); // ['foo', {...}]
+arrayLikeObj[Symbol.isConcatSpreadable] = true;
+console.log(foo.concat(arrayLikeObj)); // ['foo', 'baz']
+
+const setObj = new Set().add('qux');
+console.log(setObj[Symbol.isConcatSpreadable]); // undefined
+console.log(foo.concat(setObj)); // ['foo', Set(1)]
+setObj[Symbol.isConcatSpreadable] = true;
+console.log(foo.concat(setObj)); // ['foo']
+

Symbol.match、Symbol.matchAll ES12、Symbol.replace、Symbol.search、Symbol.split

表示一个正则表达式方法,由String.prototype上的对应的各个方法使用。正则表达式的原型上有这些函数的定义

Run
console.log(Object.getOwnPropertySymbols(RegExp.prototype));
+// [Symbol(Symbol.match), Symbol(Symbol.matchAll), Symbol(Symbol.replace), Symbol(Symbol.search), Symbol(Symbol.split)]
+

Symbol.species

表示一个函数值,该函数作为创建派生对象的构造函数。在内置类型中最常用。用于对内置类型实例方法的返回值暴露实例化派生对象的方法。 用Symbol.species定义静态的获取器方法,可以覆盖新创建实例的原型定义

Run
class Foo extends Array {}
+class Bar extends Array {
+  static get [Symbol.species]() {
+    return Array;
+  }
+}
+
+let foo = new Foo();
+console.log(foo instanceof Array); // true
+console.log(foo instanceof Foo); // true
+foo = foo.concat('foo');
+console.log(foo instanceof Array); // true
+console.log(foo instanceof Foo); // true
+
+let bar = new Bar();
+console.log(bar instanceof Array); // true
+console.log(bar instanceof Bar); // true
+bar = bar.concat('bar');
+console.log(bar instanceof Array); // true
+console.log(bar instanceof Bar); // **false**
+

Symbol.toPrimitive

表示一个方法,将对象转换为相应的原始值

根据提供给这个函数的参数string、number、default,来控制返回的原始值。

Run
class Foo {}
+const foo = new Foo();
+console.log(3 + foo); // "3[object Object]"
+console.log(3 - foo); // NaN
+console.log(String(foo)); // "[object Object]"
+
+class Bar {
+  [Symbol.toPrimitive](hint) {
+    switch (hint) {
+      case 'number':
+        return 3;
+      case 'string':
+        return 'string bar';
+      default:
+        return 'default bar';
+    }
+  }
+}
+const bar = new Bar();
+console.log(3 + bar); // "3default bar"
+console.log(3 - bar); // 0
+console.log(String(bar)); // "string bar"
+

Symbol.toStringTag

表示一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法Object.prototype.toString()使用。

Run
const s = new Set();
+console.log(s); // Set(0) {}
+console.log(s.toString()); // "[object Set]"
+console.log(s[Symbol.toStringTag]); // "Set"
+
+class Foo {}
+const foo = new Foo();
+console.log(foo); // Foo {}
+console.log(foo.toString()); // "[object Object]"
+console.log(foo[Symbol.toStringTag]); // undefined
+
+class Bar {
+  [Symbol.toStringTag] = 'Bar';
+}
+const bar = new Bar();
+console.log(bar); // Bar {Symbol(Symbol.toStringTag): 'Bar'}
+console.log(bar.toString()); // "[object Bar]"
+console.log(bar[Symbol.toStringTag]); // Bar
+

Symbol.hasInstance

表示一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用。

Run
function Foo() {}
+const foo = new Foo();
+console.log(foo instanceof Foo); // true
+
+class Bar {}
+const bar = new Bar();
+console.log(Bar[Symbol.hasInstance](bar)); // true
+

这个属性定义在Function的原型上,因此默认在所有函数和类上都可以调用。由于instanceof操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数。

Run
class Bar {}
+class Baz extends Bar {
+  static [Symbol.hasInstance]() {
+    return false;
+  }
+}
+const baz = new Baz();
+console.log(Bar[Symbol.hasInstance](baz)); // true
+console.log(baz instanceof Bar); // true
+console.log(Baz[Symbol.hasInstance](baz)); // false
+console.log(baz instanceof Baz); // false
+

Symbol.unscopables 不推荐

表示一个对象,该对象所有的包括继承的属性都会从关联对象的with环境绑定中解除

Run
const obj = { foo: 'bar' };
+with (obj) {
+  console.log(foo); // 'bar'
+}
+
+obj[Symbol.unscopables] = { foo: true };
+with (obj) {
+  console.log(foo); // ReferenceError: foo is not defined
+}
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES7.html b/JSBooks/ECMAScript/ES7.html new file mode 100644 index 0000000..30fe053 --- /dev/null +++ b/JSBooks/ECMAScript/ES7.html @@ -0,0 +1,26 @@ + + + + + + + LOFT + + + + +

includes()


includes(searchElement: T, fromIndex?: number): boolean;
+

确定数组是否包含某个元素,并根据需要返回 truefalse

Run
const arr = [1, 2, 3, 4, 5, 6, 7];
+console.log(arr.includes(5));
+// Same as
+console.log(arr.indexOf(5) >= 0);
+// true
+

指数运算符

ES7 引入指数运算符**,与Math.pow()等效。

Run
console.log(Math.pow(2, 10));
+console.log(2 ** 10);
+// 1024
+
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES8.html b/JSBooks/ECMAScript/ES8.html new file mode 100644 index 0000000..4982a66 --- /dev/null +++ b/JSBooks/ECMAScript/ES8.html @@ -0,0 +1,106 @@ + + + + + + + LOFT + + + + +

padStart()/padEnd()

padStart()用于头部补全。 返回新字符串,不修改原始字符串。 padEnd()用于尾部补全。 返回新字符串,不修改原始字符串。

/**
+ * @param {number} maxLength 当前字符串需要填充到的目标长度。若这个数值小于当前字符串的长度,则返回当前字符串本身。
+ * @param {string=} fillString 填充字符串,若字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左(padStart)/最右(padEnd)的部分,其他部分会被截断,默认值为" " (U+0020)
+ */
+padStart(maxLength: number, fillString?: string): string;
+padEnd(maxLength: number, fillString?: string): string;
+
Run
console.log('abc'.padStart(5, 'd'));
+console.log('0.0'.padStart(4, '10'));
+
+console.log('abc'.padEnd(5, 'd'));
+console.log('0.0'.padEnd(4, '10'));
+

async/await

async 函数实际上是 Generator 函数与自动执行器包装在一个函数中。

Run
async function fn(args) {
+  const res = await new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(args);
+    }, 2000);
+  });
+  console.log(res);
+}
+fn('Hello world!');
+

等同于

Run
function spawn(genFunc) {
+  return new Promise((resolve, reject) => {
+    const gen = genFunc();
+    function step(nextFunc) {
+      let next;
+      try {
+        next = nextFunc();
+      } catch (e) {
+        return reject(e);
+      }
+      if (next.done) {
+        return resolve(next.value);
+      }
+      Promise.resolve(next.value).then(
+        (v) => {
+          step(() => gen.next(v));
+        },
+        (e) => {
+          step(() => gen.throw(e));
+        }
+      );
+    }
+    step(() => gen.next(undefined));
+  });
+}
+function fn(args) {
+  spawn(function* () {
+    yield new Promise((resolve) => setTimeout(() => resolve(), 2000));
+    return args;
+  }).then((res) => {
+    console.log(res);
+  });
+}
+fn('Hello world!');
+

async/await 示例

Run
const fakeRequest = (time, val) => () =>
+  new Promise((resolve) => setTimeout(() => resolve(val), time));
+const fakeRequests = [
+  fakeRequest(2000, 'a'),
+  fakeRequest(1000, 'b'),
+  fakeRequest(3000, 'c'),
+  fakeRequest(5000, 'd'),
+];
+async function request(reqs) {
+  console.log('Start');
+  for (const req of reqs) {
+    console.log(await req());
+  }
+  console.log('End');
+}
+request(fakeRequests);
+

Object.values()

Object.values() 类似 Object.keys(),返回 Object 自身属性的所有值,不包括继承值。

Run
const obj = { foo: 'foo', bar: 'bar', baz: 'baz' };
+const values = Object.values(obj);
+console.log(values);
+// ['foo', 'bar', 'baz']
+

Object.entries()

Object.entries() 返回一个给定对象自身可枚举属性的键值对的数组。

Run
const obj = { foo: 'foo', bar: 'bar', baz: 'baz' };
+for (const [key, value] of Object.entries(obj)) {
+  console.log(`${key}: ${value}`);
+}
+// 'foo: foo'
+// 'bar: bar'
+// 'baz: baz'
+

Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors() 用于获取一个对象所有自身属性的描述符。若无,则返回空对象。

Run
const obj = {
+  foo: 'foo',
+  baz() {
+    return 'baz';
+  },
+};
+const descriptors = Object.getOwnPropertyDescriptors(obj);
+console.log(descriptors);
+

SharedArrayBuffer

SharedArrayBuffer 用于表示一个通用的,固定长度的原始二进制数组缓冲区,类似 ArrayBuffer,它们用来在共享内存(Shared Memory)上创建视图,但与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。

Atomics

Atomics 对象提供了一组静态方法用来对 SharedArrayBuffer 对象进行原子操作。 Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象相同)。 多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。

方法描述
Atomics.add()将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值
Atomics.and()将指定位置上的数组元素与给定的值相与,并返回与操作前该元素的值
Atomics.compareExchange()如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原本的值
Atomics.exchange()将数组中指定的元素更新为给定的值,并返回该元素更新前的值。
Atomics.load()返回数组中指定元素的值
Atomics.or()将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值
Atomics.store()将数组中指定的元素设置为给定的值,并返回该值
Atomics.sub()将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值
Atomics.xor()将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值
Atomics.wait()检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为 "ok"、"not-equal" 或 "time-out"。调用时,如果当前线程不允许阻塞,则会抛出异常(大多数浏览器都不允许在主线程中调用 wait())。
Atomics.wake()唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。
Atomics.isLockFree(size)可以用来检测当前系统是否支持硬件级的原子操作。对于指定大小的数组,如果当前系统支持硬件级的原子操作,则返回 true;否则就意味着对于该数组,Atomics 对象中的各原子操作都只能用锁来实现。此函数面向的是技术专家。
+ +
+ + + diff --git a/JSBooks/ECMAScript/ES9.html b/JSBooks/ECMAScript/ES9.html new file mode 100644 index 0000000..0d1a9ec --- /dev/null +++ b/JSBooks/ECMAScript/ES9.html @@ -0,0 +1,64 @@ + + + + + + + LOFT + + + + +

异步迭代(Async Iteration)

Run
async function iteration() {
+  const times = [2000, 1000, 3000, 2000];
+  const delay = (time) =>
+    new Promise((resolve) => setTimeout(() => resolve(time), time));
+  for await (const time of times.map(delay)) {
+    console.log(time);
+  }
+}
+iteration();
+

promise.finally()

无论Promise运行成功与否,finally都会触发。

Run
new Promise((resolve, reject) => {
+  reject(new Error('rejected!'));
+})
+  .then((res) => {
+    return res;
+  })
+  .finally(() => {
+    console.log('Promise finished.');
+  });
+

Object Rest 参数/Spread 扩展运算符

ES6 引入了数组的 Rest 参数及 Spread 扩展运算符。ES9 为对象结构提供了与数组相同的Rest 参数及 Spread 扩展运算符。扩展运算符可以浅拷贝一个对象。

Run
const obj = { foo: 'foo', bar: 'bar', baz: 'baz' };
+const { foo, ...rest } = obj;
+console.log(rest);
+
+const spread = { ...obj, a: 1, b: 2, c: 3 };
+console.log(spread);
+

正则表达式扩展

命名捕获组

使用命名捕获组符号?<name>进行捕获。任何匹配失败的命名组都将返回undefined

Run
const reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
+const match = reDate.exec('2018-04-30');
+console.log({ ...match.groups });
+

反向断言

Run
// 先行断言 lookahead
+const [sign] = /\D(?=\d+)/.exec('¥1234.56');
+console.log(sign);
+// ¥
+
+// 肯定反向断言 lookbehind
+const [sign1] = /(?<=\D)\d+/.exec('¥1234.56');
+console.log(sign1);
+// 1234.56
+
+// 否定反向断言 neglookbehind
+const [sign2] = /(?<!\D)\d+/.exec('¥1234.56');
+console.log(sign2);
+// 234
+

dotAll 模式

正则表达式中.匹配除回车外的任何单字符,标记s允许行终止符的出现。

Run
console.log(/what.the.heck/.test('what\nthe\nheck'));
+// false
+console.log(/what.the.heck/s.test('what\nthe\nheck'));
+// true
+

Unicode 转义

ES9 添加了对 Unicode 属性的转义:\p{...}\P{...}。在正则表达式中使用u标记,并\p/\P中以键值对方式设置需要匹配的属性而非具体内容。此特性可以避免使用特定 Unicode 区间来进行内容类型判断,提升可读性和可维护性。

Run
console.log(/\p{Script=Greek}/u.exec('π'));
+// true
+

非转义序列的模板字符串 (参考open in new window)

+ +
+ + + diff --git a/JSBooks/ECMAScript/index.html b/JSBooks/ECMAScript/index.html new file mode 100644 index 0000000..a2795e0 --- /dev/null +++ b/JSBooks/ECMAScript/index.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +

ECMAScript-262

ECMAScript-262 定义了这门语言的以下几个部分:

  • 语法
  • 类型
  • 语句
  • 关键字
  • 保留字
  • 操作符
  • 全局对象

ECMAScript 和 JavaScript 的关系是,前者是后者的规范,后者是前者的一种实现(其他实现如 JScript 和 ActionScript)。

2015 年 6 月发布的 ECMA-262 第 6 版,俗称 ES6、ES2015 或 ES Harmony。这版包含了可能是这个规范有史以来最重要的一批增强特性。正式支持了 Class、Module、Iterator、Generator(function *g() {})、箭头函数(() => {})、Promise、Reflect、Proxy 和 Symbol、Set、Map 等新的数据类型。

2016 年 6 月发布的 ECMA-262 第 7 版,也称 ES7 或 ES2016。新增Array.prototype.includes和指数操作符等特性。

2017 年 6 月发布的 ECMA-262 第 8 版,也称 ES8 或 ES2017。新增了async/awaitSharedArrayBufferObject.values()Object.entries()Object.getOwnPropertyDescriptors()以及字符串填充方法。

2018 年 6 月发布的 ECMA-262 第 9 版,也称 ES9 或 ES2018。新增了异步迭代(Async Iteration)、Rest/Spread 操作符、正则表达式新特性、Promise.finally()以及模板字符串(Template Literals)。

2019 年 6 月发布的 ECMA-262 第 10 版,也称 ES10 或 ES2019。新增了Array.prototype.flat()/flatMap()String.prototype.trimStart()/trimEnd()Object.fromEntries()Symbol.prototype.description属性、可忽略的 catch 参数、明确定义了Function.prototype.toString()方法返回值、固定了Array.prototype.sort()的顺序以及解决了 JSON 字符串兼容性问题。

2020 年 6 月发布的 ECMA-262 第 11 版,也称 ES11 或 ES2020。新增了动态importimport.metaexport增强、新的数据类型 BigInt、RegExp.prototype.matchAll()globalThisPromise.allSettled()?. , ??操作符。

2021 年 6 月发布的 ECMA-262 第 12 版,也称 ES12 或 ES2021。新增了String.prototype.replaceAll()Promise.any()AggregateError用于表示多个错误的集合、新的逻辑操作符??= , &&= , ||=、弱引用WeakRefFinalizationRegistry用于垃圾回收的注册、数字的分隔符1_000、更加精准的Array.prototype.sort

2022 年 6 月发布的 ECMA-262 第 13 版,也称 ES13 或 ES2022。模块顶层作用域支持await表达式、 新增私有类元素、静态块、in操作符支持私有类元素、正则新增d标志和其对应的hasIndices属性、提供了获取捕获组开始索引和结束索引的方法、Error实例增加cause属性,可携带更多错误信息、Strings、Arrays、TypedArrays新增at方法,支持关联访问、Object.hasOwn代替Object.prototype.hasOwnProperty,判断对象是否含有属性。

+ +
+ + + diff --git a/JSBooks/JavaScript/React/architecture.html b/JSBooks/JavaScript/React/architecture.html new file mode 100644 index 0000000..6386cc8 --- /dev/null +++ b/JSBooks/JavaScript/React/architecture.html @@ -0,0 +1,368 @@ + + + + + + + LOFT + + + + +

架构

运行栈

运行栈

图中三部分所做的工作其实就是调度器调和器渲染器所做的工作。

图中标记1区域

图中标记1区域

首次执行 ReactDOM.render 时,会创建整个应用的根节点 FiberRootNode ,还会创建当前应用的根节点 FiberNode(createHostRootFiber -> createFiber -> FiberNode)

图中标记2区域

图中标记2区域

创建完根节点后,进入首次渲染:

scheduleUpdateOnFiber 在 Fiber 上调度更新;

performSyncWorkOnRoot 调度成功后,在根节点执行更新;

创建 workInProgress 树的流程可以类比为递归的流程:

  • “递”阶段: beginWork
  • “归”阶段: completeWork

在 React 内部,

  • renderRootSync 方法即调和器的执行过程,所做的工作流程被称为 render 阶段,
  • commitRoot 方法即渲染器的执行过程, 所做的工作流程被称为 commit 阶段。

基本概念

JSX

使用 @babel/plugin-transform-react-jsx babel 插件对 jsx 进行编译。

<div>
+  <p title="moriarty">Moriarty</p>
+</div>
+
+// === babel 插件编译后
+
+/*#__PURE__*/
+React.createElement('div', null,
+  /*#__PURE__*/React.createElement('p', { title: 'moriarty' }, 'Moriarty')
+);
+

React.createElement(type, config, children) 接收三个参数,根据给定的 type 创建并返回一个新的 ReactElement

对于 Class Component babel 插件编译如下:

class A extends React.Components {
+  render() {
+    return 'A';
+  }
+}
+
+function B() {
+  return 'B';
+}
+
+<div>
+  <A />
+  <B />
+</div>
+
+// === babel 插件编译后
+
+var A = /*#__PURE__*/function(_React$Component) {
+  _inherits(A, _React$Component);
+
+  var _super = _createSuper(A);
+
+  function A() {
+    _classCallCheck(this, A);
+
+    return _super.apply(this, arguments);
+  }
+
+  _createClass(A, [{
+    key: 'render',
+    value: function render() {
+      return 'A'
+    }
+  }]);
+
+  return A;
+}(React.Component);
+
+function B() {
+  return 'B';
+}
+
+/*#__PURE__*/
+React.createElement('div', null,
+  /*#__PURE__*/React.createElement(A, null),
+  /*#__PURE__*/React.createElement(B, null),
+);
+

ReactComponentReactElement 关系

  • React.createElement 调用的结果即为 ReactElement;
  • 上述代码中的 class Afunction B 即为 ReactComponent;
  • ReactComponentReactElement 的关系是 ReactComponent 会作为 React.createElement 方法的第一个 type 参数。

jsx 与 Fiber 关系

在首次渲染时会创建 workInProgress Fiber 树,创建 Fiber 节点的依据就是组件返回的 jsx 对象 ,在更新时已经存在一棵 current Fiber 树,所以在生成 workInProgress Fiber 节点时,会将组件返回的 jsx 对象与这个组件对应的 current Fiber 节点做对比,根据对比的结果生成这个组件对应的 workInProgress Fiber 节点。

计数器简单实现


class ClickCounter extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = { count: 0 };
+    this.handleClick = this.handleClick.bind(this);
+  }
+
+  handleClick() {
+    this.setState(state => ({ count: state.count + 1 }));
+  }
+
+  render() {
+    return [
+      <button key="1" onClick={this.handleClick}> + 1 </button>,
+      <span key="2">{this.state.count}</span>
+    ]
+  }
+}
+

上述代码是一个简单的计数器组件,当点击按钮时,组件的状态在 handleClick 中更新,并触发 span 元素的文本更新。

React 在调和过程中,执行了各种活动,例如, React 在当前应用的首次渲染和状态更新后执行的高级操作:

  • 更新 ClickCounter 组件状态 state 中的 count 属性;
  • 查找并比较 ClickCounter 的子元素和它们的 props
  • 更新 span 元素的属性。

调和过程中还执行了一些其他的活动,例如调用生命周期函数或者更新 refs ,所有的这些活动在 Fiber 架构中统称为工作。这一类型的工作通常取决于 ReactElement 的类型。例如,对于一个 class 组件来说,React 需要创建一个实例,但对于函数组件来说,则不需要这步操作。React 中有很多类型的组件,class 组件,函数组件,Host 组件(DOM 节点)和 Portal 组件等等。React 组件是通过 createElement 函数的第一个参数定义的,这个函数通常在 render 函数中调用用于创建一个 ReactElement

ReactElement 与 Fiber 节点 的关系

React 的 jsx 模板如下:

render () {
+  return [
+    <button key="1" onClick={this.handleClick}> + 1 </button>,
+    <span key="2">{this.state.count}</span>
+  ];
+}
+

通过编译后,如下:

render() {
+  return [
+    React.createElement(
+      'button',
+      { key: '1', onClick: this.handleClick },
+      ' + 1 '
+    ),
+    React.createElement(
+      'span',
+      { key: '2' },
+      this.state.count
+    ),
+  ];
+}
+

React 调用 render 函数后会创建如下数据结构:

[
+  {
+    $$typeof: Symbol(react.element),
+    type: 'button',
+    key: '1',
+    props: {
+      children: ' + 1 ',
+      onClick: () => { ... },
+    },
+  },
+  {
+    $$typeof: Symbol(react.element),
+    type: 'span',
+    key: '2',
+    props: {
+      children: 0,
+    },
+  }
+]
+

在调和过程中,从 render 方法返回的每个 ReactElement 的数据都被合并到 Fiber 节点树中。每个 ReactElement 都有一个对应的 Fiber 节点。与 ReactElement 不同的是,Fiber 节点不会在每次渲染时重新创建,而是保存了组件状态和 DOM 的可变数据结构。

根据 ReactElement 的类型,框架需要执行不同的活动, 对于 class 组件需要调用生命周期和 render 方法,而对于 span Host 组件(DOM 节点)需要执行 DOM mutation。可以看出,每个 ReactElement 都被转换为相应描述需要完成工作的类型的 Fiber 节点。

Fiber 实际是一种数据结构,它表示要做的工作,或者工作单元。Fiber 架构提供了一种方便的方法来追踪、调度、暂停和终止工作。

ReactElement 首次被转换为 Fiber 节点时,React 会在 createFiberFromTypeAndProps 函数中使用来自元素的数据创建一个 Fiber 节点;在后续的更新中,React 复用之前创建的 Fiber 节点,只使用来自对应 ReactElement的数据更新必要的属性。React 可能还需要根据节点的 key 属性移动层次结构中的节点或当 render 方法不再返回相应的 ReactElement 则删除对应的节点。

React 为每个 ReactElement 创建了一个 Fiber 节点,通过这些 ReactElement 所构成的树,对应构成了其 Fiber 节点树。

current 树 和 workInProgress

首次渲染完成后,React 最终得到一棵 Fiber 节点树,它表示了用于渲染 UI 的应用程序的状态。这棵树通常被称为 current 树;当 React 开始更新时,它会构建一个 workInProgress 树,这棵树表示了要刷新到屏幕上的未来状态。

所有的工作 都在 workInProgress 树上执行。当 React 遍历 current 树时,它会为每一个现有的 Fiber 节点创建一个用于构成 workInProgress 树的 alternate节点。这个节点是使用 render 方法返回的 ReactElement 中的数据创建的。一旦更新被处理并且所有相关工作完成,React 会将 workInProgress 树作为备用树并准备刷新到屏幕上,一旦这个树刷新到屏幕上,它就变为了 current 树。

React 的核心原则之一是 一致性。 React 总是一次更新 DOM,不会显示部分更新结果。workInProgress 树作为用户不可见的草稿,React 会先处理所有组件,然后统一将它们的更改刷新到屏幕上。

React 源码中,有许多函数会从 current 树和 workInProgress 树中获取 Fiber 节点。如:

function updateHostComponent(current, workInProgress, renderExpirationTime) {
+  // ...
+}
+

每个 Fiber 节点会在 alternate 属性中保存另外一棵树的对应节点的引用,current 树中的节点指向 workInProgress 树中的节点,反之亦然。

副作用(Side-effects)

我们可以把 React 中的组件想象为一个函数,它使用 stateprops 来计算 UI 表示,任何其他活动(改变 DOM 或调用生命周期方法)都应被视为副作用open in new window

你可以看到大多数 stateprops 更新将导致副作用。由于应用的副作用是一种工作机制,除了更新以外,使用 Fiber 节点是来跟踪副作用很方便。每个 Fiber 节点都有与之对应的副作用,它们被编码在 effectTag 字段中。

Fiber 中的副作用基本定义是:在处理完更新之后需要为实例完成的工作。对 Host 组件(DOM 节点)来说,工作包括添加、更新或删除元素。对 class 组件来说,React 可能需要更新 refs 并调用 componentDidMountcomponentDidUpdate 生命周期方法,其他类型的 Fiber 节点也有其他的副作用。

副作用表

React 处理更新的速度非常快,为了达到这样的性能水平,它采用了一些有趣的技术。其中一项就是 维护一个具有快速迭代副作用的 Fiber 节点的线性列表,迭代线性列表比树要快得多,也无需花费时间在没有副作用的情况下。

这个列表的目的是:标记具有与之相关的 DOM 更新或其他副作用的节点。这个列表是 finishedWork 树的子集,并且使用 nextEffect 属性来链接而不是 current 树和 workInProgress 树中的 child 属性。

Dan Abramovopen in new window将副作用表类比为一棵圣诞树,圣诞灯将所有有效的节点绑在一起。例如:一个更新使得 c2 节点插入 DOM 之中,d2c1 更改了属性,同时 b2 节点触发了生命周期方法。副作用表将会连接这些更改,这样 React 就会跳过其他的节点。如下图:

副作用表

在遍历副作用表时,React 使用 firstEffect 指针来找出列表开始的位置,如下图:

副作用表指针

Fiber 树的根节点

每个 React 应用都有一个或多个 DOM 元素作为其容器。

const domContainer = document.querySelector('#container');
+ReactDOM.render(React.createElement(ClickCounter), domContainer);
+

React 为每一个容器创建一个 Fiber Root 对象。你可以使用 DOM 元素中的属性引用访问它。

const fiberRoot = query('#container')._reactRootContainer._internalRoot;
+

这个 Fiber Root 对象是 React 保存 Fiber 树的引用的地方。Fiber 树被存储在 Fiber Root 对象的 current 属性中。

const hostRootFiberNode = fiberRoot.current;
+

Fiber 树起始于一个特殊类型的 Fiber 节点 HostRoot,它是内部创建的,用来充当顶层组件的父组件。

fiberRoot.current.stateNode === fiberRoot; // true
+

你可以通过组件实例 _reactInternals 的属性获取对应的 Fiber 节点。

componentInstance._reactInternals;
+

Fiber 节点数据结构open in new window

上述示例中 ClickCounterspan 元素的 Fiber 节点结构如下:

{
+  stateNode: new ClickCounter {/* ... */},
+  type: class ClickCounter {},
+  elementType: class ClickCounter {},
+  alternate: null,
+  key: null,
+  updateQueue: null,
+  memoizedState: { count: 0 },
+  pendingProps: {},
+  memoizedProps: {},
+  tag: 1,
+  effectTag: 0,
+  nextEffect: null,
+  // other fields..
+}
+
+// span
+
+{
+  stateNode: new HTMLSpanElement {/* ... */}
+  type: 'span',
+  elementType: 'span',
+  alternate: null,
+  key: '2',
+  updateQueue: null,
+  memoizedState: null,
+  pendingProps: { children :0 },
+  memoizedProps: { children :0 },
+  tag: 5,
+  effectTag: 0,
+  nextEffect: null,
+  // other fields..
+}
+

stateNode

保存对组件、DOM 节点或其他与 Fiber 节点关联的 ReactElement 元素类型的类实例引用。一般来说,这个属性用于保存与 Fiber 相关的本地状态。

type

定义与此 Fiber 节点关联的函数或类。对于 class 组件来说,它指向构造函数;对 DOM 元素来说,它指向 HTML 标签类型字符串。

tag

定于 Fiber 的类型,它在 调和算法 中用于确定需要做什么工作。如上所述,工作取决于 ReactElement 的类型,createFiberFromTypeAndProps 函数将 ReactElement 映射到相应的 Fiber 节点类型。从源码中可以看出 1 代表 class 组件, 5 代表 Host 组件。

export const FunctionComponent = 0;
+export const ClassComponent = 1;
+export const IndeterminateComponent = 2; // Before we know whether it is function or class
+export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
+export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
+export const HostComponent = 5;
+export const HostText = 6;
+export const Fragment = 7;
+export const Mode = 8;
+export const ContextConsumer = 9;
+export const ContextProvider = 10;
+export const ForwardRef = 11;
+export const Profiler = 12;
+export const SuspenseComponent = 13;
+export const MemoComponent = 14;
+export const SimpleMemoComponent = 15;
+export const LazyComponent = 16;
+export const IncompleteClassComponent = 17;
+export const DehydratedFragment = 18;
+export const SuspenseListComponent = 19;
+export const FundamentalComponent = 20;
+export const ScopeComponent = 21;
+export const Block = 22;
+export const OffscreenComponent = 23;
+export const LegacyHiddenComponent = 24;
+

updateQueue

一个包含状态更新,回调和 DOM 更新的队列。

memoizedState

用于创建输出的 Fiber 的状态。当处理更新时,它反映了当前渲染在屏幕上的状态。

memoizedProps

在之前渲染中用于创建输出的 Fiber 的属性。

pendingProps

ReactElement 的新数据中更新的属性并且需要应用到子组件或者 DOM 元素。

key

一组含有子元素的列表的唯一标识符,帮助 React 找出哪些项目从列表中被更改、添加或删除。

算法

React 的主要执行工作在两个阶段:render 阶段和 commit 阶段。

在第一个 render 阶段,React 将更新应用于通过 setStateReact.render 调度的组件并找出哪些需要在 UI 中更新。如果是首次渲染,React 会为每一个通过 render 方法返回的元素创建一个新的 Fiber 节点;在后续更新中,现有的 ReactElement 的 Fiber 节点将被重用和更新。这个阶段的结果是返回一个带有副作用的 Fiber 节点树。副作用描述了下一个 commit 阶段需要完成的工作。在这个阶段,React 获取一个标记有副作用的 Fiber 树并将它们应用到实例中。它会遍历副作用表并执行 DOM 更新和其他的对用户可见的更改。

需要注意的是 render 阶段的工作可以异步执行。 React 可以根据可用时间处理一个或多个 Fiber 节点,然后暂停并保存已完成的工作,将控制权交于其他更重要的事件。之后再从它暂停的地方继续。但有时,它可能需要放弃已完成的工作,重新从头开始。这些暂停之所以成为可能是因为在此阶段执行的工作不会导致任何用户可见的更改,如 DOM 更新。

相反,commit 阶段始终是同步的。因为此阶段执行的工作会导致用户可见的更改。如 DOM 更新。这也是为什么 React 需要一次完成它们。

调用生命周期方法是 React 执行的一种工作。有些方法在 render 阶段调用,有些则在 commit 阶段调用。

以下是在 render 阶段调用的方法:

  • UNSAFE_componentWillMount() {} /* deprecated */
  • UNSAFE_componentWillReceiveProps() {} /* deprecated */
  • getDerivedStateFromProps() {}
  • shouldComponentUpdate() {}
  • UNSAFE_componentWillUpdate() {} /* deprecated */
  • render() {}

其中一些遗留的生命周期方法在 16.3 版本之后的未来版本中被删除。因为在 render 阶段不会产生像 DOM 更新这样的副作用,因此 React 可以异步地对组件进行异步更新处理(甚至在多个线程中进行)。然而,标有不安全(UNSAFE_)前缀的生命周期经常被误解和微妙地滥用,开发者倾向于将带有副作用的代码放在这些方法中,这可能会导致新的异步渲染实现出现问题。虽然只有不带不安全前缀的对应项会被删除,但它们仍可能在即将到来的 concurrent 模式中引起问题。

以下是在 commit 阶段调用的方法:

  • getSnapshotBeforeUpdate
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

这些方法在同步的 commit 阶段被执行,因此它们可能包含副作用,包括更新 DOM。

render 阶段

调和算法总是使用 renderRootSync 方法从最顶层的 HostRoot Fiber 节点开始。但是,React 会跳出(跳过)已经处理过的 Fiber 节点,直到找到工作未完成的节点。例如,当你在组件树的深处调用 setState,React 将从顶部开始,但很快跳过父组件,直接到达调用了 setState 方法的组件。

工作流程的主要步骤

所有的 Fiber 节点都 workLoopSyncopen in new window 中被处理。workLoopSync 的实现如下:

function workLoopSync() {
+  // Already timed out, so perform work without checking if we need to yield.
+  while (workInProgress !== null) {
+    performUnitOfWork(workInProgress);
+  }
+}
+

上述代码中 nextUnitOfWork 保存了对 workInProgress 树中的 Fiber 节点的引用,当 React 遍历 Fiber 树时,它使用这个变量获知是否有其他未完成工作的 Fiber 节点,当处理完当前 Fiber 节点后,该变量要包含树中下一个 Fiber 节点的引用,要么为 null ,这种情况下, React 会退出 workLoopSync 并准备提交更改。

如下四个主要函数会在遍历 Fiber 树过程中使用到,会初始化或完成工作。

  • performUnitOfWork
  • beginWork
  • completeUnitOfWork
  • completeWork

performUnitOfWorkbeginWork

function performUnitOfWork(unitOfWork) {
+  const current = unitOfWork.alternate;
+  let next = beginWork(current, unitOfWork, subtreeRenderLanes);
+  unitOfWork.memoizedProps = unitOfWork.pendingProps;
+  if (next === null) {
+    completeUnitOfWork(unitOfWork);
+  } else {
+    workInProgress = next;
+  }
+}
+function beginWork(current, workInProgress, renderLanes) {
+  /* ...some works */
+  switch (workInProgress.tag) {
+    case FunctionComponent: {
+      const Component = workInProgress.type;
+      const unresolvedProps = workInProgress.pendingProps;
+      const resolvedProps =
+        workInProgress.elementType === Component
+          ? unresolvedProps
+          : resolveDefaultProps(Component, unresolvedProps);
+      return updateFunctionComponent(
+        current,
+        workInProgress,
+        Component,
+        resolvedProps,
+        renderLanes,
+      );
+    },
+    /* ...other conditions */
+  }
+}
+
+function updateFunctionComponent(
+  current,
+  workInProgress,
+  Component,
+  resolvedProps,
+  renderLanes,
+) {
+  /* some works */
+  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
+  return workInProgress.child;
+}
+

performUnitOfWorkworkInProgress 树中接收一个 Fiber 节点,并通过调用 beginWork 函数开始工作。这个函数将会启动所有需要为 Fiber 节点执行活动的活动。 beginWork 函数总是会返回一个指向下一个需要处理的子节点的指针或者 null

如果有下一个子节点,它将会被分配给 workLoopSync 中的 next 变量,但是如果没有子节点,React 则知道它到达了分支的末端,即完成当前节点。节点一旦完成,它将需要为兄弟节点执行工作并在之后回溯到父级。这是在 completeUnitOfWork 中完成的。

completeUnitOfWorkcompleteWork

// 省略部分代码
+function completeUnitOfWork(unitOfWork) {
+  let completedWork = unitOfWork;
+  do {
+    const current = completedWork.alternate;
+    const returnFiber = completedWork.return;
+
+    let next;
+
+    next= completeWork(current, completedWork, subtreeRenderLanes);
+
+    if (next !== null) {
+      workInProgress = next;
+      return;
+    }
+
+    const siblingFiber = completedWork.sibling;
+    if (siblingFiber !== null) {
+      // If there is more work to do in this returnFiber, do that next.
+      workInProgress = siblingFiber;
+      return;
+    }
+    // Otherwise, return to the parent
+    completedWork = returnFiber;
+    // Update the next thing we're working on in case something throws.
+    workInProgress = completedWork;
+  } while(completeWork !== null)
+}
+
+function completeWork(
+  current,
+  workInProgress,
+  renderLanes
+) {
+  switch (workInProgress.tag) {
+    case FunctionComponent:
+    case MemoComponent:
+      return null;
+    case ClassComponent: {
+      const Component = workInProgress.type;
+      if (isLegacyContextProvider(Component)) {
+        popLegacyContext(workInProgress);
+      }
+      return null;
+    },
+    /* ...other conditions */
+  }
+}
+

当完成当前 Fiber 节点的工作后,它会检查是否有兄弟 Fiber。如果找到,React 会退出该函数并返回指向同级函数的指针,它将重新从它的兄弟开始的分支执行工作。此时,React 还没有完成其父节点的工作,只有当所有从子节点开始的分支都完成后,它才会完成父节点的工作并进行回溯

performUnitOfWorkcompleteUnitOfWork 函数用于迭代,主要的执行过程发生在 beginWorkcompleteWork函数中。

commit 阶段

图中标记3区域

图中标记3区域
// 省略部分代码
+function commitRoot(root) {
+  const renderPriorityLevel = getCurrentPriorityLevel();
+  runWithPriority(
+    ImmediateSchedulerPriority,
+    commitRootImpl.bind(null, root, renderPriorityLevel),
+  );
+  return null;
+}
+
+function commitRootImpl(root, renderPriorityLevel) {
+  /* some works */
+  commitBeforeMutationEffects();
+  /* some works */
+  commitMutationEffects(root, renderPriorityLevel);
+  /* some works */
+  commitLayoutEffects(root, lanes);
+}
+
+function commitBeforeMutationEffects() {
+  /* some works */
+  const current = nextEffect.alternate;
+  /* some works */
+  commitBeforeMutationEffectOnFiber(current, nextEffect);
+  /* some works */
+}
+
+function commitMutationEffects(root, renderPriorityLevel) {
+  /* some works */
+  const flags = nextEffect.flags;
+  const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
+  switch (primaryFlags) {
+    case Placement: {
+      commitPlacement(nextEffect);
+      nextEffect.flags &= ~Placement;
+      break;
+    }
+    case PlacementAndUpdate: {
+      commitPlacement(nextEffect);
+      nextEffect.flags &= ~Placement;
+      const current = nextEffect.alternate;
+      commitWork(current, nextEffect);
+      break;
+    }
+    case Hydrating: {
+      nextEffect.flags &= ~Hydrating;
+      break;
+    }
+    case HydratingAndUpdate: {
+      nextEffect.flags &= ~Hydrating;
+      const current = nextEffect.alternate;
+      commitWork(current, nextEffect);
+      break;
+    }
+    case Update: {
+      const current = nextEffect.alternate;
+      commitWork(current, nextEffect);
+      break;
+    }
+    case Deletion: {
+      commitDeletion(root, nextEffect, renderPriorityLevel);
+      break;
+    }
+  }
+  /* some works */
+}
+
+function commitLayoutEffects(root, committedLanes) {
+  /* some works */
+  const current = nextEffect.alternate;
+  commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
+  /* some works */
+}
+

在这个阶段,React 有两颗树和一个副作用列表,第一棵 current 树表示当前渲染在屏幕上的状态,之后在 render 阶段创建了一棵 workInProgress 树,表示需要被反馈到屏幕上的状态,这棵树和 current 树类似,都是通过 childsiblingreturn 指针连接。

副作用列表,它是 workInProgress 树中节点的子集,通过 nextEffect 指针连接,副作用列表是运行 render 阶段的结果。渲染的全部意义在于确定需要插入、更新或删除哪些节点以及需要调用哪些组件的生命周期方法。副作用列表保存了这些更改,也是在 commit 阶段需要迭代的一组节点。

出于调试目的,current 树可以通过 FiberRootcurrent 属性访问当前树, workInProgress 树可以通过 current 树中的 HostFiber 节点的 alternate 属性访问。

上述代码主要会执行如下几个生命周期和节点更新:

  • 在标记了 Snapshot 副作用的节点上调用 getSnapshotBeforeUpdate 方法
  • 在标记了 Deletion 副作用的节点上调用 componentWillUnmount 方法
  • 执行所有的 DOM 插入、更新和删除
  • 设置 workInProgress 树为 current
  • 在标记了 Placement 副作用的节点上调用 componentDidMount 方法
  • 在标记了 Update 副作用的节点上调用 componentDidUpdate 方法

before mutation 阶段

commitBeforeMutationEffects 执行 DOM 操作之前

会执行 commitBeforeMutationEffectOnFiber 并调用 getSnapshotBeforeUpdate 方法。

mutation 阶段

commitMutationEffects 执行 DOM 操作时

会根据 Fiber 节点的对应状态执行对应的 commit 操作, 如:执行 commitDeletion 进而调用 componentWillUnmount 方法

layout 阶段

commitLayoutEffects 执行 DOM 操作之后

会执行 commitLayoutEffectOnFiber 并调用 componentDidMountcomponentDidUpdate 方法

+ +
+ + + diff --git a/JSBooks/JavaScript/React/concept.html b/JSBooks/JavaScript/React/concept.html new file mode 100644 index 0000000..345137e --- /dev/null +++ b/JSBooks/JavaScript/React/concept.html @@ -0,0 +1,73 @@ + + + + + + + LOFT + + + + +

理念

设计理念

在开发中,CPU(计算能力) 与 IO(网络延迟) 的瓶颈制约了应用的 快速响应

CPU 方面,主流浏览器刷新频率为 ,即 刷新一次(一帧);

内执行 JS脚本 -> 样式布局 -> 样式绘制,若一帧内 JS脚本 的执行时间超过 ,则浏览器没有足够时间进行 样式布局样式绘制,浏览器就会发生掉帧卡顿现象。 一般通过 节流防抖 解决这个问题(治标不治本)。

  • 节流:限制触发更新的频率;
  • 防抖:一段时间的输入只会触发一次更新;

新的浏览器 API 中的 requestIdleCallback 函数用于在浏览器空闲时段将要调用的函数排队。

requestIdleCallback((deadline)=>{
+    console.log(deadline.timeRemaining(), deadline.didTimeout)
+});
+

timeRemaining() 函数告知当前还剩多少时间来做我需要做的工作或者说我还没有用完的所有可分配的时间,didTimeout 告知是否超时。一旦浏览器有工作要做,timeRemaining() 就会改变,需要不断检查它。

requestIdleCallback 实际上限制太大,执行频率不够,无法实现流畅的 UI 渲染,所以 React 团队不得不实现自己的版本。

React 提出 将同步更新变为异步可中断更新。将一帧的一部分时间预留给 React ,React 利用这部分时间完成自己的工作,如果当前工作需要时间过长,超出预留时间,React 会中断当前工作并将控制权交还浏览器,等待下一帧 React 的预留时间,接着进行已中断的工作,浏览器在每一帧都可以执行 样式布局样式绘制 ,即可以减少浏览器掉帧卡顿的可能。

IO 方面,如在数据请求等场景下,用户感知应用的快速响应。React Concurrent 模式 帮助将人机交互研究的结果整合到真实的 UI 中。例如在屏幕之间切换时显示过多的中间加载状态会使切换的速度变慢,或者悬停和文本输入之类的交互需要在很短的时间内处理,而点击和页面转换可以等待稍长时间而不会感到迟缓。

架构演进

旧架构

Reconciler 采用递归的方式运行,数据保存在递归的调用栈中。即 Stack Reconciler。

例如点击按钮,状态 state.count 有 1 变为 2:

若中间有中断发生时:

发生中断后,后续渲染不会发生。

新架构

首先调度器(Scheduler)会调度更新的优先级,高优先级更新会首先进入调和器(Reconciler),在调和器进行调和(reconcile) 过程(Diff 算法)时,若产生了更高优先级的更新,则正在调和的更新会被中断,更高优先级更新进入调和器。(因为调度器和调和器是在内存中工作,不会进行具体的视图操作,即使中断发生,页面也不会有出现更新不完全的视图。)当更新完成在调和器中的工作时,调和器会通知渲染器,本次更新有哪些组件需要执行对应的视图操作,由渲染器执行对应的视图操作。对于 ReactDOM 渲染器来说,这些视图操作包含了 DOM 节点的增删改查。当更新最终完成渲染,调度器会开始新一轮的调度。

例如点击按钮,状态 state.count 有 1 变为 2:

Fiber 架构 及 工作原理

代数效应open in new window:函数式编程中的概念,用于将副作用从函数调用中分离。中断恢复思想。

进程(Process)

线程(Thread)

协程(Coroutine) Generator 和 async 一样,有传染性,不能中断恢复。

纤程(Fiber)

旧架构中的 Reconciler 采用递归的方式运行,数据保存在递归的调用栈中。即 Stack Reconciler。

从根节点一直向下“递”到子节点,再从子节点一路向上“归”到根节点,在“归”阶段,函数处理完子节点后,会返回它的父节点。

新架构中的 Reconciler 基于 Fiber 节点 实现。即 Fiber Reconciler。

作为静态数据结构,每个 Fiber 节点对应一个组件,保存该组件的类型,对应的 DOM 节点等信息,即虚拟 DOM。

作为动态工作单元,保存组件需要更新的状态以及需要执行的副作用。

采用遍历的形式实现可中断递归,复用了 Stack Reconciler 思想。

function App() {
+  const [num, add] = useState(0);
+
+  return (
+    <p onClick={() => add(num + 1)}>
+      Content
+      {num}
+    </p>
+  );
+}
+
+ReactDOM.render(<App />, document.getElementById('root'));
+

双缓存机制

内存中构建并直接替换的技术。

在 mount 时不存在对应的 current Fiber 树,在 update 时存在一棵 current Fiber 树。

源码文件目录与调试


根目录
+|-- fixtures # 包含一些给贡献者准备的小型 React 测试项目
+|-- packages # 包含元数据 与 React 仓库中所有 packages 的源码
+|-- scripts # 各种工具链脚本,如git、jest、eslint等
+
根目录
+|-- packages # 包含元数据 与 React 仓库中所有 packages 的源码
+  |-- react # React 基础包
+  |-- scheduler # 调度器
+  |-- react-reconciler # 调和器,可用于构建自己的 Renderer
+  |-- react-dom # DOM与SSR渲染器
+  |-- react-native-renderer # RN渲染器
+  |-- react-noop-renderer # debug fiber 渲染器
+  |-- react-test-renderer
+  |-- react-art # Canvas与SVG渲染器
+  |-- shared #公共方法
+  |-- react-server # 创建自定义SSR流
+  |-- react-client # 创建自定义流
+  |-- react-fetch # 用于数据请求
+  |-- react-interactions # 用于测试交互相关的内部特性,如 React
+  |-- react-is # 用于测试组件是否是某类型
+  |-- react-refresh # “热重载”的 React 官方实现
+

调试

拉取 React 17.0.0.dev 源码

# git clone -b [BRANCH_NAME] https://github.com/facebook/react.git [DIR_NAME]
+
+git clone -b 17.0.0.dev https://github.com/facebook/react.git react-source
+
+# 执行
+cd react-source
+yarn build react/index,react-dom/index,scheduler --type=NODE
+# 生成 build 文件夹
+cd build/node_modules # 打开 build/node_modules
+cd react && yarn link # 进入 react 文件夹,使用 yarn link 创建软连接
+cd ../react-dom && yarn link # 进入 react-dom 文件夹,使用 yarn link 创建软连接
+
+# 与 react-source 文件夹 同级
+create-react-app react-source-demo # 使用 create-react-app 创建 react 项目
+cd react-source-demo
+rimraf node_modules # 删除 node_modules 文件夹
+# 修改 package.json 中 react 与 react-dom 版本为 17.0.0
+yarn
+yarn link react react-dom # 关联 react-source 中创建的软连接
+yarn start # 运行测试项目,调试源码
+
+ +
+ + + diff --git a/JSBooks/JavaScript/React/implementation.html b/JSBooks/JavaScript/React/implementation.html new file mode 100644 index 0000000..2966f26 --- /dev/null +++ b/JSBooks/JavaScript/React/implementation.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +

实现

Diff 算法

Diff 算法局限与解决方案

单一节点 Diff

多节点 Diff

状态更新

UpdateupdateQueue

Update 优先级机制

ReactDOM.render 执行流程

this.setState 执行流程

生命周期

UNSAFE_componentWillXXX 钩子及替代方案

componentDidXXX 钩子

shouldComponentUpdate 钩子

Hooks

简易 useState 实现

useStateuseReducer

useEffectuseLayoutEffect

useMemouseCallback

useRef

异步调度

Scheduler 实现

lane 模型

更新的中断、继续与重置

useTransition

+ +
+ + + diff --git a/JSBooks/JavaScript/React/index.html b/JSBooks/JavaScript/React/index.html new file mode 100644 index 0000000..5370a34 --- /dev/null +++ b/JSBooks/JavaScript/React/index.html @@ -0,0 +1,17 @@ + + + + + + + React | LOFT + + + + +

React

理念

架构

实现

+ +
+ + + diff --git "a/JSBooks/JavaScript/Vue\345\216\237\347\220\206/index.html" "b/JSBooks/JavaScript/Vue\345\216\237\347\220\206/index.html" new file mode 100644 index 0000000..fac222e --- /dev/null +++ "b/JSBooks/JavaScript/Vue\345\216\237\347\220\206/index.html" @@ -0,0 +1,398 @@ + + + + + + + LOFT + + + + +

Vue 原理 - MVVM 的双向绑定

实现数据绑定的方法:

  • 发布订阅模式 backbone.jsopen in new window

    使用自定义的 data 属性在 HTML 代码中指明绑定。所有绑定过的对象以及 DOM 元素都将订阅一个发布者对象。任何时候这个对象或者 HTML 输入字段被侦测到发生变化,会触发发布者通知事件,告知所有订阅者发生的变化,相应的,如果数据发生相应变化,将会广播并传播到所有绑定的对象和元素上。

  • 脏值检查 angular.jsopen in new window

    在 angular.js 作用域内定义的变量发生变化时(在作用域 scope 内定义的所有变量都有一个 watch,用于监听变量的状态),触发所有变量的循环检查(从根作用 rootscope 开始向内检查,即按锚的深度检查,每一次独立的循环检查为一次 digest cycle ),当检查到数据变更时,则同步数据(如果 model 中的数据发生变更,则向 view 同步,如果 view 中的数据发生变更,则向 model 同步),直到所有数据稳定。当所有数据稳定后,还会执行一次额外的全量检查。脏值检测有上限,一般来说超过 10 次循环检查,第 11 次就会抛出错误。

    angular.js 只在如下事件触发时进行脏值检查:

    • DOM 事件(如用户输入文本,ng-click)
    • XHR 响应事件( $http )
    • 浏览器 Localtion 变更事件( $location )
    • 定时器事件( $timeout$interval )
    • 执行 $digest$apply 方法
  • 数据劫持 vue.js 2.xopen in new window/3.xopen in new window

    vue.js 通过数据劫持(2.x 版本通过 Object.defineProperty() 实现,3.x 版本通过 Proxy 实现),劫持各个属性的 gettersetter 并结合发布订阅模式,在数据变动时发布消息给订阅者,触发相应的监听回调。

实现 vue.js 2.x 的数据绑定

实现 MVVM 的双向绑定需要实现以下几点:

  • 数据监听器 Observer。对数据对象的所有属性进行监听,如果有变动则获取最新值并通知订阅者;
  • 指令解析器 Compiler。对每个元素节点的指令进行扫描与解析,根据指令模板替换数据,以及绑定相应的更新函数;
  • 观察者 Watcher。连接 ObserverCompiler的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。
  • MVVM 构造器。关联上述三者。

数据监听器 Observer

通过 Object.defineProperty() 监听属性的变动。对于要 observe 的数据对象进行递归遍历,对其所有属性包括子属性对象的属性都添加 gettersetter,这样的话,对该数据对象的属性进行赋值操作,就会触发 setter ,即监听到了数据变化。

class Observer {
+  constructor(data) {
+    this.data = data;
+    this.init(data);
+  }
+
+  defineReactive(data, key, val) {
+    observe(val); // 递归监听子属性
+    Object.defineProperty(data, key, {
+      enumerable: true,
+      configurable: false,
+      get() {
+        return val;
+      },
+      set(newVal) {
+        console.log(`detect change: ${val} --> ${newVal}`);
+        val = newVal;
+      },
+    });
+  }
+
+  init(data) {
+    Object.keys(data).forEach((key) => {
+      this.defineReactive(data, key, data[key]);
+    });
+  }
+}
+
+function observe(data) {
+  if (!data || typeof data !== 'object') return;
+  return new Observer(data);
+}
+

监听示例

Run
const foo = {
+  name: 'Walter White',
+  children: [undefined, undefined],
+};
+
+observe(foo);
+foo.name = 'Heisenberg';
+// 'detect change: Walter White --> Heisenberg'
+foo.children[0] = 'Walter Junior';
+// 'detect change: undefined --> Walter Junior'
+

依赖订阅器 Dep

现在我们可以监听数据的变化了,同时我们需要实现消息收集器,收集订阅者(数据依赖),并在数据变动时触发通知同时调用订阅者的更新时间。

let uid = 0;
+class Dep {
+  constructor() {
+    this.id = uid++;
+    this.subscribers = [];
+  }
+
+  add(subscriber) {
+    this.subscribers.push(subscriber);
+  }
+
+  remove(subscriber) {
+    const index = this.subscribers.findIndex(subscriber);
+    if (index !== -1) {
+      this.subscribers.splice(index, 1);
+    }
+  }
+
+  notify() {
+    this.subscribers.forEach((subscriber) => {
+      subscriber.update();
+    });
+  }
+}
+
+class Observer {
+  constructor(data) {
+    this.data = data;
+    this.init(data);
+  }
+
+  defineReactive(data, key, val) {
+    const dep = new Dep();
+    observe(val); // 递归监听子属性
+    Object.defineProperty(data, key, {
+      enumerable: true,
+      configurable: false,
+      get() {
+        return val;
+      },
+      set(newVal) {
+        if (val === newVal) return;
+        console.log(`detect change: ${val} --> ${newVal}`);
+        val = newVal;
+        dep.notify();
+      },
+    });
+  }
+
+  init(data) {
+    Object.keys(data).forEach((key) => {
+      this.defineReactive(data, key, data[key]);
+    });
+  }
+}
+

添加订阅器示例

Run
const foo = {
+  name: 'Walter White',
+  children: [undefined, undefined],
+};
+
+observe(foo);
+foo.name = 'Heisenberg';
+// 'detect change: Walter White --> Heisenberg'
+foo.children[0] = 'Walter Junior';
+// 'detect change: undefined --> Walter Junior'
+

依赖订阅器 添加订阅者Watcher

根据上图,可以看出需要通过 Dep 添加订阅者 Watcher

class Watcher {
+  constructor(vm, expOrFn, cb) {
+    this.vm = vm;
+    this.expOrFn = expOrFn;
+    this.cb = cb;
+    this.depIds = {};
+
+    if (typeof expOrFn === 'function') {
+      this.getter = expOrFn;
+    } else {
+      this.getter = this.parseGetter(expOrFn.trim());
+    }
+
+    this.value = this.get();
+  }
+
+  update() {
+    this.run();
+  }
+
+  run() {
+    const value = this.get();
+    const oldVal = this.value;
+    if (value !== oldVal) {
+      this.value = value;
+      this.cb.call(this.vm, value, oldVal);
+    }
+  }
+
+  /**
+   * 将当前订阅者指向自身
+   * 触发getter,将自己的属性
+   */
+  get() {
+    Dep.target = this;
+    const value = this.getter.call(this.vm, this.vm);
+    Dep.target = null;
+    return value;
+  }
+
+  parseGetter(exp) {
+    if (/[^\w.$]/.test(exp)) return;
+    const exps = exp.split('.');
+    return (obj) => {
+      for (let i = 0, len = exps.length; i < len; i += 1) {
+        if (!obj) return;
+        obj = obj[exps[i]];
+      }
+      return obj;
+    };
+  }
+
+  /**
+   * 维护一个depIds用来存储新添加属性,并为新的属性增加新的setter与dep,
+   * 并将当前watcher加入新的dep中,这样对应的watcher才能得到通知。
+   * @param {Dep} dep 
+   */
+  addDep(dep) {
+    if (!this.depIds.hasOwnProperty(dep.id)) {
+      dep.add(this);
+      this.depIds[dep.id] = dep;
+    }
+  }
+}
+

指令解析器 Compiler

解析指令模板,将模板中的变量替换成数据,初始化渲染页面,将每个指令的对应节点绑定更新函数,添加监听数据订阅者,当数据变动,收到依赖订阅器通知,更新视图。

class Compiler {
+
+  constructor(el, vm) {
+    this.$vm = vm;
+    this.$el = this.isElementNode(el) ? el : document.querySelector(el);
+    if (this.$el) {
+      this.$fragment = this.node2Fragment(this.$el);
+      this.init();
+      this.$el.appendChild(this.$fragment);
+    }
+  }
+
+  init() {
+    this.compileElement(this.$fragment);
+  }
+
+  /**
+   * 遍历所有节点及其子节点,扫描解析编译,
+   * 调用对应指令渲染函数渲染数据并绑定对应的更新函数
+   * @param {Element} el 
+   */
+  compileElement(el) {
+    const childNodes = el.childNodes;
+    [].slice.call(childNodes).forEach(node => {
+      const text = node.textContent;
+      const reg = /\{\{(.*)\}\}/;
+      if (this.isElementNode(node)) {
+        this.compile(node);
+      } else if (this.isTextNode(node) && reg.test(text)) {
+        this.compileText(node, RegExp.$1.trim());
+      }
+      if (node.childNodes && node.childNodes.length) {
+        this.compileElement(node);
+      }
+    });
+  }
+
+  compile(node) {
+    const nodeAttrs = node.attributes;
+    [].slice.call(nodeAttrs).forEach(attr => {
+      const attrName = attr.name;
+      if (this.isDirective(attrName)) {
+        const exp = attr.value;
+        const dir = attrName.substring(2);
+        if (this.isEventDirective(dir)) {
+          Utils.eventHandler(node, this.$vm, exp, dir);
+        } else {
+          Utils[dir] && Utils[dir](node, this.$vm, exp);
+        }
+
+        node.removeAttribute(attrName);
+      }
+    });
+  }
+
+  node2Fragment(el) {
+    const fragment = document.createDocumentFragment();
+    let child = el.firstChild;
+    while (child) {
+      fragment.appendChild(child);
+      child = el.firstChild;
+    }
+    return fragment;
+  }
+
+  compileText(node, exp) {
+    Utils.text(node, this.$vm, exp);
+  }
+
+  isElementNode(node) {
+    return node.nodeType === 1;
+  }
+
+  isTextNode(node) {
+    return node.nodeType === 3;
+  }
+
+  isDirective(attr) {
+    return attr.startsWith('v-');
+  }
+
+  isEventDirective(dir) {
+    return dir.startsWith('on');
+  }
+}
+
+var Utils = {
+  text(node, vm, exp) {
+    this.bind(node, vm, exp, 'text');
+  },
+  html(node, vm, exp) {
+    this.bind(node, vm, exp, 'html');
+  },
+  class(node, vm, exp) {
+    this.bind(node, vm, exp, 'class');
+  },
+  model(node, vm, exp) {
+    this.bind(node, vm, exp, 'model');
+    let val = this._getVMVal(vm, exp);
+    node.addEventListener('input', e => {
+      const newVal = e.target.value;
+      if (val === newVal) return;
+
+      this._setVMVal(vm, exp, newVal);
+      val = newVal;
+    });
+  },
+  bind(node, vm, exp, dir) {
+    const updaterFn = Updater[`${dir}Updater`];
+    updaterFn && updaterFn(node, this._getVMVal(vm, exp));
+    new watchUserConfigFile(vm, exp, (value, oldVal) => {
+      updaterFn && updaterFn(node, value, oldVal);
+    });
+  },
+  eventHandler(node, vm, exp, dir) {
+    const eventType = dir.split(':')[1];
+    const fn = vm.$options.methods && vm.$options.methods[exp];
+    if (eventType && fn) {
+      node.addEventListener(eventType, fn.bind(vm), false);
+    }
+  },
+  _getVMVal(vm, exp) {
+    let val = vm;
+    let exp = exp.split('.');
+    exp.forEach(k => val = val[k]);
+    return val;
+  },
+  _setVMVal(vm, exp, value) {
+    let val = vm;
+    let exp = exp.split('.');
+    exp.forEach((k, i) => {
+      if (i < exp.length - 1) {
+        val = val[k];
+      } else {
+        val[k] = value;
+      }
+    });
+  }
+};
+
+var Updater = {
+  textUpdater(node, value) {
+    node.textContent = typeof value === 'undefined' ? '' : value;
+  },
+  htmlUpdater(node, value) {
+    node.innerHTML = typeof value === 'undefined' ? '' : value;
+  },
+  classUpdater(node, value, oldVal) {
+    let className = node.className;
+    className = className.replace(oldVal, '').replace(/\s$/, '');
+    const space = className && String(value) ? ' ' : '';
+    node.className = className + space + value;
+  },
+  modelUpdater(node, value, oldVal) {
+    node.value = typeof value === 'undefined' ? '' : value;
+  }
+};
+

入口 MVVM 构造器

数据绑定入口,关联上述三者,使用 Observer 监听自身 model 数据变化,使用 Compiler 解析编译模板指令,使用 Watcher 连接 ObserverCompiler,使其可以相互通信。从而实现双向绑定。

class MVVM {
+  constructor(options = {}) {
+    this.$options = options;
+    this._data = this.$options.data;
+    const data = this._data;
+    Object.keys(data).forEach(key => {
+      this._proxyData(key);
+    });
+
+    this._initComputed();
+
+    observe(data, this);
+
+    this.$compiler = new Compiler(options.el || document.body, this);
+  }
+
+  /**
+   * 属性代理方法,将_data中的属性代理到this本身,方便访问。
+   * @param {string} key 
+   * @param {() => void} setter 
+   */
+  _proxyData(key, setter) {
+    const _this = this;
+    setter = setter || Object.defineProperty(this, key, {
+      enumerable: true,
+      configurable: false,
+      get() {
+        return _this._data[key];
+      },
+      set(newVal) {
+        this._data[key] = newVal;
+      }
+    });
+  }
+
+  _initComputed() {
+    const computed = this.$options.computed;
+    if (typeof computed === 'object') {
+      Object.keys(computed).forEach(key => {
+        Object.defineProperty(this, key, {
+          get: typeof computed[key] === 'function'
+            ? computed[key]
+            : computed[key].get,
+          set() { }
+        });
+      });
+    }
+  }
+
+  $watch(key, cb) {
+    new Watcher(this, key, cb);
+  }
+}
+

MVVM Vue2 示例


{{ getHelloWord }}

MVVM Vue3 示例


+ +
+ + + diff --git a/JSBooks/JavaScript/gc.html b/JSBooks/JavaScript/gc.html new file mode 100644 index 0000000..7101169 --- /dev/null +++ b/JSBooks/JavaScript/gc.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +

JavaScript 垃圾回收(Garbage Collection)

通常情况下,垃圾回收分为 手动回收自动回收 两种策略。

调用栈中的数据回收

JavaScript 引擎会通过向下移动 ESP(记录当前执行状态的指针)来销毁该函数保存在栈中的执行上下文。

堆中的数据回收

在 V8 引擎中,会把堆分为 新生区(存放生存时间短的对象) 和 老生区(存放生存时间久的对象) 两个区域。V8 使用两个不同的垃圾回收器进行高效垃圾回收。主垃圾回收器负责老生区的垃圾回收,副垃圾回收器负责新生区的垃圾回收。后续垃圾回收使用 GC 缩写代替。

主副垃圾回收器有共同的执行 GC 流程:

  1. 标记空间中的活动对象(正在使用的对象)与非活动对象(可进行 GC 的对象);
  2. 在所有标记完成之后,回收所有非活动对象的占用内存(即统一清理所有被标记为可回收的对象);
  3. 内存整理,一般情况下,频繁回收对象,内存会产生大量不连续的空间(即内存碎片),此时如果需要分配较大连续内存时,有可能出现内存不足的情况。因此需要整理内存碎片。

新生区中的垃圾回收

新生区中使用 Scavenge 算法。将新生区空间对半划分为 对象(from)区域空闲(to)区域,新加入的对象会存放到对象区域,当对象区域快写满时,执行一次 GC 操作。

副垃圾回收器会把存活的对象复制到空闲区域,同时对它们进行排序,相当于完成内存整理操作。复制完成后,将对象区域和空闲区域角色互换,即原来的空闲区域变为对象区域,原来的对象区域变为空闲区域,这样就完成了整个 GC 操作。角色互换的操作让新生区的两块区域可以无限重复使用。

为了执行效率,一般新生区的空间设置不大,为 1~8 M 左右,因此很容易被存活的对象装满整个区域,此时 V8 引擎引入了 对象晋升 策略,即经过两次 GC 依然存活的对象,”晋升“到老生区。

老生区中的垃圾回收

老生区中使用 Mark-Sweep 算法,即标记-清除算法。

从一组根元素开始,递归遍历根元素,在遍历过程中,能到达的元素被标记为活动对象,不可到达的元素被标记为可回收对象,标记完成后进行清除过程。

清除完成后,会产生大量不连续的内存碎片,碎片过多会导致分配较大连续内存时内存不足,此时是使用 Mark-Compact 即标记-整理算法,标记过程与前面相同,标记完成后,让所有存活的对象都向一端移动,然后清理掉边界以外的内存,从而清理出连续的内存块。

全停顿行为

JavaScript 运行在主线程上,执行 GC 算法时,需要将正在执行的 JavaScript 脚本暂停,待 GC 完成后在恢复脚本执行。这种行为即为全停顿。

新生区的 GC,基本不会有全停顿,但老生区占用空间大,执行 GC 可能会长时间占用主线程,此时就会造成页面卡顿现象。

V8 采用 Incremental Marking 算法,即增量标记算法。将标记过程细分为小的子标记过程,让 GC 标记与 JavaScript 应用逻辑交替进行,直到标记阶段完成。

将大任务分割成小任务,小任务执行时间短,穿插在 JavaScript 任务中执行,从而尽可能减少卡顿现象。这个设计思路与 React Fiber 很像。

早期的 GC 机制 - 引用计数

引擎维护一张引用表,保存内存里资源的引用次数,如果一个资源的引用次数为 0,则表示该资源内存可以被回收。

由于这个机制的缺点是不能回收循环引用,因此被废弃。

内存泄露(Memory leak)

当不在用到的对象内存没有及时被回收时,我们称为内存泄露。

原因

  1. 数据缓存大小超限;
  2. 浏览器队列消费不及时,导致一些作用域变量不能及时释放;
  3. 隐式全局变量。意外定义了全局变量。最好规范定义变量;
  4. 定时器引用未清除;
  5. 不正确的使用闭包。闭包内变量的间接/错误引用;
  6. 事件重复监听;
  7. 未清理的 console 输出,也可能造成内存泄露。

避免内存泄露的方法

  1. 尽量减少全局变量的使用,避免意外定义全局变量;
  2. 闭包内的变量引用(如 Dom 元素),要及时清理;
  3. 定时器引用及时清除;
  4. 使用 WeakSetWeakmap 结构,它们都是弱引用,不影响 GC 机制;
  5. 生成环境,及时清理 console 输出。
+ +
+ + + diff --git a/JSBooks/JavaScript/index.html b/JSBooks/JavaScript/index.html new file mode 100644 index 0000000..ec5d715 --- /dev/null +++ b/JSBooks/JavaScript/index.html @@ -0,0 +1,17 @@ + + + + + + + LOFT + + + + +

JavaScript 基础

完整的 JavaScript 实现包含 3 个部分:

  • 核心(ECMAScript)。ECMA-262open in new window所定义的语言。
  • 文档对象模型(DOM,Document Object Model)。提供与网页内容交互的方法与接口。
  • 浏览器对象模型(BOM,Browser Object Model)。提供与浏览器交互的方法与接口。

ECMAScript

ECMAScript-262 定义了 这门语言的七个部分:

  • 语法
  • 类型
  • 语句
  • 关键字
  • 保留字
  • 操作符
  • 全局对象

ECMAScript 是这个语言的规范,JavaScript 实现了这个规范,Adobe ActionScript 也实现了这个规范。

DOM

文档对象模型(DOM,Document Object Model)是一个应用编程接口(API),用于在 HTML 中使用扩展的 XML。DOM 将整个页面抽象为一组分层节点。DOM 通过创建文档的树,是开发者可以使用 DOM API 轻松控制(增加节点、删除节点、替换节点、修改节点)网页的内容与结构。

提示

DOM 并非只能通过 JavaScript 访问,其他很多语言也实现了 DOM。但对浏览器来说,DOM 就是使用 ECMAScript 实现的。

下面列出的是基于 XML 的,每一种都增加了该语言独有的 DOM 方法和接口:

  • 可伸缩矢量图(SVG, Scalable Vector Graphics)
  • 数学标记语言(MathML, Mathematical Markup Language)
  • 同步多媒体集成语言(SMIL, Synchoronized Multimedia Integration Language)

BOM

浏览器对象模型(BOM,Browser Object Model)用于支持访问和操作浏览器的窗口。HTML5的出现以正式规范的方式涵盖了尽可能多的BOM特性。比如:

  • 弹出新浏览器窗口的能力;
  • 移动、缩放及关闭浏览器窗口的能力;
  • 对cookie的支持;
  • navigator对象。 提供关于浏览器的详细信息;
  • location对象。提供浏览器加载页面的详细信息;
  • screen对象。提供关于用户屏幕部分的详细信息;
  • performance对象。提供浏览器内存占用、导航行为及时间统计的详细信息;
  • XMLHttpRequest/(IE)ActiveXObject对象。提供浏览器与服务器交互的能力;
+ +
+ + + diff --git a/JSBooks/JavaScript/mathematicalNotation.html b/JSBooks/JavaScript/mathematicalNotation.html new file mode 100644 index 0000000..16fba13 --- /dev/null +++ b/JSBooks/JavaScript/mathematicalNotation.html @@ -0,0 +1,144 @@ + + + + + + + LOFT + + + + +

数学标记Katexopen in new window

数乘运算/Scalar multiplication

const result = 3 * 4;
+
const result = 4 * k * j;
+

向量乘法/Vector multiplication

function multiply(a, b) {
+  return [a[0] * b[0], a[1] * b[1]];
+}
+
+function multiplyScalar(a, scalar) {
+  return [a[0] * scalar, a[1] * scalar];
+}
+
+const s = 4;
+const k = [1, 2];
+const j = [2, 3];
+const v = multiply(k, j);
+const result = multiplyScalar(v, s); // [8, 24]
+

点乘/数量积/内积/Dot Product

function dot(a, b) {
+  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+}
+
+const k = [0, 1, 0];
+const j = [1, 0, 0];
+const result = dot(k, j); // 0
+

叉乘/向量积/外积/Cross Product

function cross(a, b) {
+  const [ax, ay, az] = a;
+  const [bz, by, bz] = b;
+
+  return [
+    ay * bz - az * by, // rx
+    az * bx - ax * bz, // ry
+    ax * by - ay * bx, // rz
+  ];
+}
+
+const k = [0, 1, 0];
+const j = [1, 0, 0];
+const result = cross(k, j); // [0, 0, -1]
+

求和/Summation

function sum(start, end, expression) {
+  let res = 0;
+  for (let i = start; i <= end; i++) {
+    res += expression(i);
+  }
+  return res;
+}
+
Run
const result = sum(1, 100, (i) => i); // 5050
+console.log(result);
+
Run
const result = sum(1, 100, (i) => 2 * i + 1); // 10200
+console.log(result);
+
Run
function sum(s1, s2, expression) {
+  const [start1, end1] = s1;
+  const [start2, end2] = s2;
+  let res = 0;
+  for (let i = start1; i <= end1; i++) {
+    for (let j = start2; j <= end2; j++) {
+      res += expression(i, j);
+    }
+  }
+  return res;
+}
+const result = sum([1, 2], [4, 6], (i, j) => 3 * i * j); // 135
+console.log(result);
+

求积/Multiplication

function mul(start, end, expression) {
+  let res = 1;
+  for (let i = start; i <= end; i++) {
+    res *= expression(i);
+  }
+  return res;
+}
+
Run
const result = mul(1, 7, (i) => i);
+console.log(result); // 5040
+

绝对值/Absolute value

Run
const val = -1;
+const result = Math.abs(val);
+console.log(result); // 1
+

欧氏范数/Euclidean norm

Run
function euclidean(vector) {
+  const [x, y, z] = vector;
+  return Math.sqrt(x ** 2 + y ** 2 + z ** 2);
+}
+const result = euclidean([0, 6, -8]);
+console.log(result); // 10
+

行列式/Determinant

Run
function determinant(a) {
+  return a[0] * a[3] - a[2] * a[1];
+}
+const result = determinant([1, 0, 0, 1]);
+console.log(result); // 1
+

单位矢量/Unit vector

Run
function normalize(vector) {
+  const [x, y, z] = vector;
+  const squaredLength = x ** 2 + y ** 2 + z ** 2;
+  if (squaredLength > 0) {
+    const length = Math.sqrt(squaredLength);
+    return [x / length, y / length, z / length];
+  }
+  return vector;
+}
+const result = normalize([1, 0, 0]);
+console.log(result); // [1, 0, 0]
+const result2 = normalize([0, 8, -6]);
+console.log(result2); // [0, 0.8, -0.6]
+

元素集合/Element Set

Run
const A = [1, 3, 4, 5, 6, 7, 10, 20];
+const result = A.includes(6);
+console.log(result); // true
+

方程/Function

Run
function cube(x) {
+  return Math.pow(x, 3);
+}
+const result = cube(3);
+console.log(result); // 27
+
Run
function squareroot(x, y) {
+  return Math.sqrt(x ** 2 + y ** 2);
+}
+const result = squareroot(3, 4);
+console.log(result); // 5
+

分段函数/Piecewise function

Run
function piecewise(x) {
+  if (x >= 1) {
+    return (Math.pow(x, 2) - x) / x;
+  }
+  return 0;
+}
+const result = piecewise(3);
+console.log(result); // 2
+

符号函数/Signum function

Run
function sgn(x) {
+  if (x < 0) return -1;
+  if (x > 0) return 1;
+  return 0;
+}
+const result = sgn(3);
+console.log(result); // 1
+

向上取整(ceiling)/向下取整(floor)/四舍五入(round)

Run
Math.ceil(x); // 向上取整
+Math.floor(x); // 向下取整
+Math.round(x); // 四舍五入
+
+ +
+ + + diff --git a/JSBooks/JavaScript/this.html b/JSBooks/JavaScript/this.html new file mode 100644 index 0000000..6dbd43d --- /dev/null +++ b/JSBooks/JavaScript/this.html @@ -0,0 +1,162 @@ + + + + + + + LOFT + + + + +

JavaScript 中的 this

下面是一个示例,通过给getName()say()显式传入一个上下文对象。

Run
function getName(context) {
+  return context.name;
+}
+
+function say(context) {
+  console.log(`Hello, I'm ${getName(context)}.`);
+}
+
+say({ name: 'Moriarty' });
+

this 提供了一种更优雅的方式隐式传递一个对象引用,从而使得 API 的设计更加简洁且便于复用。

Run
function getName() {
+  return this.name;
+}
+
+function say() {
+  console.log(`Hello, I'm ${getName.call(this)}`);
+}
+
+say.call({ name: 'Moriarty' });
+

随着应用越来越庞大,显式传递上下文对象的使用方式也可能会越来越复杂,从而使代码变得混乱。使用 this 会在一定程度上减少这种混乱。

我们需要清楚的是,this在运行时进行绑定的,它的上下文取决于函数调用时的各种条件。 this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式,也就是函数的调用位置

函数的调用位置

要确定 this 指向,就要正确分析函数的调用位置,也就需要分析函数调用栈(即到达当前执行位置所调用的所有函数)。函数的调用位置就在当前正在执行的函数的前一个调用中。

SBS
function baz() {
+  console.log('baz');
+}
+
+function bar() {
+  console.log('bar');
+  baz();
+}
+
+function foo() {
+  console.log('foo');
+  bar();
+}
+
+foo();
+

 



 
 



 
 


 

绑定规则

默认绑定

最常用的绑定方式,即独立的函数调用。

Run
// 全局作用域
+function foo() {
+  console.log(this.a); // 'Hello, world!'
+}
+
+var a = 'Hello, world!';
+
+foo();
+
+function bar() {
+  'use strict';
+  try {
+    console.log(this.b);
+  } catch (e) {
+    console.log(e); // TypeError: Cannot read properties of undefined (reading 'b')
+  }
+}
+
+var b = 'What the heck?';
+
+bar();
+
+function baz() {
+  console.log(this.c); // 'This is important!'
+}
+
+var c = 'This is important!';
+
+(function () {
+  'use strict';
+  baz();
+})();
+

 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 
 
 
 

 
 
 
 
 
 
 
 
 
 

上面例子非严格模式中,foo 函数调用时应用了 this 默认绑定,this 指向全局对象。foo() 直接使用了不带任何修饰符的函数引用进行调用,因此只能使用默认绑定。 在严格模式下,不能将全局对象用于默认绑定。 this 会绑定到 undefined。 但在这一种情况下,只有 baz 函数运行在非严格模式下,默认绑定会将其绑定到全局对象,在严格模式下调用 baz() 不会影响默认绑定。

隐式绑定


Run
const foo = {
+  a: 'Hello',
+  log() {
+    console.log(this.a);
+  },
+};
+
+foo.log(); // 'Hello'
+

当函数引用有上下文对象时,隐式绑定会把函数调用中的 this 绑定到这个上下文对象。通常来说,对象属性引用链中最后一层调用对象即为 this 的绑定对象。

Run
var b = 'global b';
+
+function say() {
+  console.log(this.b);
+}
+
+const foo = {
+  a: 'Hello',
+  say,
+  bar: {
+    b: 'World',
+    say,
+  },
+};
+
+foo.bar.say(); // 'World'
+
+const say2 = foo.say;
+say2(); // 'global b'
+
+const say3 = foo.bar.say;
+say3(); //  'global b'
+

















 



 

18/22行,say2say3 引用的实际上是 say 函数本身,因此其调用方式实际上应用了 默认绑定。可以看出其丢失 this 绑定。 另一种常见的丢失 this 绑定,回调函数丢失 this 绑定如下:

Run
function say() {
+  console.log(this.a);
+}
+
+function callSay(func) {
+  func();
+}
+
+const foo = {
+  a: 'Hello',
+  say,
+};
+
+var a = 'World';
+
+callSay(foo.say); // 'World'
+

显式绑定

我们可以使用函数的内置方法: bindcall 以及 apply 方法,显式绑定 this 指向。

Run
function say() {
+  console.log(this.a);
+}
+
+const foo = { a: 'Hello' };
+
+say.call(foo); // 'Hello'
+

通过 say.call(obj),可以在调用 say 时把它的 this 强制绑定到 obj 上。当这个 obj 是一个原始值时,该原始值会通过“装箱”函数( new String()new Boolean()new Number() )转换为其对象形式。

apply(this: Function, thisArg: any, argArray?: any): any;
+
+call(this: Function, thisArg: any, ...argArray: any[]): any;
+
+bind(this: Function, thisArg: any, ...argArray: any[]): any;
+

callapply 都是绑定 this 并运行函数,从函数签名可以看出 callapply 的区别在于参数形式的不同。 bind 方法只是一种硬绑定,只绑定 this 并不运行函数。

Run
function say(b) {
+  console.log(`${this.a}, ${b}!`);
+}
+
+const foo = { a: 'Hello' };
+
+const bindSay = say.bind(foo);
+
+bindSay('world'); // 'Hello, world!'
+

new 绑定

JavaScript 中 new 的机制与面向类的语言机制完全不同。JavaScript 中的构造函数实际上只是一些使用 new 操作符时被调用的函数。

实际上,包括内置对象函数在内的所有函数都可以用 new 来调用,这种调用被称为 构造函数调用。 使用 new 来调用函数时,会自动执行下面的操作:

  • 创建一个新对象;
  • 对新对象执行 [[Prototype]] 连接;
  • 将新对象绑定到函数调用的 this
  • 如果函数没有返回其他对象,则 new 表达式中的函数会自动返回上述新对象。
Run
function Foo(name) {
+  this.name = name;
+}
+
+const foo = new Foo('Hello');
+console.log(foo.name); // 'Hello'
+

优先级

当把 nullundefined 作为 this 的绑定对象传入 callapplybind 时,这些值会在调用时被忽略,实际应用的是默认绑定规则。

DMZ 对象

DMZ 对象(demilitarized zone) 非军事区对象,即一个空的非委托对象。

如果我们在忽略 this 绑定时总是传入一个 DMZ 对象,可以将 this 的使用限制在这个空对象中,不会对全局对象产生任何副作用(影响)。

Run
function foo(a, b) {
+  console.log(`${a} ${b}!`);
+}
+const NULL = Object.create(null);
+foo.apply(NULL, ['Hello', 'world']); // 'Hello world!'
+const bindFoo = foo.bind(NULL, 'Hello');
+bindFoo('world'); // 'Hello world!'
+
+ +
+ + + diff --git a/JSBooks/index.html b/JSBooks/index.html new file mode 100644 index 0000000..f2ed9a9 --- /dev/null +++ b/JSBooks/index.html @@ -0,0 +1,17 @@ + + + + + + + JavaScript基础 | LOFT + + + + +

JavaScript 实现

ECMAScript 新特性简介

+ +
+ + + diff --git a/Leetcode/Array/1-two-sum.html b/Leetcode/Array/1-two-sum.html new file mode 100644 index 0000000..f5fa84c --- /dev/null +++ b/Leetcode/Array/1-two-sum.html @@ -0,0 +1,64 @@ + + + + + + + LOFT + + + + +

二数之和

给定一个整数数组 nums 和一个整数目标值 target ,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回答案。

  • 2 <= nums.length <= 10e4
  • -10e9 <= nums[i] <= 10e9
  • -10e9 <= target <= 10e9
  • 只会存在一个有效答案
input : nums = [2, 7, 11, 15], target = 9
+output: [0, 1]
+exp   : 因为 nums[0] + nums[1] == 9, 返回[0, 1]。
+input : nums = [3, 2, 4], target = 6
+output: [1, 2]
+input : nums = [3, 3], target = 6
+output: [0, 1]
+

解法 1: 暴力解法


Run
function twoSum(nums, target) {
+  const len = nums.length;
+  for (let i = 0; i < len; i++) {
+    const prev = nums[i];
+    for (let j = i + 1; j < len; j++) {
+      const next = nums[j];
+      logger(`${prev}, ${next}`);
+      suspend();
+      if (prev + next === target) {
+        done();
+        return [i, j];
+      }
+    }
+  }
+}
+
+// twoSum([11, 15, 2, 7], 9);
+// twoSum([3, 3], 6);
+

解法 2:哈希表


Run
function twoSum(nums, target) {
+  const len = nums.length;
+  const map = {};
+  for (let i = 0; i < len; i++) {
+    const num = nums[i];
+    const targetNum = target - num;
+    const targetIndex = map[targetNum];
+    if (targetIndex !== undefined) {
+      logger(`${targetIndex}, ${i}`);
+      suspend();
+      done();
+      return [targetIndex, i];
+    } else {
+      logger(`${num}, ${i}`);
+      suspend();
+      map[num] = i;
+    }
+  }
+}
+
+// twoSum([11, 15, 2, 7], 9);
+// twoSum([3, 3], 6);
+
+ +
+ + + diff --git a/Leetcode/Array/14-longest-common-prefix.html b/Leetcode/Array/14-longest-common-prefix.html new file mode 100644 index 0000000..5dc51e4 --- /dev/null +++ b/Leetcode/Array/14-longest-common-prefix.html @@ -0,0 +1,76 @@ + + + + + + + LOFT + + + + +

最长公共前缀

编写一个函数来查找字符串数组总的最长公共前缀。如果不存在公共前缀,返回空字符串 ''

input : strs = ['flower', 'flow', 'flight']
+output: 'fl'
+input : strs = ['dog', 'racecar', 'car']
+output: ''
+

解法 1: 暴力解法


Run
function longestCommonPrefix(strs) {
+  const len = strs.length;
+  let minLen = strs[0].length;
+  for (let i = 0; i < len; i++) {
+    if (minLen > strs[i].length) {
+      minLen = strs[i].length;
+    }
+  }
+  const arr = Array(minLen);
+  let res = '';
+  for (let i = 0; i < minLen; i++) {
+    let prev = '';
+    for (let j = 0; j < len; j++) {
+      const str = strs[j];
+      if (!arr[i]) {
+        arr[i] = 1;
+      }
+      if (prev && prev === str[i]) {
+        arr[i]++;
+      }
+      prev = str[i];
+    }
+    prev = '';
+    if (arr[i] === len) {
+      res += strs[0][i];
+      logger(res);
+      suspend();
+    }
+    if (arr[i] !== len) {
+      done();
+      return res;
+    }
+  }
+  done();
+  return res;
+}
+

解法 2


Run
function longestCommonPrefix(strs) {
+  if (strs.length < 1) return '';
+  let prefix = strs[0];
+  const len = strs.length;
+  for (let i = 0; i < len; i++) {
+    const str = strs[i];
+    while (str.indexOf(prefix) !== 0) {
+      if (prefix.length === 0) {
+        done();
+        return '';
+      }
+      prefix = prefix.slice(0, prefix.length - 1);
+      logger(`${str}-${prefix}`);
+      suspend();
+    }
+  }
+  done();
+  return prefix;
+}
+
+ +
+ + + diff --git a/Leetcode/Array/15-3sum.html b/Leetcode/Array/15-3sum.html new file mode 100644 index 0000000..dcd26b3 --- /dev/null +++ b/Leetcode/Array/15-3sum.html @@ -0,0 +1,112 @@ + + + + + + + LOFT + + + + +

三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。

  • 3 <= nums.length <= 3000
  • -10e5 <= nums[i] <= 10e5
 input : nums = [-1, 0, 1, 2, -1, -4]
+ output: [[-1, -1, 2], [-1, 0, 1]]
+ exp   :
+         nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
+         nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
+         nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
+         不同的三元组是 [-1,0,1][-1,-1,2] 。
+         注意,输出的顺序和三元组的顺序并不重要。
+ input : nums = [0, 1, 1]
+ output: []
+ exp   : 唯一可能的三元组和不为0。
+ input : nums = [0, 0, 0]
+ output: [[0, 0, 0]]
+ exp   : 唯一可能的三元组和不为0。
+

解法 1


Run
function threeSum(nums) {
+  const len = nums.length;
+  const map = {};
+  for (let i = 0; i < len; i++) {
+    const numi = nums[i];
+    for (let j = 0; j < len; j++) {
+      const numj = nums[j];
+      if (i === j) {
+        break;
+      }
+
+      let sum = numi + numj;
+      sum = sum === 0 ? 0 : -sum;
+      const idx = nums.indexOf(sum);
+      if (idx >= 0 && i !== idx && j !== idx) {
+        const arr = [numi, numj, sum];
+        arr.sort((a, b) => a - b);
+        if (!map[arr.join(',')]) {
+          logger(arr);
+          suspend();
+        }
+        map[arr.join(',')] = arr;
+      }
+    }
+  }
+  done();
+  return Object.values(map);
+}
+/*
+threeSum([-1, 0, 1, 2, -1, -4]);
+// [[-1, 0, 1], [-1, -1, 2]]
+threeSum([0, 1, 1]);
+// []
+threeSum([0, 0, 0]);
+// [0, 0, 0]
+threeSum([0, 0, 0, 0, 0, 0, 0, 0]);
+// [0, 0, 0]
+threeSum([1, -1, -1, 0]);
+// [-1, 0, 1]
+threeSum([1, -1, 0, 0]);
+// [-1, 0, 1]
+threeSum([-2, 0, 0, 2, 2]);
+// [-2, 0, 2]
+*/
+

解法 2


Run
function threeSum(nums) {
+  nums.sort((a, b) => a - b);
+  const len = nums.length;
+  let p = 0;
+  const res = [];
+  while (p < len) {
+    let base = nums[p];
+    if (base > 0) {
+      p++;
+      break;
+    }
+    let left = p + 1;
+    let right = len - 1;
+    if (p === 0 || base !== nums[p - 1]) {
+      while (left < right) {
+        const numL = nums[left];
+        const numR = nums[right];
+        if (base + numL + numR === 0) {
+          res.push([base, numL, numR]);
+          logger([base, numL, numR]);
+          suspend();
+          while (left < right && numL === nums[left + 1]) left++;
+          while (left < right && numR === nums[right - 1]) right--;
+          left++;
+          right--;
+        } else if (base + numL + numR < 0) {
+          left++;
+        } else {
+          right--;
+        }
+      }
+    }
+    p++;
+  }
+  done();
+  return res;
+}
+
+ +
+ + + diff --git a/Leetcode/Array/18-4sum.html b/Leetcode/Array/18-4sum.html new file mode 100644 index 0000000..59c5249 --- /dev/null +++ b/Leetcode/Array/18-4sum.html @@ -0,0 +1,134 @@ + + + + + + + LOFT + + + + +

四数之和

给你一个由 n 个整数组成的数组  nums ,和一个目标值 target 。请你找出并返回满 足下述全部条件且不重复的四元组  [nums[a], nums[b], nums[c], nums[d]] (若两个 四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n

  • a、b、c 和 d 互不相同

  • nums[a] + nums[b] + nums[c] + nums[d] == target

    你可以按 任意顺序 返回答案。

  • 1 <= nums.length <= 2000

  • -10e9 <= nums[i] <= 10e9

  • -10e9 <= target <= 10e9

input : nums = [1, 0, -1, 0, -2, 2]
+output: [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
+input : nums = [2, 2, 2, 2, 2]
+output: [[2, 2, 2, 2]]
+

解法 1


Run
function fourSum(nums, target) {
+  if (nums.length < 4) {
+    logger([]);
+    done();
+    return [];
+  }
+  nums.sort((a, b) => a - b);
+  const res = [];
+  const len = nums.length;
+  for (let i = 0; i < len - 3; i++) {
+    const numA = nums[i];
+    if (i > 0 && numA === nums[i - 1]) continue;
+    if (numA + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;
+    if (numA + nums[len - 3] + nums[len - 2] + nums[len - 1] < target) continue;
+    for (let j = i + 1; j < len - 2; j++) {
+      const numB = nums[j];
+      if (j > i + 1 && numB === nums[j - 1]) continue;
+      if (numA + numB + nums[j + 1] + nums[j + 2] > target) break;
+      if (numA + numB + nums[len - 2] + nums[len - 1] < target) continue;
+      let left = j + 1;
+      let right = len - 1;
+      while (left < right) {
+        const numL = nums[left];
+        const numR = nums[right];
+        const sum = numA + numB + numL + numR;
+        if (sum === target) {
+          res.push([numA, numB, numL, numR]);
+          logger([numA, numB, numL, numR]);
+          suspend();
+          while (left < right && numL === nums[left + 1]) left++;
+          while (left < right && numR === nums[right - 1]) right--;
+          left++;
+          right--;
+        } else if (sum < target) {
+          left++;
+        } else {
+          right--;
+        }
+      }
+    }
+  }
+  done();
+  return res;
+}
+/* 
+fourSum([1, 0, -1, 0, -2, 2], 0);
+// [-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]
+fourSum([2, 2, 2, 2, 2], 8);
+// [2, 2, 2, 2]
+fourSum([2, 2, 3, 2, -1], 6);
+// [-1, 2, 2, 3]
+fourSum([-2, -1, -1, 1, 1, 2, 2], 0);
+// [-2, -1, 1, 2], [-1, -1, 1, 1]
+*/
+

解法 2


Run
function fourSum(nums, target) {
+  const quadruplets = [];
+  if (nums.length < 4) {
+    logger([]);
+    done();
+    return quadruplets;
+  }
+  nums.sort((x, y) => x - y);
+  const length = nums.length;
+  for (let i = 0; i < length - 3; i++) {
+    if (i > 0 && nums[i] === nums[i - 1]) {
+      continue;
+    }
+    if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
+      break;
+    }
+    if (
+      nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] <
+      target
+    ) {
+      continue;
+    }
+    for (let j = i + 1; j < length - 2; j++) {
+      if (j > i + 1 && nums[j] === nums[j - 1]) {
+        continue;
+      }
+      if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
+        break;
+      }
+      if (nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
+        continue;
+      }
+      let left = j + 1,
+        right = length - 1;
+      while (left < right) {
+        const sum = nums[i] + nums[j] + nums[left] + nums[right];
+        if (sum === target) {
+          quadruplets.push([nums[i], nums[j], nums[left], nums[right]]);
+          logger([nums[i], nums[j], nums[left], nums[right]]);
+          suspend();
+          while (left < right && nums[left] === nums[left + 1]) {
+            left++;
+          }
+          left++;
+          while (left < right && nums[right] === nums[right - 1]) {
+            right--;
+          }
+          right--;
+        } else if (sum < target) {
+          left++;
+        } else {
+          right--;
+        }
+      }
+    }
+  }
+  done();
+  return quadruplets;
+}
+
+ +
+ + + diff --git a/Leetcode/Array/6-zigzag-conversion.html b/Leetcode/Array/6-zigzag-conversion.html new file mode 100644 index 0000000..5b32f67 --- /dev/null +++ b/Leetcode/Array/6-zigzag-conversion.html @@ -0,0 +1,194 @@ + + + + + + + LOFT + + + + +

Z/N 字形变换

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行  Z 字形排列。 比如输入字符串为 "PAYPALISHIRING"  行数为 3 时,排列如下:

P   A   H   N
+A P L S I I G
+Y   I   R
+

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。 请你实现这个将字符串进行指定行数变换的函数:string convert(string s, int numRows);

  • 1 <= s.length <= 1000
  • s 由英文字母(小写和大写)、',''.' 组成
  • 1 <= numRows <= 1000
input : s = 'PAYPALISHIRING', numRows = 3
+output: 'PAHNAPLSIIGYIR'
+input : s = 'PAYPALISHIRING', numRows = 4
+output: 'PINALSIGYAHRPI'
+exp   :
+        P     I    N
+        A   L S  I G
+        Y A   H R
+        P     I
+input : s = 'A', numRows = 1
+output: 'A'
+

解法 1: 暴力解法


Run
function convert(s, numRows) {
+  if (numRows === 1) {
+    logger(s);
+    done();
+    return s;
+  }
+  const len = s.length;
+  const numSingleLetterCol = numRows - 2;
+  const modColumns = len % (numRows + numSingleLetterCol);
+  const numColumns =
+    Math.floor(len / (numRows + numSingleLetterCol)) *
+      (1 + numSingleLetterCol) +
+    (modColumns === 0 ? 0 : modColumns > numRows ? 2 : 1);
+  const res = Array(numRows);
+  for (let i = 0; i < numRows; i++) {
+    res[i] = Array(numColumns);
+  }
+  let row = 0;
+  let col = 0;
+  let order = true;
+  for (let i = 0; i < len; i++) {
+    const letter = s[i];
+
+    res[row][col] = letter;
+    logger(res.join(','));
+    suspend();
+    if (numRows === 2) {
+      if (row >= 1) {
+        row = 0;
+        col++;
+      } else {
+        row++;
+      }
+      continue;
+    }
+    if (row >= numRows - 1) {
+      order = false;
+    }
+
+    if (order) {
+      row++;
+    } else {
+      row--;
+      col++;
+    }
+
+    if (row < 0) {
+      row = 0;
+    } else if (row === 0) {
+      order = true;
+    }
+  }
+
+  let output = '';
+  for (let i = 0; i < numRows; i++) {
+    output += res[i].join('');
+  }
+  logger(output);
+  done();
+  return output;
+}
+
+// convert('PAYPALISHIRING', 1);
+// 'PAYPALISHIRING'
+// convert('PAYPALISHIRING', 2);
+// 'PYAIHRNAPLSIIG'
+// convert('PAYPALISHIRING', 3);
+// 'PAHNAPLSIIGYIR'
+// convert('PAYPALISHIRING', 4);
+// 'PINALSIGYAHRPI'
+// convert('PAYPALISHIRING', 5);
+// 'PHASIYIRPLIGAN'
+// convert('PAYPALISHIRING', 6);
+// 'PRAIIYHNPSGAIL'
+// convert('A', 1);
+

解法 2


Run
function convert(s, numRows) {
+  if (numRows === 1) {
+    logger(s);
+    done();
+    return s;
+  }
+  const len = s.length;
+  const slot = 2 * numRows - 2;
+  let res = '';
+  for (let row = 0; row < numRows; row++) {
+    for (let j = row; j < len; ) {
+      const letter = s[j];
+      logger(letter);
+      suspend();
+      res += letter;
+      if (row !== 0 && row !== numRows - 1) {
+        const singleLetter = s[j + (numRows - 1 - row) * 2];
+        if (singleLetter) {
+          logger(singleLetter);
+          suspend();
+          res += singleLetter;
+        }
+        j += slot;
+      } else {
+        j += slot;
+      }
+    }
+  }
+  logger(res);
+  done();
+  return res;
+}
+
+// convert('PAYPALISHIRING', 1);
+// 'PAYPALISHIRING'
+// convert('PAYPALISHIRING', 2);
+// 'PYAIHRNAPLSIIG'
+// convert('PAYPALISHIRING', 3);
+// 'PAHNAPLSIIGYIR'
+// convert('PAYPALISHIRING', 4);
+// 'PINALSIGYAHRPI'
+// convert('PAYPALISHIRING', 5);
+// 'PHASIYIRPLIGAN'
+// convert('PAYPALISHIRING', 6);
+// 'PRAIIYHNPSGAIL'
+// convert('A', 1);
+

解法 3


Run
function convert(s, numRows) {
+  const n = s.length;
+  const r = numRows;
+  if (r === 1 || r >= n) {
+    logger(s);
+    done();
+    return s;
+  }
+  const t = r * 2 - 2;
+  const ans = [];
+  for (let i = 0; i < r; i++) {
+    // 枚举矩阵的行
+    for (let j = 0; j < n - i; j += t) {
+      // 枚举每个周期的起始下标
+      ans.push(s[j + i]); // 当前周期的第一个字符
+      logger(s[j + i]);
+      suspend();
+      if (0 < i && i < r - 1 && j + t - i < n) {
+        ans.push(s[j + t - i]); // 当前周期的第二个字符
+        logger(s[j + t - i]);
+        suspend();
+      }
+    }
+  }
+  logger(ans.join(''));
+  done();
+  return ans.join('');
+}
+
+// convert('PAYPALISHIRING', 1);
+// 'PAYPALISHIRING'
+// convert('PAYPALISHIRING', 2);
+// 'PYAIHRNAPLSIIG'
+// convert('PAYPALISHIRING', 3);
+// 'PAHNAPLSIIGYIR'
+// convert('PAYPALISHIRING', 4);
+// 'PINALSIGYAHRPI'
+// convert('PAYPALISHIRING', 5);
+// 'PHASIYIRPLIGAN'
+// convert('PAYPALISHIRING', 6);
+// 'PRAIIYHNPSGAIL'
+// convert('A', 1);
+
+ +
+ + + diff --git a/Leetcode/QA.html b/Leetcode/QA.html new file mode 100644 index 0000000..c529037 --- /dev/null +++ b/Leetcode/QA.html @@ -0,0 +1,25 @@ + + + + + + + LOFT + + + + +

介绍项目的难点

let var const 有什么区别

你知道哪些 http 头部

怎么和服务器保持连接

http 请求跨域问题,你都知道哪些解决跨域的方法

webpack 怎么优化

你了解哪些请求方法,分别有哪些作用和不同

你觉得 TypeScript 和 JavaScript 有什么区别

TypeScript 你都用过哪些类型

TypeScript 中的 type 和 interface 有什么区别

React 怎么优化

算法题:合并乱序区间

Vue 的 diff 算法

当组件创建和更新时,vue 均会执行内部的 update 函数,该函数在内部调用 render 函数生成虚拟 dom 树,组件会指向新树,然后 vue 将新旧两树进行对比,找到差异点,最终更新到真实 dom。

对比差异的过程即为 diff,vue 在内部通过一个叫 patch 的函数完成该过程。 在对比时(diff 时机),vue 采用深度优先、逐层比较的方式进行对比。

在判断两个节点是否相同时,vue 是通过虚拟节点的 key 和 tag 来进行判断的。

首先对根节点进行对比:

  • 相同,将旧节点关联的真实 dom 的引用挂载到新节点上,根据需要更新属性到真实 dom ,接着再对比其子节点数组;

    • 对比子节点数组时,vue 对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比,这样做是为了尽可能复用真实 dom ,尽量少的销毁和创建真实 dom

      • 相同,则进入和根节点一样的对比流程;

      • 不同,则移动真实 dom 到合适的位置。

  • 不相同,则按照新节点的信息递归创建所有真实 dom ,同时挂载到对应的虚拟节点上,然后移除掉旧 dom

diff 时机

当组件创建时,以及依赖的属性或数据变化时,会运行一个函数,该函数会做两件事:

  • 运行 _render 函数,生成一棵新的虚拟 dom 树(vnode tree);
  • 运行 _update 函数,传入虚拟 dom 树的根节点,对比新旧两棵树,完成对真实 dom 的更新。
function Vue() {
+  // other code
+  const updateComponent = () => {
+    this._update(this._render); // diff过程
+  };
+  new Watcher(updateComponent);
+  // other code
+}
+

_update函数作用

_update 函数接收一个 vnode 参数,即新生成的虚拟 dom 树,同时, _update 函数通过当前组件的 _vnode 属性,拿到旧的虚拟 dom 树。 _update 函数首先会给组件的 _vnode 属性重新赋值,让它指向新树。

+ +
+ + + diff --git a/Tutorial/githubproxy.html b/Tutorial/githubproxy.html new file mode 100644 index 0000000..8fe9287 --- /dev/null +++ b/Tutorial/githubproxy.html @@ -0,0 +1,53 @@ + + + + + + + LOFT + + + + +

GitHub代理问题

GitHub clone 加速

一般 clone 仓库会使用 ssh 或者 https 两种方式。

# ssh
+$ git clone git@github.com:xxx/yyy.git
+# https
+$ git clone https://github.com:xxx/yyy.git
+

设置 Http Proxy


$ git config --global http.proxy socks5://127.0.0.1:7890
+

因为git底层使用libcurl发送http请求,而libcurl的代理使用 socks5://时会在本地解析DNS,实际使用中我们希望DNS也在远程解析,所以使用socks5h,即

$ git config --global http.proxy socks5h://127.0.0.1:7890
+

其中 h代表host,包括了域名解析,即域名解析也强制走这个代理。

但这样配置的话会使本机所有的 git 服务都走了代理,假如你在国内主机上部署了自己的 gitea,服务地址 https://gitea.example.com,那么可以只配置 GitHub 的 http proxy,即

$ git config --global http.https://github.com.proxy socks5://127.0.0.1:7890
+

这样做实际上修改了 ~/.gitconfig 文件:

[http "https://github.com"]
+        proxy = socks5://127.0.0.1:7890
+

设置 SSH Proxy

Linux & macOS

配置文件在用户目录下的 .ssh/config 其中 ncopen in new window 程序位于 /usr/bin/nc

netcat is a simple unix utility which reads and writes data across network connections, using TCP or UDP protocol. It is designed to be a reliable "back-end" tool that can be used directly or easily driven by other programs and scripts. At the same time, it is a feature-rich network debugging and exploration tool, since it can create almost any kind of connection you would need and has several interesting built-in capabilities. Netcat, or "nc" as the actual program is named, should have been supplied long ago as another one of those cryptic but standard Unix tools.

netcat 是一个简单的 unix 实用程序,它使用 TCP 或 UDP 协议跨网络连接读取和写入数据。 它被设计成一个可靠的“后端”工具,可以直接使用或由其他程序和脚本轻松驱动。 同时,它还是一个功能丰富的网络调试和探索工具,因为它几乎可以创建您需要的任何类型的连接,并且具有几个有趣的内置功能。 Netcat,或实际程序命名的“nc”,早就应该作为另一种神秘但标准的 Unix 工具提供。

$ cat ~/.ssh/config
+
+Host github.com
+ Hostname ssh.github.com
+ IdentityFile /xxx/.ssh/github_id_rsa
+ User git
+ Port 443
+ ProxyCommand nc -v -x 127.0.0.1:7890 %h %p
+
Window

Win 下与之对应的 netcat 程序是 connect.exe,程序位于 Git 默认安装路径 C:\Program Files\Git\mingw64\bin,win 下推荐使用 Git Bash,路径也是 Linux style

因为 connect 程序内置在 Git 中,只要是正常安装 Git 的电脑环境都有这个程序,在 Git Bash 终端输入 connect 即可知晓程序路径在 C:\Program Files\Git\mingw64\bin\connect.exe

注意

路径有空格可能会有问题,找不到路径

UserName@PC MINGW64 ~
+$ connect
+connect --- simple relaying command via proxy.
+Version 1.105
+usage: C:\Program Files\Git\mingw64\bin\connect.exe [-dnhst45] [-p local-port]
+          [-H proxy-server[:port]] [-S [user@]socks-server[:port]]
+          [-T proxy-server[:port]]
+          [-c telnet-proxy-command]
+          host port
+
$ cat ~/.ssh/config
+
+Host github.com
+ Hostname ssh.github.com
+ IdentityFile /c/users/xxx/.ssh/github_id_rsa
+ User git
+ Port 443
+ ProxyCommand connect -S 127.0.0.1:7890 %h %p
+
为什么 hostname 是 ssh.github.com,ssh 默认是 22 端口,为什么要用 443 端口?

有些代理对22端口做了限制,可能会遇到问题,所以使用443端口更加安全稳定可靠。

如果代理设置了用户名和密码基础认证呢?

如 clash 的 config.yaml 中就可以添加如下配置以增加 http 基础认证。

authentication:
+  - "USERNAME:PASSWORD"
+

那么写成 ProxyCommand nc -v -x USERNAME@127.0.0.1:7890 %h %p 执行命令的时候终端会让输入密码。

+ +
+ + + diff --git a/Tutorial/index.html b/Tutorial/index.html new file mode 100644 index 0000000..f36c3c7 --- /dev/null +++ b/Tutorial/index.html @@ -0,0 +1,17 @@ + + + + + + + 解决方案及教程 | LOFT + + + + +

W、W、W

这章主题主要收集相关问题解决方案及教程,方便个人查阅。

+ +
+ + + diff --git a/assets/1-two-sum.html-3ff91204.js b/assets/1-two-sum.html-3ff91204.js new file mode 100644 index 0000000..18d691a --- /dev/null +++ b/assets/1-two-sum.html-3ff91204.js @@ -0,0 +1,48 @@ +import{_ as o,M as c,p as u,q as p,R as l,N as a,V as e,Q as n,t as s,a0 as i}from"./framework-5411b43d.js";const r={},k=n("h2",{id:"二数之和",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#二数之和","aria-hidden":"true"},"#"),s(" 二数之和")],-1),d=i("

给定一个整数数组 nums 和一个整数目标值 target ,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回答案。

",3),m=n("pre",{class:"language-yaml"},[n("code",null,[n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" nums = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"7"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"11"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"15"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(` target = 9 +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(` +`),n("span",{class:"token key atrule"},"exp"),s(" "),n("span",{class:"token punctuation"},":"),s(" 因为 nums"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),s(" + nums"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(" == 9"),n("span",{class:"token punctuation"},","),s(" 返回"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(`。 +`),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" nums = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"4"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(` target = 6 +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(` +`),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" nums = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(` target = 6 +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(` +`)])],-1),B=n("h3",{id:"解法-1-暴力解法",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-1-暴力解法","aria-hidden":"true"},"#"),s(" 解法 1: 暴力解法")],-1),g=n("br",null,null,-1),b=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"twoSum"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("nums"),n("span",{class:"token punctuation"},","),s(" target")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" prev "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" j "),n("span",{class:"token operator"},"="),s(" i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(" j "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" j"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" next "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token template-string"},[n("span",{class:"token template-punctuation string"},"`"),n("span",{class:"token interpolation"},[n("span",{class:"token interpolation-punctuation punctuation"},"${"),s("prev"),n("span",{class:"token interpolation-punctuation punctuation"},"}")]),n("span",{class:"token string"},", "),n("span",{class:"token interpolation"},[n("span",{class:"token interpolation-punctuation punctuation"},"${"),s("next"),n("span",{class:"token interpolation-punctuation punctuation"},"}")]),n("span",{class:"token template-punctuation string"},"`")]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("prev "),n("span",{class:"token operator"},"+"),s(" next "),n("span",{class:"token operator"},"==="),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},","),s(" j"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token comment"},"// twoSum([11, 15, 2, 7], 9);"),s(` +`),n("span",{class:"token comment"},"// twoSum([3, 3], 6);"),s(` +`)])],-1),D=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1),v=n("h3",{id:"解法-2-哈希表",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-2-哈希表","aria-hidden":"true"},"#"),s(" 解法 2:哈希表")],-1),A=n("br",null,null,-1),f=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"twoSum"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("nums"),n("span",{class:"token punctuation"},","),s(" target")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" map "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"{"),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" num "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" targetNum "),n("span",{class:"token operator"},"="),s(" target "),n("span",{class:"token operator"},"-"),s(" num"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" targetIndex "),n("span",{class:"token operator"},"="),s(" map"),n("span",{class:"token punctuation"},"["),s("targetNum"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("targetIndex "),n("span",{class:"token operator"},"!=="),s(),n("span",{class:"token keyword"},"undefined"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token template-string"},[n("span",{class:"token template-punctuation string"},"`"),n("span",{class:"token interpolation"},[n("span",{class:"token interpolation-punctuation punctuation"},"${"),s("targetIndex"),n("span",{class:"token interpolation-punctuation punctuation"},"}")]),n("span",{class:"token string"},", "),n("span",{class:"token interpolation"},[n("span",{class:"token interpolation-punctuation punctuation"},"${"),s("i"),n("span",{class:"token interpolation-punctuation punctuation"},"}")]),n("span",{class:"token template-punctuation string"},"`")]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(),n("span",{class:"token punctuation"},"["),s("targetIndex"),n("span",{class:"token punctuation"},","),s(" i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token template-string"},[n("span",{class:"token template-punctuation string"},"`"),n("span",{class:"token interpolation"},[n("span",{class:"token interpolation-punctuation punctuation"},"${"),s("num"),n("span",{class:"token interpolation-punctuation punctuation"},"}")]),n("span",{class:"token string"},", "),n("span",{class:"token interpolation"},[n("span",{class:"token interpolation-punctuation punctuation"},"${"),s("i"),n("span",{class:"token interpolation-punctuation punctuation"},"}")]),n("span",{class:"token template-punctuation string"},"`")]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + map`),n("span",{class:"token punctuation"},"["),s("num"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(" i"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token comment"},"// twoSum([11, 15, 2, 7], 9);"),s(` +`),n("span",{class:"token comment"},"// twoSum([3, 3], 6);"),s(` +`)])],-1),C=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1);function w(_,h){const t=c("CodeTool");return u(),p("div",null,[k,l(" "),d,a(t,{class:"language-yaml no-win-ctrl","data-ext":"yml",fullscreen:"undefined",steps:"undefined",action:"undefined",winCtrl:"false",async:"undefined",content:"input%20%3A%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%2C%20target%20%3D%209%0Aoutput%3A%20%5B0%2C%201%5D%0Aexp%20%20%20%3A%20%E5%9B%A0%E4%B8%BA%20nums%5B0%5D%20%2B%20nums%5B1%5D%20%3D%3D%209%2C%20%E8%BF%94%E5%9B%9E%5B0%2C%201%5D%E3%80%82%0Ainput%20%3A%20nums%20%3D%20%5B3%2C%202%2C%204%5D%2C%20target%20%3D%206%0Aoutput%3A%20%5B1%2C%202%5D%0Ainput%20%3A%20nums%20%3D%20%5B3%2C%203%5D%2C%20target%20%3D%206%0Aoutput%3A%20%5B0%2C%201%5D%0A",ext:"yaml"},{default:e(()=>[m]),_:1}),B,g,a(t,{id:"example1",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"[3, 2, 4], 6",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"[3, 2, 4], 6",content:"function%20twoSum(nums%2C%20target)%20%7B%0A%20%20const%20len%20%3D%20nums.length%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20len%3B%20i%2B%2B)%20%7B%0A%20%20%20%20const%20prev%20%3D%20nums%5Bi%5D%3B%0A%20%20%20%20for%20(let%20j%20%3D%20i%20%2B%201%3B%20j%20%3C%20len%3B%20j%2B%2B)%20%7B%0A%20%20%20%20%20%20const%20next%20%3D%20nums%5Bj%5D%3B%0A%20%20%20%20%20%20logger(%60%24%7Bprev%7D%2C%20%24%7Bnext%7D%60)%3B%0A%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20if%20(prev%20%2B%20next%20%3D%3D%3D%20target)%20%7B%0A%20%20%20%20%20%20%20%20done()%3B%0A%20%20%20%20%20%20%20%20return%20%5Bi%2C%20j%5D%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%0A%0A%2F%2F%20twoSum(%5B11%2C%2015%2C%202%2C%207%5D%2C%209)%3B%0A%2F%2F%20twoSum(%5B3%2C%203%5D%2C%206)%3B%0A",ext:"javascript"},{default:e(()=>[b,D]),_:1}),v,A,a(t,{id:"example2",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"[3, 2, 4], 6",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"[3, 2, 4], 6",content:"function%20twoSum(nums%2C%20target)%20%7B%0A%20%20const%20len%20%3D%20nums.length%3B%0A%20%20const%20map%20%3D%20%7B%7D%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20len%3B%20i%2B%2B)%20%7B%0A%20%20%20%20const%20num%20%3D%20nums%5Bi%5D%3B%0A%20%20%20%20const%20targetNum%20%3D%20target%20-%20num%3B%0A%20%20%20%20const%20targetIndex%20%3D%20map%5BtargetNum%5D%3B%0A%20%20%20%20if%20(targetIndex%20!%3D%3D%20undefined)%20%7B%0A%20%20%20%20%20%20logger(%60%24%7BtargetIndex%7D%2C%20%24%7Bi%7D%60)%3B%0A%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20done()%3B%0A%20%20%20%20%20%20return%20%5BtargetIndex%2C%20i%5D%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20logger(%60%24%7Bnum%7D%2C%20%24%7Bi%7D%60)%3B%0A%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20map%5Bnum%5D%20%3D%20i%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%0A%0A%2F%2F%20twoSum(%5B11%2C%2015%2C%202%2C%207%5D%2C%209)%3B%0A%2F%2F%20twoSum(%5B3%2C%203%5D%2C%206)%3B%0A",ext:"javascript"},{default:e(()=>[f,C]),_:1})])}const x=o(r,[["render",w],["__file","1-two-sum.html.vue"]]);export{x as default}; diff --git a/assets/1-two-sum.html-f3a55376.js b/assets/1-two-sum.html-f3a55376.js new file mode 100644 index 0000000..b58cfe8 --- /dev/null +++ b/assets/1-two-sum.html-f3a55376.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7444be02","path":"/Leetcode/Array/1-two-sum.html","title":"","lang":"zh-CN","frontmatter":{"pageClass":"page-indent"},"headers":[{"level":2,"title":"二数之和","slug":"二数之和","link":"#二数之和","children":[{"level":3,"title":"解法 1: 暴力解法","slug":"解法-1-暴力解法","link":"#解法-1-暴力解法","children":[]},{"level":3,"title":"解法 2:哈希表","slug":"解法-2-哈希表","link":"#解法-2-哈希表","children":[]}]}],"git":{"updatedTime":1683834292000,"contributors":[{"name":"Moriarty47","email":"wang_jn_xian@163.com","commits":1}]},"filePathRelative":"Leetcode/Array/1-two-sum.md"}');export{e as data}; diff --git a/assets/14-longest-common-prefix.html-073b04b3.js b/assets/14-longest-common-prefix.html-073b04b3.js new file mode 100644 index 0000000..1b47b9a --- /dev/null +++ b/assets/14-longest-common-prefix.html-073b04b3.js @@ -0,0 +1,60 @@ +import{_ as o,M as c,p,q as l,N as t,V as e,Q as n,t as s}from"./framework-5411b43d.js";const u={},i=n("h2",{id:"最长公共前缀",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#最长公共前缀","aria-hidden":"true"},"#"),s(" 最长公共前缀")],-1),r=n("p",null,[s("编写一个函数来查找字符串数组总的最长公共前缀。如果不存在公共前缀,返回空字符串 "),n("code",null,"''"),s(" 。")],-1),k=n("pre",{class:"language-yaml"},[n("code",null,[n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" strs = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token string"},"'flower'"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},"'flow'"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},"'flight'"),n("span",{class:"token punctuation"},"]"),s(` +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token string"},"'fl'"),s(` +`),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" strs = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token string"},"'dog'"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},"'racecar'"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},"'car'"),n("span",{class:"token punctuation"},"]"),s(` +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token string"},"''"),s(` +`)])],-1),d=n("h3",{id:"解法-1-暴力解法",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-1-暴力解法","aria-hidden":"true"},"#"),s(" 解法 1: 暴力解法")],-1),m=n("br",null,null,-1),f=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"longestCommonPrefix"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},"strs"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" strs"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" minLen "),n("span",{class:"token operator"},"="),s(" strs"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("minLen "),n("span",{class:"token operator"},">"),s(" strs"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + minLen `),n("span",{class:"token operator"},"="),s(" strs"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"const"),s(" arr "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token function"},"Array"),n("span",{class:"token punctuation"},"("),s("minLen"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" res "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" minLen"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"let"),s(" prev "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" j "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" j "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" j"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" str "),n("span",{class:"token operator"},"="),s(" strs"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),s("arr"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + arr`),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("prev "),n("span",{class:"token operator"},"&&"),s(" prev "),n("span",{class:"token operator"},"==="),s(" str"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + arr`),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + prev `),n("span",{class:"token operator"},"="),s(" str"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + prev `),n("span",{class:"token operator"},"="),s(),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("arr"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"==="),s(" len"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + res `),n("span",{class:"token operator"},"+="),s(" strs"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("res"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("arr"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"!=="),s(" len"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" res"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" res"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])],-1),B=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1),b=n("h3",{id:"解法-2",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-2","aria-hidden":"true"},"#"),s(" 解法 2")],-1),v=n("br",null,null,-1),g=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"longestCommonPrefix"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},"strs"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("strs"),n("span",{class:"token punctuation"},"."),s("length "),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"return"),s(),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" prefix "),n("span",{class:"token operator"},"="),s(" strs"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" strs"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" str "),n("span",{class:"token operator"},"="),s(" strs"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("str"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"indexOf"),n("span",{class:"token punctuation"},"("),s("prefix"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"!=="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("prefix"),n("span",{class:"token punctuation"},"."),s("length "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + prefix `),n("span",{class:"token operator"},"="),s(" prefix"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"slice"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(" prefix"),n("span",{class:"token punctuation"},"."),s("length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token template-string"},[n("span",{class:"token template-punctuation string"},"`"),n("span",{class:"token interpolation"},[n("span",{class:"token interpolation-punctuation punctuation"},"${"),s("str"),n("span",{class:"token interpolation-punctuation punctuation"},"}")]),n("span",{class:"token string"},"-"),n("span",{class:"token interpolation"},[n("span",{class:"token interpolation-punctuation punctuation"},"${"),s("prefix"),n("span",{class:"token interpolation-punctuation punctuation"},"}")]),n("span",{class:"token template-punctuation string"},"`")]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" prefix"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])],-1),D=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1);function A(h,w){const a=c("CodeTool");return p(),l("div",null,[i,r,t(a,{class:"language-yaml no-win-ctrl","data-ext":"yml",fullscreen:"undefined",steps:"undefined",action:"undefined",winCtrl:"false",async:"undefined",content:"input%20%3A%20strs%20%3D%20%5B'flower'%2C%20'flow'%2C%20'flight'%5D%0Aoutput%3A%20'fl'%0Ainput%20%3A%20strs%20%3D%20%5B'dog'%2C%20'racecar'%2C%20'car'%5D%0Aoutput%3A%20''%0A",ext:"yaml"},{default:e(()=>[k]),_:1}),d,m,t(a,{id:"example1",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"['flower', 'flow', 'flight']",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"['flower', 'flow', 'flight']",content:"function%20longestCommonPrefix(strs)%20%7B%0A%20%20const%20len%20%3D%20strs.length%3B%0A%20%20let%20minLen%20%3D%20strs%5B0%5D.length%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20len%3B%20i%2B%2B)%20%7B%0A%20%20%20%20if%20(minLen%20%3E%20strs%5Bi%5D.length)%20%7B%0A%20%20%20%20%20%20minLen%20%3D%20strs%5Bi%5D.length%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20const%20arr%20%3D%20Array(minLen)%3B%0A%20%20let%20res%20%3D%20''%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20minLen%3B%20i%2B%2B)%20%7B%0A%20%20%20%20let%20prev%20%3D%20''%3B%0A%20%20%20%20for%20(let%20j%20%3D%200%3B%20j%20%3C%20len%3B%20j%2B%2B)%20%7B%0A%20%20%20%20%20%20const%20str%20%3D%20strs%5Bj%5D%3B%0A%20%20%20%20%20%20if%20(!arr%5Bi%5D)%20%7B%0A%20%20%20%20%20%20%20%20arr%5Bi%5D%20%3D%201%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(prev%20%26%26%20prev%20%3D%3D%3D%20str%5Bi%5D)%20%7B%0A%20%20%20%20%20%20%20%20arr%5Bi%5D%2B%2B%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20prev%20%3D%20str%5Bi%5D%3B%0A%20%20%20%20%7D%0A%20%20%20%20prev%20%3D%20''%3B%0A%20%20%20%20if%20(arr%5Bi%5D%20%3D%3D%3D%20len)%20%7B%0A%20%20%20%20%20%20res%20%2B%3D%20strs%5B0%5D%5Bi%5D%3B%0A%20%20%20%20%20%20logger(res)%3B%0A%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%7D%0A%20%20%20%20if%20(arr%5Bi%5D%20!%3D%3D%20len)%20%7B%0A%20%20%20%20%20%20done()%3B%0A%20%20%20%20%20%20return%20res%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20done()%3B%0A%20%20return%20res%3B%0A%7D%0A",ext:"javascript"},{default:e(()=>[f,B]),_:1}),b,v,t(a,{id:"example2",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"['flower', 'flow', 'flight']",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"['flower', 'flow', 'flight']",content:"function%20longestCommonPrefix(strs)%20%7B%0A%20%20if%20(strs.length%20%3C%201)%20return%20''%3B%0A%20%20let%20prefix%20%3D%20strs%5B0%5D%3B%0A%20%20const%20len%20%3D%20strs.length%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20len%3B%20i%2B%2B)%20%7B%0A%20%20%20%20const%20str%20%3D%20strs%5Bi%5D%3B%0A%20%20%20%20while%20(str.indexOf(prefix)%20!%3D%3D%200)%20%7B%0A%20%20%20%20%20%20if%20(prefix.length%20%3D%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20done()%3B%0A%20%20%20%20%20%20%20%20return%20''%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20prefix%20%3D%20prefix.slice(0%2C%20prefix.length%20-%201)%3B%0A%20%20%20%20%20%20logger(%60%24%7Bstr%7D-%24%7Bprefix%7D%60)%3B%0A%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20done()%3B%0A%20%20return%20prefix%3B%0A%7D%0A",ext:"javascript"},{default:e(()=>[g,D]),_:1})])}const x=o(u,[["render",A],["__file","14-longest-common-prefix.html.vue"]]);export{x as default}; diff --git a/assets/14-longest-common-prefix.html-465081db.js b/assets/14-longest-common-prefix.html-465081db.js new file mode 100644 index 0000000..36868cd --- /dev/null +++ b/assets/14-longest-common-prefix.html-465081db.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7e1a168a","path":"/Leetcode/Array/14-longest-common-prefix.html","title":"","lang":"zh-CN","frontmatter":{"pageClass":"page-indent"},"headers":[{"level":2,"title":"最长公共前缀","slug":"最长公共前缀","link":"#最长公共前缀","children":[{"level":3,"title":"解法 1: 暴力解法","slug":"解法-1-暴力解法","link":"#解法-1-暴力解法","children":[]},{"level":3,"title":"解法 2","slug":"解法-2","link":"#解法-2","children":[]}]}],"git":{"updatedTime":1683834292000,"contributors":[{"name":"Moriarty47","email":"wang_jn_xian@163.com","commits":1}]},"filePathRelative":"Leetcode/Array/14-longest-common-prefix.md"}');export{e as data}; diff --git a/assets/15-3sum.html-7e96aef9.js b/assets/15-3sum.html-7e96aef9.js new file mode 100644 index 0000000..6f6fe30 --- /dev/null +++ b/assets/15-3sum.html-7e96aef9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1823ae0f","path":"/Leetcode/Array/15-3sum.html","title":"","lang":"zh-CN","frontmatter":{"pageClass":"page-indent"},"headers":[{"level":2,"title":"三数之和","slug":"三数之和","link":"#三数之和","children":[{"level":3,"title":"解法 1","slug":"解法-1","link":"#解法-1","children":[]},{"level":3,"title":"解法 2","slug":"解法-2","link":"#解法-2","children":[]}]}],"git":{"updatedTime":1683834292000,"contributors":[{"name":"Moriarty47","email":"wang_jn_xian@163.com","commits":1}]},"filePathRelative":"Leetcode/Array/15-3sum.md"}');export{e as data}; diff --git a/assets/15-3sum.html-eb2e5c75.js b/assets/15-3sum.html-eb2e5c75.js new file mode 100644 index 0000000..3672d68 --- /dev/null +++ b/assets/15-3sum.html-eb2e5c75.js @@ -0,0 +1,96 @@ +import{_ as o,M as c,p as u,q as p,N as t,V as e,a0 as l,Q as n,t as s}from"./framework-5411b43d.js";const i={},k=l('

三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。

',3),r=n("pre",{class:"language-yaml"},[n("code",null,[s(),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" nums = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"-4"),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token key atrule"},"exp"),s(" "),n("span",{class:"token punctuation"},":"),s(` + nums`),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),s(" + nums"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(" + nums"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(" = ("),n("span",{class:"token punctuation"},"-"),s(`1) + 0 + 1 = 0 。 + nums`),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(" + nums"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(" + nums"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"4"),n("span",{class:"token punctuation"},"]"),s(" = 0 + 1 + ("),n("span",{class:"token punctuation"},"-"),s(`1) = 0 。 + nums`),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),s(" + nums"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},"]"),s(" + nums"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"4"),n("span",{class:"token punctuation"},"]"),s(" = ("),n("span",{class:"token punctuation"},"-"),s("1) + 2 + ("),n("span",{class:"token punctuation"},"-"),s(`1) = 0 。 + 不同的三元组是 `),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(" 和 "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(` 。 + 注意,输出的顺序和三元组的顺序并不重要。 + `),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" nums = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token key atrule"},"exp"),s(" "),n("span",{class:"token punctuation"},":"),s(` 唯一可能的三元组和不为0。 + `),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" nums = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token key atrule"},"exp"),s(" "),n("span",{class:"token punctuation"},":"),s(` 唯一可能的三元组和不为0。 +`)])],-1),m=n("h3",{id:"解法-1",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-1","aria-hidden":"true"},"#"),s(" 解法 1")],-1),d=n("br",null,null,-1),B=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"threeSum"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},"nums"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" map "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"{"),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numi "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" j "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" j "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" j"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numj "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("i "),n("span",{class:"token operator"},"==="),s(" j"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"break"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"let"),s(" sum "),n("span",{class:"token operator"},"="),s(" numi "),n("span",{class:"token operator"},"+"),s(" numj"),n("span",{class:"token punctuation"},";"),s(` + sum `),n("span",{class:"token operator"},"="),s(" sum "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"?"),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},":"),s(),n("span",{class:"token operator"},"-"),s("sum"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" idx "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"indexOf"),n("span",{class:"token punctuation"},"("),s("sum"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("idx "),n("span",{class:"token operator"},">="),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"&&"),s(" i "),n("span",{class:"token operator"},"!=="),s(" idx "),n("span",{class:"token operator"},"&&"),s(" j "),n("span",{class:"token operator"},"!=="),s(" idx"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" arr "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"["),s("numi"),n("span",{class:"token punctuation"},","),s(" numj"),n("span",{class:"token punctuation"},","),s(" sum"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + arr`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sort"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("a"),n("span",{class:"token punctuation"},","),s(" b")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(" a "),n("span",{class:"token operator"},"-"),s(" b"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),s("map"),n("span",{class:"token punctuation"},"["),s("arr"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"join"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},"','"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("arr"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + map`),n("span",{class:"token punctuation"},"["),s("arr"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"join"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},"','"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(" arr"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" Object"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"values"),n("span",{class:"token punctuation"},"("),s("map"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token comment"},`/* +threeSum([-1, 0, 1, 2, -1, -4]); +// [[-1, 0, 1], [-1, -1, 2]] +threeSum([0, 1, 1]); +// [] +threeSum([0, 0, 0]); +// [0, 0, 0] +threeSum([0, 0, 0, 0, 0, 0, 0, 0]); +// [0, 0, 0] +threeSum([1, -1, -1, 0]); +// [-1, 0, 1] +threeSum([1, -1, 0, 0]); +// [-1, 0, 1] +threeSum([-2, 0, 0, 2, 2]); +// [-2, 0, 2] +*/`),s(` +`)])],-1),b=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1),D=n("h3",{id:"解法-2",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-2","aria-hidden":"true"},"#"),s(" 解法 2")],-1),A=n("br",null,null,-1),v=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"threeSum"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},"nums"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + nums`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sort"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("a"),n("span",{class:"token punctuation"},","),s(" b")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(" a "),n("span",{class:"token operator"},"-"),s(" b"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" p "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" res "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("p "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"let"),s(" base "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("p"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("base "),n("span",{class:"token operator"},">"),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + p`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"break"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"let"),s(" left "),n("span",{class:"token operator"},"="),s(" p "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" right "),n("span",{class:"token operator"},"="),s(" len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("p "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"||"),s(" base "),n("span",{class:"token operator"},"!=="),s(" nums"),n("span",{class:"token punctuation"},"["),s("p "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numL "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("left"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numR "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("right"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("base "),n("span",{class:"token operator"},"+"),s(" numL "),n("span",{class:"token operator"},"+"),s(" numR "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + res`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"push"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"["),s("base"),n("span",{class:"token punctuation"},","),s(" numL"),n("span",{class:"token punctuation"},","),s(" numR"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"["),s("base"),n("span",{class:"token punctuation"},","),s(" numL"),n("span",{class:"token punctuation"},","),s(" numR"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right "),n("span",{class:"token operator"},"&&"),s(" numL "),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("left "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(" left"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right "),n("span",{class:"token operator"},"&&"),s(" numR "),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("right "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(" right"),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + left`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + right`),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("base "),n("span",{class:"token operator"},"+"),s(" numL "),n("span",{class:"token operator"},"+"),s(" numR "),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + left`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token punctuation"},"{"),s(` + right`),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + p`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" res"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])],-1),C=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1);function f(h,E){const a=c("CodeTool");return u(),p("div",null,[k,t(a,{class:"language-yaml no-win-ctrl","data-ext":"yml",fullscreen:"undefined",steps:"undefined",action:"undefined",winCtrl:"false",async:"undefined",content:"%20input%20%3A%20nums%20%3D%20%5B-1%2C%200%2C%201%2C%202%2C%20-1%2C%20-4%5D%0A%20output%3A%20%5B%5B-1%2C%20-1%2C%202%5D%2C%20%5B-1%2C%200%2C%201%5D%5D%0A%20exp%20%20%20%3A%0A%20%20%20%20%20%20%20%20%20nums%5B0%5D%20%2B%20nums%5B1%5D%20%2B%20nums%5B2%5D%20%3D%20(-1)%20%2B%200%20%2B%201%20%3D%200%20%E3%80%82%0A%20%20%20%20%20%20%20%20%20nums%5B1%5D%20%2B%20nums%5B2%5D%20%2B%20nums%5B4%5D%20%3D%200%20%2B%201%20%2B%20(-1)%20%3D%200%20%E3%80%82%0A%20%20%20%20%20%20%20%20%20nums%5B0%5D%20%2B%20nums%5B3%5D%20%2B%20nums%5B4%5D%20%3D%20(-1)%20%2B%202%20%2B%20(-1)%20%3D%200%20%E3%80%82%0A%20%20%20%20%20%20%20%20%20%E4%B8%8D%E5%90%8C%E7%9A%84%E4%B8%89%E5%85%83%E7%BB%84%E6%98%AF%20%5B-1%2C0%2C1%5D%20%E5%92%8C%20%5B-1%2C-1%2C2%5D%20%E3%80%82%0A%20%20%20%20%20%20%20%20%20%E6%B3%A8%E6%84%8F%EF%BC%8C%E8%BE%93%E5%87%BA%E7%9A%84%E9%A1%BA%E5%BA%8F%E5%92%8C%E4%B8%89%E5%85%83%E7%BB%84%E7%9A%84%E9%A1%BA%E5%BA%8F%E5%B9%B6%E4%B8%8D%E9%87%8D%E8%A6%81%E3%80%82%0A%20input%20%3A%20nums%20%3D%20%5B0%2C%201%2C%201%5D%0A%20output%3A%20%5B%5D%0A%20exp%20%20%20%3A%20%E5%94%AF%E4%B8%80%E5%8F%AF%E8%83%BD%E7%9A%84%E4%B8%89%E5%85%83%E7%BB%84%E5%92%8C%E4%B8%8D%E4%B8%BA0%E3%80%82%0A%20input%20%3A%20nums%20%3D%20%5B0%2C%200%2C%200%5D%0A%20output%3A%20%5B%5B0%2C%200%2C%200%5D%5D%0A%20exp%20%20%20%3A%20%E5%94%AF%E4%B8%80%E5%8F%AF%E8%83%BD%E7%9A%84%E4%B8%89%E5%85%83%E7%BB%84%E5%92%8C%E4%B8%8D%E4%B8%BA0%E3%80%82%0A",ext:"yaml"},{default:e(()=>[r]),_:1}),m,d,t(a,{id:"example1",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"[-1, 0, 1, 2, -1, -4]",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"[-1, 0, 1, 2, -1, -4]",content:"function%20threeSum(nums)%20%7B%0A%20%20const%20len%20%3D%20nums.length%3B%0A%20%20const%20map%20%3D%20%7B%7D%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20len%3B%20i%2B%2B)%20%7B%0A%20%20%20%20const%20numi%20%3D%20nums%5Bi%5D%3B%0A%20%20%20%20for%20(let%20j%20%3D%200%3B%20j%20%3C%20len%3B%20j%2B%2B)%20%7B%0A%20%20%20%20%20%20const%20numj%20%3D%20nums%5Bj%5D%3B%0A%20%20%20%20%20%20if%20(i%20%3D%3D%3D%20j)%20%7B%0A%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20let%20sum%20%3D%20numi%20%2B%20numj%3B%0A%20%20%20%20%20%20sum%20%3D%20sum%20%3D%3D%3D%200%20%3F%200%20%3A%20-sum%3B%0A%20%20%20%20%20%20const%20idx%20%3D%20nums.indexOf(sum)%3B%0A%20%20%20%20%20%20if%20(idx%20%3E%3D%200%20%26%26%20i%20!%3D%3D%20idx%20%26%26%20j%20!%3D%3D%20idx)%20%7B%0A%20%20%20%20%20%20%20%20const%20arr%20%3D%20%5Bnumi%2C%20numj%2C%20sum%5D%3B%0A%20%20%20%20%20%20%20%20arr.sort((a%2C%20b)%20%3D%3E%20a%20-%20b)%3B%0A%20%20%20%20%20%20%20%20if%20(!map%5Barr.join('%2C')%5D)%20%7B%0A%20%20%20%20%20%20%20%20%20%20logger(arr)%3B%0A%20%20%20%20%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20map%5Barr.join('%2C')%5D%20%3D%20arr%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20done()%3B%0A%20%20return%20Object.values(map)%3B%0A%7D%0A%2F*%0AthreeSum(%5B-1%2C%200%2C%201%2C%202%2C%20-1%2C%20-4%5D)%3B%0A%2F%2F%20%5B%5B-1%2C%200%2C%201%5D%2C%20%5B-1%2C%20-1%2C%202%5D%5D%0AthreeSum(%5B0%2C%201%2C%201%5D)%3B%0A%2F%2F%20%5B%5D%0AthreeSum(%5B0%2C%200%2C%200%5D)%3B%0A%2F%2F%20%5B0%2C%200%2C%200%5D%0AthreeSum(%5B0%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%5D)%3B%0A%2F%2F%20%5B0%2C%200%2C%200%5D%0AthreeSum(%5B1%2C%20-1%2C%20-1%2C%200%5D)%3B%0A%2F%2F%20%5B-1%2C%200%2C%201%5D%0AthreeSum(%5B1%2C%20-1%2C%200%2C%200%5D)%3B%0A%2F%2F%20%5B-1%2C%200%2C%201%5D%0AthreeSum(%5B-2%2C%200%2C%200%2C%202%2C%202%5D)%3B%0A%2F%2F%20%5B-2%2C%200%2C%202%5D%0A*%2F%0A",ext:"javascript"},{default:e(()=>[B,b]),_:1}),D,A,t(a,{id:"example2",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"[-1, 0, 1, 2, -1, -4]",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"[-1, 0, 1, 2, -1, -4]",content:"function%20threeSum(nums)%20%7B%0A%20%20nums.sort((a%2C%20b)%20%3D%3E%20a%20-%20b)%3B%0A%20%20const%20len%20%3D%20nums.length%3B%0A%20%20let%20p%20%3D%200%3B%0A%20%20const%20res%20%3D%20%5B%5D%3B%0A%20%20while%20(p%20%3C%20len)%20%7B%0A%20%20%20%20let%20base%20%3D%20nums%5Bp%5D%3B%0A%20%20%20%20if%20(base%20%3E%200)%20%7B%0A%20%20%20%20%20%20p%2B%2B%3B%0A%20%20%20%20%20%20break%3B%0A%20%20%20%20%7D%0A%20%20%20%20let%20left%20%3D%20p%20%2B%201%3B%0A%20%20%20%20let%20right%20%3D%20len%20-%201%3B%0A%20%20%20%20if%20(p%20%3D%3D%3D%200%20%7C%7C%20base%20!%3D%3D%20nums%5Bp%20-%201%5D)%20%7B%0A%20%20%20%20%20%20while%20(left%20%3C%20right)%20%7B%0A%20%20%20%20%20%20%20%20const%20numL%20%3D%20nums%5Bleft%5D%3B%0A%20%20%20%20%20%20%20%20const%20numR%20%3D%20nums%5Bright%5D%3B%0A%20%20%20%20%20%20%20%20if%20(base%20%2B%20numL%20%2B%20numR%20%3D%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20res.push(%5Bbase%2C%20numL%2C%20numR%5D)%3B%0A%20%20%20%20%20%20%20%20%20%20logger(%5Bbase%2C%20numL%2C%20numR%5D)%3B%0A%20%20%20%20%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20%20%20%20%20while%20(left%20%3C%20right%20%26%26%20numL%20%3D%3D%3D%20nums%5Bleft%20%2B%201%5D)%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%20%20while%20(left%20%3C%20right%20%26%26%20numR%20%3D%3D%3D%20nums%5Bright%20-%201%5D)%20right--%3B%0A%20%20%20%20%20%20%20%20%20%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%20%20right--%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20if%20(base%20%2B%20numL%20%2B%20numR%20%3C%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20%20%20right--%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%20%20p%2B%2B%3B%0A%20%20%7D%0A%20%20done()%3B%0A%20%20return%20res%3B%0A%7D%0A",ext:"javascript"},{default:e(()=>[v,C]),_:1})])}const w=o(i,[["render",f],["__file","15-3sum.html.vue"]]);export{w as default}; diff --git a/assets/18-4sum.html-946c996f.js b/assets/18-4sum.html-946c996f.js new file mode 100644 index 0000000..af881a1 --- /dev/null +++ b/assets/18-4sum.html-946c996f.js @@ -0,0 +1,118 @@ +import{_ as e,M as c,p as u,q as p,N as t,V as o,a0 as l,Q as n,t as s}from"./framework-5411b43d.js";const i={},k=l('

四数之和

给你一个由 n 个整数组成的数组  nums ,和一个目标值 target 。请你找出并返回满 足下述全部条件且不重复的四元组  [nums[a], nums[b], nums[c], nums[d]] (若两个 四元组元素一一对应,则认为两个四元组重复):

',3),r=n("pre",{class:"language-yaml"},[n("code",null,[n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" nums = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"-2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(` +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"-2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"-2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"-1"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"]"),s(` +`),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" nums = "),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(` +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"]"),s(` +`)])],-1),m=n("h3",{id:"解法-1",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-1","aria-hidden":"true"},"#"),s(" 解法 1")],-1),d=n("br",null,null,-1),B=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"fourSum"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("nums"),n("span",{class:"token punctuation"},","),s(" target")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("nums"),n("span",{class:"token punctuation"},"."),s("length "),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"4"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + nums`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sort"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("a"),n("span",{class:"token punctuation"},","),s(" b")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(" a "),n("span",{class:"token operator"},"-"),s(" b"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" res "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numA "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("i "),n("span",{class:"token operator"},">"),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"&&"),s(" numA "),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("i "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("numA "),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},">"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"break"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("numA "),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"<"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" j "),n("span",{class:"token operator"},"="),s(" i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(" j "),n("span",{class:"token operator"},"<"),s(" len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},";"),s(" j"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numB "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("j "),n("span",{class:"token operator"},">"),s(" i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},"&&"),s(" numB "),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("numA "),n("span",{class:"token operator"},"+"),s(" numB "),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},">"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"break"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("numA "),n("span",{class:"token operator"},"+"),s(" numB "),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"<"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" left "),n("span",{class:"token operator"},"="),s(" j "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" right "),n("span",{class:"token operator"},"="),s(" len "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numL "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("left"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numR "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("right"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" sum "),n("span",{class:"token operator"},"="),s(" numA "),n("span",{class:"token operator"},"+"),s(" numB "),n("span",{class:"token operator"},"+"),s(" numL "),n("span",{class:"token operator"},"+"),s(" numR"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("sum "),n("span",{class:"token operator"},"==="),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + res`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"push"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"["),s("numA"),n("span",{class:"token punctuation"},","),s(" numB"),n("span",{class:"token punctuation"},","),s(" numL"),n("span",{class:"token punctuation"},","),s(" numR"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"["),s("numA"),n("span",{class:"token punctuation"},","),s(" numB"),n("span",{class:"token punctuation"},","),s(" numL"),n("span",{class:"token punctuation"},","),s(" numR"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right "),n("span",{class:"token operator"},"&&"),s(" numL "),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("left "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(" left"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right "),n("span",{class:"token operator"},"&&"),s(" numR "),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("right "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(" right"),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + left`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + right`),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("sum "),n("span",{class:"token operator"},"<"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + left`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token punctuation"},"{"),s(` + right`),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" res"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token comment"},`/* +fourSum([1, 0, -1, 0, -2, 2], 0); +// [-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1] +fourSum([2, 2, 2, 2, 2], 8); +// [2, 2, 2, 2] +fourSum([2, 2, 3, 2, -1], 6); +// [-1, 2, 2, 3] +fourSum([-2, -1, -1, 1, 1, 2, 2], 0); +// [-2, -1, 1, 2], [-1, -1, 1, 1] +*/`),s(` +`)])],-1),b=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1),D=n("h3",{id:"解法-2",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-2","aria-hidden":"true"},"#"),s(" 解法 2")],-1),f=n("br",null,null,-1),A=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"fourSum"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("nums"),n("span",{class:"token punctuation"},","),s(" target")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" quadruplets "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("nums"),n("span",{class:"token punctuation"},"."),s("length "),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"4"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" quadruplets"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + nums`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sort"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("x"),n("span",{class:"token punctuation"},","),s(" y")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(" x "),n("span",{class:"token operator"},"-"),s(" y"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" length "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("i "),n("span",{class:"token operator"},">"),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"&&"),s(" nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("i "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},">"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"break"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s(` + nums`),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"<"),s(` + target + `),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" j "),n("span",{class:"token operator"},"="),s(" i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(" j "),n("span",{class:"token operator"},"<"),s(" length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},";"),s(" j"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("j "),n("span",{class:"token operator"},">"),s(" i "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},"&&"),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},">"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"break"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"<"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"let"),s(" left "),n("span",{class:"token operator"},"="),s(" j "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},","),s(` + right `),n("span",{class:"token operator"},"="),s(" length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" sum "),n("span",{class:"token operator"},"="),s(" nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("left"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"+"),s(" nums"),n("span",{class:"token punctuation"},"["),s("right"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("sum "),n("span",{class:"token operator"},"==="),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + quadruplets`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"push"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"["),s("nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(" nums"),n("span",{class:"token punctuation"},"["),s("left"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(" nums"),n("span",{class:"token punctuation"},"["),s("right"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"["),s("nums"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(" nums"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(" nums"),n("span",{class:"token punctuation"},"["),s("left"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},","),s(" nums"),n("span",{class:"token punctuation"},"["),s("right"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right "),n("span",{class:"token operator"},"&&"),s(" nums"),n("span",{class:"token punctuation"},"["),s("left"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("left "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + left`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + left`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("left "),n("span",{class:"token operator"},"<"),s(" right "),n("span",{class:"token operator"},"&&"),s(" nums"),n("span",{class:"token punctuation"},"["),s("right"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"==="),s(" nums"),n("span",{class:"token punctuation"},"["),s("right "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + right`),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + right`),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("sum "),n("span",{class:"token operator"},"<"),s(" target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + left`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token punctuation"},"{"),s(` + right`),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" quadruplets"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])],-1),g=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1);function v(h,C){const a=c("CodeTool");return u(),p("div",null,[k,t(a,{class:"language-yaml no-win-ctrl","data-ext":"yml",fullscreen:"undefined",steps:"undefined",action:"undefined",winCtrl:"false",async:"undefined",content:"input%20%3A%20nums%20%3D%20%5B1%2C%200%2C%20-1%2C%200%2C%20-2%2C%202%5D%0Aoutput%3A%20%5B%5B-2%2C%20-1%2C%201%2C%202%5D%2C%20%5B-2%2C%200%2C%200%2C%202%5D%2C%20%5B-1%2C%200%2C%200%2C%201%5D%5D%0Ainput%20%3A%20nums%20%3D%20%5B2%2C%202%2C%202%2C%202%2C%202%5D%0Aoutput%3A%20%5B%5B2%2C%202%2C%202%2C%202%5D%5D%0A",ext:"yaml"},{default:o(()=>[r]),_:1}),m,d,t(a,{id:"example1",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"[1, 0, -1, 0, -2, 2], 0",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"[1, 0, -1, 0, -2, 2], 0",content:"function%20fourSum(nums%2C%20target)%20%7B%0A%20%20if%20(nums.length%20%3C%204)%20%7B%0A%20%20%20%20logger(%5B%5D)%3B%0A%20%20%20%20done()%3B%0A%20%20%20%20return%20%5B%5D%3B%0A%20%20%7D%0A%20%20nums.sort((a%2C%20b)%20%3D%3E%20a%20-%20b)%3B%0A%20%20const%20res%20%3D%20%5B%5D%3B%0A%20%20const%20len%20%3D%20nums.length%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20len%20-%203%3B%20i%2B%2B)%20%7B%0A%20%20%20%20const%20numA%20%3D%20nums%5Bi%5D%3B%0A%20%20%20%20if%20(i%20%3E%200%20%26%26%20numA%20%3D%3D%3D%20nums%5Bi%20-%201%5D)%20continue%3B%0A%20%20%20%20if%20(numA%20%2B%20nums%5Bi%20%2B%201%5D%20%2B%20nums%5Bi%20%2B%202%5D%20%2B%20nums%5Bi%20%2B%203%5D%20%3E%20target)%20break%3B%0A%20%20%20%20if%20(numA%20%2B%20nums%5Blen%20-%203%5D%20%2B%20nums%5Blen%20-%202%5D%20%2B%20nums%5Blen%20-%201%5D%20%3C%20target)%20continue%3B%0A%20%20%20%20for%20(let%20j%20%3D%20i%20%2B%201%3B%20j%20%3C%20len%20-%202%3B%20j%2B%2B)%20%7B%0A%20%20%20%20%20%20const%20numB%20%3D%20nums%5Bj%5D%3B%0A%20%20%20%20%20%20if%20(j%20%3E%20i%20%2B%201%20%26%26%20numB%20%3D%3D%3D%20nums%5Bj%20-%201%5D)%20continue%3B%0A%20%20%20%20%20%20if%20(numA%20%2B%20numB%20%2B%20nums%5Bj%20%2B%201%5D%20%2B%20nums%5Bj%20%2B%202%5D%20%3E%20target)%20break%3B%0A%20%20%20%20%20%20if%20(numA%20%2B%20numB%20%2B%20nums%5Blen%20-%202%5D%20%2B%20nums%5Blen%20-%201%5D%20%3C%20target)%20continue%3B%0A%20%20%20%20%20%20let%20left%20%3D%20j%20%2B%201%3B%0A%20%20%20%20%20%20let%20right%20%3D%20len%20-%201%3B%0A%20%20%20%20%20%20while%20(left%20%3C%20right)%20%7B%0A%20%20%20%20%20%20%20%20const%20numL%20%3D%20nums%5Bleft%5D%3B%0A%20%20%20%20%20%20%20%20const%20numR%20%3D%20nums%5Bright%5D%3B%0A%20%20%20%20%20%20%20%20const%20sum%20%3D%20numA%20%2B%20numB%20%2B%20numL%20%2B%20numR%3B%0A%20%20%20%20%20%20%20%20if%20(sum%20%3D%3D%3D%20target)%20%7B%0A%20%20%20%20%20%20%20%20%20%20res.push(%5BnumA%2C%20numB%2C%20numL%2C%20numR%5D)%3B%0A%20%20%20%20%20%20%20%20%20%20logger(%5BnumA%2C%20numB%2C%20numL%2C%20numR%5D)%3B%0A%20%20%20%20%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20%20%20%20%20while%20(left%20%3C%20right%20%26%26%20numL%20%3D%3D%3D%20nums%5Bleft%20%2B%201%5D)%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%20%20while%20(left%20%3C%20right%20%26%26%20numR%20%3D%3D%3D%20nums%5Bright%20-%201%5D)%20right--%3B%0A%20%20%20%20%20%20%20%20%20%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%20%20right--%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20if%20(sum%20%3C%20target)%20%7B%0A%20%20%20%20%20%20%20%20%20%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20%20%20right--%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20done()%3B%0A%20%20return%20res%3B%0A%7D%0A%2F*%20%0AfourSum(%5B1%2C%200%2C%20-1%2C%200%2C%20-2%2C%202%5D%2C%200)%3B%0A%2F%2F%20%5B-2%2C%20-1%2C%201%2C%202%5D%2C%20%5B-2%2C%200%2C%200%2C%202%5D%2C%20%5B-1%2C%200%2C%200%2C%201%5D%0AfourSum(%5B2%2C%202%2C%202%2C%202%2C%202%5D%2C%208)%3B%0A%2F%2F%20%5B2%2C%202%2C%202%2C%202%5D%0AfourSum(%5B2%2C%202%2C%203%2C%202%2C%20-1%5D%2C%206)%3B%0A%2F%2F%20%5B-1%2C%202%2C%202%2C%203%5D%0AfourSum(%5B-2%2C%20-1%2C%20-1%2C%201%2C%201%2C%202%2C%202%5D%2C%200)%3B%0A%2F%2F%20%5B-2%2C%20-1%2C%201%2C%202%5D%2C%20%5B-1%2C%20-1%2C%201%2C%201%5D%0A*%2F%0A",ext:"javascript"},{default:o(()=>[B,b]),_:1}),D,f,t(a,{id:"example2",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"[1, 0, -1, 0, -2, 2], 0",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"[1, 0, -1, 0, -2, 2], 0",content:"function%20fourSum(nums%2C%20target)%20%7B%0A%20%20const%20quadruplets%20%3D%20%5B%5D%3B%0A%20%20if%20(nums.length%20%3C%204)%20%7B%0A%20%20%20%20logger(%5B%5D)%3B%0A%20%20%20%20done()%3B%0A%20%20%20%20return%20quadruplets%3B%0A%20%20%7D%0A%20%20nums.sort((x%2C%20y)%20%3D%3E%20x%20-%20y)%3B%0A%20%20const%20length%20%3D%20nums.length%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20length%20-%203%3B%20i%2B%2B)%20%7B%0A%20%20%20%20if%20(i%20%3E%200%20%26%26%20nums%5Bi%5D%20%3D%3D%3D%20nums%5Bi%20-%201%5D)%20%7B%0A%20%20%20%20%20%20continue%3B%0A%20%20%20%20%7D%0A%20%20%20%20if%20(nums%5Bi%5D%20%2B%20nums%5Bi%20%2B%201%5D%20%2B%20nums%5Bi%20%2B%202%5D%20%2B%20nums%5Bi%20%2B%203%5D%20%3E%20target)%20%7B%0A%20%20%20%20%20%20break%3B%0A%20%20%20%20%7D%0A%20%20%20%20if%20(%0A%20%20%20%20%20%20nums%5Bi%5D%20%2B%20nums%5Blength%20-%203%5D%20%2B%20nums%5Blength%20-%202%5D%20%2B%20nums%5Blength%20-%201%5D%20%3C%0A%20%20%20%20%20%20target%0A%20%20%20%20)%20%7B%0A%20%20%20%20%20%20continue%3B%0A%20%20%20%20%7D%0A%20%20%20%20for%20(let%20j%20%3D%20i%20%2B%201%3B%20j%20%3C%20length%20-%202%3B%20j%2B%2B)%20%7B%0A%20%20%20%20%20%20if%20(j%20%3E%20i%20%2B%201%20%26%26%20nums%5Bj%5D%20%3D%3D%3D%20nums%5Bj%20-%201%5D)%20%7B%0A%20%20%20%20%20%20%20%20continue%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%2B%20nums%5Bj%20%2B%201%5D%20%2B%20nums%5Bj%20%2B%202%5D%20%3E%20target)%20%7B%0A%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%2B%20nums%5Blength%20-%202%5D%20%2B%20nums%5Blength%20-%201%5D%20%3C%20target)%20%7B%0A%20%20%20%20%20%20%20%20continue%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20let%20left%20%3D%20j%20%2B%201%2C%0A%20%20%20%20%20%20%20%20right%20%3D%20length%20-%201%3B%0A%20%20%20%20%20%20while%20(left%20%3C%20right)%20%7B%0A%20%20%20%20%20%20%20%20const%20sum%20%3D%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%2B%20nums%5Bleft%5D%20%2B%20nums%5Bright%5D%3B%0A%20%20%20%20%20%20%20%20if%20(sum%20%3D%3D%3D%20target)%20%7B%0A%20%20%20%20%20%20%20%20%20%20quadruplets.push(%5Bnums%5Bi%5D%2C%20nums%5Bj%5D%2C%20nums%5Bleft%5D%2C%20nums%5Bright%5D%5D)%3B%0A%20%20%20%20%20%20%20%20%20%20logger(%5Bnums%5Bi%5D%2C%20nums%5Bj%5D%2C%20nums%5Bleft%5D%2C%20nums%5Bright%5D%5D)%3B%0A%20%20%20%20%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20%20%20%20%20while%20(left%20%3C%20right%20%26%26%20nums%5Bleft%5D%20%3D%3D%3D%20nums%5Bleft%20%2B%201%5D)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%20%20while%20(left%20%3C%20right%20%26%26%20nums%5Bright%5D%20%3D%3D%3D%20nums%5Bright%20-%201%5D)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20right--%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20right--%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20if%20(sum%20%3C%20target)%20%7B%0A%20%20%20%20%20%20%20%20%20%20left%2B%2B%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20%20%20right--%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20done()%3B%0A%20%20return%20quadruplets%3B%0A%7D%0A",ext:"javascript"},{default:o(()=>[A,g]),_:1})])}const y=e(i,[["render",v],["__file","18-4sum.html.vue"]]);export{y as default}; diff --git a/assets/18-4sum.html-f5377a98.js b/assets/18-4sum.html-f5377a98.js new file mode 100644 index 0000000..f6f7e88 --- /dev/null +++ b/assets/18-4sum.html-f5377a98.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6b163553","path":"/Leetcode/Array/18-4sum.html","title":"","lang":"zh-CN","frontmatter":{"pageClass":"page-indent"},"headers":[{"level":2,"title":"四数之和","slug":"四数之和","link":"#四数之和","children":[{"level":3,"title":"解法 1","slug":"解法-1","link":"#解法-1","children":[]},{"level":3,"title":"解法 2","slug":"解法-2","link":"#解法-2","children":[]}]}],"git":{"updatedTime":1683834292000,"contributors":[{"name":"Moriarty47","email":"wang_jn_xian@163.com","commits":1}]},"filePathRelative":"Leetcode/Array/18-4sum.md"}');export{e as data}; diff --git a/assets/404.html-be216922.js b/assets/404.html-be216922.js new file mode 100644 index 0000000..efad73c --- /dev/null +++ b/assets/404.html-be216922.js @@ -0,0 +1 @@ +import{_ as e,p as t,q as _}from"./framework-5411b43d.js";const c={};function r(n,o){return t(),_("div")}const a=e(c,[["render",r],["__file","404.html.vue"]]);export{a as default}; diff --git a/assets/404.html-f9875e7b.js b/assets/404.html-f9875e7b.js new file mode 100644 index 0000000..5a0b8de --- /dev/null +++ b/assets/404.html-f9875e7b.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-3706649a","path":"/404.html","title":"","lang":"zh-CN","frontmatter":{"layout":"NotFound"},"headers":[],"git":{},"filePathRelative":null}');export{t as data}; diff --git a/assets/6-zigzag-conversion.html-02434e20.js b/assets/6-zigzag-conversion.html-02434e20.js new file mode 100644 index 0000000..6ea8e5e --- /dev/null +++ b/assets/6-zigzag-conversion.html-02434e20.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-482b868c","path":"/Leetcode/Array/6-zigzag-conversion.html","title":"","lang":"zh-CN","frontmatter":{"pageClass":"page-indent"},"headers":[{"level":2,"title":"Z/N 字形变换","slug":"z-n-字形变换","link":"#z-n-字形变换","children":[{"level":3,"title":"解法 1: 暴力解法","slug":"解法-1-暴力解法","link":"#解法-1-暴力解法","children":[]},{"level":3,"title":"解法 2","slug":"解法-2","link":"#解法-2","children":[]},{"level":3,"title":"解法 3","slug":"解法-3","link":"#解法-3","children":[]}]}],"git":{"updatedTime":1683834292000,"contributors":[{"name":"Moriarty47","email":"wang_jn_xian@163.com","commits":1}]},"filePathRelative":"Leetcode/Array/6-zigzag-conversion.md"}');export{e as data}; diff --git a/assets/6-zigzag-conversion.html-d320ed4d.js b/assets/6-zigzag-conversion.html-d320ed4d.js new file mode 100644 index 0000000..5c93d86 --- /dev/null +++ b/assets/6-zigzag-conversion.html-d320ed4d.js @@ -0,0 +1,178 @@ +import{_ as o,M as c,p as l,q as p,N as t,V as e,Q as n,t as s,a0 as u}from"./framework-5411b43d.js";const i={},r=n("h2",{id:"z-n-字形变换",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#z-n-字形变换","aria-hidden":"true"},"#"),s(" Z/N 字形变换")],-1),k=n("p",null,[s("将一个给定字符串 "),n("code",null,"s"),s(" 根据给定的行数 "),n("code",null,"numRows"),s(" ,以从上往下、从左到右进行  Z 字形排列。 比如输入字符串为 "),n("code",null,'"PAYPALISHIRING"'),s("  行数为 "),n("code",null,"3"),s(" 时,排列如下:")],-1),A=n("pre",{class:"language-yaml"},[n("code",null,`P A H N +A P L S I I G +Y I R +`)],-1),d=u("

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。 请你实现这个将字符串进行指定行数变换的函数:string convert(string s, int numRows);

",2),m=n("pre",{class:"language-yaml"},[n("code",null,[n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" s = 'PAYPALISHIRING'"),n("span",{class:"token punctuation"},","),s(` numRows = 3 +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token string"},"'PAHNAPLSIIGYIR'"),s(` +`),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" s = 'PAYPALISHIRING'"),n("span",{class:"token punctuation"},","),s(` numRows = 4 +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token string"},"'PINALSIGYAHRPI'"),s(` +`),n("span",{class:"token key atrule"},"exp"),s(" "),n("span",{class:"token punctuation"},":"),s(` + P I N + A L S I G + Y A H R + P I +`),n("span",{class:"token key atrule"},"input"),s(),n("span",{class:"token punctuation"},":"),s(" s = 'A'"),n("span",{class:"token punctuation"},","),s(` numRows = 1 +`),n("span",{class:"token key atrule"},"output"),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token string"},"'A'"),s(` +`)])],-1),I=n("h3",{id:"解法-1-暴力解法",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-1-暴力解法","aria-hidden":"true"},"#"),s(" 解法 1: 暴力解法")],-1),v=n("br",null,null,-1),b=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"convert"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("s"),n("span",{class:"token punctuation"},","),s(" numRows")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("numRows "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("s"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" s"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" s"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numSingleLetterCol "),n("span",{class:"token operator"},"="),s(" numRows "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" modColumns "),n("span",{class:"token operator"},"="),s(" len "),n("span",{class:"token operator"},"%"),s(),n("span",{class:"token punctuation"},"("),s("numRows "),n("span",{class:"token operator"},"+"),s(" numSingleLetterCol"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" numColumns "),n("span",{class:"token operator"},"="),s(` + Math`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"floor"),n("span",{class:"token punctuation"},"("),s("len "),n("span",{class:"token operator"},"/"),s(),n("span",{class:"token punctuation"},"("),s("numRows "),n("span",{class:"token operator"},"+"),s(" numSingleLetterCol"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"*"),s(` + `),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},"+"),s(" numSingleLetterCol"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"+"),s(` + `),n("span",{class:"token punctuation"},"("),s("modColumns "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"?"),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},":"),s(" modColumns "),n("span",{class:"token operator"},">"),s(" numRows "),n("span",{class:"token operator"},"?"),s(),n("span",{class:"token number"},"2"),s(),n("span",{class:"token operator"},":"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" res "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token function"},"Array"),n("span",{class:"token punctuation"},"("),s("numRows"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" numRows"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + res`),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token function"},"Array"),n("span",{class:"token punctuation"},"("),s("numColumns"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"let"),s(" row "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" col "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" order "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" letter "),n("span",{class:"token operator"},"="),s(" s"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + + res`),n("span",{class:"token punctuation"},"["),s("row"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"["),s("col"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(" letter"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("res"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"join"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},"','"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("numRows "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("row "),n("span",{class:"token operator"},">="),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + row `),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(` + col`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token punctuation"},"{"),s(` + row`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"continue"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("row "),n("span",{class:"token operator"},">="),s(" numRows "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + order `),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("order"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + row`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token punctuation"},"{"),s(` + row`),n("span",{class:"token operator"},"--"),n("span",{class:"token punctuation"},";"),s(` + col`),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("row "),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + row `),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("row "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + order `),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"let"),s(" output "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" numRows"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + output `),n("span",{class:"token operator"},"+="),s(" res"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"join"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("output"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" output"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 1);"),s(` +`),n("span",{class:"token comment"},"// 'PAYPALISHIRING'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 2);"),s(` +`),n("span",{class:"token comment"},"// 'PYAIHRNAPLSIIG'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 3);"),s(` +`),n("span",{class:"token comment"},"// 'PAHNAPLSIIGYIR'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 4);"),s(` +`),n("span",{class:"token comment"},"// 'PINALSIGYAHRPI'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 5);"),s(` +`),n("span",{class:"token comment"},"// 'PHASIYIRPLIGAN'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 6);"),s(` +`),n("span",{class:"token comment"},"// 'PRAIIYHNPSGAIL'"),s(` +`),n("span",{class:"token comment"},"// convert('A', 1);"),s(` +`)])],-1),B=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1),P=n("h3",{id:"解法-2",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-2","aria-hidden":"true"},"#"),s(" 解法 2")],-1),w=n("br",null,null,-1),R=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"convert"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("s"),n("span",{class:"token punctuation"},","),s(" numRows")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("numRows "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("s"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" s"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"const"),s(" len "),n("span",{class:"token operator"},"="),s(" s"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" slot "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"2"),s(),n("span",{class:"token operator"},"*"),s(" numRows "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" res "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" row "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" row "),n("span",{class:"token operator"},"<"),s(" numRows"),n("span",{class:"token punctuation"},";"),s(" row"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" j "),n("span",{class:"token operator"},"="),s(" row"),n("span",{class:"token punctuation"},";"),s(" j "),n("span",{class:"token operator"},"<"),s(" len"),n("span",{class:"token punctuation"},";"),s(),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" letter "),n("span",{class:"token operator"},"="),s(" s"),n("span",{class:"token punctuation"},"["),s("j"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("letter"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + res `),n("span",{class:"token operator"},"+="),s(" letter"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("row "),n("span",{class:"token operator"},"!=="),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"&&"),s(" row "),n("span",{class:"token operator"},"!=="),s(" numRows "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" singleLetter "),n("span",{class:"token operator"},"="),s(" s"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token punctuation"},"("),s("numRows "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},"-"),s(" row"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"*"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("singleLetter"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("singleLetter"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + res `),n("span",{class:"token operator"},"+="),s(" singleLetter"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + j `),n("span",{class:"token operator"},"+="),s(" slot"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token punctuation"},"{"),s(` + j `),n("span",{class:"token operator"},"+="),s(" slot"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("res"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" res"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 1);"),s(` +`),n("span",{class:"token comment"},"// 'PAYPALISHIRING'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 2);"),s(` +`),n("span",{class:"token comment"},"// 'PYAIHRNAPLSIIG'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 3);"),s(` +`),n("span",{class:"token comment"},"// 'PAHNAPLSIIGYIR'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 4);"),s(` +`),n("span",{class:"token comment"},"// 'PINALSIGYAHRPI'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 5);"),s(` +`),n("span",{class:"token comment"},"// 'PHASIYIRPLIGAN'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 6);"),s(` +`),n("span",{class:"token comment"},"// 'PRAIIYHNPSGAIL'"),s(` +`),n("span",{class:"token comment"},"// convert('A', 1);"),s(` +`)])],-1),f=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1),D=n("h3",{id:"解法-3",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#解法-3","aria-hidden":"true"},"#"),s(" 解法 3")],-1),L=n("br",null,null,-1),g=n("pre",{class:"language-javascript"},[n("code",null,[n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"convert"),n("span",{class:"token punctuation"},"("),n("span",{class:"token parameter"},[s("s"),n("span",{class:"token punctuation"},","),s(" numRows")]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" n "),n("span",{class:"token operator"},"="),s(" s"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" r "),n("span",{class:"token operator"},"="),s(" numRows"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("r "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},"||"),s(" r "),n("span",{class:"token operator"},">="),s(" n"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("s"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" s"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"const"),s(" t "),n("span",{class:"token operator"},"="),s(" r "),n("span",{class:"token operator"},"*"),s(),n("span",{class:"token number"},"2"),s(),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" ans "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i "),n("span",{class:"token operator"},"<"),s(" r"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"// 枚举矩阵的行"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" j "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" j "),n("span",{class:"token operator"},"<"),s(" n "),n("span",{class:"token operator"},"-"),s(" i"),n("span",{class:"token punctuation"},";"),s(" j "),n("span",{class:"token operator"},"+="),s(" t"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"// 枚举每个周期的起始下标"),s(` + ans`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"push"),n("span",{class:"token punctuation"},"("),s("s"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(" i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(),n("span",{class:"token comment"},"// 当前周期的第一个字符"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("s"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(" i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"<"),s(" i "),n("span",{class:"token operator"},"&&"),s(" i "),n("span",{class:"token operator"},"<"),s(" r "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},"&&"),s(" j "),n("span",{class:"token operator"},"+"),s(" t "),n("span",{class:"token operator"},"-"),s(" i "),n("span",{class:"token operator"},"<"),s(" n"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + ans`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"push"),n("span",{class:"token punctuation"},"("),s("s"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(" t "),n("span",{class:"token operator"},"-"),s(" i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(),n("span",{class:"token comment"},"// 当前周期的第二个字符"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("s"),n("span",{class:"token punctuation"},"["),s("j "),n("span",{class:"token operator"},"+"),s(" t "),n("span",{class:"token operator"},"-"),s(" i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"suspend"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"logger"),n("span",{class:"token punctuation"},"("),s("ans"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"join"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token function"},"done"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" ans"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"join"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},"''"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 1);"),s(` +`),n("span",{class:"token comment"},"// 'PAYPALISHIRING'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 2);"),s(` +`),n("span",{class:"token comment"},"// 'PYAIHRNAPLSIIG'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 3);"),s(` +`),n("span",{class:"token comment"},"// 'PAHNAPLSIIGYIR'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 4);"),s(` +`),n("span",{class:"token comment"},"// 'PINALSIGYAHRPI'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 5);"),s(` +`),n("span",{class:"token comment"},"// 'PHASIYIRPLIGAN'"),s(` +`),n("span",{class:"token comment"},"// convert('PAYPALISHIRING', 6);"),s(` +`),n("span",{class:"token comment"},"// 'PRAIIYHNPSGAIL'"),s(` +`),n("span",{class:"token comment"},"// convert('A', 1);"),s(` +`)])],-1),S=n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})],-1);function N(F,G){const a=c("CodeTool");return l(),p("div",null,[r,k,t(a,{class:"language-yaml no-win-ctrl","data-ext":"yml",fullscreen:"undefined",steps:"undefined",action:"undefined",winCtrl:"false",async:"undefined",content:"P%20%20%20A%20%20%20H%20%20%20N%0AA%20P%20L%20S%20I%20I%20G%0AY%20%20%20I%20%20%20R%0A",ext:"yaml"},{default:e(()=>[A]),_:1}),d,t(a,{class:"language-yaml no-win-ctrl","data-ext":"yml",fullscreen:"undefined",steps:"undefined",action:"undefined",winCtrl:"false",async:"undefined",content:"input%20%3A%20s%20%3D%20'PAYPALISHIRING'%2C%20numRows%20%3D%203%0Aoutput%3A%20'PAHNAPLSIIGYIR'%0Ainput%20%3A%20s%20%3D%20'PAYPALISHIRING'%2C%20numRows%20%3D%204%0Aoutput%3A%20'PINALSIGYAHRPI'%0Aexp%20%20%20%3A%0A%20%20%20%20%20%20%20%20P%20%20%20%20%20I%20%20%20%20N%0A%20%20%20%20%20%20%20%20A%20%20%20L%20S%20%20I%20G%0A%20%20%20%20%20%20%20%20Y%20A%20%20%20H%20R%0A%20%20%20%20%20%20%20%20P%20%20%20%20%20I%0Ainput%20%3A%20s%20%3D%20'A'%2C%20numRows%20%3D%201%0Aoutput%3A%20'A'%0A",ext:"yaml"},{default:e(()=>[m]),_:1}),I,v,t(a,{id:"example1",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"'PAYPALISHIRING', 2",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"'PAYPALISHIRING', 2",content:"function%20convert(s%2C%20numRows)%20%7B%0A%20%20if%20(numRows%20%3D%3D%3D%201)%20%7B%0A%20%20%20%20logger(s)%3B%0A%20%20%20%20done()%3B%0A%20%20%20%20return%20s%3B%0A%20%20%7D%0A%20%20const%20len%20%3D%20s.length%3B%0A%20%20const%20numSingleLetterCol%20%3D%20numRows%20-%202%3B%0A%20%20const%20modColumns%20%3D%20len%20%25%20(numRows%20%2B%20numSingleLetterCol)%3B%0A%20%20const%20numColumns%20%3D%0A%20%20%20%20Math.floor(len%20%2F%20(numRows%20%2B%20numSingleLetterCol))%20*%0A%20%20%20%20%20%20(1%20%2B%20numSingleLetterCol)%20%2B%0A%20%20%20%20(modColumns%20%3D%3D%3D%200%20%3F%200%20%3A%20modColumns%20%3E%20numRows%20%3F%202%20%3A%201)%3B%0A%20%20const%20res%20%3D%20Array(numRows)%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20numRows%3B%20i%2B%2B)%20%7B%0A%20%20%20%20res%5Bi%5D%20%3D%20Array(numColumns)%3B%0A%20%20%7D%0A%20%20let%20row%20%3D%200%3B%0A%20%20let%20col%20%3D%200%3B%0A%20%20let%20order%20%3D%20true%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20len%3B%20i%2B%2B)%20%7B%0A%20%20%20%20const%20letter%20%3D%20s%5Bi%5D%3B%0A%0A%20%20%20%20res%5Brow%5D%5Bcol%5D%20%3D%20letter%3B%0A%20%20%20%20logger(res.join('%2C'))%3B%0A%20%20%20%20suspend()%3B%0A%20%20%20%20if%20(numRows%20%3D%3D%3D%202)%20%7B%0A%20%20%20%20%20%20if%20(row%20%3E%3D%201)%20%7B%0A%20%20%20%20%20%20%20%20row%20%3D%200%3B%0A%20%20%20%20%20%20%20%20col%2B%2B%3B%0A%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20row%2B%2B%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20continue%3B%0A%20%20%20%20%7D%0A%20%20%20%20if%20(row%20%3E%3D%20numRows%20-%201)%20%7B%0A%20%20%20%20%20%20order%20%3D%20false%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(order)%20%7B%0A%20%20%20%20%20%20row%2B%2B%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20row--%3B%0A%20%20%20%20%20%20col%2B%2B%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(row%20%3C%200)%20%7B%0A%20%20%20%20%20%20row%20%3D%200%3B%0A%20%20%20%20%7D%20else%20if%20(row%20%3D%3D%3D%200)%20%7B%0A%20%20%20%20%20%20order%20%3D%20true%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20let%20output%20%3D%20''%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20numRows%3B%20i%2B%2B)%20%7B%0A%20%20%20%20output%20%2B%3D%20res%5Bi%5D.join('')%3B%0A%20%20%7D%0A%20%20logger(output)%3B%0A%20%20done()%3B%0A%20%20return%20output%3B%0A%7D%0A%0A%2F%2F%20convert('PAYPALISHIRING'%2C%201)%3B%0A%2F%2F%20'PAYPALISHIRING'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%202)%3B%0A%2F%2F%20'PYAIHRNAPLSIIG'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%203)%3B%0A%2F%2F%20'PAHNAPLSIIGYIR'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%204)%3B%0A%2F%2F%20'PINALSIGYAHRPI'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%205)%3B%0A%2F%2F%20'PHASIYIRPLIGAN'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%206)%3B%0A%2F%2F%20'PRAIIYHNPSGAIL'%0A%2F%2F%20convert('A'%2C%201)%3B%0A",ext:"javascript"},{default:e(()=>[b,B]),_:1}),P,w,t(a,{id:"example2",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"'PAYPALISHIRING', 2",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"'PAYPALISHIRING', 2",content:"function%20convert(s%2C%20numRows)%20%7B%0A%20%20if%20(numRows%20%3D%3D%3D%201)%20%7B%0A%20%20%20%20logger(s)%3B%0A%20%20%20%20done()%3B%0A%20%20%20%20return%20s%3B%0A%20%20%7D%0A%20%20const%20len%20%3D%20s.length%3B%0A%20%20const%20slot%20%3D%202%20*%20numRows%20-%202%3B%0A%20%20let%20res%20%3D%20''%3B%0A%20%20for%20(let%20row%20%3D%200%3B%20row%20%3C%20numRows%3B%20row%2B%2B)%20%7B%0A%20%20%20%20for%20(let%20j%20%3D%20row%3B%20j%20%3C%20len%3B%20)%20%7B%0A%20%20%20%20%20%20const%20letter%20%3D%20s%5Bj%5D%3B%0A%20%20%20%20%20%20logger(letter)%3B%0A%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20res%20%2B%3D%20letter%3B%0A%20%20%20%20%20%20if%20(row%20!%3D%3D%200%20%26%26%20row%20!%3D%3D%20numRows%20-%201)%20%7B%0A%20%20%20%20%20%20%20%20const%20singleLetter%20%3D%20s%5Bj%20%2B%20(numRows%20-%201%20-%20row)%20*%202%5D%3B%0A%20%20%20%20%20%20%20%20if%20(singleLetter)%20%7B%0A%20%20%20%20%20%20%20%20%20%20logger(singleLetter)%3B%0A%20%20%20%20%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20singleLetter%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%20slot%3B%0A%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20j%20%2B%3D%20slot%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20logger(res)%3B%0A%20%20done()%3B%0A%20%20return%20res%3B%0A%7D%0A%0A%2F%2F%20convert('PAYPALISHIRING'%2C%201)%3B%0A%2F%2F%20'PAYPALISHIRING'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%202)%3B%0A%2F%2F%20'PYAIHRNAPLSIIG'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%203)%3B%0A%2F%2F%20'PAHNAPLSIIGYIR'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%204)%3B%0A%2F%2F%20'PINALSIGYAHRPI'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%205)%3B%0A%2F%2F%20'PHASIYIRPLIGAN'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%206)%3B%0A%2F%2F%20'PRAIIYHNPSGAIL'%0A%2F%2F%20convert('A'%2C%201)%3B%0A",ext:"javascript"},{default:e(()=>[R,f]),_:1}),D,L,t(a,{id:"example3",class:"language-javascript line-numbers-mode","data-ext":"js","data-action":"run","data-async":"'PAYPALISHIRING', 2",fullscreen:"undefined",steps:"undefined",action:"run",winCtrl:"undefined",async:"'PAYPALISHIRING', 2",content:"function%20convert(s%2C%20numRows)%20%7B%0A%20%20const%20n%20%3D%20s.length%3B%0A%20%20const%20r%20%3D%20numRows%3B%0A%20%20if%20(r%20%3D%3D%3D%201%20%7C%7C%20r%20%3E%3D%20n)%20%7B%0A%20%20%20%20logger(s)%3B%0A%20%20%20%20done()%3B%0A%20%20%20%20return%20s%3B%0A%20%20%7D%0A%20%20const%20t%20%3D%20r%20*%202%20-%202%3B%0A%20%20const%20ans%20%3D%20%5B%5D%3B%0A%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20r%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%2F%2F%20%E6%9E%9A%E4%B8%BE%E7%9F%A9%E9%98%B5%E7%9A%84%E8%A1%8C%0A%20%20%20%20for%20(let%20j%20%3D%200%3B%20j%20%3C%20n%20-%20i%3B%20j%20%2B%3D%20t)%20%7B%0A%20%20%20%20%20%20%2F%2F%20%E6%9E%9A%E4%B8%BE%E6%AF%8F%E4%B8%AA%E5%91%A8%E6%9C%9F%E7%9A%84%E8%B5%B7%E5%A7%8B%E4%B8%8B%E6%A0%87%0A%20%20%20%20%20%20ans.push(s%5Bj%20%2B%20i%5D)%3B%20%2F%2F%20%E5%BD%93%E5%89%8D%E5%91%A8%E6%9C%9F%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20logger(s%5Bj%20%2B%20i%5D)%3B%0A%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20if%20(0%20%3C%20i%20%26%26%20i%20%3C%20r%20-%201%20%26%26%20j%20%2B%20t%20-%20i%20%3C%20n)%20%7B%0A%20%20%20%20%20%20%20%20ans.push(s%5Bj%20%2B%20t%20-%20i%5D)%3B%20%2F%2F%20%E5%BD%93%E5%89%8D%E5%91%A8%E6%9C%9F%E7%9A%84%E7%AC%AC%E4%BA%8C%E4%B8%AA%E5%AD%97%E7%AC%A6%0A%20%20%20%20%20%20%20%20logger(s%5Bj%20%2B%20t%20-%20i%5D)%3B%0A%20%20%20%20%20%20%20%20suspend()%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20logger(ans.join(''))%3B%0A%20%20done()%3B%0A%20%20return%20ans.join('')%3B%0A%7D%0A%0A%2F%2F%20convert('PAYPALISHIRING'%2C%201)%3B%0A%2F%2F%20'PAYPALISHIRING'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%202)%3B%0A%2F%2F%20'PYAIHRNAPLSIIG'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%203)%3B%0A%2F%2F%20'PAHNAPLSIIGYIR'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%204)%3B%0A%2F%2F%20'PINALSIGYAHRPI'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%205)%3B%0A%2F%2F%20'PHASIYIRPLIGAN'%0A%2F%2F%20convert('PAYPALISHIRING'%2C%206)%3B%0A%2F%2F%20'PRAIIYHNPSGAIL'%0A%2F%2F%20convert('A'%2C%201)%3B%0A",ext:"javascript"},{default:e(()=>[g,S]),_:1})])}const Y=o(i,[["render",N],["__file","6-zigzag-conversion.html.vue"]]);export{Y as default}; diff --git a/assets/AccessibilityPage-3e59a843.js b/assets/AccessibilityPage-3e59a843.js new file mode 100644 index 0000000..79639a9 --- /dev/null +++ b/assets/AccessibilityPage-3e59a843.js @@ -0,0 +1 @@ +import{a as o}from"./index-d2316ac8.js";import{c as _,p as e,q as t,O as a,P as l,v as i,Q as c,z as r,_ as d}from"./framework-5411b43d.js";import"./app-bf7bc9cc.js";const p={key:0,class:"subHeading"},g={key:1,class:"pageItem"},u={class:"settingsIcon"},f={class:"itemInfo"},y={class:"itemTitle"},m={class:"itemDesc"},b=_({__name:"AccessibilityPage",setup(h){const n=o.Accessibility;return(k,v)=>(e(),t("div",null,[(e(!0),t(a,null,l(r(n),s=>(e(),t(a,{key:s.key},[s.subHeading?(e(),t("div",p,i(s.title),1)):(e(),t("div",g,[c("span",u,i(s.icon),1),c("div",f,[c("p",y,i(s.title),1),c("p",m,i(s.description),1)])]))],64))),128))]))}}),B=d(b,[["__file","AccessibilityPage.vue"]]);export{B as default}; diff --git a/assets/AccountsPage-0e10f7db.js b/assets/AccountsPage-0e10f7db.js new file mode 100644 index 0000000..1769869 --- /dev/null +++ b/assets/AccountsPage-0e10f7db.js @@ -0,0 +1 @@ +import{u as _,I as l}from"./Playground-3eb19026.js";import{a as u}from"./index-d2316ac8.js";import{$ as i}from"./app-bf7bc9cc.js";import{c as p,p as n,q as a,Q as s,N as d,z as t,v as c,O as m,P as f,_ as g}from"./framework-5411b43d.js";const h={class:"accountTop"},I={class:"accountInfo"},v={class:"accountName"},y={class:"accountLocal"},A={class:"accountType"},P={class:"settingsIcon"},T={class:"itemInfo"},k={class:"itemTitle"},L={class:"itemDesc"},N=p({__name:"AccountsPage",setup(S){const{user:e}=_(),r=u.Accounts;return(x,B)=>(n(),a("div",null,[s("div",h,[d(l,{src:t(e).avatar,alt:t(e).username,size:90},null,8,["src","alt"]),s("div",I,[s("p",v,c(t(e).username),1),s("p",y,c(t(i)("SettingsI18n.AccountsI18n.Local")(t(e).isLocal)),1),s("p",A,c(t(i)("SettingsI18n.AccountsI18n.AccountType")(t(e).accountType)),1)])]),(n(!0),a(m,null,f(t(r),o=>(n(),a("div",{key:o.key,class:"pageItem"},[s("span",P,c(o.icon),1),s("div",T,[s("p",k,c(o.title),1),s("p",L,c(o.description),1)])]))),128))]))}}),$=g(N,[["__file","AccountsPage.vue"]]);export{$ as default}; diff --git a/assets/AppsPage-c044cc86.js b/assets/AppsPage-c044cc86.js new file mode 100644 index 0000000..3d2f5d9 --- /dev/null +++ b/assets/AppsPage-c044cc86.js @@ -0,0 +1 @@ +import{a as p}from"./index-d2316ac8.js";import{c as r,p as t,q as o,O as _,P as l,Q as e,v as n,z as d,_ as f}from"./framework-5411b43d.js";import"./app-bf7bc9cc.js";const g=["onClick"],u={class:"settingsIcon"},m={class:"itemInfo"},k={class:"itemTitle"},h={class:"itemDesc"},v=r({__name:"AppsPage",setup(C){const c=p.Apps;function i(a){console.log(a)}return(a,P)=>(t(),o("div",null,[(t(!0),o(_,null,l(d(c),s=>(t(),o("div",{key:s.key,class:"pageItem",onClick:y=>i(s.key)},[e("span",u,n(s.icon),1),e("div",m,[e("p",k,n(s.title),1),e("p",h,n(s.description),1)])],8,g))),128))]))}}),I=f(v,[["__file","AppsPage.vue"]]);export{I as default}; diff --git a/assets/BluetoothDevicesPage-2bf3e681.js b/assets/BluetoothDevicesPage-2bf3e681.js new file mode 100644 index 0000000..12a5040 --- /dev/null +++ b/assets/BluetoothDevicesPage-2bf3e681.js @@ -0,0 +1 @@ +import{u}from"./Playground-3eb19026.js";import{a as p}from"./index-d2316ac8.js";import{$ as d}from"./app-bf7bc9cc.js";import{c as h,p as t,q as s,O as f,P as m,Q as c,v as o,z as a,_ as v}from"./framework-5411b43d.js";const g=["onClick"],k={class:"settingsIcon"},B={class:"itemInfo"},D={class:"itemTitle"},y={key:0,class:"itemDesc"},C={key:1,class:"itemDesc"},P=h({__name:"BluetoothDevicesPage",setup(x){const i=p.BluetoothDevices,{bluetooth:l,connectBluetooth:r}=u();function _(n){if(n==="AddDevice"||n==="Bluetooth"){r(()=>{});return}}return(n,I)=>(t(),s("div",null,[(t(!0),s(f,null,m(a(i),e=>(t(),s("div",{key:e.key,class:"pageItem",onClick:S=>_(e.key)},[c("span",k,o(e.icon),1),c("div",B,[c("p",D,o(e.title),1),e.key==="Bluetooth"?(t(),s("p",y,o(e.description)+": "+o(a(d)("State")(a(l).isConnected.value)),1)):(t(),s("p",C,o(e.description),1))])],8,g))),128))]))}}),A=v(P,[["__file","BluetoothDevicesPage.vue"]]);export{A as default}; diff --git a/assets/Calculator-0957e3ad.png b/assets/Calculator-0957e3ad.png new file mode 100644 index 0000000..d7497b5 Binary files /dev/null and b/assets/Calculator-0957e3ad.png differ diff --git a/assets/Camera-0aa41906.png b/assets/Camera-0aa41906.png new file mode 100644 index 0000000..5df4d06 Binary files /dev/null and b/assets/Camera-0aa41906.png differ diff --git a/assets/ChevronDown-1b714050.svg b/assets/ChevronDown-1b714050.svg new file mode 100644 index 0000000..3dc9468 --- /dev/null +++ b/assets/ChevronDown-1b714050.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ChevronRight-a1cfe980.svg b/assets/ChevronRight-a1cfe980.svg new file mode 100644 index 0000000..512b6f5 --- /dev/null +++ b/assets/ChevronRight-a1cfe980.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ChevronUp-dcfffbd5.svg b/assets/ChevronUp-dcfffbd5.svg new file mode 100644 index 0000000..2d92eb0 --- /dev/null +++ b/assets/ChevronUp-dcfffbd5.svg @@ -0,0 +1 @@ + diff --git a/assets/Clock-081da855.png b/assets/Clock-081da855.png new file mode 100644 index 0000000..60ebcde Binary files /dev/null and b/assets/Clock-081da855.png differ diff --git a/assets/CodeLink-dd177f41.js b/assets/CodeLink-dd177f41.js new file mode 100644 index 0000000..01197cf --- /dev/null +++ b/assets/CodeLink-dd177f41.js @@ -0,0 +1 @@ +import{c as a,p as i,q as d,s as h,t as u,v as f,_ as p}from"./framework-5411b43d.js";const m=a({__name:"CodeLink",props:{to:{type:String,required:!0},text:{type:String,required:!1,default:""}},setup(o){const n=o,r=(e,s)=>{const l=s.parentElement.querySelectorAll(`[for="${e}"]`);l.forEach(t=>{t.classList.add("highlightBlock")}),setTimeout(()=>{l.forEach(t=>{t.classList.remove("highlightBlock")})},2e3)};function c(){const e=document.getElementById(n.to);e&&(e.scrollIntoView({block:"center",behavior:"smooth"}),r(n.to,e))}return(e,s)=>(i(),d("span",{class:"code-link",onClick:c},[h(e.$slots,"default",{},()=>[u(f(o.text),1)])]))}}),k=p(m,[["__file","CodeLink.vue"]]);export{k as default}; diff --git a/assets/CodeTool-4b08e5c5.js b/assets/CodeTool-4b08e5c5.js new file mode 100644 index 0000000..e4c66f6 --- /dev/null +++ b/assets/CodeTool-4b08e5c5.js @@ -0,0 +1,30 @@ +import{K as commonjsGlobal,T as Toast,X as Xicons}from"./app-bf7bc9cc.js";import{c as defineComponent,r as ref,F as shallowRef,o as onMounted,I as onUnmounted,p as openBlock,q as createElementBlock,Q as createBaseVNode,E as nextTick,j as h$1,_ as _export_sfc,h as computed,N as createVNode,w as normalizeClass,z as unref,v as toDisplayString,aj as useAttrs,R as createCommentVNode,x as normalizeStyle,U as createBlock,s as renderSlot}from"./framework-5411b43d.js";var lottieExports={},lottie$1={get exports(){return lottieExports},set exports(t){lottieExports=t}};(function(module,exports){typeof navigator<"u"&&function(t,e){module.exports=e()}(commonjsGlobal,function(){var svgNS="http://www.w3.org/2000/svg",locationHref="",_useWebWorker=!1,initialDefaultFrame=-999999,setWebWorker=function(e){_useWebWorker=!!e},getWebWorker=function(){return _useWebWorker},setLocationHref=function(e){locationHref=e},getLocationHref=function(){return locationHref};function createTag(t){return document.createElement(t)}function extendPrototype(t,e){var r,i=t.length,s;for(r=0;r1?r[1]=1:r[1]<=0&&(r[1]=0),HSVtoRGB(r[0],r[1],r[2])}function addBrightnessToRGB(t,e){var r=RGBtoHSV(t[0]*255,t[1]*255,t[2]*255);return r[2]+=e,r[2]>1?r[2]=1:r[2]<0&&(r[2]=0),HSVtoRGB(r[0],r[1],r[2])}function addHueToRGB(t,e){var r=RGBtoHSV(t[0]*255,t[1]*255,t[2]*255);return r[0]+=e/360,r[0]>1?r[0]-=1:r[0]<0&&(r[0]+=1),HSVtoRGB(r[0],r[1],r[2])}var rgbToHex=function(){var t=[],e,r;for(e=0;e<256;e+=1)r=e.toString(16),t[e]=r.length===1?"0"+r:r;return function(i,s,n){return i<0&&(i=0),s<0&&(s=0),n<0&&(n=0),"#"+t[i]+t[s]+t[n]}}(),setSubframeEnabled=function(e){subframeEnabled=!!e},getSubframeEnabled=function(){return subframeEnabled},setExpressionsPlugin=function(e){expressionsPlugin=e},getExpressionsPlugin=function(){return expressionsPlugin},setExpressionInterfaces=function(e){expressionsInterfaces=e},getExpressionInterfaces=function(){return expressionsInterfaces},setDefaultCurveSegments=function(e){defaultCurveSegments=e},getDefaultCurveSegments=function(){return defaultCurveSegments},setIdPrefix=function(e){idPrefix$1=e};function createNS(t){return document.createElementNS(svgNS,t)}function _typeof$5(t){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?_typeof$5=function(r){return typeof r}:_typeof$5=function(r){return r&&typeof Symbol=="function"&&r.constructor===Symbol&&r!==Symbol.prototype?"symbol":typeof r},_typeof$5(t)}var dataManager=function(){var t=1,e=[],r,i,s={onmessage:function(){},postMessage:function(E){r({data:E})}},n={postMessage:function(E){s.onmessage({data:E})}};function a(d){if(window.Worker&&window.Blob&&getWebWorker()){var E=new Blob(["var _workerSelf = self; self.onmessage = ",d.toString()],{type:"text/javascript"}),y=URL.createObjectURL(E);return new Worker(y)}return r=d,s}function c(){i||(i=a(function(E){function y(){function C(B,F){var T,x,M=B.length,R,V,N,q;for(x=0;x=0;F-=1)if(B[F].ty==="sh")if(B[F].ks.k.i)b(B[F].ks.k);else for(M=B[F].ks.k.length,x=0;xT[0]?!0:T[0]>B[0]?!1:B[1]>T[1]?!0:T[1]>B[1]?!1:B[2]>T[2]?!0:T[2]>B[2]?!1:null}var _=function(){var B=[4,4,14];function F(x){var M=x.t.d;x.t.d={k:[{s:M,t:0}]}}function T(x){var M,R=x.length;for(M=0;M=0;M-=1)if(x[M].ty==="sh")if(x[M].ks.k.i)x[M].ks.k.c=x[M].closed;else for(N=x[M].ks.k.length,V=0;V500)&&(this._imageLoaded(),clearInterval(f)),u+=1}.bind(this),50)}function n(o){var u=i(o,this.assetsPath,this.path),f=createNS("image");isSafari?this.testImageLoaded(f):f.addEventListener("load",this._imageLoaded,!1),f.addEventListener("error",function(){l.img=t,this._imageLoaded()}.bind(this),!1),f.setAttributeNS("http://www.w3.org/1999/xlink","href",u),this._elementHelper.append?this._elementHelper.append(f):this._elementHelper.appendChild(f);var l={img:f,assetData:o};return l}function a(o){var u=i(o,this.assetsPath,this.path),f=createTag("img");f.crossOrigin="anonymous",f.addEventListener("load",this._imageLoaded,!1),f.addEventListener("error",function(){l.img=t,this._imageLoaded()}.bind(this),!1),f.src=u;var l={img:f,assetData:o};return l}function c(o){var u={assetData:o},f=i(o,this.assetsPath,this.path);return dataManager.loadData(f,function(l){u.img=l,this._footageLoaded()}.bind(this),function(){u.img={},this._footageLoaded()}.bind(this)),u}function p(o,u){this.imagesLoadedCb=u;var f,l=o.length;for(f=0;fthis.animationData.op&&(this.animationData.op=t.op,this.totalFrames=Math.floor(t.op-this.animationData.ip));var e=this.animationData.layers,r,i=e.length,s=t.layers,n,a=s.length;for(n=0;nthis.timeCompleted&&(this.currentFrame=this.timeCompleted),this.trigger("enterFrame"),this.renderFrame(),this.trigger("drawnFrame")},AnimationItem.prototype.renderFrame=function(){if(!(this.isLoaded===!1||!this.renderer))try{this.renderer.renderFrame(this.currentFrame+this.firstFrame)}catch(t){this.triggerRenderFrameError(t)}},AnimationItem.prototype.play=function(t){t&&this.name!==t||this.isPaused===!0&&(this.isPaused=!1,this.trigger("_pause"),this.audioController.resume(),this._idle&&(this._idle=!1,this.trigger("_active")))},AnimationItem.prototype.pause=function(t){t&&this.name!==t||this.isPaused===!1&&(this.isPaused=!0,this.trigger("_play"),this._idle=!0,this.trigger("_idle"),this.audioController.pause())},AnimationItem.prototype.togglePause=function(t){t&&this.name!==t||(this.isPaused===!0?this.play():this.pause())},AnimationItem.prototype.stop=function(t){t&&this.name!==t||(this.pause(),this.playCount=0,this._completedLoop=!1,this.setCurrentRawFrameValue(0))},AnimationItem.prototype.getMarkerData=function(t){for(var e,r=0;r=this.totalFrames-1&&this.frameModifier>0?!this.loop||this.playCount===this.loop?this.checkSegments(e>this.totalFrames?e%this.totalFrames:0)||(r=!0,e=this.totalFrames-1):e>=this.totalFrames?(this.playCount+=1,this.checkSegments(e%this.totalFrames)||(this.setCurrentRawFrameValue(e%this.totalFrames),this._completedLoop=!0,this.trigger("loopComplete"))):this.setCurrentRawFrameValue(e):e<0?this.checkSegments(e%this.totalFrames)||(this.loop&&!(this.playCount--<=0&&this.loop!==!0)?(this.setCurrentRawFrameValue(this.totalFrames+e%this.totalFrames),this._completedLoop?this.trigger("loopComplete"):this._completedLoop=!0):(r=!0,e=0)):this.setCurrentRawFrameValue(e),r&&(this.setCurrentRawFrameValue(e),this.pause(),this.trigger("complete"))}},AnimationItem.prototype.adjustSegment=function(t,e){this.playCount=0,t[1]0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(-1)),this.totalFrames=t[0]-t[1],this.timeCompleted=this.totalFrames,this.firstFrame=t[1],this.setCurrentRawFrameValue(this.totalFrames-.001-e)):t[1]>t[0]&&(this.frameModifier<0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(1)),this.totalFrames=t[1]-t[0],this.timeCompleted=this.totalFrames,this.firstFrame=t[0],this.setCurrentRawFrameValue(.001+e)),this.trigger("segmentStart")},AnimationItem.prototype.setSegment=function(t,e){var r=-1;this.isPaused&&(this.currentRawFrame+this.firstFramee&&(r=e-t)),this.firstFrame=t,this.totalFrames=e-t,this.timeCompleted=this.totalFrames,r!==-1&&this.goToAndStop(r,!0)},AnimationItem.prototype.playSegments=function(t,e){if(e&&(this.segments.length=0),_typeof$4(t[0])==="object"){var r,i=t.length;for(r=0;r=0;T-=1)e[T].animation.destroy(F)}function k(F,T,x){var M=[].concat([].slice.call(document.getElementsByClassName("lottie")),[].slice.call(document.getElementsByClassName("bodymovin"))),R,V=M.length;for(R=0;R0?l=k:f=k;while(Math.abs(_)>n&&++I=s?C(f,D,l,b):L===0?D:g(f,_,_+p,l,b)}},t}(),pooling=function(){function t(e){return e.concat(createSizedArray(e.length))}return{double:t}}(),poolFactory=function(){return function(t,e,r){var i=0,s=t,n=createSizedArray(s),a={newElement:c,release:p};function c(){var m;return i?(i-=1,m=n[i]):m=e(),m}function p(m){i===s&&(n=pooling.double(n),s*=2),r&&r(m),n[i]=m,i+=1}return a}}(),bezierLengthPool=function(){function t(){return{addedLength:0,percents:createTypedArray("float32",getDefaultCurveSegments()),lengths:createTypedArray("float32",getDefaultCurveSegments())}}return poolFactory(8,t)}(),segmentsLengthPool=function(){function t(){return{lengths:[],totalLength:0}}function e(r){var i,s=r.lengths.length;for(i=0;i-.001&&u<.001}function r(d,E,y,g,C,o,u,f,l){if(y===0&&o===0&&l===0)return e(d,E,g,C,u,f);var b=t.sqrt(t.pow(g-d,2)+t.pow(C-E,2)+t.pow(o-y,2)),S=t.sqrt(t.pow(u-d,2)+t.pow(f-E,2)+t.pow(l-y,2)),_=t.sqrt(t.pow(u-g,2)+t.pow(f-C,2)+t.pow(l-o,2)),k;return b>S?b>_?k=b-S-_:k=_-S-b:_>S?k=_-S-b:k=S-b-_,k>-1e-4&&k<1e-4}var i=function(){return function(d,E,y,g){var C=getDefaultCurveSegments(),o,u,f,l,b,S=0,_,k=[],I=[],G=bezierLengthPool.newElement();for(f=y.length,o=0;ou?-1:1,b=!0;b;)if(g[o]<=u&&g[o+1]>u?(f=(u-g[o])/(g[o+1]-g[o]),b=!1):o+=l,o<0||o>=C-1){if(o===C-1)return y[o];b=!1}return y[o]+(y[o+1]-y[o])*f}function m(d,E,y,g,C,o){var u=p(C,o),f=1-u,l=t.round((f*f*f*d[0]+(u*f*f+f*u*f+f*f*u)*y[0]+(u*u*f+f*u*u+u*f*u)*g[0]+u*u*u*E[0])*1e3)/1e3,b=t.round((f*f*f*d[1]+(u*f*f+f*u*f+f*f*u)*y[1]+(u*u*f+f*u*u+u*f*u)*g[1]+u*u*u*E[1])*1e3)/1e3;return[l,b]}var P=createTypedArray("float32",8);function A(d,E,y,g,C,o,u){C<0?C=0:C>1&&(C=1);var f=p(C,u);o=o>1?1:o;var l=p(o,u),b,S=d.length,_=1-f,k=1-l,I=_*_*_,G=f*_*_*3,D=f*f*_*3,L=f*f*f,z=_*_*k,O=f*_*k+_*f*k+_*_*l,B=f*f*k+_*f*l+f*_*l,F=f*f*l,T=_*k*k,x=f*k*k+_*l*k+_*k*l,M=f*l*k+_*l*l+f*k*l,R=f*l*l,V=k*k*k,N=l*k*k+k*l*k+k*k*l,q=l*l*k+k*l*l+l*k*l,j=l*l*l;for(b=0;b=I.t-u){k.h&&(k=I),l=0;break}if(I.t-u>C){l=b;break}b=T||C=T?R.points.length-1:0;for(L=R.points[V].point.length,D=0;D=j&&N=T)f[0]=M[0],f[1]=M[1],f[2]=M[2];else if(C<=x)f[0]=k.s[0],f[1]=k.s[1],f[2]=k.s[2];else{var Z=n(k.s),Y=n(M),J=(C-x)/(T-x);s(f,i(Z,Y,J))}else for(b=0;b=T?z=1:C1e-6?(L=Math.acos(z),O=Math.sin(L),B=Math.sin((1-u)*L)/O,F=Math.sin(u*L)/O):(B=1-u,F=u),f[0]=B*l+F*k,f[1]=B*b+F*I,f[2]=B*S+F*G,f[3]=B*_+F*D,f}function s(C,o){var u=o[0],f=o[1],l=o[2],b=o[3],S=Math.atan2(2*f*b-2*u*l,1-2*f*f-2*l*l),_=Math.asin(2*u*f+2*l*b),k=Math.atan2(2*u*b-2*f*l,1-2*u*u-2*l*l);C[0]=S/degToRads,C[1]=_/degToRads,C[2]=k/degToRads}function n(C){var o=C[0]*degToRads,u=C[1]*degToRads,f=C[2]*degToRads,l=Math.cos(o/2),b=Math.cos(u/2),S=Math.cos(f/2),_=Math.sin(o/2),k=Math.sin(u/2),I=Math.sin(f/2),G=l*b*S-_*k*I,D=_*k*S+l*b*I,L=_*b*S+l*k*I,z=l*k*S-_*b*I;return[D,L,z,G]}function a(){var C=this.comp.renderedFrame-this.offsetTime,o=this.keyframes[0].t-this.offsetTime,u=this.keyframes[this.keyframes.length-1].t-this.offsetTime;if(!(C===this._caching.lastFrame||this._caching.lastFrame!==t&&(this._caching.lastFrame>=u&&C>=u||this._caching.lastFrame=C&&(this._caching._lastKeyframeIndex=-1,this._caching.lastIndex=0);var f=this.interpolateValue(C,this._caching);this.pv=f}return this._caching.lastFrame=C,this.pv}function c(C){var o;if(this.propType==="unidimensional")o=C*this.mult,e(this.v-o)>1e-5&&(this.v=o,this._mdf=!0);else for(var u=0,f=this.v.length;u1e-5&&(this.v[u]=o,this._mdf=!0),u+=1}function p(){if(!(this.elem.globalData.frameId===this.frameId||!this.effectsSequence.length)){if(this.lock){this.setVValue(this.pv);return}this.lock=!0,this._mdf=this._isFirstFrame;var C,o=this.effectsSequence.length,u=this.kf?this.pv:this.data.k;for(C=0;C=this._maxLength&&this.doubleArrayLength(),r){case"v":n=this.v;break;case"i":n=this.i;break;case"o":n=this.o;break;default:n=[];break}(!n[i]||n[i]&&!s)&&(n[i]=pointPool.newElement()),n[i][0]=t,n[i][1]=e},ShapePath.prototype.setTripleAt=function(t,e,r,i,s,n,a,c){this.setXYAt(t,e,"v",a,c),this.setXYAt(r,i,"o",a,c),this.setXYAt(s,n,"i",a,c)},ShapePath.prototype.reverse=function(){var t=new ShapePath;t.setPathData(this.c,this._length);var e=this.v,r=this.o,i=this.i,s=0;this.c&&(t.setTripleAt(e[0][0],e[0][1],i[0][0],i[0][1],r[0][0],r[0][1],0,!1),s=1);var n=this._length-1,a=this._length,c;for(c=s;c=O[O.length-1].t-this.offsetTime)b=O[O.length-1].s?O[O.length-1].s[0]:O[O.length-2].e[0],_=!0;else{for(var B=l,F=O.length-1,T=!0,x,M,R;T&&(x=O[B],M=O[B+1],!(M.t-this.offsetTime>o));)B=M.t-this.offsetTime)L=1;else if(of&&o>f)||(this._caching.lastIndex=l0||T>-1e-6&&T<0?i(T*x)/x:T}function F(){var T=this.props,x=B(T[0]),M=B(T[1]),R=B(T[4]),V=B(T[5]),N=B(T[12]),q=B(T[13]);return"matrix("+x+","+M+","+R+","+V+","+N+","+q+")"}return function(){this.reset=s,this.rotate=n,this.rotateX=a,this.rotateY=c,this.rotateZ=p,this.skew=P,this.skewFromAxis=A,this.shear=m,this.scale=d,this.setTransform=E,this.translate=y,this.transform=g,this.applyToPoint=l,this.applyToX=b,this.applyToY=S,this.applyToZ=_,this.applyToPointArray=L,this.applyToTriplePoints=D,this.applyToPointStringified=z,this.toCSS=O,this.to2dCSS=F,this.clone=u,this.cloneFromProps=f,this.equals=o,this.inversePoints=G,this.inversePoint=I,this.getInverseMatrix=k,this._t=this.transform,this.isIdentity=C,this._identity=!0,this._identityCalculated=!1,this.props=createTypedArray("float32",16),this.reset()}}();function _typeof$3(t){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?_typeof$3=function(r){return typeof r}:_typeof$3=function(r){return r&&typeof Symbol=="function"&&r.constructor===Symbol&&r!==Symbol.prototype?"symbol":typeof r},_typeof$3(t)}var lottie={};function setLocation(t){setLocationHref(t)}function searchAnimations(){animationManager.searchAnimations()}function setSubframeRendering(t){setSubframeEnabled(t)}function setPrefix(t){setIdPrefix(t)}function loadAnimation(t){return animationManager.loadAnimation(t)}function setQuality(t){if(typeof t=="string")switch(t){case"high":setDefaultCurveSegments(200);break;default:case"medium":setDefaultCurveSegments(50);break;case"low":setDefaultCurveSegments(10);break}else!isNaN(t)&&t>1&&setDefaultCurveSegments(t)}function inBrowser(){return typeof navigator<"u"}function installPlugin(t,e){t==="expressions"&&setExpressionsPlugin(e)}function getFactory(t){switch(t){case"propertyFactory":return PropertyFactory;case"shapePropertyFactory":return ShapePropertyFactory;case"matrix":return Matrix;default:return null}}lottie.play=animationManager.play,lottie.pause=animationManager.pause,lottie.setLocationHref=setLocation,lottie.togglePause=animationManager.togglePause,lottie.setSpeed=animationManager.setSpeed,lottie.setDirection=animationManager.setDirection,lottie.stop=animationManager.stop,lottie.searchAnimations=searchAnimations,lottie.registerAnimation=animationManager.registerAnimation,lottie.loadAnimation=loadAnimation,lottie.setSubframeRendering=setSubframeRendering,lottie.resize=animationManager.resize,lottie.goToAndStop=animationManager.goToAndStop,lottie.destroy=animationManager.destroy,lottie.setQuality=setQuality,lottie.inBrowser=inBrowser,lottie.installPlugin=installPlugin,lottie.freeze=animationManager.freeze,lottie.unfreeze=animationManager.unfreeze,lottie.setVolume=animationManager.setVolume,lottie.mute=animationManager.mute,lottie.unmute=animationManager.unmute,lottie.getRegisteredAnimations=animationManager.getRegisteredAnimations,lottie.useWebWorker=setWebWorker,lottie.setIDPrefix=setPrefix,lottie.__getFactory=getFactory,lottie.version="5.10.2";function checkReady(){document.readyState==="complete"&&(clearInterval(readyStateCheckInterval),searchAnimations())}function getQueryVariable(t){for(var e=queryString.split("&"),r=0;r=1?n.push({s:t-1,e:e-1}):(n.push({s:t,e:1}),n.push({s:0,e:e-1}));var a=[],c,p=n.length,m;for(c=0;ci+r)){var P,A;m.s*s<=i?P=0:P=(m.s*s-i)/r,m.e*s>=i+r?A=1:A=(m.e*s-i)/r,a.push([P,A])}return a.length||a.push([0,0]),a},TrimModifier.prototype.releasePathsData=function(t){var e,r=t.length;for(e=0;e1?e=1+i:this.s.v<0?e=0+i:e=this.s.v+i,this.e.v>1?r=1+i:this.e.v<0?r=0+i:r=this.e.v+i,e>r){var s=e;e=r,r=s}e=Math.round(e*1e4)*1e-4,r=Math.round(r*1e4)*1e-4,this.sValue=e,this.eValue=r}else e=this.sValue,r=this.eValue;var n,a,c=this.shapes.length,p,m,P,A,d,E=0;if(r===e)for(a=0;a=0;a-=1)if(g=this.shapes[a],g.shape._mdf){for(C=g.localShapeCollection,C.releaseShapes(),this.m===2&&c>1?(l=this.calculateShapeEdges(e,r,g.totalShapeLength,f,E),f+=g.totalShapeLength):l=[[o,u]],m=l.length,p=0;p=1?y.push({s:g.totalShapeLength*(o-1),e:g.totalShapeLength*(u-1)}):(y.push({s:g.totalShapeLength*o,e:g.totalShapeLength}),y.push({s:0,e:g.totalShapeLength*(u-1)}));var b=this.addShapes(g,y[0]);if(y[0].s!==y[0].e){if(y.length>1){var S=g.shape.paths.shapes[g.shape.paths._length-1];if(S.c){var _=b.pop();this.addPaths(b,C),b=this.addShapes(g,y[1],_)}else this.addPaths(b,C),b=this.addShapes(g,y[1])}this.addPaths(b,C)}}g.shape.paths=C}}},TrimModifier.prototype.addPaths=function(t,e){var r,i=t.length;for(r=0;re.e){r.c=!1;break}else e.s<=m&&e.e>=m+P.addedLength?(this.addSegment(s[n].v[c-1],s[n].o[c-1],s[n].i[c],s[n].v[c],r,A,C),C=!1):(E=bez.getNewSegment(s[n].v[c-1],s[n].v[c],s[n].o[c-1],s[n].i[c],(e.s-m)/P.addedLength,(e.e-m)/P.addedLength,d[c-1]),this.addSegmentFromArray(E,r,A,C),C=!1,r.c=!1),m+=P.addedLength,A+=1;if(s[n].c&&d.length){if(P=d[c-1],m<=e.e){var o=d[c-1].addedLength;e.s<=m&&e.e>=m+o?(this.addSegment(s[n].v[c-1],s[n].o[c-1],s[n].i[0],s[n].v[0],r,A,C),C=!1):(E=bez.getNewSegment(s[n].v[c-1],s[n].v[0],s[n].o[c-1],s[n].i[0],(e.s-m)/o,(e.e-m)/o,d[c-1]),this.addSegmentFromArray(E,r,A,C),C=!1,r.c=!1)}else r.c=!1;m+=P.addedLength,A+=1}if(r._length&&(r.setXYAt(r.v[g][0],r.v[g][1],"i",g),r.setXYAt(r.v[r._length-1][0],r.v[r._length-1][1],"o",r._length-1)),m>e.e)break;n=this.p.keyframes[this.p.keyframes.length-1].t?(P=this.p.getValueAtTime(this.p.keyframes[this.p.keyframes.length-1].t/m,0),A=this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length-1].t-.05)/m,0)):(P=this.p.pv,A=this.p.getValueAtTime((this.p._caching.lastFrame+this.p.offsetTime-.01)/m,this.p.offsetTime));else if(this.px&&this.px.keyframes&&this.py.keyframes&&this.px.getValueAtTime&&this.py.getValueAtTime){P=[],A=[];var d=this.px,E=this.py;d._caching.lastFrame+d.offsetTime<=d.keyframes[0].t?(P[0]=d.getValueAtTime((d.keyframes[0].t+.01)/m,0),P[1]=E.getValueAtTime((E.keyframes[0].t+.01)/m,0),A[0]=d.getValueAtTime(d.keyframes[0].t/m,0),A[1]=E.getValueAtTime(E.keyframes[0].t/m,0)):d._caching.lastFrame+d.offsetTime>=d.keyframes[d.keyframes.length-1].t?(P[0]=d.getValueAtTime(d.keyframes[d.keyframes.length-1].t/m,0),P[1]=E.getValueAtTime(E.keyframes[E.keyframes.length-1].t/m,0),A[0]=d.getValueAtTime((d.keyframes[d.keyframes.length-1].t-.01)/m,0),A[1]=E.getValueAtTime((E.keyframes[E.keyframes.length-1].t-.01)/m,0)):(P=[d.pv,E.pv],A[0]=d.getValueAtTime((d._caching.lastFrame+d.offsetTime-.01)/m,d.offsetTime),A[1]=E.getValueAtTime((E._caching.lastFrame+E.offsetTime-.01)/m,E.offsetTime))}else A=t,P=A;this.v.rotate(-Math.atan2(P[1]-A[1],P[0]-A[0]))}this.data.p&&this.data.p.s?this.data.p.z?this.v.translate(this.px.v,this.py.v,-this.pz.v):this.v.translate(this.px.v,this.py.v,0):this.v.translate(this.p.v[0],this.p.v[1],-this.p.v[2])}this.frameId=this.elem.globalData.frameId}}function i(){if(!this.a.k)this.pre.translate(-this.a.v[0],-this.a.v[1],this.a.v[2]),this.appliedTransformations=1;else return;if(!this.s.effectsSequence.length)this.pre.scale(this.s.v[0],this.s.v[1],this.s.v[2]),this.appliedTransformations=2;else return;if(this.sk)if(!this.sk.effectsSequence.length&&!this.sa.effectsSequence.length)this.pre.skewFromAxis(-this.sk.v,this.sa.v),this.appliedTransformations=3;else return;this.r?this.r.effectsSequence.length||(this.pre.rotate(-this.r.v),this.appliedTransformations=4):!this.rz.effectsSequence.length&&!this.ry.effectsSequence.length&&!this.rx.effectsSequence.length&&!this.or.effectsSequence.length&&(this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]),this.appliedTransformations=4)}function s(){}function n(p){this._addDynamicProperty(p),this.elem.addDynamicProperty(p),this._isDirty=!0}function a(p,m,P){if(this.elem=p,this.frameId=-1,this.propType="transform",this.data=m,this.v=new Matrix,this.pre=new Matrix,this.appliedTransformations=0,this.initDynamicPropertyContainer(P||p),m.p&&m.p.s?(this.px=PropertyFactory.getProp(p,m.p.x,0,0,this),this.py=PropertyFactory.getProp(p,m.p.y,0,0,this),m.p.z&&(this.pz=PropertyFactory.getProp(p,m.p.z,0,0,this))):this.p=PropertyFactory.getProp(p,m.p||{k:[0,0,0]},1,0,this),m.rx){if(this.rx=PropertyFactory.getProp(p,m.rx,0,degToRads,this),this.ry=PropertyFactory.getProp(p,m.ry,0,degToRads,this),this.rz=PropertyFactory.getProp(p,m.rz,0,degToRads,this),m.or.k[0].ti){var A,d=m.or.k.length;for(A=0;A0;)r-=1,this._elements.unshift(e[r]);this.dynamicProperties.length?this.k=!0:this.getValue(!0)},RepeaterModifier.prototype.resetElements=function(t){var e,r=t.length;for(e=0;e0?Math.floor(d):Math.ceil(d),g=this.pMatrix.props,C=this.rMatrix.props,o=this.sMatrix.props;this.pMatrix.reset(),this.rMatrix.reset(),this.sMatrix.reset(),this.tMatrix.reset(),this.matrix.reset();var u=0;if(d>0){for(;uy;)this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!0),u-=1;E&&(this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,-E,!0),u-=E)}i=this.data.m===1?0:this._currentCopies-1,s=this.data.m===1?1:-1,n=this._currentCopies;for(var f,l;n;){if(e=this.elemsData[i].it,r=e[e.length-1].transform.mProps.v.props,l=r.length,e[e.length-1].transform.mProps._mdf=!0,e[e.length-1].transform.op._mdf=!0,e[e.length-1].transform.op.v=this._currentCopies===1?this.so.v:this.so.v+(this.eo.v-this.so.v)*(i/(this._currentCopies-1)),u!==0){for((i!==0&&s===1||i!==this._currentCopies-1&&s===-1)&&this.applyTransforms(this.pMatrix,this.rMatrix,this.sMatrix,this.tr,1,!1),this.matrix.transform(C[0],C[1],C[2],C[3],C[4],C[5],C[6],C[7],C[8],C[9],C[10],C[11],C[12],C[13],C[14],C[15]),this.matrix.transform(o[0],o[1],o[2],o[3],o[4],o[5],o[6],o[7],o[8],o[9],o[10],o[11],o[12],o[13],o[14],o[15]),this.matrix.transform(g[0],g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15]),f=0;f0&&i<1?[e]:[]:[e-i,e+i].filter(function(s){return s>0&&s<1})},PolynomialBezier.prototype.split=function(t){if(t<=0)return[singlePoint(this.points[0]),this];if(t>=1)return[this,singlePoint(this.points[this.points.length-1])];var e=lerpPoint(this.points[0],this.points[1],t),r=lerpPoint(this.points[1],this.points[2],t),i=lerpPoint(this.points[2],this.points[3],t),s=lerpPoint(e,r,t),n=lerpPoint(r,i,t),a=lerpPoint(s,n,t);return[new PolynomialBezier(this.points[0],e,s,a,!0),new PolynomialBezier(a,n,i,this.points[3],!0)]};function extrema(t,e){var r=t.points[0][e],i=t.points[t.points.length-1][e];if(r>i){var s=i;i=r,r=s}for(var n=quadRoots(3*t.a[e],2*t.b[e],t.c[e]),a=0;a0&&n[a]<1){var c=t.point(n[a])[e];ci&&(i=c)}return{min:r,max:i}}PolynomialBezier.prototype.bounds=function(){return{x:extrema(this,0),y:extrema(this,1)}},PolynomialBezier.prototype.boundingBox=function(){var t=this.bounds();return{left:t.x.min,right:t.x.max,top:t.y.min,bottom:t.y.max,width:t.x.max-t.x.min,height:t.y.max-t.y.min,cx:(t.x.max+t.x.min)/2,cy:(t.y.max+t.y.min)/2}};function intersectData(t,e,r){var i=t.boundingBox();return{cx:i.cx,cy:i.cy,width:i.width,height:i.height,bez:t,t:(e+r)/2,t1:e,t2:r}}function splitData(t){var e=t.bez.split(.5);return[intersectData(e[0],t.t1,t.t),intersectData(e[1],t.t,t.t2)]}function boxIntersect(t,e){return Math.abs(t.cx-e.cx)*2=n||t.width<=i&&t.height<=i&&e.width<=i&&e.height<=i){s.push([t.t,e.t]);return}var a=splitData(t),c=splitData(e);intersectsImpl(a[0],c[0],r+1,i,s,n),intersectsImpl(a[0],c[1],r+1,i,s,n),intersectsImpl(a[1],c[0],r+1,i,s,n),intersectsImpl(a[1],c[1],r+1,i,s,n)}}PolynomialBezier.prototype.intersections=function(t,e,r){e===void 0&&(e=2),r===void 0&&(r=7);var i=[];return intersectsImpl(intersectData(this,0,1),intersectData(t,0,1),0,e,i,r),i},PolynomialBezier.shapeSegment=function(t,e){var r=(e+1)%t.length();return new PolynomialBezier(t.v[e],t.o[e],t.i[r],t.v[r],!0)},PolynomialBezier.shapeSegmentInverted=function(t,e){var r=(e+1)%t.length();return new PolynomialBezier(t.v[r],t.i[r],t.o[e],t.v[e],!0)};function crossProduct(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function lineIntersection(t,e,r,i){var s=[t[0],t[1],1],n=[e[0],e[1],1],a=[r[0],r[1],1],c=[i[0],i[1],1],p=crossProduct(crossProduct(s,n),crossProduct(a,c));return floatZero(p[2])?null:[p[0]/p[2],p[1]/p[2]]}function polarOffset(t,e,r){return[t[0]+Math.cos(e)*r,t[1]-Math.sin(e)*r]}function pointDistance(t,e){return Math.hypot(t[0]-e[0],t[1]-e[1])}function pointEqual(t,e){return floatEqual(t[0],e[0])&&floatEqual(t[1],e[1])}function ZigZagModifier(){}extendPrototype([ShapeModifier],ZigZagModifier),ZigZagModifier.prototype.initModifierProperties=function(t,e){this.getValue=this.processKeys,this.amplitude=PropertyFactory.getProp(t,e.s,0,null,this),this.frequency=PropertyFactory.getProp(t,e.r,0,null,this),this.pointsType=PropertyFactory.getProp(t,e.pt,0,null,this),this._isAnimated=this.amplitude.effectsSequence.length!==0||this.frequency.effectsSequence.length!==0||this.pointsType.effectsSequence.length!==0};function setPoint(t,e,r,i,s,n,a){var c=r-Math.PI/2,p=r+Math.PI/2,m=e[0]+Math.cos(r)*i*s,P=e[1]-Math.sin(r)*i*s;t.setTripleAt(m,P,m+Math.cos(c)*n,P-Math.sin(c)*n,m+Math.cos(p)*a,P-Math.sin(p)*a,t.length())}function getPerpendicularVector(t,e){var r=[e[0]-t[0],e[1]-t[1]],i=-Math.PI*.5,s=[Math.cos(i)*r[0]-Math.sin(i)*r[1],Math.sin(i)*r[0]+Math.cos(i)*r[1]];return s}function getProjectingAngle(t,e){var r=e===0?t.length()-1:e-1,i=(e+1)%t.length(),s=t.v[r],n=t.v[i],a=getPerpendicularVector(s,n);return Math.atan2(0,1)-Math.atan2(a[1],a[0])}function zigZagCorner(t,e,r,i,s,n,a){var c=getProjectingAngle(e,r),p=e.v[r%e._length],m=e.v[r===0?e._length-1:r-1],P=e.v[(r+1)%e._length],A=n===2?Math.sqrt(Math.pow(p[0]-m[0],2)+Math.pow(p[1]-m[1],2)):0,d=n===2?Math.sqrt(Math.pow(p[0]-P[0],2)+Math.pow(p[1]-P[1],2)):0;setPoint(t,e.v[r%e._length],c,a,i,d/((s+1)*2),A/((s+1)*2))}function zigZagSegment(t,e,r,i,s,n){for(var a=0;a1&&e.length>1&&(s=getIntersection(t[0],e[e.length-1]),s)?[[t[0].split(s[0])[0]],[e[e.length-1].split(s[1])[1]]]:[r,i]}function pruneIntersections(t){for(var e,r=1;r1&&(e=pruneSegmentIntersection(t[t.length-1],t[0]),t[t.length-1]=e[0],t[0]=e[1]),t}function offsetSegmentSplit(t,e){var r=t.inflectionPoints(),i,s,n,a;if(r.length===0)return[offsetSegment(t,e)];if(r.length===1||floatEqual(r[1],1))return n=t.split(r[0]),i=n[0],s=n[1],[offsetSegment(i,e),offsetSegment(s,e)];n=t.split(r[0]),i=n[0];var c=(r[1]-r[0])/(1-r[0]);return n=n[1].split(c),a=n[0],s=n[1],[offsetSegment(i,e),offsetSegment(a,e),offsetSegment(s,e)]}function OffsetPathModifier(){}extendPrototype([ShapeModifier],OffsetPathModifier),OffsetPathModifier.prototype.initModifierProperties=function(t,e){this.getValue=this.processKeys,this.amount=PropertyFactory.getProp(t,e.a,0,null,this),this.miterLimit=PropertyFactory.getProp(t,e.ml,0,null,this),this.lineJoin=e.lj,this._isAnimated=this.amount.effectsSequence.length!==0},OffsetPathModifier.prototype.processPath=function(t,e,r,i){var s=shapePool.newElement();s.c=t.c;var n=t.length();t.c||(n-=1);var a,c,p,m=[];for(a=0;a=0;a-=1)p=PolynomialBezier.shapeSegmentInverted(t,a),m.push(offsetSegmentSplit(p,e));m=pruneIntersections(m);var P=null,A=null;for(a=0;a0&&(G=!1),G){var z=createTag("style");z.setAttribute("f-forigin",S[_].fOrigin),z.setAttribute("f-origin",S[_].origin),z.setAttribute("f-family",S[_].fFamily),z.type="text/css",z.innerText="@font-face {font-family: "+S[_].fFamily+"; font-style: normal; src: url('"+S[_].fPath+"');}",b.appendChild(z)}}else if(S[_].fOrigin==="g"||S[_].origin===1){for(D=document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]'),L=0;Le?this.isInRange!==!0&&(this.globalData._mdf=!0,this._mdf=!0,this.isInRange=!0,this.show()):this.isInRange!==!1&&(this.globalData._mdf=!0,this.isInRange=!1,this.hide())},renderRenderable:function(){var e,r=this.renderableComponents.length;for(e=0;e.1)&&this.audio.seek(this._currentTime/this.globalData.frameRate):(this.audio.play(),this.audio.seek(this._currentTime/this.globalData.frameRate),this._isPlaying=!0))},AudioElement.prototype.show=function(){},AudioElement.prototype.hide=function(){this.audio.pause(),this._isPlaying=!1},AudioElement.prototype.pause=function(){this.audio.pause(),this._isPlaying=!1,this._canPlay=!1},AudioElement.prototype.resume=function(){this._canPlay=!0},AudioElement.prototype.setRate=function(t){this.audio.rate(t)},AudioElement.prototype.volume=function(t){this._volumeMultiplier=t,this._previousVolume=t*this._volume,this.audio.volume(this._previousVolume)},AudioElement.prototype.getBaseElement=function(){return null},AudioElement.prototype.destroy=function(){},AudioElement.prototype.sourceRectAtTime=function(){},AudioElement.prototype.initExpressions=function(){};function BaseRenderer(){}BaseRenderer.prototype.checkLayers=function(t){var e,r=this.layers.length,i;for(this.completeLayers=!0,e=r-1;e>=0;e-=1)this.elements[e]||(i=this.layers[e],i.ip-i.st<=t-this.layers[e].st&&i.op-i.st>t-this.layers[e].st&&this.buildItem(e)),this.completeLayers=this.elements[e]?this.completeLayers:!1;this.checkPendingElements()},BaseRenderer.prototype.createItem=function(t){switch(t.ty){case 2:return this.createImage(t);case 0:return this.createComp(t);case 1:return this.createSolid(t);case 3:return this.createNull(t);case 4:return this.createShape(t);case 5:return this.createText(t);case 6:return this.createAudio(t);case 13:return this.createCamera(t);case 15:return this.createFootage(t);default:return this.createNull(t)}},BaseRenderer.prototype.createCamera=function(){throw new Error("You're using a 3d camera. Try the html renderer.")},BaseRenderer.prototype.createAudio=function(t){return new AudioElement(t,this.globalData,this)},BaseRenderer.prototype.createFootage=function(t){return new FootageElement(t,this.globalData,this)},BaseRenderer.prototype.buildAllItems=function(){var t,e=this.layers.length;for(t=0;t0&&(this.maskElement.setAttribute("id",d),this.element.maskedElement.setAttribute(u,"url("+getLocationHref()+"#"+d+")"),i.appendChild(this.maskElement)),this.viewData.length&&this.element.addRenderableComponent(this)}MaskElement.prototype.getMaskProperty=function(t){return this.viewData[t].prop},MaskElement.prototype.renderFrame=function(t){var e=this.element.finalTransform.mat,r,i=this.masksProperties.length;for(r=0;r1&&(i+=" C"+e.o[s-1][0]+","+e.o[s-1][1]+" "+e.i[0][0]+","+e.i[0][1]+" "+e.v[0][0]+","+e.v[0][1]),r.lastPath!==i){var a="";r.elem&&(e.c&&(a=t.inv?this.solidPath+i:i),r.elem.setAttribute("d",a)),r.lastPath=i}},MaskElement.prototype.destroy=function(){this.element=null,this.globalData=null,this.maskElement=null,this.data=null,this.masksProperties=null};var filtersFactory=function(){var t={};t.createFilter=e,t.createAlphaToLuminanceFilter=r;function e(i,s){var n=createNS("filter");return n.setAttribute("id",i),s!==!0&&(n.setAttribute("filterUnits","objectBoundingBox"),n.setAttribute("x","0%"),n.setAttribute("y","0%"),n.setAttribute("width","100%"),n.setAttribute("height","100%")),n}function r(){var i=createNS("feColorMatrix");return i.setAttribute("type","matrix"),i.setAttribute("color-interpolation-filters","sRGB"),i.setAttribute("values","0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1"),i}return t}(),featureSupport=function(){var t={maskType:!0,svgLumaHidden:!0,offscreenCanvas:typeof OffscreenCanvas<"u"};return(/MSIE 10/i.test(navigator.userAgent)||/MSIE 9/i.test(navigator.userAgent)||/rv:11.0/i.test(navigator.userAgent)||/Edge\/\d./i.test(navigator.userAgent))&&(t.maskType=!1),/firefox/i.test(navigator.userAgent)&&(t.svgLumaHidden=!1),t}(),registeredEffects={},idPrefix="filter_result_";function SVGEffects(t){var e,r="SourceGraphic",i=t.data.ef?t.data.ef.length:0,s=createElementID(),n=filtersFactory.createFilter(s,!0),a=0;this.filters=[];var c;for(e=0;e=0&&(i=this.shapeModifiers[e].processShapes(this._isFirstFrame),!i);e-=1);}},searchProcessedElement:function(e){for(var r=this.processedElements,i=0,s=r.length;i.01)return!1;r+=1}return!0},GradientProperty.prototype.checkCollapsable=function(){if(this.o.length/2!==this.c.length/4)return!1;if(this.data.k.k[0].s)for(var t=0,e=this.data.k.k.length;t0;)o=d.transformers[G].mProps._mdf||o,I-=1,G-=1;if(o)for(I=b-d.styles[f].lvl,G=d.transformers.length-1;I>0;)k=d.transformers[G].mProps.v.props,_.transform(k[0],k[1],k[2],k[3],k[4],k[5],k[6],k[7],k[8],k[9],k[10],k[11],k[12],k[13],k[14],k[15]),I-=1,G-=1}else _=t;if(S=d.sh.paths,g=S._length,o){for(C="",y=0;y=1?z=.99:z<=-1&&(z=-.99);var O=D*z,B=Math.cos(L+d.a.v)*O+C[0],F=Math.sin(L+d.a.v)*O+C[1];y.setAttribute("fx",B),y.setAttribute("fy",F),g&&!d.g._collapsable&&(d.of.setAttribute("fx",B),d.of.setAttribute("fy",F))}}}function P(A,d,E){var y=d.style,g=d.d;g&&(g._mdf||E)&&g.dashStr&&(y.pElem.setAttribute("stroke-dasharray",g.dashStr),y.pElem.setAttribute("stroke-dashoffset",g.dashoffset[0])),d.c&&(d.c._mdf||E)&&y.pElem.setAttribute("stroke","rgb("+bmFloor(d.c.v[0])+","+bmFloor(d.c.v[1])+","+bmFloor(d.c.v[2])+")"),(d.o._mdf||E)&&y.pElem.setAttribute("stroke-opacity",d.o.v),(d.w._mdf||E)&&(y.pElem.setAttribute("stroke-width",d.w.v),y.msElem&&y.msElem.setAttribute("stroke-width",d.w.v))}return r}();function SVGShapeElement(t,e,r){this.shapes=[],this.shapesData=t.shapes,this.stylesList=[],this.shapeModifiers=[],this.itemsData=[],this.processedElements=[],this.animatedContents=[],this.initElement(t,e,r),this.prevViewData=[]}extendPrototype([BaseElement,TransformElement,SVGBaseElement,IShapeElement,HierarchyElement,FrameElement,RenderableDOMElement],SVGShapeElement),SVGShapeElement.prototype.initSecondaryElement=function(){},SVGShapeElement.prototype.identityMatrix=new Matrix,SVGShapeElement.prototype.buildExpressionInterface=function(){},SVGShapeElement.prototype.createContent=function(){this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,this.layerElement,0,[],!0),this.filterUniqueShapes()},SVGShapeElement.prototype.filterUniqueShapes=function(){var t,e=this.shapes.length,r,i,s=this.stylesList.length,n,a=[],c=!1;for(i=0;i1&&c&&this.setShapesAsAnimated(a)}},SVGShapeElement.prototype.setShapesAsAnimated=function(t){var e,r=t.length;for(e=0;e=0;p-=1){if(C=this.searchProcessedElement(t[p]),C?e[p]=r[C-1]:t[p]._render=a,t[p].ty==="fl"||t[p].ty==="st"||t[p].ty==="gf"||t[p].ty==="gs"||t[p].ty==="no")C?e[p].style.closed=!1:e[p]=this.createStyleElement(t[p],s),t[p]._render&&e[p].style.pElem.parentNode!==i&&i.appendChild(e[p].style.pElem),d.push(e[p].style);else if(t[p].ty==="gr"){if(!C)e[p]=this.createGroupElement(t[p]);else for(A=e[p].it.length,P=0;P1,this.kf&&this.addEffect(this.getKeyframeValue.bind(this)),this.kf},TextProperty.prototype.addEffect=function(t){this.effectsSequence.push(t),this.elem.addDynamicProperty(this)},TextProperty.prototype.getValue=function(t){if(!((this.elem.globalData.frameId===this.frameId||!this.effectsSequence.length)&&!t)){this.currentData.t=this.data.d.k[this.keysIndex].s.t;var e=this.currentData,r=this.keysIndex;if(this.lock){this.setCurrentData(this.currentData);return}this.lock=!0,this._mdf=!1;var i,s=this.effectsSequence.length,n=t||this.data.d.k[this.keysIndex].s;for(i=0;ie);)r+=1;return this.keysIndex!==r&&(this.keysIndex=r),this.data.d.k[this.keysIndex].s},TextProperty.prototype.buildFinalText=function(t){for(var e=[],r=0,i=t.length,s,n,a=!1;r=55296&&s<=56319?(n=t.charCodeAt(r+1),n>=56320&&n<=57343?(a||FontManager.isModifier(s,n)?(e[e.length-1]+=t.substr(r,2),a=!1):e.push(t.substr(r,2)),r+=1):e.push(t.charAt(r))):s>56319?(n=t.charCodeAt(r+1),FontManager.isZeroWidthJoiner(s,n)?(a=!0,e[e.length-1]+=t.substr(r,2),r+=1):e.push(t.charAt(r))):FontManager.isZeroWidthJoiner(s)?(e[e.length-1]+=t.charAt(r),a=!0):e.push(t.charAt(r)),r+=1;return e},TextProperty.prototype.completeTextData=function(t){t.__complete=!0;var e=this.elem.globalData.fontManager,r=this.data,i=[],s,n,a,c=0,p,m=r.m.g,P=0,A=0,d=0,E=[],y=0,g=0,C,o,u=e.getFontByName(t.f),f,l=0,b=getFontProperties(u);t.fWeight=b.weight,t.fStyle=b.style,t.finalSize=t.s,t.finalText=this.buildFinalText(t.t),n=t.finalText.length,t.finalLineHeight=t.lh;var S=t.tr/1e3*t.finalSize,_;if(t.sz)for(var k=!0,I=t.sz[0],G=t.sz[1],D,L;k;){L=this.buildFinalText(t.t),D=0,y=0,n=L.length,S=t.tr/1e3*t.finalSize;var z=-1;for(s=0;sI&&L[s]!==" "?(z===-1?n+=1:s=z,D+=t.finalLineHeight||t.finalSize*1.2,L.splice(s,z===s?1:0,"\r"),z=-1,y=0):(y+=l,y+=S);D+=u.ascent*t.finalSize/100,this.canResize&&t.finalSize>this.minimumFontSize&&Gg?y:g,y=-2*S,p="",a=!0,d+=1):p=B,e.chars?(f=e.getCharData(B,u.fStyle,e.getFontByName(t.f).fFamily),l=a?0:f.w*t.finalSize/100):l=e.measureText(p,t.f,t.finalSize),B===" "?O+=l+S:(y+=l+S+O,O=0),i.push({l,an:l,add:P,n:a,anIndexes:[],val:p,line:d,animatorJustifyOffset:0}),m==2){if(P+=l,p===""||p===" "||s===n-1){for((p===""||p===" ")&&(P-=l);A<=s;)i[A].an=P,i[A].ind=c,i[A].extra=l,A+=1;c+=1,P=0}}else if(m==3){if(P+=l,p===""||s===n-1){for(p===""&&(P-=l);A<=s;)i[A].an=P,i[A].ind=c,i[A].extra=l,A+=1;P=0,c+=1}}else i[c].ind=c,i[c].extra=0,c+=1;if(t.l=i,g=y>g?y:g,E.push(y),t.sz)t.boxWidth=t.sz[0],t.justifyOffset=0;else switch(t.boxWidth=g,t.j){case 1:t.justifyOffset=-t.boxWidth;break;case 2:t.justifyOffset=-t.boxWidth/2;break;default:t.justifyOffset=0}t.lineWidths=E;var F=r.a,T,x;o=F.length;var M,R,V=[];for(C=0;C0?c=this.ne.v/100:p=-this.ne.v/100,this.xe.v>0?m=1-this.xe.v/100:P=1+this.xe.v/100;var A=BezierFactory.getBezierEasing(c,p,m,P).get,d=0,E=this.finalS,y=this.finalE,g=this.data.sh;if(g===2)y===E?d=a>=y?1:0:d=t(0,e(.5/(y-E)+(a-E)/(y-E),1)),d=A(d);else if(g===3)y===E?d=a>=y?0:1:d=1-t(0,e(.5/(y-E)+(a-E)/(y-E),1)),d=A(d);else if(g===4)y===E?d=0:(d=t(0,e(.5/(y-E)+(a-E)/(y-E),1)),d<.5?d*=2:d=1-2*(d-.5)),d=A(d);else if(g===5){if(y===E)d=0;else{var C=y-E;a=e(t(0,a+.5-E),y-E);var o=-C/2+a,u=C/2;d=Math.sqrt(1-o*o/(u*u))}d=A(d)}else g===6?(y===E?d=0:(a=e(t(0,a+.5-E),y-E),d=(1+Math.cos(Math.PI+Math.PI*2*a/(y-E)))/2),d=A(d)):(a>=r(E)&&(a-E<0?d=t(0,e(e(y,1)-(E-a),1)):d=t(0,e(y-a,1))),d=A(d));if(this.sm.v!==100){var f=this.sm.v*.01;f===0&&(f=1e-8);var l=.5-f*.5;d1&&(d=1))}return d*this.a.v},getValue:function(a){this.iterateDynamicProperties(),this._mdf=a||this._mdf,this._currentTextLength=this.elem.textProperty.currentData.l.length||0,a&&this.data.r===2&&(this.e.v=this._currentTextLength);var c=this.data.r===2?1:100/this.data.totalChars,p=this.o.v/c,m=this.s.v/c+p,P=this.e.v/c+p;if(m>P){var A=m;m=P,P=A}this.finalS=m,this.finalE=P}},extendPrototype([DynamicPropertyContainer],i);function s(n,a,c){return new i(n,a)}return{getTextSelectorProp:s}}();function TextAnimatorDataProperty(t,e,r){var i={propType:!1},s=PropertyFactory.getProp,n=e.a;this.a={r:n.r?s(t,n.r,0,degToRads,r):i,rx:n.rx?s(t,n.rx,0,degToRads,r):i,ry:n.ry?s(t,n.ry,0,degToRads,r):i,sk:n.sk?s(t,n.sk,0,degToRads,r):i,sa:n.sa?s(t,n.sa,0,degToRads,r):i,s:n.s?s(t,n.s,1,.01,r):i,a:n.a?s(t,n.a,1,0,r):i,o:n.o?s(t,n.o,0,.01,r):i,p:n.p?s(t,n.p,1,0,r):i,sw:n.sw?s(t,n.sw,0,0,r):i,sc:n.sc?s(t,n.sc,1,0,r):i,fc:n.fc?s(t,n.fc,1,0,r):i,fh:n.fh?s(t,n.fh,0,0,r):i,fs:n.fs?s(t,n.fs,0,.01,r):i,fb:n.fb?s(t,n.fb,0,.01,r):i,t:n.t?s(t,n.t,0,0,r):i},this.s=TextSelectorProp.getTextSelectorProp(t,e.s,r),this.s.t=e.s.t}function TextAnimatorProperty(t,e,r){this._isFirstFrame=!0,this._hasMaskedPath=!1,this._frameId=-1,this._textData=t,this._renderType=e,this._elem=r,this._animatorsData=createSizedArray(this._textData.a.length),this._pathData={},this._moreOptions={alignment:{}},this.renderedLetters=[],this.lettersChangedFlag=!1,this.initDynamicPropertyContainer(r)}TextAnimatorProperty.prototype.searchProperties=function(){var t,e=this._textData.a.length,r,i=PropertyFactory.getProp;for(t=0;t=y+rt||!b?(I=(y+rt-C)/g.partialLength,j=l.point[0]+(g.point[0]-l.point[0])*I,X=l.point[1]+(g.point[1]-l.point[1])*I,n.translate(-r[0]*d[P].an*.005,-(r[1]*O)*.01),o=!1):b&&(C+=g.partialLength,u+=1,u>=b.length&&(u=0,f+=1,S[f]?b=S[f].points:D.v.c?(u=0,f=0,b=S[f].points):(C-=g.partialLength,b=null)),b&&(l=g,g=b[u],_=g.partialLength));q=d[P].an/2-d[P].add,n.translate(-q,0,0)}else q=d[P].an/2-d[P].add,n.translate(-q,0,0),n.translate(-r[0]*d[P].an*.005,-r[1]*O*.01,0);for(x=0;xt?this.textSpans[t].span:createNS(c?"g":"text"),f<=t){if(p.setAttribute("stroke-linecap","butt"),p.setAttribute("stroke-linejoin","round"),p.setAttribute("stroke-miterlimit","4"),this.textSpans[t].span=p,c){var b=createNS("g");p.appendChild(b),this.textSpans[t].childSpan=b}this.textSpans[t].span=p,this.layerElement.appendChild(p)}p.style.display="inherit"}if(m.reset(),A&&(a[t].n&&(d=-g,E+=r.yOffset,E+=y?1:0,y=!1),this.applyTextPropertiesToMatrix(r,m,a[t].line,d,E),d+=a[t].l||0,d+=g),c){l=this.globalData.fontManager.getCharData(r.finalText[t],i.fStyle,this.globalData.fontManager.getFontByName(r.f).fFamily);var S;if(l.t===1)S=new SVGCompElement(l.data,this.globalData,this);else{var _=emptyShapeData;l.data&&l.data.shapes&&(_=this.buildShapeData(l.data,r.finalSize)),S=new SVGShapeElement(_,this.globalData,this)}if(this.textSpans[t].glyph){var k=this.textSpans[t].glyph;this.textSpans[t].childSpan.removeChild(k.layerElement),k.destroy()}this.textSpans[t].glyph=S,S._debug=!0,S.prepareFrame(0),S.renderFrame(),this.textSpans[t].childSpan.appendChild(S.layerElement),l.t===1&&this.textSpans[t].childSpan.setAttribute("transform","scale("+r.finalSize/100+","+r.finalSize/100+")")}else A&&p.setAttribute("transform","translate("+m.props[12]+","+m.props[13]+")"),p.textContent=a[t].val,p.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve")}A&&p&&p.setAttribute("d",P)}for(;t=0;e-=1)(this.completeLayers||this.elements[e])&&this.elements[e].prepareFrame(t-this.layers[e].st);if(this.globalData._mdf)for(e=0;e=0;r-=1)(this.completeLayers||this.elements[r])&&(this.elements[r].prepareFrame(this.renderedFrame-this.layers[r].st),this.elements[r]._mdf&&(this._mdf=!0))}},ICompElement.prototype.renderInnerContent=function(){var t,e=this.layers.length;for(t=0;t=0;i-=1)a=e.transforms[i].transform.mProps.v.props,e.finalTransform.transform(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15])}e._mdf=n},processSequences:function(e){var r,i=this.sequenceList.length;for(r=0;r=1){this.buffers=[];var e=this.globalData.canvasContext,r=assetLoader.createCanvas(e.canvas.width,e.canvas.height);this.buffers.push(r);var i=assetLoader.createCanvas(e.canvas.width,e.canvas.height);this.buffers.push(i),this.data.tt>=3&&!document._isProxy&&assetLoader.loadLumaCanvas()}this.canvasContext=this.globalData.canvasContext,this.transformCanvas=this.globalData.transformCanvas,this.renderableEffectsManager=new CVEffects},createContent:function(){},setBlendMode:function(){var e=this.globalData;if(e.blendMode!==this.data.bm){e.blendMode=this.data.bm;var r=getBlendMode(this.data.bm);e.canvasContext.globalCompositeOperation=r}},createRenderableComponents:function(){this.maskManager=new CVMaskElement(this.data,this)},hideElement:function(){!this.hidden&&(!this.isInRange||this.isTransparent)&&(this.hidden=!0)},showElement:function(){this.isInRange&&!this.isTransparent&&(this.hidden=!1,this._isFirstFrame=!0,this.maskManager._isFirstFrame=!0)},clearCanvas:function(e){e.clearRect(this.transformCanvas.tx,this.transformCanvas.ty,this.transformCanvas.w*this.transformCanvas.sx,this.transformCanvas.h*this.transformCanvas.sy)},prepareLayer:function(){if(this.data.tt>=1){var e=this.buffers[0],r=e.getContext("2d");this.clearCanvas(r),r.drawImage(this.canvasContext.canvas,0,0),this.currentTransform=this.canvasContext.getTransform(),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform)}},exitLayer:function(){if(this.data.tt>=1){var e=this.buffers[1],r=e.getContext("2d");this.clearCanvas(r),r.drawImage(this.canvasContext.canvas,0,0),this.canvasContext.setTransform(1,0,0,1,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.setTransform(this.currentTransform);var i=this.comp.getElementById("tp"in this.data?this.data.tp:this.data.ind-1);if(i.renderFrame(!0),this.canvasContext.setTransform(1,0,0,1,0,0),this.data.tt>=3&&!document._isProxy){var s=assetLoader.getLumaCanvas(this.canvasContext.canvas),n=s.getContext("2d");n.drawImage(this.canvasContext.canvas,0,0),this.clearCanvas(this.canvasContext),this.canvasContext.drawImage(s,0,0)}this.canvasContext.globalCompositeOperation=operationsMap[this.data.tt],this.canvasContext.drawImage(e,0,0),this.canvasContext.globalCompositeOperation="destination-over",this.canvasContext.drawImage(this.buffers[0],0,0),this.canvasContext.setTransform(this.currentTransform),this.canvasContext.globalCompositeOperation="source-over"}},renderFrame:function(e){if(!(this.hidden||this.data.hd)&&!(this.data.td===1&&!e)){this.renderTransform(),this.renderRenderable(),this.setBlendMode();var r=this.data.ty===0;this.prepareLayer(),this.globalData.renderer.save(r),this.globalData.renderer.ctxTransform(this.finalTransform.mat.props),this.globalData.renderer.ctxOpacity(this.finalTransform.mProp.o.v),this.renderInnerContent(),this.globalData.renderer.restore(r),this.exitLayer(),this.maskManager.hasMasks&&this.globalData.renderer.restore(!0),this._isFirstFrame&&(this._isFirstFrame=!1)}},destroy:function(){this.canvasContext=null,this.data=null,this.globalData=null,this.maskManager.destroy()},mHelper:new Matrix},CVBaseElement.prototype.hide=CVBaseElement.prototype.hideElement,CVBaseElement.prototype.show=CVBaseElement.prototype.showElement;function CVShapeData(t,e,r,i){this.styledShapes=[],this.tr=[0,0,0,0,0,0];var s=4;e.ty==="rc"?s=5:e.ty==="el"?s=6:e.ty==="sr"&&(s=7),this.sh=ShapePropertyFactory.getShapeProp(t,e,s,t);var n,a=r.length,c;for(n=0;n=0;n-=1){if(A=this.searchProcessedElement(t[n]),A?e[n]=r[A-1]:t[n]._shouldRender=i,t[n].ty==="fl"||t[n].ty==="st"||t[n].ty==="gf"||t[n].ty==="gs")A?e[n].style.closed=!1:e[n]=this.createStyleElement(t[n],y),m.push(e[n].style);else if(t[n].ty==="gr"){if(!A)e[n]=this.createGroupElement(t[n]);else for(p=e[n].it.length,c=0;c=0;s-=1)e[s].ty==="tr"?(a=r[s].transform,this.renderShapeTransform(t,a)):e[s].ty==="sh"||e[s].ty==="el"||e[s].ty==="rc"||e[s].ty==="sr"?this.renderPath(e[s],r[s]):e[s].ty==="fl"?this.renderFill(e[s],r[s],a):e[s].ty==="st"?this.renderStroke(e[s],r[s],a):e[s].ty==="gf"||e[s].ty==="gs"?this.renderGradientFill(e[s],r[s],a):e[s].ty==="gr"?this.renderShape(a,e[s].it,r[s].it):e[s].ty;i&&this.drawLayer()},CVShapeElement.prototype.renderStyledShape=function(t,e){if(this._isFirstFrame||e._mdf||t.transforms._mdf){var r=t.trNodes,i=e.paths,s,n,a,c=i._length;r.length=0;var p=t.transforms.finalTransform;for(a=0;a=1?P=.99:P<=-1&&(P=-.99);var A=p*P,d=Math.cos(m+e.a.v)*A+a[0],E=Math.sin(m+e.a.v)*A+a[1];s=n.createRadialGradient(d,E,0,a[0],a[1],p)}var y,g=t.g.p,C=e.g.c,o=1;for(y=0;yn&&p==="xMidYMid slice"||ss&&c==="meet"||ns&&c==="slice")?this.transformCanvas.tx=(r-this.transformCanvas.w*(i/this.transformCanvas.h))/2*this.renderConfig.dpr:m==="xMax"&&(ns&&c==="slice")?this.transformCanvas.tx=(r-this.transformCanvas.w*(i/this.transformCanvas.h))*this.renderConfig.dpr:this.transformCanvas.tx=0,P==="YMid"&&(n>s&&c==="meet"||ns&&c==="meet"||n=0;t-=1)this.elements[t]&&this.elements[t].destroy();this.elements.length=0,this.globalData.canvasContext=null,this.animationItem.container=null,this.destroyed=!0},CanvasRendererBase.prototype.renderFrame=function(t,e){if(!(this.renderedFrame===t&&this.renderConfig.clearCanvas===!0&&!e||this.destroyed||t===-1)){this.renderedFrame=t,this.globalData.frameNum=t-this.animationItem._isFirstFrame,this.globalData.frameId+=1,this.globalData._mdf=!this.renderConfig.clearCanvas||e,this.globalData.projectInterface.currentFrame=t;var r,i=this.layers.length;for(this.completeLayers||this.checkLayers(t),r=0;r=0;r-=1)(this.completeLayers||this.elements[r])&&this.elements[r].renderFrame();this.renderConfig.clearCanvas!==!0&&this.restore()}}},CanvasRendererBase.prototype.buildItem=function(t){var e=this.elements;if(!(e[t]||this.layers[t].ty===99)){var r=this.createItem(this.layers[t],this,this.globalData);e[t]=r,r.initExpressions()}},CanvasRendererBase.prototype.checkPendingElements=function(){for(;this.pendingElements.length;){var t=this.pendingElements.pop();t.checkParenting()}},CanvasRendererBase.prototype.hide=function(){this.animationItem.container.style.display="none"},CanvasRendererBase.prototype.show=function(){this.animationItem.container.style.display="block"};function CVCompElement(t,e,r){this.completeLayers=!1,this.layers=t.layers,this.pendingElements=[],this.elements=createSizedArray(this.layers.length),this.initElement(t,e,r),this.tm=t.tm?PropertyFactory.getProp(this,t.tm,0,e.frameRate,this):{_placeholder:!0}}extendPrototype([CanvasRendererBase,ICompElement,CVBaseElement],CVCompElement),CVCompElement.prototype.renderInnerContent=function(){var t=this.canvasContext;t.beginPath(),t.moveTo(0,0),t.lineTo(this.data.w,0),t.lineTo(this.data.w,this.data.h),t.lineTo(0,this.data.h),t.lineTo(0,0),t.clip();var e,r=this.layers.length;for(e=r-1;e>=0;e-=1)(this.completeLayers||this.elements[e])&&this.elements[e].renderFrame()},CVCompElement.prototype.destroy=function(){var t,e=this.layers.length;for(t=e-1;t>=0;t-=1)this.elements[t]&&this.elements[t].destroy();this.layers=null,this.elements=null},CVCompElement.prototype.createComp=function(t){return new CVCompElement(t,this.globalData,this)};function CanvasRenderer(t,e){this.animationItem=t,this.renderConfig={clearCanvas:e&&e.clearCanvas!==void 0?e.clearCanvas:!0,context:e&&e.context||null,progressiveLoad:e&&e.progressiveLoad||!1,preserveAspectRatio:e&&e.preserveAspectRatio||"xMidYMid meet",imagePreserveAspectRatio:e&&e.imagePreserveAspectRatio||"xMidYMid slice",contentVisibility:e&&e.contentVisibility||"visible",className:e&&e.className||"",id:e&&e.id||"",runExpressions:!e||e.runExpressions===void 0||e.runExpressions},this.renderConfig.dpr=e&&e.dpr||1,this.animationItem.wrapper&&(this.renderConfig.dpr=e&&e.dpr||window.devicePixelRatio||1),this.renderedFrame=-1,this.globalData={frameNum:-1,_mdf:!1,renderConfig:this.renderConfig,currentGlobalAlpha:-1},this.contextData=new CVContextData,this.elements=[],this.pendingElements=[],this.transformMat=new Matrix,this.completeLayers=!1,this.rendererType="canvas"}extendPrototype([CanvasRendererBase],CanvasRenderer),CanvasRenderer.prototype.createComp=function(t){return new CVCompElement(t,this.globalData,this)};function HBaseElement(){}HBaseElement.prototype={checkBlendMode:function(){},initRendererElement:function(){this.baseElement=createTag(this.data.tg||"div"),this.data.hasMask?(this.svgElement=createNS("svg"),this.layerElement=createNS("g"),this.maskedElement=this.layerElement,this.svgElement.appendChild(this.layerElement),this.baseElement.appendChild(this.svgElement)):this.layerElement=this.baseElement,styleDiv(this.baseElement)},createContainerElements:function(){this.renderableEffectsManager=new CVEffects,this.transformedElement=this.baseElement,this.maskedElement=this.layerElement,this.data.ln&&this.layerElement.setAttribute("id",this.data.ln),this.data.cl&&this.layerElement.setAttribute("class",this.data.cl),this.data.bm!==0&&this.setBlendMode()},renderElement:function(){var e=this.transformedElement?this.transformedElement.style:{};if(this.finalTransform._matMdf){var r=this.finalTransform.mat.toCSS();e.transform=r,e.webkitTransform=r}this.finalTransform._opMdf&&(e.opacity=this.finalTransform.mProp.o.v)},renderFrame:function(){this.data.hd||this.hidden||(this.renderTransform(),this.renderRenderable(),this.renderElement(),this.renderInnerContent(),this._isFirstFrame&&(this._isFirstFrame=!1))},destroy:function(){this.layerElement=null,this.transformedElement=null,this.matteElement&&(this.matteElement=null),this.maskManager&&(this.maskManager.destroy(),this.maskManager=null)},createRenderableComponents:function(){this.maskManager=new MaskElement(this.data,this,this.globalData)},addEffects:function(){},setMatte:function(){}},HBaseElement.prototype.getBaseElement=SVGBaseElement.prototype.getBaseElement,HBaseElement.prototype.destroyBaseElement=HBaseElement.prototype.destroy,HBaseElement.prototype.buildElementParenting=BaseRenderer.prototype.buildElementParenting;function HSolidElement(t,e,r){this.initElement(t,e,r)}extendPrototype([BaseElement,TransformElement,HBaseElement,HierarchyElement,FrameElement,RenderableDOMElement],HSolidElement),HSolidElement.prototype.createContent=function(){var t;this.data.hasMask?(t=createNS("rect"),t.setAttribute("width",this.data.sw),t.setAttribute("height",this.data.sh),t.setAttribute("fill",this.data.sc),this.svgElement.setAttribute("width",this.data.sw),this.svgElement.setAttribute("height",this.data.sh)):(t=createTag("div"),t.style.width=this.data.sw+"px",t.style.height=this.data.sh+"px",t.style.backgroundColor=this.data.sc),this.layerElement.appendChild(t)};function HShapeElement(t,e,r){this.shapes=[],this.shapesData=t.shapes,this.stylesList=[],this.shapeModifiers=[],this.itemsData=[],this.processedElements=[],this.animatedContents=[],this.shapesContainer=createNS("g"),this.initElement(t,e,r),this.prevViewData=[],this.currentBBox={x:999999,y:-999999,h:0,w:0}}extendPrototype([BaseElement,TransformElement,HSolidElement,SVGShapeElement,HBaseElement,HierarchyElement,FrameElement,RenderableElement],HShapeElement),HShapeElement.prototype._renderShapeFrame=HShapeElement.prototype.renderInnerContent,HShapeElement.prototype.createContent=function(){var t;if(this.baseElement.style.fontSize=0,this.data.hasMask)this.layerElement.appendChild(this.shapesContainer),t=this.svgElement;else{t=createNS("svg");var e=this.comp.data?this.comp.data:this.globalData.compSize;t.setAttribute("width",e.w),t.setAttribute("height",e.h),t.appendChild(this.shapesContainer),this.layerElement.appendChild(t)}this.searchShapes(this.shapesData,this.itemsData,this.prevViewData,this.shapesContainer,0,[],!0),this.filterUniqueShapes(),this.shapeCont=t},HShapeElement.prototype.getTransformedPoint=function(t,e){var r,i=t.length;for(r=0;r0&&p<1&&s[d].push(this.calculateF(p,t,e,r,i,d))):(m=a*a-4*c*n,m>=0&&(P=(-a+bmSqrt(m))/(2*n),P>0&&P<1&&s[d].push(this.calculateF(P,t,e,r,i,d)),A=(-a-bmSqrt(m))/(2*n),A>0&&A<1&&s[d].push(this.calculateF(A,t,e,r,i,d)))));this.shapeBoundingBox.left=bmMin.apply(null,s[0]),this.shapeBoundingBox.top=bmMin.apply(null,s[1]),this.shapeBoundingBox.right=bmMax.apply(null,s[0]),this.shapeBoundingBox.bottom=bmMax.apply(null,s[1])},HShapeElement.prototype.calculateF=function(t,e,r,i,s,n){return bmPow(1-t,3)*e[n]+3*bmPow(1-t,2)*t*r[n]+3*(1-t)*bmPow(t,2)*i[n]+bmPow(t,3)*s[n]},HShapeElement.prototype.calculateBoundingBox=function(t,e){var r,i=t.length;for(r=0;rr&&(r=s)}r*=t.mult}else r=t.v*t.mult;e.x-=r,e.xMax+=r,e.y-=r,e.yMax+=r},HShapeElement.prototype.currentBoxContains=function(t){return this.currentBBox.x<=t.x&&this.currentBBox.y<=t.y&&this.currentBBox.width+this.currentBBox.x>=t.x+t.width&&this.currentBBox.height+this.currentBBox.y>=t.y+t.height},HShapeElement.prototype.renderInnerContent=function(){if(this._renderShapeFrame(),!this.hidden&&(this._isFirstFrame||this._mdf)){var t=this.tempBoundingBox,e=999999;if(t.x=e,t.xMax=-e,t.y=e,t.yMax=-e,this.calculateBoundingBox(this.itemsData,t),t.width=t.xMax=0;e-=1){var i=this.hierarchy[e].finalTransform.mProp;this.mat.translate(-i.p.v[0],-i.p.v[1],i.p.v[2]),this.mat.rotateX(-i.or.v[0]).rotateY(-i.or.v[1]).rotateZ(i.or.v[2]),this.mat.rotateX(-i.rx.v).rotateY(-i.ry.v).rotateZ(i.rz.v),this.mat.scale(1/i.s.v[0],1/i.s.v[1],1/i.s.v[2]),this.mat.translate(i.a.v[0],i.a.v[1],i.a.v[2])}if(this.p?this.mat.translate(-this.p.v[0],-this.p.v[1],this.p.v[2]):this.mat.translate(-this.px.v,-this.py.v,this.pz.v),this.a){var s;this.p?s=[this.p.v[0]-this.a.v[0],this.p.v[1]-this.a.v[1],this.p.v[2]-this.a.v[2]]:s=[this.px.v-this.a.v[0],this.py.v-this.a.v[1],this.pz.v-this.a.v[2]];var n=Math.sqrt(Math.pow(s[0],2)+Math.pow(s[1],2)+Math.pow(s[2],2)),a=[s[0]/n,s[1]/n,s[2]/n],c=Math.sqrt(a[2]*a[2]+a[0]*a[0]),p=Math.atan2(a[1],c),m=Math.atan2(a[0],-a[2]);this.mat.rotateY(m).rotateX(-p)}this.mat.rotateX(-this.rx.v).rotateY(-this.ry.v).rotateZ(this.rz.v),this.mat.rotateX(-this.or.v[0]).rotateY(-this.or.v[1]).rotateZ(this.or.v[2]),this.mat.translate(this.globalData.compSize.w/2,this.globalData.compSize.h/2,0),this.mat.translate(0,0,this.pe.v);var P=!this._prevMat.equals(this.mat);if((P||this.pe._mdf)&&this.comp.threeDElements){r=this.comp.threeDElements.length;var A,d,E;for(e=0;e=t)return this.threeDElements[e].perspectiveElem;e+=1}return null},HybridRendererBase.prototype.createThreeDContainer=function(t,e){var r=createTag("div"),i,s;styleDiv(r);var n=createTag("div");if(styleDiv(n),e==="3d"){i=r.style,i.width=this.globalData.compSize.w+"px",i.height=this.globalData.compSize.h+"px";var a="50% 50%";i.webkitTransformOrigin=a,i.mozTransformOrigin=a,i.transformOrigin=a,s=n.style;var c="matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)";s.transform=c,s.webkitTransform=c}r.appendChild(n);var p={container:n,perspectiveElem:r,startPos:t,endPos:t,type:e};return this.threeDElements.push(p),p},HybridRendererBase.prototype.build3dContainers=function(){var t,e=this.layers.length,r,i="";for(t=0;t=0;t-=1)this.resizerElem.appendChild(this.threeDElements[t].perspectiveElem)},HybridRendererBase.prototype.addTo3dContainer=function(t,e){for(var r=0,i=this.threeDElements.length;rr?(s=t/this.globalData.compSize.w,n=t/this.globalData.compSize.w,a=0,c=(e-this.globalData.compSize.h*(t/this.globalData.compSize.w))/2):(s=e/this.globalData.compSize.h,n=e/this.globalData.compSize.h,a=(t-this.globalData.compSize.w*(e/this.globalData.compSize.h))/2,c=0);var p=this.resizerElem.style;p.webkitTransform="matrix3d("+s+",0,0,0,0,"+n+",0,0,0,0,1,0,"+a+","+c+",0,1)",p.transform=p.webkitTransform},HybridRendererBase.prototype.renderFrame=SVGRenderer.prototype.renderFrame,HybridRendererBase.prototype.hide=function(){this.resizerElem.style.display="none"},HybridRendererBase.prototype.show=function(){this.resizerElem.style.display="block"},HybridRendererBase.prototype.initItems=function(){if(this.buildAllItems(),this.camera)this.camera.setup();else{var t=this.globalData.compSize.w,e=this.globalData.compSize.h,r,i=this.threeDElements.length;for(r=0;r=m;)D/=2,L/=2,z>>>=1;return(D+z)/L};return I.int32=function(){return k.g(4)|0},I.quick=function(){return k.g(4)/4294967296},I.double=I,C(u(k.S),t),(l.pass||b||function(G,D,L,z){return z&&(z.S&&y(z,k),G.state=function(){return y(k,{})}),L?(e[a]=G,D):G})(I,_,"global"in l?l.global:this==e,l.state)}e["seed"+a]=d;function E(f){var l,b=f.length,S=this,_=0,k=S.i=S.j=0,I=S.S=[];for(b||(f=[b++]);_r){var i=r;r=e,e=i}return Math.min(Math.max(t,e),r)}function radiansToDegrees(t){return t/degToRads}var radians_to_degrees=radiansToDegrees;function degreesToRadians(t){return t*degToRads}var degrees_to_radians=radiansToDegrees,helperLengthArray=[0,0,0,0,0,0];function length(t,e){if(typeof t=="number"||t instanceof Number)return e=e||0,Math.abs(t-e);e||(e=helperLengthArray);var r,i=Math.min(t.length,e.length),s=0;for(r=0;r.5?m/(2-s-n):m/(s+n),s){case e:a=(r-i)/m+(r1&&(r-=1),r<1/6?t+(e-t)*6*r:r<1/2?e:r<2/3?t+(e-t)*(2/3-r)*6:t}function hslToRgb(t){var e=t[0],r=t[1],i=t[2],s,n,a;if(r===0)s=i,a=i,n=i;else{var c=i<.5?i*(1+r):i+r-i*r,p=2*i-c;s=hue2rgb(p,c,e+1/3),n=hue2rgb(p,c,e),a=hue2rgb(p,c,e-1/3)}return[s,n,a,t[3]]}function linear(t,e,r,i,s){if((i===void 0||s===void 0)&&(i=e,s=r,e=0,r=1),r=r)return s;var a=r===e?0:(t-e)/(r-e);if(!i.length)return i+(s-i)*a;var c,p=i.length,m=createTypedArray("float32",p);for(c=0;c1){for(s=0;s1?e=1:e<0&&(e=0);var a=t(e);if($bm_isInstanceOfArray(s)){var c,p=s.length,m=createTypedArray("float32",p);for(c=0;cdata.k[e].t&&tdata.k[e+1].t-t?(i=e+2,s=data.k[e+1].t):(i=e+1,s=data.k[e].t);break}i===-1&&(i=e+1,s=data.k[e].t)}var n={};return n.index=i,n.time=s/elem.comp.globalData.frameRate,n}function key(t){var e,r,i;if(!data.k.length||typeof data.k[0]=="number")throw new Error("The property has no keyframe at index "+t);t-=1,e={time:data.k[t].t/elem.comp.globalData.frameRate,value:[]};var s=Object.prototype.hasOwnProperty.call(data.k[t],"s")?data.k[t].s:data.k[t-1].e;for(i=s.length,r=0;rC.length-1)&&(E=C.length-1),f=C[C.length-1-E].t,u=o-f);var l,b,S;if(d==="pingpong"){var _=Math.floor((g-f)/u);if(_%2!==0)return this.getValueAtTime((u-(g-f)%u+f)/this.comp.globalData.frameRate,0)}else if(d==="offset"){var k=this.getValueAtTime(f/this.comp.globalData.frameRate,0),I=this.getValueAtTime(o/this.comp.globalData.frameRate,0),G=this.getValueAtTime(((g-f)%u+f)/this.comp.globalData.frameRate,0),D=Math.floor((g-f)/u);if(this.pv.length){for(S=new Array(k.length),b=S.length,l=0;l=o)return this.pv;var u,f;y?(E?u=Math.abs(this.elem.comp.globalData.frameRate*E):u=Math.max(0,this.elem.data.op-o),f=o+u):((!E||E>C.length-1)&&(E=C.length-1),f=C[E].t,u=f-o);var l,b,S;if(d==="pingpong"){var _=Math.floor((o-g)/u);if(_%2===0)return this.getValueAtTime(((o-g)%u+o)/this.comp.globalData.frameRate,0)}else if(d==="offset"){var k=this.getValueAtTime(o/this.comp.globalData.frameRate,0),I=this.getValueAtTime(f/this.comp.globalData.frameRate,0),G=this.getValueAtTime((u-(o-g)%u+o)/this.comp.globalData.frameRate,0),D=Math.floor((o-g)/u)+1;if(this.pv.length){for(S=new Array(k.length),b=S.length,l=0;l1?(C-g)/(E-1):1,u=0,f=0,l;this.pv.length?l=createTypedArray("float32",this.pv.length):l=0;for(var b;uu){var _=f,k=g.c&&f===l-1?0:f+1,I=(u-b)/o[f].addedLength;S=bez.getPointInSegment(g.v[_],g.v[k],g.o[_],g.i[k],I,o[f]);break}else b+=o[f].addedLength;f+=1}return S||(S=g.c?[g.v[0][0],g.v[0][1]]:[g.v[g._length-1][0],g.v[g._length-1][1]]),S},vectorOnPath:function(E,y,g){E==1?E=this.v.c:E==0&&(E=.999);var C=this.pointOnPath(E,y),o=this.pointOnPath(E+.001,y),u=o[0]-C[0],f=o[1]-C[1],l=Math.sqrt(Math.pow(u,2)+Math.pow(f,2));if(l===0)return[0,0];var b=g==="tangent"?[u/l,f/l]:[-f/l,u/l];return b},tangentOnPath:function(E,y){return this.vectorOnPath(E,y,"tangent")},normalOnPath:function(E,y){return this.vectorOnPath(E,y,"normal")},setGroupProperty:expressionHelpers.setGroupProperty,getValueAtTime:expressionHelpers.getStaticValueAtTime},extendPrototype([P],p),extendPrototype([P],m),m.prototype.getValueAtTime=c,m.prototype.initiateExpression=ExpressionManager.initiateExpression;var A=ShapePropertyFactory.getShapeProp;ShapePropertyFactory.getShapeProp=function(d,E,y,g,C){var o=A(d,E,y,g,C);return o.propertyIndex=E.ix,o.lock=!1,y===3?expressionHelpers.searchExpressions(d,E.pt,o):y===4&&expressionHelpers.searchExpressions(d,E.ks,o),o.k&&d.addDynamicProperty(o),o}}function initialize$1(){addPropertyDecorator()}function addDecorator(){function t(){return this.data.d.x?(this.calculateExpression=ExpressionManager.initiateExpression.bind(this)(this.elem,this.data.d,this),this.addEffect(this.getExpressionValue.bind(this)),!0):null}TextProperty.prototype.getExpressionValue=function(e,r){var i=this.calculateExpression(r);if(e.t!==i){var s={};return this.copyData(s,e),s.t=i.toString(),s.__complete=!1,s}return e},TextProperty.prototype.searchProperty=function(){var e=this.searchKeyframes(),r=this.searchExpressions();return this.kf=e||r,this.kf},TextProperty.prototype.searchExpressions=t}function initialize(){addDecorator()}function SVGComposableEffect(){}SVGComposableEffect.prototype={createMergeNode:function t(e,r){var i=createNS("feMerge");i.setAttribute("result",e);var s,n;for(n=0;n=m?A=y<0?i:s:A=i+E*Math.pow((c-t)/y,1/r),P[d]=A,d+=1,n+=256/(a-1);return P.join(" ")},SVGProLevelsFilter.prototype.renderFrame=function(t){if(t||this.filterManager._mdf){var e,r=this.filterManager.effectElements;this.feFuncRComposed&&(t||r[3].p._mdf||r[4].p._mdf||r[5].p._mdf||r[6].p._mdf||r[7].p._mdf)&&(e=this.getTableValue(r[3].p.v,r[4].p.v,r[5].p.v,r[6].p.v,r[7].p.v),this.feFuncRComposed.setAttribute("tableValues",e),this.feFuncGComposed.setAttribute("tableValues",e),this.feFuncBComposed.setAttribute("tableValues",e)),this.feFuncR&&(t||r[10].p._mdf||r[11].p._mdf||r[12].p._mdf||r[13].p._mdf||r[14].p._mdf)&&(e=this.getTableValue(r[10].p.v,r[11].p.v,r[12].p.v,r[13].p.v,r[14].p.v),this.feFuncR.setAttribute("tableValues",e)),this.feFuncG&&(t||r[17].p._mdf||r[18].p._mdf||r[19].p._mdf||r[20].p._mdf||r[21].p._mdf)&&(e=this.getTableValue(r[17].p.v,r[18].p.v,r[19].p.v,r[20].p.v,r[21].p.v),this.feFuncG.setAttribute("tableValues",e)),this.feFuncB&&(t||r[24].p._mdf||r[25].p._mdf||r[26].p._mdf||r[27].p._mdf||r[28].p._mdf)&&(e=this.getTableValue(r[24].p.v,r[25].p.v,r[26].p.v,r[27].p.v,r[28].p.v),this.feFuncB.setAttribute("tableValues",e)),this.feFuncA&&(t||r[31].p._mdf||r[32].p._mdf||r[33].p._mdf||r[34].p._mdf||r[35].p._mdf)&&(e=this.getTableValue(r[31].p.v,r[32].p.v,r[33].p.v,r[34].p.v,r[35].p.v),this.feFuncA.setAttribute("tableValues",e))}};function SVGDropShadowEffect(t,e,r,i,s){var n=e.container.globalData.renderConfig.filterSize,a=e.data.fs||n;t.setAttribute("x",a.x||n.x),t.setAttribute("y",a.y||n.y),t.setAttribute("width",a.width||n.width),t.setAttribute("height",a.height||n.height),this.filterManager=e;var c=createNS("feGaussianBlur");c.setAttribute("in","SourceAlpha"),c.setAttribute("result",i+"_drop_shadow_1"),c.setAttribute("stdDeviation","0"),this.feGaussianBlur=c,t.appendChild(c);var p=createNS("feOffset");p.setAttribute("dx","25"),p.setAttribute("dy","0"),p.setAttribute("in",i+"_drop_shadow_1"),p.setAttribute("result",i+"_drop_shadow_2"),this.feOffset=p,t.appendChild(p);var m=createNS("feFlood");m.setAttribute("flood-color","#00ff00"),m.setAttribute("flood-opacity","1"),m.setAttribute("result",i+"_drop_shadow_3"),this.feFlood=m,t.appendChild(m);var P=createNS("feComposite");P.setAttribute("in",i+"_drop_shadow_3"),P.setAttribute("in2",i+"_drop_shadow_2"),P.setAttribute("operator","in"),P.setAttribute("result",i+"_drop_shadow_4"),t.appendChild(P);var A=this.createMergeNode(i,[i+"_drop_shadow_4",s]);t.appendChild(A)}extendPrototype([SVGComposableEffect],SVGDropShadowEffect),SVGDropShadowEffect.prototype.renderFrame=function(t){if(t||this.filterManager._mdf){if((t||this.filterManager.effectElements[4].p._mdf)&&this.feGaussianBlur.setAttribute("stdDeviation",this.filterManager.effectElements[4].p.v/4),t||this.filterManager.effectElements[0].p._mdf){var e=this.filterManager.effectElements[0].p.v;this.feFlood.setAttribute("flood-color",rgbToHex(Math.round(e[0]*255),Math.round(e[1]*255),Math.round(e[2]*255)))}if((t||this.filterManager.effectElements[1].p._mdf)&&this.feFlood.setAttribute("flood-opacity",this.filterManager.effectElements[1].p.v/255),t||this.filterManager.effectElements[2].p._mdf||this.filterManager.effectElements[3].p._mdf){var r=this.filterManager.effectElements[3].p.v,i=(this.filterManager.effectElements[2].p.v-90)*degToRads,s=r*Math.cos(i),n=r*Math.sin(i);this.feOffset.setAttribute("dx",s),this.feOffset.setAttribute("dy",n)}}};var _svgMatteSymbols=[];function SVGMatte3Effect(t,e,r){this.initialized=!1,this.filterManager=e,this.filterElem=t,this.elem=r,r.matteElement=createNS("g"),r.matteElement.appendChild(r.layerElement),r.matteElement.appendChild(r.transformedElement),r.baseElement=r.matteElement}SVGMatte3Effect.prototype.findSymbol=function(t){for(var e=0,r=_svgMatteSymbols.length;e10&&(t=t.slice(0,10).join(` +`)+` +...`),t)}function formatArray(t,e=0){if(e>2)return"[...]";const r=e===1?3:10;return t.length>r&&(t=t.slice(0,r),t.push("...")),"["+t.map(i=>format(i,e+1)).join(", ")+"]"}function formatString(t,e=0){const r=e>=2?20:100;return t.length>r&&(t=t.slice(0,r-1)+"…"),`'${t}'`}function formatObject(t,e=0){let r=t.constructor.name;if(r==="Object"&&t[Symbol.toStringTag]&&(r=t[Symbol.toStringTag]),r!=="Object"&&t.toString!==Object.prototype.toString)return t.toString();let i="";if(r!=="Object"&&(i+=r+(r==="Set"?`(${t.size})`:"")+" "),i+="{",e>1)i+="...";else{const s=[];for(const n in t)Object.prototype.hasOwnProperty.call(t,n)&&s.push(`${n}: ${format(t[n],e+1)}`);Object.getOwnPropertySymbols(t).forEach(n=>{s.push(`${n.toString()}: ${format(t[n],e+1)}`)}),i+=s.join(", ")}return i+="}",i}const log=function(...t){return consoleFormat(t)},showJs="http://localhost:8080/blog/showjs.html",showHtml="http://localhost:8080/blog/showhtml.html",_CodeBox=class{static getInstance(t){return _CodeBox.instances.get(t)}constructor(t){if(this.btnEle=t,this.wrapperEle=t.parentElement.parentElement,!this.wrapperEle)throw new Error("No code block wrapper detected.");if(this.isJS=this.wrapperEle.classList.contains("language-javascript"),this.isHTML=this.wrapperEle.classList.contains("language-html"),!this.isJS&&!this.isHTML)throw new Error("Should be javascript or html text.");if(_CodeBox.instances.has(this.wrapperEle))return _CodeBox.getInstance(this.wrapperEle);_CodeBox.instances.set(this.wrapperEle,this),this.codeBoxId=this.wrapperEle.id,this.codeEle=this.wrapperEle.querySelector("code"),this.inputEle=this.wrapperEle.querySelector("input"),this.resultLength=0;let e="";this.wrapperEle.hasAttribute("data-use-context")&&(e=this.wrapperEle.getAttribute("data-use-context").split(",").map(s=>document.getElementById(s).querySelector("code").textContent).join(` +`)+` +`);const r=e+this.codeEle.textContent;if(this.isHTML)return this.formatRunHTML(r),this;this.formatRunCode(r)}setTimer(t){this.toastTimer=t}formatRunCode(t){const e=t.replace(/\/\/\s*\S+\n/g,"").replace(/logger\((.+)\);\n/g,(r,i)=>`console.log(${i}); +`);this.wrapperEle.hasAttribute("data-global")?this.runCode=this.code=e.replace(/console.log\((.+)\);/g,r=>(this.resultLength++,r)):this.wrapperEle.hasAttribute("data-step-hl")?this.runCode=this.code=e.replace(/function/g,r=>`async ${r}`).replace(/console.log\((.+)\);/g,(r,i)=>`await new Promise(resolve=>resolves.push(resolve)); +_result.push(${i}); +cb(); +`):this.wrapperEle.hasAttribute("data-async")?this.runCode=this.code=e.replace(/console.log\((.+)\);\n/g,(r,i)=>(this.resultLength++,`_save(${i}); +`)):this.runCode=this.code=e.replace(/console.log\((.+)\);\n/g,(r,i)=>(this.resultLength++,`_save(${i}); +cb(); +`)),this.wrapperEle.hasAttribute("data-length")&&(this.resultLength=+this.wrapperEle.getAttribute("data-length")),this.wrapperEle.hasAttribute("data-async")&&(this.code=this.code.replace(/function/g,r=>`async ${r}`),this.code=this.code.replace(/suspend\(\);\n/g,()=>`await new Promise(resolve => setTimeout(() => resolve(), 3000)); +`),this.runCode=`(async () => { +${this.code} +})(); +`),this.isTrusted=this.wrapperEle.hasAttribute("data-trusted"),this.isNoStrict=this.wrapperEle.hasAttribute("data-no-strict"),this.useStrict=!this.isNoStrict&&this.isJS?`"use strict;" +`:"",this.runCode=`${this.useStrict}globalThis.__codeBoxId="${this.codeBoxId}"; + +${this.runCode}; +`}formatRunHTML(t){const e=t.replace(/\/\/\s*\S+\n/g,"");this.runCode=this.code=e.replace(/console.log\((.+)\);/g,r=>(this.resultLength++,r)),this.wrapperEle.hasAttribute("data-length")&&(this.resultLength=+this.wrapperEle.getAttribute("data-length")),this.runCode=` + + diff --git a/playground/index.html b/playground/index.html new file mode 100644 index 0000000..1bf9abd --- /dev/null +++ b/playground/index.html @@ -0,0 +1,17 @@ + + + + + + + 游乐场 | LOFT + + + + + + +
+ + + diff --git a/print.css b/print.css new file mode 100644 index 0000000..6ded3e4 --- /dev/null +++ b/print.css @@ -0,0 +1,31 @@ +@media print { + + header.navbar, + .sidebar-mask, + .sidebar, + .footer, + .banner, + .print-btn { + display: none !important; + } + + body { + color: #333 !important; + } + + html.dark body { + color: #333 !important; + } + + a[href]::after { + content: none !important; + } + + .dark .page-resume { + color: #333 !important; + } + + .page-resume .theme-default-content>div { + margin-top: -160px; + } +} \ No newline at end of file diff --git a/resume/index.html b/resume/index.html new file mode 100644 index 0000000..637a9c3 --- /dev/null +++ b/resume/index.html @@ -0,0 +1,17 @@ + + + + + + + 简历 | LOFT + + + + +
+ +
+ + + diff --git a/showhtml.html b/showhtml.html new file mode 100644 index 0000000..e152845 --- /dev/null +++ b/showhtml.html @@ -0,0 +1,185 @@ + + + + + + + IFrame + + + + diff --git a/showjs.html b/showjs.html new file mode 100644 index 0000000..8d0082c --- /dev/null +++ b/showjs.html @@ -0,0 +1,173 @@ + + + + + + + IFrame + + + + +