<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Murraya</title>
        <link>https://paragraph.com/@murraya</link>
        <description>undefined</description>
        <lastBuildDate>Sun, 21 Jun 2026 12:45:31 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Murraya</title>
            <url>https://storage.googleapis.com/papyrus_images/cce9311e830cd5e566a568ff5e57615b8dbec4c265313472c65c68d74da5b947.png</url>
            <link>https://paragraph.com/@murraya</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[webpack 常用的配置]]></title>
            <link>https://paragraph.com/@murraya/webpack-2</link>
            <guid>5UXpBeCZtF1iJ6GsK1G3</guid>
            <pubDate>Wed, 11 May 2022 16:24:05 GMT</pubDate>
            <description><![CDATA[clean-webpack-plugin作用：打包之前清除output的文件夹，避免存在之前的打包结果；var path = require("path"); var { CleanWebpackPlugin } = require("clean-webpack-plugin"); module.exports = { //... output: { path: path.resolve(__dirname, 'dist'), filename: "main[contenthash: 5].js" }, plugin: [ new CleanWebpackPlugin() ] } Html-webpack-plugin作用：自动生成html文件var path = require("path"); var { CleanWebpackPlugin } = require("clean-webpack-plugin"); var HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = function...]]></description>
            <content:encoded><![CDATA[<h2 id="h-clean-webpack-plugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">clean-webpack-plugin</h2><p>作用：打包之前清除output的文件夹，避免存在之前的打包结果；</p><pre data-type="codeBlock" text="var path = require(&quot;path&quot;);
var { CleanWebpackPlugin } = require(&quot;clean-webpack-plugin&quot;);

module.exports = {
  //...
  output: {
    path: path.resolve(__dirname, &apos;dist&apos;),
    filename: &quot;main[contenthash: 5].js&quot;
  },
  plugin: [
    new CleanWebpackPlugin()
  ]
}
"><code><span class="hljs-keyword">var</span> path <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">"path"</span>);
<span class="hljs-keyword">var</span> { CleanWebpackPlugin } <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">"clean-webpack-plugin"</span>);

module.exports <span class="hljs-operator">=</span> {
  <span class="hljs-comment">//...</span>
  output: {
    path: path.resolve(__dirname, <span class="hljs-string">'dist'</span>),
    filename: <span class="hljs-string">"main[contenthash: 5].js"</span>
  },
  plugin: [
    <span class="hljs-keyword">new</span> CleanWebpackPlugin()
  ]
}
</code></pre><h2 id="h-html-webpack-plugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Html-webpack-plugin</h2><p>作用：自动生成html文件</p><pre data-type="codeBlock" text="var path = require(&quot;path&quot;);
var { CleanWebpackPlugin } = require(&quot;clean-webpack-plugin&quot;);
var HtmlWebpackPlugin = require(&quot;html-webpack-plugin&quot;);

module.exports = function(env){
    return {
        output: {
            path: path.resolve(__dirname, &apos;dist&apos;),
            filename: &apos;main[contenthash:5].js&apos;
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: &apos;./public/index.html&apos;,
                title: &apos;my app&apos;,
                scriptLoading: &apos;blocking&apos;,
                // chunks: 入口配置的key
                filename: &apos;home.html&apos;
            })
        ]
    }
}
"><code><span class="hljs-keyword">var</span> path <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">"path"</span>);
<span class="hljs-keyword">var</span> { CleanWebpackPlugin } <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">"clean-webpack-plugin"</span>);
<span class="hljs-keyword">var</span> HtmlWebpackPlugin <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">"html-webpack-plugin"</span>);

module.exports <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">env</span>)</span>{
    <span class="hljs-keyword">return</span> {
        output: {
            path: path.resolve(__dirname, <span class="hljs-string">'dist'</span>),
            filename: <span class="hljs-string">'main[contenthash:5].js'</span>
        },
        plugins: [
            <span class="hljs-keyword">new</span> HtmlWebpackPlugin({
                template: <span class="hljs-string">'./public/index.html'</span>,
                title: <span class="hljs-string">'my app'</span>,
                scriptLoading: <span class="hljs-string">'blocking'</span>,
                <span class="hljs-comment">// chunks: 入口配置的key</span>
                filename: <span class="hljs-string">'home.html'</span>
            })
        ]
    }
}
</code></pre><h2 id="h-copy-webpack-plugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Copy-webpack-plugin</h2><p>复制静态资源，通常处理assets里的图片、pdf等资源的迁移</p><pre data-type="codeBlock" text="var path = require(&quot;path&quot;);
var CopyWebpackPlugin = require(&quot;copy-webpack-plugin&quot;);

module.exports = function(env){
    return {
        output: {
            path: path.resolve(__dirname, &apos;dist&apos;),
            filename: &apos;main[contenthash:5].js&apos;
        },
        plugins: [
            new CopyWebpackPlugin({
                patterns: [
                    { from: &apos;./public&apos;, to: &apos;./&apos; }
                ]
            })
        ]
    }
}
"><code><span class="hljs-keyword">var</span> path <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">"path"</span>);
<span class="hljs-keyword">var</span> CopyWebpackPlugin <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">"copy-webpack-plugin"</span>);

module.exports <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">env</span>)</span>{
    <span class="hljs-keyword">return</span> {
        output: {
            path: path.resolve(__dirname, <span class="hljs-string">'dist'</span>),
            filename: <span class="hljs-string">'main[contenthash:5].js'</span>
        },
        plugins: [
            <span class="hljs-keyword">new</span> CopyWebpackPlugin({
                patterns: [
                    { <span class="hljs-keyword">from</span>: <span class="hljs-string">'./public'</span>, to: <span class="hljs-string">'./'</span> }
                ]
            })
        ]
    }
}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">开发服务器</h2><p>在<strong>开发阶段</strong>，目前遇到的问题是打包、运行、调试过程过于繁琐，回顾一下我们的操作流程：</p><ol><li><p>编写代码</p></li><li><p>控制台运行命令完成打包</p></li><li><p>打开页面查看效果</p></li><li><p>继续编写代码，回到步骤2</p></li></ol><p>并且，我们往往希望把最终生成的代码和页面部署到服务器上，来模拟真实环境</p><p>为了解决这些问题，webpack官方制作了一个单独的库：<strong>webpack-dev-server</strong></p><p>它<strong>既不是plugin也不是loader</strong></p><p>先来看看它怎么用</p><ol><li><p>安装</p></li><li><p>执行<code>webpack-dev-server</code>命令</p></li></ol><p><code>webpack-dev-server</code>命令几乎支持所有的webpack命令参数，如<code>--config</code>、<code>-env</code>等等，你可以把它当作webpack命令使用</p><p>这个命令是专门为开发阶段服务的，真正部署的时候还是得使用webpack命令</p><p>当我们执行<code>webpack-dev-server</code>命令后，它做了以下操作：</p><ol><li><p>内部执行webpack命令，传递命令参数</p></li><li><p>开启watch</p></li><li><p>注册hooks：类似于plugin，webpack-dev-server会向webpack中注册一些钩子函数，主要功能如下：</p><ol><li><p>将资源列表（aseets）保存起来</p></li><li><p>禁止webpack输出文件</p></li></ol></li><li><p>用express开启一个服务器，监听某个端口，当请求到达后，根据请求的路径，给予相应的资源内容</p></li></ol><p><strong>配置</strong></p><p>针对webpack-dev-server的配置，参考：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.webpackjs.com/configuration/dev-server/">https://www.webpackjs.com/configuration/dev-server/</a></p><p>常见配置有：</p><ul><li><p>port：配置监听端口</p></li><li><p>proxy：配置代理，常用于跨域访问</p></li><li><p>stats：配置控制台输出内容</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">普通文件处理</h2><p>file-loader: 生成依赖的文件到输出目录，然后将模块文件设置为：导出一个路径\</p><pre data-type="codeBlock" text="//file-loader
function loader(source){
    // source：文件内容（图片内容 buffer）
    // 1. 生成一个具有相同文件内容的文件到输出目录
    // 2. 返回一段代码   export default &quot;文件名&quot;
}
"><code>//file-loader
<span class="hljs-keyword">function</span> loader(<span class="hljs-built_in">source</span>){
    // <span class="hljs-built_in">source</span>：文件内容（图片内容 buffer）
    // 1. 生成一个具有相同文件内容的文件到输出目录
    // 2. 返回一段代码   <span class="hljs-built_in">export</span> default <span class="hljs-string">"文件名"</span>
}
</code></pre><p>url-loader：将依赖的文件转换为：导出一个base64格式的字符串</p><pre data-type="codeBlock" text="//url-loader
function loader(source){
    // source：文件内容（图片内容 buffer）
    // 1. 根据buffer生成一个base64编码
    // 2. 返回一段代码   export default &quot;base64编码&quot;
}
"><code>//url-loader
<span class="hljs-keyword">function</span> loader(<span class="hljs-built_in">source</span>){
    // <span class="hljs-built_in">source</span>：文件内容（图片内容 buffer）
    // 1. 根据buffer生成一个<span class="hljs-built_in">base64</span>编码
    // 2. 返回一段代码   <span class="hljs-built_in">export</span> default <span class="hljs-string">"base64编码"</span>
}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">解决路径问题</h2><p>在使用file-loader或url-loader时，可能会遇到一个非常有趣的问题</p><p>比如，通过webpack打包的目录结构如下：</p><pre data-type="codeBlock" text="dist
    |—— img
        |—— a.png  #file-loader生成的文件
    |—— scripts
        |—— main.js  #export default &quot;img/a.png&quot;
    |—— html
        |—— index.html #&lt;script src=&quot;../scripts/main.js&quot; &gt;&lt;/script&gt;
"><code>dist
    <span class="hljs-operator">|</span>—— img
        <span class="hljs-operator">|</span>—— a.png  #file<span class="hljs-operator">-</span>loader生成的文件
    <span class="hljs-operator">|</span>—— scripts
        <span class="hljs-operator">|</span>—— main.js  #export default <span class="hljs-string">"img/a.png"</span>
    <span class="hljs-operator">|</span>—— html
        <span class="hljs-operator">|</span>—— index.html #<span class="hljs-operator">&#x3C;</span>script src<span class="hljs-operator">=</span><span class="hljs-string">"../scripts/main.js"</span> <span class="hljs-operator">></span><span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>script<span class="hljs-operator">></span>
</code></pre><p>这种问题发生的根本原因：模块中的路径来自于某个loader或plugin，当产生路径时，loader或plugin只有相对于dist目录的路径，并不知道该路径将在哪个资源中使用，从而无法确定最终正确的路径</p><p>面对这种情况，需要依靠webpack的配置publicPath解决</p><p>注意⚠️：</p><ul><li><p>publicPath配置，会生成一个简单的字符串，有些loader或者插件会使用这个字符串（例如：在生成路径的时候将这个字符串放到路径的最前面），一般会把这个配置位&apos;/&apos;</p></li><li><p>有些loader或者插件自己也会带有一个publicPath，用来做单独的配置；</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">内置插件</h2><p>所有的webpack内置插件都作为webpack的静态属性存在的，使用下面的方式即可创建一个插件对象</p><pre data-type="codeBlock" text="const webpack = require(&quot;webpack&quot;)

new webpack.插件名(options)
"><code>const webpack <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">"webpack"</span>)

<span class="hljs-keyword">new</span> webpack.插件名(options)
</code></pre><h2 id="h-defineplugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">DefinePlugin</h2><p>全局常量定义插件，使用该插件通常定义一些常量值，例如：</p><pre data-type="codeBlock" text="new webpack.DefinePlugin({
    PI: `Math.PI`, // PI = Math.PI
    VERSION: `&quot;1.0.0&quot;`, // VERSION = &quot;1.0.0&quot;
    DOMAIN: JSON.stringify(&quot;duyi.com&quot;)
})
"><code><span class="hljs-keyword">new</span> webpack.DefinePlugin({
    PI: `Math.PI`, <span class="hljs-comment">// PI = Math.PI</span>
    VERSION: `<span class="hljs-string">"1.0.0"</span>`, <span class="hljs-comment">// VERSION = "1.0.0"</span>
    DOMAIN: JSON.stringify(<span class="hljs-string">"duyi.com"</span>)
})
</code></pre><p>这样一来，在源码中，我们可以直接使用插件中提供的常量，当webpack编译完成后，会自动替换为常量的值</p><h2 id="h-bannerplugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">BannerPlugin</h2><p>它可以为每个chunk生成的文件头部添加一行注释，一般用于添加作者、公司、版权等信息</p><pre data-type="codeBlock" text="new webpack.BannerPlugin({
  banner: `
  hash:[hash]
  chunkhash:[chunkhash]
  name:[name]
  author:yuanjin
  corporation:duyi
  `
})
"><code><span class="hljs-keyword">new</span> webpack.<span class="hljs-title function_ invoke__">BannerPlugin</span>({
  <span class="hljs-attr">banner</span>: `
  <span class="hljs-attr">hash</span>:[hash]
  <span class="hljs-attr">chunkhash</span>:[chunkhash]
  <span class="hljs-attr">name</span>:[name]
  <span class="hljs-attr">author</span>:yuanjin
  <span class="hljs-attr">corporation</span>:duyi
  `
})
</code></pre><h2 id="h-provideplugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ProvidePlugin</h2><p>自动加载模块，而不必到处 import 或 require</p><pre data-type="codeBlock" text="new webpack.ProvidePlugin({
  $: &apos;jquery&apos;,
  _: &apos;lodash&apos;
})
"><code><span class="hljs-keyword">new</span> webpack.ProvidePlugin({
  $: <span class="hljs-string">'jquery'</span>,
  <span class="hljs-keyword">_</span>: <span class="hljs-string">'lodash'</span>
})
</code></pre><p>然后在我们任意源码中：</p><pre data-type="codeBlock" text="$(&apos;#item&apos;); // &lt;= 起作用
_.drop([1, 2, 3], 2); // &lt;= 起作用
"><code>$(<span class="hljs-string">'#item'</span>); <span class="hljs-comment">// &#x3C;= 起作用</span>
<span class="hljs-keyword">_</span>.drop([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>], <span class="hljs-number">2</span>); <span class="hljs-comment">// &#x3C;= 起作用</span>
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[react-ref]]></title>
            <link>https://paragraph.com/@murraya/react-ref</link>
            <guid>LkfSV86iac7HvcO4qamX</guid>
            <pubDate>Wed, 11 May 2022 16:22:29 GMT</pubDate>
            <description><![CDATA[refreference: 引用 场景：希望直接使用dom元素中的某个方法，或者希望直接使用自定义组件中的某个方法ref作用于内置的html组件，得到的将是真实的dom对象ref作用于类组件，得到的将是类的实例ref不能作用于函数组件ref不再推荐使用字符串赋值，字符串赋值的方式将来可能会被移出 目前，ref推荐使用对象或者是函数 对象 通过 React.createRef 函数创建constructor(props) { super(props); this.txt = React.createRef(); // 或者 this.txt = { current: null } } render() { return ( &#x3C;input ref={this.txt} /> ) } // 在完成渲染时给current赋值 函数constructor(props) { super(props); this.txt = null; } render() { return ( &#x3C;input ref={el => this.txt = el } /> ) } // 在完成...]]></description>
            <content:encoded><![CDATA[<h2 id="h-ref" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ref</h2><p>reference: 引用</p><p><strong>场景：希望直接使用dom元素中的某个方法，或者希望直接使用自定义组件中的某个方法</strong></p><ul><li><p>ref作用于内置的html组件，得到的将是真实的dom对象</p></li><li><p>ref作用于类组件，得到的将是类的实例</p></li><li><p>ref不能作用于函数组件</p></li></ul><p>ref不再推荐使用字符串赋值，字符串赋值的方式将来可能会被移出</p><p>目前，ref推荐使用对象或者是函数</p><p><strong>对象</strong></p><p>通过 React.createRef 函数创建</p><pre data-type="codeBlock" text="constructor(props) {
  super(props);
  this.txt = React.createRef();
  // 或者 this.txt = {
    current: null
  }
}

render() {
  return (
    &lt;input ref={this.txt} /&gt;
  )
}

// 在完成渲染时给current赋值
"><code><span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-built_in">super</span>(props);
  <span class="hljs-built_in">this</span>.txt <span class="hljs-operator">=</span> React.createRef();
  <span class="hljs-comment">// 或者 this.txt = {</span>
    current: null
  }
}

render() {
  <span class="hljs-keyword">return</span> (
    <span class="hljs-operator">&#x3C;</span>input ref<span class="hljs-operator">=</span>{<span class="hljs-built_in">this</span>.txt} <span class="hljs-operator">/</span><span class="hljs-operator">></span>
  )
}

<span class="hljs-comment">// 在完成渲染时给current赋值</span>
</code></pre><p><strong>函数</strong></p><pre data-type="codeBlock" text="constructor(props) {
  super(props);
  this.txt = null;
}

render() {
  return (
    &lt;input ref={el =&gt; this.txt = el } /&gt;
  )
}

// 在完成渲染时给current赋值
"><code><span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-built_in">super</span>(props);
  <span class="hljs-built_in">this</span>.txt <span class="hljs-operator">=</span> null;
}

render() {
  <span class="hljs-keyword">return</span> (
    <span class="hljs-operator">&#x3C;</span>input ref<span class="hljs-operator">=</span>{el <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-built_in">this</span>.txt <span class="hljs-operator">=</span> el } <span class="hljs-operator">/</span><span class="hljs-operator">></span>
  )
}

<span class="hljs-comment">// 在完成渲染时给current赋值</span>
</code></pre><p>函数的调用时间：</p><ol><li><p>componentDidMount的时候会调用该函数</p><ol><li><p>在componentDidMount事件中可以使用ref</p></li></ol></li><li><p>如果ref的值发生了变动（旧的函数被新的函数替代），分别调用旧的函数以及新的函数，时间点出现在componentDidUpdate之前</p><ol><li><p>旧的函数被调用时，传递null</p></li><li><p>新的函数被调用时，传递对象</p></li></ol></li><li><p>如果ref所在的组件被卸载，会调用函数</p></li></ol><p><strong>谨慎使用ref</strong></p><p>能够使用属性和状态进行控制，就不要使用ref。</p><ol><li><p>调用真实的DOM对象中的方法</p></li><li><p>某个时候需要调用类组件的方法</p></li></ol><h2 id="h-ref" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ref转发</h2><p>forwardRef注意：就是一个高阶函数，只能传递函数组件。</p>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[Typescript]]></title>
            <link>https://paragraph.com/@murraya/typescript</link>
            <guid>4y9vuoIPS1QQlfJ1mgKC</guid>
            <pubDate>Wed, 11 May 2022 16:19:59 GMT</pubDate>
            <description><![CDATA[概述先来比较一段代码；js代码function sayName(name) { if(Math.random() &#x3C; 0.5) { return 'shao xing' } return 404 } let name = sayname(0); nmae = name.split(" ").filter(it => it).map(item => item[0].touppercase(1)+ item.subStr()).join(" "); console.log(name); ts代码function sayName(name) { if(Math.random() &#x3C; 0.5) { return 'shao xing' } return 404 } let name = sayname(0); nmae = name.split(" ").filter(it => it).map(item => item[0].touppercase(1)+ item.subStr()).join(" "); console.log(name); 在vscode编辑器...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">概述</h2><p>先来比较一段代码；</p><ul><li><p>js代码</p></li></ul><pre data-type="codeBlock" text="function sayName(name) {
    if(Math.random() &lt; 0.5) {
        return &apos;shao xing&apos;
    }
    return 404
}

let name = sayname(0);

nmae = name.split(&quot; &quot;).filter(it =&gt; it).map(item =&gt; item[0].touppercase(1)+ item.subStr()).join(&quot; &quot;);

console.log(name);
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sayName</span>(<span class="hljs-params">name</span>) </span>{
    <span class="hljs-keyword">if</span>(Math.random() <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">0</span><span class="hljs-number">.5</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'shao xing'</span>
    }
    <span class="hljs-keyword">return</span> <span class="hljs-number">404</span>
}

let name <span class="hljs-operator">=</span> sayname(<span class="hljs-number">0</span>);

nmae <span class="hljs-operator">=</span> name.split(<span class="hljs-string">" "</span>).filter(it <span class="hljs-operator">=</span><span class="hljs-operator">></span> it).map(item <span class="hljs-operator">=</span><span class="hljs-operator">></span> item[<span class="hljs-number">0</span>].touppercase(<span class="hljs-number">1</span>)<span class="hljs-operator">+</span> item.subStr()).join(<span class="hljs-string">" "</span>);

console.log(name);
</code></pre><ul><li><p>ts代码</p></li></ul><pre data-type="codeBlock" text="function sayName(name) {
    if(Math.random() &lt; 0.5) {
        return &apos;shao xing&apos;
    }
    return 404
}

let name = sayname(0);

nmae = name.split(&quot; &quot;).filter(it =&gt; it).map(item =&gt; item[0].touppercase(1)+ item.subStr()).join(&quot; &quot;);

console.log(name);
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sayName</span>(<span class="hljs-params">name</span>) </span>{
    <span class="hljs-keyword">if</span>(Math.random() <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">0</span><span class="hljs-number">.5</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'shao xing'</span>
    }
    <span class="hljs-keyword">return</span> <span class="hljs-number">404</span>
}

let name <span class="hljs-operator">=</span> sayname(<span class="hljs-number">0</span>);

nmae <span class="hljs-operator">=</span> name.split(<span class="hljs-string">" "</span>).filter(it <span class="hljs-operator">=</span><span class="hljs-operator">></span> it).map(item <span class="hljs-operator">=</span><span class="hljs-operator">></span> item[<span class="hljs-number">0</span>].touppercase(<span class="hljs-number">1</span>)<span class="hljs-operator">+</span> item.subStr()).join(<span class="hljs-string">" "</span>);

console.log(name);
</code></pre><p>在vscode编辑器中比较这两段代码; 我们在js开发中容易犯的错误</p><ul><li><p>使用了不存在的变量、函数或者成员</p></li><li><p>类型错误（一个不确定的类型当成一个确定的类型处理）</p></li><li><br></li></ul><p>JS语言的原罪：</p><ul><li><p>js是弱类型的语言</p></li><li><p>js是解释性的语言（代码看一段执行一段，我们的代码必须运行出来才能发现问题）</p></li></ul><p>导致： 我们的开发过程中大多数时间都是在排除错误；（效率很低下）</p><p>TYPESCRIPT语言能很好解决上述的问题；</p><h2 id="h-ts" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">TS的特点以及如何解决我们的问题</h2><ul><li><p>TS是JS的一个超集；一个<strong>可选</strong>的类型系统（学习曲线很平滑）；</p></li><li><p>浏览器和node环境中无法直接识别TS代码；（tsc -&gt; js）;</p></li></ul><h2 id="h-nodets" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">在node环境中搭建ts开发环境</h2><ul><li><p>安装 npm i typescript -g</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">基本类型检查</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">基本类型约束</h3><pre data-type="codeBlock" text="如何进行类型约束
约束变量、函数的参数、函数的返回值
格式：
在变量、函数的参数、函数的返回值加上  : type
"><code>如何进行类型约束
约束变量、函数的参数、函数的返回值
格式：
在变量、函数的参数、函数的返回值加上  : <span class="hljs-built_in">type</span>
</code></pre><pre data-type="codeBlock" text="function sum (a, b) {
    return a + b;
}
"><code>function sum (<span class="hljs-selector-tag">a</span>, <span class="hljs-selector-tag">b</span>) {
    return <span class="hljs-selector-tag">a</span> + <span class="hljs-selector-tag">b</span>;
}
</code></pre><pre data-type="codeBlock" text="function sum(a: number, b: number): number {
    return a+b;
}

sum(3, 4);

let num: number = sum(3, 4);

sum = 34;
"><code>function sum(a: number, b: number): number {
    return a+b<span class="hljs-comment">;</span>
}

sum(3, 4)<span class="hljs-comment">;</span>

let num: <span class="hljs-attr">number</span> = sum(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>)<span class="hljs-comment">;</span>

<span class="hljs-attr">sum</span> = <span class="hljs-number">34</span><span class="hljs-comment">;</span>
</code></pre><p>类型检查给我们带来了很多好处；</p><ul><li><p>严格的类型检查让上面的第9行代码就会报错；</p></li><li><p>可以通过现有的类型进行类型推导，最大程度地减少代码量。上面的代码可以简化为</p></li></ul><pre data-type="codeBlock" text="function sum(a: number, b: number) {
    return a + b;
}

let num = sum(3, 4);

num = &apos;123&apos;;
"><code>function sum(a: number, b: number) {
    return a + b<span class="hljs-comment">;</span>
}

let <span class="hljs-attr">num</span> = sum(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>)<span class="hljs-comment">;</span>

<span class="hljs-attr">num</span> = <span class="hljs-string">'123'</span><span class="hljs-comment">;</span>
</code></pre><ul><li><p>补充：基本类型的约束很容易。如何区分数字和数字字符串；</p></li></ul><p>一般两种情况：在不超过js精度的情况下，如果按照数学的读法，它的类型就是数字，反之是字符串 超过精度，则全是字符串；</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">基本类型</h3><pre data-type="codeBlock" text="number 数字
string 字符串
boolean 布尔类型
any[] 等价于 Array&lt;any&gt; 数组   更加倾向于第一个，第二种在react项目中可能存在冲突
object 对象 约束形为{}的变量

null和undefined 这两个类型是以上类型的子类型
    let num: string = undefined;
  num.toUpperCase(); // 会报错
  我们不希望出现这样的情况；一般选择在配置文件中加入 **strictNullChecks: true** 配置项
"><code><span class="hljs-built_in">number</span> 数字
<span class="hljs-built_in">string</span> 字符串
<span class="hljs-built_in">boolean</span> 布尔类型
<span class="hljs-built_in">any</span>[] 等价于 <span class="hljs-title class_">Array</span>&#x3C;<span class="hljs-built_in">any</span>> 数组   更加倾向于第一个，第二种在react项目中可能存在冲突
<span class="hljs-built_in">object</span> 对象 约束形为{}的变量

<span class="hljs-literal">null</span>和<span class="hljs-literal">undefined</span> 这两个类型是以上类型的子类型
    <span class="hljs-keyword">let</span> <span class="hljs-attr">num</span>: <span class="hljs-built_in">string</span> = <span class="hljs-literal">undefined</span>;
  num.<span class="hljs-title function_">toUpperCase</span>(); <span class="hljs-comment">// 会报错</span>
  我们不希望出现这样的情况；一般选择在配置文件中加入 **<span class="hljs-attr">strictNullChecks</span>: <span class="hljs-literal">true</span>** 配置项
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">其他的常用类型</h3><ul><li><p>联合类型（&quot;|&quot;）</p></li></ul><p>可以配合类型保护进行判断，typeof可以触发简单类型的类型保护</p><pre data-type="codeBlock" text="// 类型保护
let str: string | undefined
if(typeof str === &apos;string&apos;) {
      // 代码在这里就能确定str的类型了，代码有提示的
} else {
    // 无提示
}
"><code><span class="hljs-comment">// 类型保护</span>
let str: <span class="hljs-keyword">string</span> <span class="hljs-operator">|</span> undefined
<span class="hljs-keyword">if</span>(typeof str <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">'string'</span>) {
      <span class="hljs-comment">// 代码在这里就能确定str的类型了，代码有提示的</span>
} <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// 无提示</span>
}
</code></pre><ul><li><p>void类型--通常用来约束函数的返回类型，表示该函数没有任何的返回</p></li></ul><pre data-type="codeBlock" text="function printMenu(): void {
    console.log(111);
}
"><code><span class="hljs-keyword">function</span> <span class="hljs-title function_">printMenu</span>(<span class="hljs-params"></span>): <span class="hljs-keyword">void</span> {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-number">111</span>);
}
</code></pre><ul><li><p>never类型--通常用来约束函数的返回值，表示该函数永远都不会结束</p></li></ul><p><strong>注：这里要查一下文档</strong></p><pre data-type="codeBlock" text="function throwError(msg: string): never{
    throw new Error(msg);
  // console.log(111);
}

function doSome() {
    while(true) {
      // do ..
  }
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">throwError</span>(<span class="hljs-params"><span class="hljs-built_in">msg</span>: <span class="hljs-keyword">string</span></span>): <span class="hljs-title">never</span></span>{
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-built_in">msg</span>);
  <span class="hljs-comment">// console.log(111);</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doSome</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {
      <span class="hljs-comment">// do ..</span>
  }
}
</code></pre><ul><li><p><strong>字面量类型</strong>：使用一个值进行约束</p></li></ul><p><strong>注：这里和后面的接口、类型别名做比较</strong></p><pre data-type="codeBlock" text="let a: &apos;1&apos; = &apos;1&apos;;
let user: {name: string, age: number} = {name: &apos;tom&apos;, age: 18}
"><code>let <span class="hljs-selector-tag">a</span>: <span class="hljs-string">'1'</span> = <span class="hljs-string">'1'</span>;
let user: {name: string, age: number} = {name: <span class="hljs-string">'tom'</span>, age: <span class="hljs-number">18</span>}
</code></pre><ul><li><p>元祖类型(tuple)：一个固定的长度的数组，并且数组中每一项的类型是确定的；</p></li></ul><pre data-type="codeBlock" text="let tu: [string, number] = [&apos;1&apos;, 1]
"><code><span class="hljs-keyword">let</span> <span class="hljs-attr">tu</span>: [<span class="hljs-built_in">string</span>, <span class="hljs-built_in">number</span>] = [<span class="hljs-string">'1'</span>, <span class="hljs-number">1</span>]
</code></pre><ul><li><p>any类型</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">类型别名</h3><pre data-type="codeBlock" text="let u: {
    name: string,
  age: number,
  gender: &apos;男&apos; | &apos;女&apos;
}

function getUsers(): {
    name: string,
  age: number,
  gender: &apos;男&apos;|&apos;女&apos;
}[] {
    return [{
      name: &apos;tome&apos;,
    age: 18,
    gender: &apos;男&apos;
  }]
}

getUsers();

// 上述代码中对变量的约束存在很多冗余， 我们可以把一些重复的类型提取出来，减少代码量
"><code><span class="hljs-keyword">let</span> <span class="hljs-attr">u</span>: {
    <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>,
  <span class="hljs-attr">age</span>: <span class="hljs-built_in">number</span>,
  <span class="hljs-attr">gender</span>: <span class="hljs-string">'男'</span> | <span class="hljs-string">'女'</span>
}

<span class="hljs-keyword">function</span> <span class="hljs-title function_">getUsers</span>(<span class="hljs-params"></span>): {
    <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>,
  <span class="hljs-attr">age</span>: <span class="hljs-built_in">number</span>,
  <span class="hljs-attr">gender</span>: <span class="hljs-string">'男'</span>|<span class="hljs-string">'女'</span>
}[] {
    <span class="hljs-keyword">return</span> [{
      <span class="hljs-attr">name</span>: <span class="hljs-string">'tome'</span>,
    <span class="hljs-attr">age</span>: <span class="hljs-number">18</span>,
    <span class="hljs-attr">gender</span>: <span class="hljs-string">'男'</span>
  }]
}

<span class="hljs-title function_">getUsers</span>();

<span class="hljs-comment">// 上述代码中对变量的约束存在很多冗余， 我们可以把一些重复的类型提取出来，减少代码量</span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"></h3><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">函数的相关约束</h3><ul><li><p>重点讨论<strong>函数重载</strong></p></li></ul><pre data-type="codeBlock" text="function combine(a: number, b: number): number
function combine(a: string, b: string): string
function combine(a: number|string, b: number|string): number|string{
    if(typeof a === &apos;string&apos; &amp;&amp; typeof b === &apos;string&apos;) {
      return a + b
  } else if(typeof a === &apos;number&apos; &amp;&amp; typeof b === &apos;number&apos;) {
      return a * b
  }
  throw Error(&apos;传入的参数类型必须相同&apos;)
}

let num = combine(1, 2);
let num2 = combine(&apos;1&apos;, &apos;2&apos;);
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">combine</span>(<span class="hljs-params">a: number, b: number</span>): <span class="hljs-title">number</span>
<span class="hljs-title"><span class="hljs-keyword">function</span></span> <span class="hljs-title">combine</span>(<span class="hljs-params">a: <span class="hljs-keyword">string</span>, b: <span class="hljs-keyword">string</span></span>): <span class="hljs-title"><span class="hljs-keyword">string</span></span>
<span class="hljs-title"><span class="hljs-keyword">function</span></span> <span class="hljs-title">combine</span>(<span class="hljs-params">a: number|<span class="hljs-keyword">string</span>, b: number|<span class="hljs-keyword">string</span></span>): <span class="hljs-title">number</span>|<span class="hljs-title"><span class="hljs-keyword">string</span></span></span>{
    <span class="hljs-keyword">if</span>(typeof a <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">'string'</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> typeof b <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">'string'</span>) {
      <span class="hljs-keyword">return</span> a <span class="hljs-operator">+</span> b
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(typeof a <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">'number'</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> typeof b <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">'number'</span>) {
      <span class="hljs-keyword">return</span> a <span class="hljs-operator">*</span> b
  }
  <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'传入的参数类型必须相同'</span>)
}

let num <span class="hljs-operator">=</span> combine(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>);
let num2 <span class="hljs-operator">=</span> combine(<span class="hljs-string">'1'</span>, <span class="hljs-string">'2'</span>);
</code></pre><ul><li><p>可选参数</p></li></ul><pre data-type="codeBlock" text="function sum(a: number, b: number, c?: number){
    if(c) {
        return a + b + c
    } else {
        return a + b
    }
}
sum(1, 2);
"><code><span class="hljs-keyword">function</span> <span class="hljs-built_in">sum</span><span class="hljs-punctuation">(</span>a<span class="hljs-operator">:</span> number<span class="hljs-punctuation">,</span> b<span class="hljs-operator">:</span> number<span class="hljs-punctuation">,</span> <span class="hljs-built_in">c</span><span class="hljs-operator">?</span><span class="hljs-operator">:</span> number<span class="hljs-punctuation">)</span><span class="hljs-punctuation">{</span>
    <span class="hljs-keyword">if</span><span class="hljs-punctuation">(</span><span class="hljs-built_in">c</span><span class="hljs-punctuation">)</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-built_in">return</span> a <span class="hljs-operator">+</span> b <span class="hljs-operator">+</span> <span class="hljs-built_in">c</span>
    <span class="hljs-punctuation">}</span> <span class="hljs-keyword">else</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-built_in">return</span> a <span class="hljs-operator">+</span> b
    <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
<span class="hljs-built_in">sum</span><span class="hljs-punctuation">(</span><span class="hljs-number">1</span><span class="hljs-punctuation">,</span> <span class="hljs-number">2</span><span class="hljs-punctuation">)</span>;
</code></pre><ul><li><p>默认参数（默认就是可选的参数）</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">扩展类型（类型别名、枚举、接口、类）</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">枚举</h3><p>通常用于约束某个变量的取值范围（可以穷举出来的） 字面量+联合类型也是可以达到约束变量的取值范围的目的；</p><ul><li><p>字面量类型的不足</p><ul><li><p>字面量类型会重复使用，造成代码重复，可以使用类型别名解决</p></li></ul></li></ul><pre data-type="codeBlock" text="let gender: &apos;男&apos;|&apos;女&apos;;
function searchByGender(g: &apos;男&apos; | &apos;女&apos;) {
    // do some
}
// 这里的字面量类型会重复使用，我们来使用类型别名改造一下
type Gender = &apos;男&apos;|&apos;女&apos;
let gender: Gender = &quot;男&quot;;
function searchByGender(g: Gender) {
    // do some
}
"><code>let gender: <span class="hljs-string">'男'</span><span class="hljs-operator">|</span><span class="hljs-string">'女'</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">searchByGender</span>(<span class="hljs-params">g: <span class="hljs-string">'男'</span> | <span class="hljs-string">'女'</span></span>) </span>{
    <span class="hljs-comment">// do some</span>
}
<span class="hljs-comment">// 这里的字面量类型会重复使用，我们来使用类型别名改造一下</span>
<span class="hljs-keyword">type</span> Gender <span class="hljs-operator">=</span> <span class="hljs-string">'男'</span><span class="hljs-operator">|</span><span class="hljs-string">'女'</span>
let gender: Gender <span class="hljs-operator">=</span> <span class="hljs-string">"男"</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">searchByGender</span>(<span class="hljs-params">g: Gender</span>) </span>{
    <span class="hljs-comment">// do some</span>
}
</code></pre><ul><li><p>逻辑含义和真实值产生混淆，当修改真实的值的时候，会造成大量的修改</p></li></ul><pre data-type="codeBlock" text="上述代码中的男和女是真实的值，我们使用真实的值做逻辑判断，会存在很多问题
男|女  雄雌  帅哥|美女  male|female  gentleman|lady  小姐姐|小哥哥
"><code>上述代码中的男和女是真实的值，我们使用真实的值做逻辑判断，会存在很多问题
男<span class="hljs-operator">|</span>女  雄雌  帅哥<span class="hljs-operator">|</span>美女  male<span class="hljs-operator">|</span>female  gentleman<span class="hljs-operator">|</span>lady  小姐姐<span class="hljs-operator">|</span>小哥哥
</code></pre><ul><li><p>字面量类型不会进入到编译的结果中</p></li></ul><pre data-type="codeBlock" text="在编译的结果中我们没法使用Gender这个类型做一些循环操作；比如一副牌的花色。我想循环打印，使用字面量类型是做不到的
"><code></code></pre><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">枚举的使用</h4><pre data-type="codeBlock" text="enum Gender {
    male = &apos;男&apos;,
    female = &apos;女&apos;
}
let gender: Gender = Gender.male;

function searchByGender(g: Gender) {
    // do some
}
searchByGender(Gender.male);
"><code><span class="hljs-keyword">enum</span> <span class="hljs-title">Gender</span> {
    male <span class="hljs-operator">=</span> <span class="hljs-string">'男'</span>,
    female <span class="hljs-operator">=</span> <span class="hljs-string">'女'</span>
}
let gender: Gender <span class="hljs-operator">=</span> Gender.male;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">searchByGender</span>(<span class="hljs-params">g: Gender</span>) </span>{
    <span class="hljs-comment">// do some</span>
}
searchByGender(Gender.male);
</code></pre><ul><li><p>枚举的规则</p><ul><li><p>枚举的字段值可以是字符串或者数字</p></li><li><p>数字枚举的值会自动自增</p></li><li><p>被数字枚举约束的变量，可以直接赋值为数字</p></li></ul></li><li><p>最佳实践</p><ul><li><p>不要在枚举中既出现字符串又出现数字</p></li><li><p>不要直接使用真实的值</p></li></ul></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">接口</h3><p>TS的接口：用于约束类、对象、函数的契约（标准）； 接口不出现在编译结果中；</p><p>思考：接口和类型别名的差异和区别；</p><ul><li><p>简单示例</p></li></ul><pre data-type="codeBlock" text="interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: &apos;Tom&apos;,
    age: 25,
    gender: &apos;male&apos;
};

// index.ts(9,5): error TS2322: Type &apos;{ name: string; age: number; gender: string; }&apos; is not assignable to type &apos;Person&apos;.
//   Object literal may only specify known properties, and &apos;gender&apos; does not exist in type &apos;Person&apos;.
"><code><span class="hljs-keyword">interface</span> <span class="hljs-title class_">Person</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>;
    <span class="hljs-attr">age</span>: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">let</span> <span class="hljs-attr">tom</span>: <span class="hljs-title class_">Person</span> = {
    <span class="hljs-attr">name</span>: <span class="hljs-string">'Tom'</span>,
    <span class="hljs-attr">age</span>: <span class="hljs-number">25</span>,
    <span class="hljs-attr">gender</span>: <span class="hljs-string">'male'</span>
};

<span class="hljs-comment">// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.</span>
<span class="hljs-comment">//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.</span>
</code></pre><p>严格限制，多一些参数、少一些参数都不被允许。可选用？</p><ul><li><p>任意属性</p></li></ul><p>有时候我们希望一个接口允许有任意的属性，可以使用如下方式：</p><pre data-type="codeBlock" text="interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: &apos;Tom&apos;,
    gender: &apos;male&apos;
};
"><code>interface Person {
    name: string;
    age?: number;
    <span class="hljs-selector-attr">[propName: string]</span>: any;
}

let tom: Person = {
    name: <span class="hljs-string">'Tom'</span>,
    gender: <span class="hljs-string">'male'</span>
};
</code></pre><p>使用 [propName: string] 定义了任意属性取 string 类型的值。 需要注意的是，<strong>一旦定义了任意属性，那么确定属性和可选属性的类型都必须是它的类型的子集</strong>：</p><ul><li><p>只读属性</p></li></ul><pre data-type="codeBlock" text="interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}
"><code><span class="hljs-keyword">interface</span> <span class="hljs-title class_">Person</span> {
    <span class="hljs-keyword">readonly</span> <span class="hljs-attr">id</span>: <span class="hljs-built_in">number</span>;
    <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>;
    age?: <span class="hljs-built_in">number</span>;
    [<span class="hljs-attr">propName</span>: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">any</span>;
}
</code></pre><h2 id="h-ts" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">在TS中使用模块化</h2><p>在前端领域中的模块化标准：ES6、common.js、amd、umd、system、esnext；</p><h3 id="h-ts" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">ts中如何书写模块化标准？</h3><p>统一使用ES6的模块化标准</p><ul><li><p>最好不要使用默认导出</p></li><li><p>导入时不要加.ts的后缀名</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">编译结果中的模块化（可配置的）</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9175ebb84850393331490ec01dc725a96101f565ef0d4db1ccf0938f03a0c41c.png" alt="截屏2021-03-31 下午4.44.23.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">截屏2021-03-31 下午4.44.23.png</figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">泛型（附属在函数、类、接口、类型别名之上的类型）</h2><p>函数在定义时需要固定一些类型，但是具体的类型需要在调用时才能明确；</p><ul><li><p>在函数的中的应用</p></li></ul><p>解决函数中多个位置的类型保持一致或者有关联的信息丢失的问题</p><pre data-type="codeBlock" text="function take(arr: string | any[], n: number): any[] {
  if(n &gt;= arr.length) {
    return arr;
  }
  const newArr: any[] = [];
  for (let i = 0; i &lt; n; i++) {
    newArr.push(arr[i])
  }
  return newArr
}

console.log(take([12,1,2,13,1,21,33,1], 2))
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">take</span>(<span class="hljs-params">arr: <span class="hljs-keyword">string</span> | any[], n: number</span>): <span class="hljs-title">any</span>[] </span>{
  <span class="hljs-keyword">if</span>(n <span class="hljs-operator">></span><span class="hljs-operator">=</span> arr.<span class="hljs-built_in">length</span>) {
    <span class="hljs-keyword">return</span> arr;
  }
  const newArr: any[] <span class="hljs-operator">=</span> [];
  <span class="hljs-keyword">for</span> (let i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> n; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
    newArr.<span class="hljs-built_in">push</span>(arr[i])
  }
  <span class="hljs-keyword">return</span> newArr
}

console.log(take([<span class="hljs-number">12</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">13</span>,<span class="hljs-number">1</span>,<span class="hljs-number">21</span>,<span class="hljs-number">33</span>,<span class="hljs-number">1</span>], <span class="hljs-number">2</span>))
</code></pre><p>上面这个代码，想截取固定长度的数组返回；由于不同的情况，我们不得不限制三处的any[]，但其实这三处是一个类型，我们会丢失这个信息；为解决这个问题，我们引入泛型的概念；</p><pre data-type="codeBlock" text="function take&lt;T&gt;(arr: T[], n: number): T[] {
  if(n &gt;= arr.length) {
    return arr;
  }
  const newArr: T[] = [];
  // eslint-disable-next-line no-plusplus
  for (let i = 0; i &lt; n; i++) {
    newArr.push(arr[i])
  }
  return newArr
}

const newArr = take&lt;number&gt;([12,1,2,13,1,21,33,1], 2);
==&gt;智能推导 const newArr = take([12,1,2,13,1,21,33,1], 2);
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">take</span>&#x3C;<span class="hljs-title">T</span>>(<span class="hljs-params">arr: T[], n: number</span>): <span class="hljs-title">T</span>[] </span>{
  <span class="hljs-keyword">if</span>(n <span class="hljs-operator">></span><span class="hljs-operator">=</span> arr.<span class="hljs-built_in">length</span>) {
    <span class="hljs-keyword">return</span> arr;
  }
  const newArr: T[] <span class="hljs-operator">=</span> [];
  <span class="hljs-comment">// eslint-disable-next-line no-plusplus</span>
  <span class="hljs-keyword">for</span> (let i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> n; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
    newArr.<span class="hljs-built_in">push</span>(arr[i])
  }
  <span class="hljs-keyword">return</span> newArr
}

const newArr <span class="hljs-operator">=</span> take<span class="hljs-operator">&#x3C;</span>number<span class="hljs-operator">></span>([<span class="hljs-number">12</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">13</span>,<span class="hljs-number">1</span>,<span class="hljs-number">21</span>,<span class="hljs-number">33</span>,<span class="hljs-number">1</span>], <span class="hljs-number">2</span>);
<span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">></span>智能推导 const newArr <span class="hljs-operator">=</span> take([<span class="hljs-number">12</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">13</span>,<span class="hljs-number">1</span>,<span class="hljs-number">21</span>,<span class="hljs-number">33</span>,<span class="hljs-number">1</span>], <span class="hljs-number">2</span>);
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[ES6]]></title>
            <link>https://paragraph.com/@murraya/es6</link>
            <guid>1Iret2fkujTr5G0tX43I</guid>
            <pubDate>Wed, 11 May 2022 16:17:51 GMT</pubDate>
            <description><![CDATA[概述在JS发展过程中，表现出强大的活力，但是语言也存在一些问题。为了满足前端领域的新的需求，2015年官方出来的ECMAScript新版本--ES6，又称作ES2015，且从ES2015开始，官方不再使用数字作为版本编号，而采用年份。2015年之后，几乎每年都有更新版本，有些改动比较小，有些改动比较激进；所以我们重点讨论ES2015版本；重要的变化块级绑定ES6新增let、const命名方式let和const申明的变量只在它所在的代码块中有效；不存在变量声明提升；不允许重复声明；Es6中的函数改动默认参数（在函数定义时形参中指定默认值）剩余参数展开运算符函数的双重作用（普通函数和构造函数）Es6中定义一个API可以在函数内部判断是否使用new来调用了该函数 new.target ** **这个表达式得到的是如果没有使用new来调用函数，则返回undefined 如果使用了，则返回new关键字后面的函数函数的双重作用 function Proson(firstName, lastName) { // es6之前判断是否使用new；这个方式并不完美，可能会被绕开 if(this in...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">概述</h1><p>在JS发展过程中，表现出强大的活力，但是语言也存在一些问题。为了满足前端领域的新的需求，2015年官方出来的ECMAScript新版本--ES6，又称作ES2015，且从ES2015开始，官方不再使用数字作为版本编号，而采用年份。2015年之后，几乎每年都有更新版本，有些改动比较小，有些改动比较激进；所以我们重点讨论ES2015版本；</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">重要的变化</h1><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">块级绑定</h2><p>ES6新增let、const命名方式</p><ul><li><p>let和const申明的变量只在它所在的代码块中有效；</p></li><li><p>不存在变量声明提升；</p></li><li><p>不允许重复声明；</p></li></ul><h2 id="h-es6" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Es6中的函数改动</h2><ul><li><p><strong>默认参数（在函数定义时形参中指定默认值</strong>）</p></li><li><p><strong>剩余参数</strong></p></li><li><p><strong>展开运算符</strong></p></li><li><p><strong>函数的双重作用（普通函数和构造函数）</strong></p></li></ul><p>Es6中定义一个API可以在函数内部判断是否使用new来调用了该函数         <strong>new.target</strong> **        **这个表达式得到的是如果没有使用new来调用函数，则返回undefined         如果使用了，则返回new关键字后面的函数</p><pre data-type="codeBlock" text="函数的双重作用
function Proson(firstName, lastName) {
    // es6之前判断是否使用new；这个方式并不完美，可能会被绕开
    if(this instanceof Person){
        Throw new Error(“该函数必须使用new来调用&quot;)
    }
    // es6的方式
    if(new.target === undefined) {
        Throw new Error(“该函数必须使用new来调用&quot;)
    }
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = `${firstName} ${lastName}`
}

const p1 = new Person(‘张’, ’三’);
Const p2 = Person(’张’, ’三’);
const p3 = Person.call(p1,’张’, ’三’);
"><code>函数的双重作用
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Proson</span>(<span class="hljs-params">firstName, lastName</span>) </span>{
    <span class="hljs-comment">// es6之前判断是否使用new；这个方式并不完美，可能会被绕开</span>
    <span class="hljs-keyword">if</span>(<span class="hljs-built_in">this</span> instanceof Person){
        Throw <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(“该函数必须使用<span class="hljs-keyword">new</span>来调用<span class="hljs-string">")
    }
    // es6的方式
    if(new.target === undefined) {
        Throw new Error(“该函数必须使用new来调用"</span>)
    }
    <span class="hljs-built_in">this</span>.firstName <span class="hljs-operator">=</span> firstName;
    <span class="hljs-built_in">this</span>.lastName <span class="hljs-operator">=</span> lastName;
    <span class="hljs-built_in">this</span>.fullName <span class="hljs-operator">=</span> `${firstName} ${lastName}`
}

const p1 <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Person(‘张’, ’三’);
Const p2 <span class="hljs-operator">=</span> Person(’张’, ’三’);
const p3 <span class="hljs-operator">=</span> Person.<span class="hljs-built_in">call</span>(p1,’张’, ’三’);
</code></pre><ul><li><p><strong>箭头函数</strong></p><p>回顾 this指向的情况 <strong>普通函数</strong></p></li></ul><ol><li><p>通过对象调用函数，this指向对象；</p></li><li><p>直接调用函数，this指向全局对象；</p></li><li><p>通过new关键字调用，this指向新创建的对象；</p></li><li><p>通过call、apply和bind调用函数，this指向制定的对象；</p></li><li><p>事件函数中，this指向事件源对象；</p></li></ol><p><strong>箭头函数</strong></p><pre data-type="codeBlock" text="const sdsd = function (df) {                           
    return df * 2;                  ====&gt;         const sdsd = (df) =&gt; df * 2;
}
"><code>const sdsd <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">df</span>) </span>{                           
    <span class="hljs-keyword">return</span> df <span class="hljs-operator">*</span> <span class="hljs-number">2</span>;                  <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">></span>         const sdsd <span class="hljs-operator">=</span> (df) <span class="hljs-operator">=</span><span class="hljs-operator">></span> df <span class="hljs-operator">*</span> <span class="hljs-number">2</span>;
}
</code></pre><p><strong>箭头函数的this取决于声明函数（书写位置）时的this；与调用的方式无关；</strong></p><p><strong>细节：</strong> 箭头函数中不存在this、arguments、new.target，如果使用了这些则使用的是函数外层的this、arguments、new.target；        没有原型，所以对内存占用小，比较经济； 箭头函数不能作为构造函数;</p><p><strong>使用场景</strong>： 临时使用的函数，并不会刻意的使用它，比如： 事件处理函数；异步处理函数；其他临时性函数；为了绑定外层this的函数；在不影响其他代码的情况下，保持代码的整洁；</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">对象的变化</h2><ul><li><p><strong>成员速写</strong></p></li></ul><p>key和value(变量）一样时可以简写</p><ul><li><p><strong>方法速写</strong></p></li></ul><p>方法省略冒号和function</p><ul><li><p><strong>计算属性名</strong></p></li></ul><p>在ES6之前对象的key不能用变量表示，在ES6中可以使用［变量］来表示key。</p><pre data-type="codeBlock" text="const props1 = &apos;name&apos;;
const props2 = &apos;age&apos;;
const props3 = &apos;sayHello&apos;;
const nationality = &apos;中国&apos;;
const user = {
  nationality,
    [props1]: &apos;张三&apos;,
  [props2]: 20,
  [props3]() {
      console.log(this[props1] + this[props2]);
  }
}
"><code>const <span class="hljs-attr">props1</span> = <span class="hljs-string">'name'</span><span class="hljs-comment">;</span>
const <span class="hljs-attr">props2</span> = <span class="hljs-string">'age'</span><span class="hljs-comment">;</span>
const <span class="hljs-attr">props3</span> = <span class="hljs-string">'sayHello'</span><span class="hljs-comment">;</span>
const <span class="hljs-attr">nationality</span> = <span class="hljs-string">'中国'</span><span class="hljs-comment">;</span>
const <span class="hljs-attr">user</span> = {
  nationality,
    <span class="hljs-section">[props1]</span>: '张三',
  <span class="hljs-section">[props2]</span>: 20,
  <span class="hljs-section">[props3]</span>() {
      console.log(this<span class="hljs-section">[props1]</span> + this<span class="hljs-section">[props2]</span>)<span class="hljs-comment">;</span>
  }
}
</code></pre><ul><li><p><strong>新增的API</strong></p><ul><li><p><strong>Object is</strong></p></li></ul></li></ul><p>判断两个对象是否相等。和**===**的用法大体一致，除了两种情况：    NaN===NaN  false    <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://Object.is">Object.is</a>(NaN,NaN)   true    －0 ===0      <strong>true   </strong><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://Object.is"><strong>Object.is</strong></a><strong>(0, -0)   false</strong></p><ul><li><p><strong>Object.assign</strong></p></li></ul><p>可以传递多个参数，会将第一个参数之后的参数混合到第一个参数中并返回第一个参数，这个方法会<strong>改变</strong>第一个参数，所以目前使用的情况不多，可以被ES7中的<strong>展开运算符</strong>所取代。</p><ul><li><p>Object.getOwnPropertyNames（不常用）</p></li><li><p>Object.setPrototypeOf（不常用）</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">解构</h2><ul><li><p><strong>对象的解构</strong></p></li></ul><pre data-type="codeBlock" text="let user = {
    name: &apos;tom&apos;,
    age: 18,
    address: {
        province: &apos;湖南&apos;,
        city: &apos;常德&apos;
    }
}

let {name, age, address: {province, city, add=123}} = user;
let {name, ...other} = user;

console.log(name); // &apos;tom&apos;
console.log(age); // 18
concole.log(other); // {age: 18, address: { province: &apos;湖南&apos;, city: &apos;常德&apos;}}
console.log(province); // &apos;湖南&apos;  这里是深层次的解构方式
console.log(address); // undefined 这里并不会定义address变量，而是进一步解构
console.log(add); // 123   这里是解构默认值的写法
"><code>let user <span class="hljs-operator">=</span> {
    name: <span class="hljs-string">'tom'</span>,
    age: <span class="hljs-number">18</span>,
    <span class="hljs-keyword">address</span>: {
        province: <span class="hljs-string">'湖南'</span>,
        city: <span class="hljs-string">'常德'</span>
    }
}

let {name, age, <span class="hljs-keyword">address</span>: {province, city, add<span class="hljs-operator">=</span><span class="hljs-number">123</span>}} <span class="hljs-operator">=</span> user;
let {name, ...other} <span class="hljs-operator">=</span> user;

console.log(name); <span class="hljs-comment">// 'tom'</span>
console.log(age); <span class="hljs-comment">// 18</span>
concole.log(other); <span class="hljs-comment">// {age: 18, address: { province: '湖南', city: '常德'}}</span>
console.log(province); <span class="hljs-comment">// '湖南'  这里是深层次的解构方式</span>
console.log(<span class="hljs-keyword">address</span>); <span class="hljs-comment">// undefined 这里并不会定义address变量，而是进一步解构</span>
console.log(add); <span class="hljs-comment">// 123   这里是解构默认值的写法</span>
</code></pre><p>非同名属性解构：</p><pre data-type="codeBlock" text="let {name, age: nianji = 12 , address: {province, city, address=123}} = user;
// 这里会先定义‘nianji’变量，然后user中读取同名户型赋值（其中&apos;nianji&apos;读取age属性）
数组的解构
"><code>let {name, age: nianji <span class="hljs-operator">=</span> <span class="hljs-number">12</span> , <span class="hljs-keyword">address</span>: {province, city, <span class="hljs-keyword">address</span><span class="hljs-operator">=</span><span class="hljs-number">123</span>}} <span class="hljs-operator">=</span> user;
<span class="hljs-comment">// 这里会先定义‘nianji’变量，然后user中读取同名户型赋值（其中'nianji'读取age属性）</span>
数组的解构
</code></pre><ul><li><p><strong>数组的解构</strong></p></li></ul><pre data-type="codeBlock" text="let arr1 = [1,2,3,4,5,6, [10, 11, 12], {a:20, b: 21, c: 22}];
const {0: n1, 3: n3} = arr1;
n1 // 1
n3: // 4
const [n1, n2] = arr1;
n1 // 1,
n2 // 2
const [n1, , n3] = arr1;
n1 // 1,
n3 // 3
const [n1, ...n2] = arr1;
n2 // [2,3,4,5,6]
const [,,,,,, [,b1, c1], {a, b}] = arr1;
"><code>let arr1 <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>, [<span class="hljs-number">10</span>, <span class="hljs-number">11</span>, <span class="hljs-number">12</span>], {a:<span class="hljs-number">20</span>, b: <span class="hljs-number">21</span>, c: <span class="hljs-number">22</span>}];
const {<span class="hljs-number">0</span>: n1, <span class="hljs-number">3</span>: n3} <span class="hljs-operator">=</span> arr1;
n1 <span class="hljs-comment">// 1</span>
n3: <span class="hljs-comment">// 4</span>
const [n1, n2] <span class="hljs-operator">=</span> arr1;
n1 <span class="hljs-comment">// 1,</span>
n2 <span class="hljs-comment">// 2</span>
const [n1, , n3] <span class="hljs-operator">=</span> arr1;
n1 <span class="hljs-comment">// 1,</span>
n3 <span class="hljs-comment">// 3</span>
const [n1, ...n2] <span class="hljs-operator">=</span> arr1;
n2 <span class="hljs-comment">// [2,3,4,5,6]</span>
const [,,,,,, [,b1, c1], {a, b}] <span class="hljs-operator">=</span> arr1;
</code></pre><ul><li><p><strong>参数的解构</strong></p></li></ul><pre data-type="codeBlock" text="function as({name, age}){
    console.log(name, age);// 123, 18
}
as({name: 123, age: 18});
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span>(<span class="hljs-params">{name, age}</span>)</span>{
    console.log(name, age);<span class="hljs-comment">// 123, 18</span>
}
<span class="hljs-keyword">as</span>({name: <span class="hljs-number">123</span>, age: <span class="hljs-number">18</span>});
</code></pre><h2 id="h-promise" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">异步解决方案--Promise</h2><p>ES6总结了各种异步场景，并提取出一种通用的异步模型</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/75faf92196df3ad5b35688d2b9c2a0918b75cfad0e50cebe5c447bf3ecc6d681.png" alt="37686281-8600-407f-9c93-a97feda10283.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">37686281-8600-407f-9c93-a97feda10283.png</figcaption></figure><p>当任务处于已决阶段时，它只能是resolved和rejected两种状态中的一种，表示任务有了一个结果。比如：从服务器拿到了数据（resolved）、网络不好的时候没有拿到数据（rejected）</p><p>任务开始时，始终是未决阶段，那任务如何才能走到已决阶段呢？ ES6认为，任务在未决阶段的时候，有能力将其推向已决。比如，当从服务器中拿到数据后，我们就从未决阶段推向已决的resolved状态，如果网络不好，导致出错。我们就从未决阶段推向已决的rejected状态； 我们把从未决阶段推向已决的resolved状态的过程，叫做resolve，从未决推向已决的rejected状态的过程，叫做reject</p><p>这种状态和阶段的变化是不可逆的，也就是说，一旦推向了已决，就无法重新改变状态 任务从未决到已决时，可能附带一些数据，比如：跑步完成后的用时、网络请求后从服务器拿到的数据</p><p>任务已决后（有了结果），可能需要进一步做后续处理，如果任务成功了（resolved），有后续处理，如果任务失败了（rejected），仍然可能有后续处理 我们把针对resolved的后续处理，称之为thenable，针对rejected的后续处理，称之为catchable；</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">迭代器</h2><ul><li><p><strong>迭代</strong>：将数据从数据结构中按照一定的顺序取出来，不断取出的过程，但是不一定全部取完；</p></li><li><p><strong>遍历</strong>：将数据从数据结构中一个个取出来。一定是依次全部取完；</p></li></ul><p>再日常的写代码中，迭代和遍历往往边界模糊，在代码中的表现往往差异不大。但是这是两种思想；</p><p><strong>迭代器</strong>是一个对象，是对迭代过程的封装，在不同的语言中有不同的表现形式，通常为对象；</p><p>迭代模式：一种设计模式，用于统一迭代过程，并规范迭代器的规格：</p><ul><li><p>迭代器应该具有的到下一个数据的能力；</p></li><li><p>迭代器应该具有判断是否存在下一个数据的能力；</p></li></ul><h3 id="h-js" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">js中的迭代器</h3><p>JS规定，如果一个对象具有next方法，并且该方法返回一个对象，该对象的格式如下：</p><pre data-type="codeBlock" text="{
    value: 值, done: ‘是否迭代完成’
}
"><code>{
    <span class="hljs-attr">value:</span> <span class="hljs-string">值</span>, <span class="hljs-attr">done:</span> <span class="hljs-string">‘是否迭代完成’</span>
}
</code></pre><p>数组的迭代器</p><pre data-type="codeBlock" text="const arr = [1,2,3,4,5,6];
function createIterator(arr) {
  let i = 0;
  return {
    next() {
      let result = {
        value: arr[i],
        done: i &gt;= arr.length
      }
      i++;
      return result;
    }
  }
}
"><code>const arr <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>];
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createIterator</span>(<span class="hljs-params">arr</span>) </span>{
  let i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
  <span class="hljs-keyword">return</span> {
    next() {
      let result <span class="hljs-operator">=</span> {
        <span class="hljs-built_in">value</span>: arr[i],
        done: i <span class="hljs-operator">></span><span class="hljs-operator">=</span> arr.<span class="hljs-built_in">length</span>
      }
      i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
      <span class="hljs-keyword">return</span> result;
    }
  }
}
</code></pre><p>例子：</p><pre data-type="codeBlock" text="function creactFeiboIterator() {
  let prev1 = 1;
  let prev2 = 1;
  let n = 1;
  return {
    next() {
      let value;
      if (n &lt;= 2) {
          value = 1;
      } else {
          value = prev2 + prev1;
      }
      const result = {
        value,
        done: false,
      };
      prev2 = prev1;
      prev1 = result.value;
      n += 1;
      return result;
    },
  };
}
const feiboIterator = creactFeiboIterator();
console.log(feiboIterator.next());
console.log(feiboIterator.next());
console.log(feiboIterator.next());
console.log(feiboIterator.next());
console.log(feiboIterator.next());
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">creactFeiboIterator</span>(<span class="hljs-params"></span>) </span>{
  let prev1 <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
  let prev2 <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
  let n <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
  <span class="hljs-keyword">return</span> {
    next() {
      let value;
      <span class="hljs-keyword">if</span> (n <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-number">2</span>) {
          value <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
      } <span class="hljs-keyword">else</span> {
          value <span class="hljs-operator">=</span> prev2 <span class="hljs-operator">+</span> prev1;
      }
      const result <span class="hljs-operator">=</span> {
        value,
        done: <span class="hljs-literal">false</span>,
      };
      prev2 <span class="hljs-operator">=</span> prev1;
      prev1 <span class="hljs-operator">=</span> result.<span class="hljs-built_in">value</span>;
      n <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
      <span class="hljs-keyword">return</span> result;
    },
  };
}
const feiboIterator <span class="hljs-operator">=</span> creactFeiboIterator();
console.log(feiboIterator.next());
console.log(feiboIterator.next());
console.log(feiboIterator.next());
console.log(feiboIterator.next());
console.log(feiboIterator.next());
</code></pre><h3 id="h-for-of" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">可迭代协议和for of循环</h3><p>ES规定，如果一个对象具有知名符号属性**[Symbol.iterator]**,并且其属性值是一个迭代器创建函数，则这个对象是可迭代的；</p><p><strong>例如数组：</strong></p><pre data-type="codeBlock" text="const ddd = [1, 2, 3, 4, 5, 6, 7];
const arrIterator = ddd[Symbol.iterator]();
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
"><code>const ddd <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>];
const arrIterator <span class="hljs-operator">=</span> ddd[Symbol.iterator]();
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
</code></pre><p><strong>dom对象（伪数组）</strong></p><pre data-type="codeBlock" text="// &lt;p&gt;&lt;/p&gt;
// &lt;p&gt;&lt;/p&gt;
// &lt;p&gt;&lt;/p&gt;
// &lt;p&gt;&lt;/p&gt;
const ps = document.querySelectorAll(&apos;p&apos;);
const pIerator = ps[Symbol.iterator]();
console.log(pIerator.next());
console.log(pIerator.next());
console.log(pIerator.next());
console.log(pIerator.next());
"><code><span class="hljs-comment">// &#x3C;p>&#x3C;/p></span>
<span class="hljs-comment">// &#x3C;p>&#x3C;/p></span>
<span class="hljs-comment">// &#x3C;p>&#x3C;/p></span>
<span class="hljs-comment">// &#x3C;p>&#x3C;/p></span>
const ps <span class="hljs-operator">=</span> document.querySelectorAll(<span class="hljs-string">'p'</span>);
const pIerator <span class="hljs-operator">=</span> ps[Symbol.iterator]();
console.log(pIerator.next());
console.log(pIerator.next());
console.log(pIerator.next());
console.log(pIerator.next());
</code></pre><p><strong>数组的遍历</strong></p><pre data-type="codeBlock" text="const ddd = [1, 2, 3, 4, 5, 6, 7];
const arrIterator = ddd[Symbol.iterator]();
let result = arrIterator.next();
while (!result.done) {
    console.log(result.value);
    result = arrIterator.next();
}
// 这里的遍历可以抽象为一个特定的模式：
"><code>const ddd <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>];
const arrIterator <span class="hljs-operator">=</span> ddd[Symbol.iterator]();
let result <span class="hljs-operator">=</span> arrIterator.next();
<span class="hljs-keyword">while</span> (<span class="hljs-operator">!</span>result.done) {
    console.log(result.<span class="hljs-built_in">value</span>);
    result <span class="hljs-operator">=</span> arrIterator.next();
}
<span class="hljs-comment">// 这里的遍历可以抽象为一个特定的模式：</span>
</code></pre><p>ES6中提供了一个for of循环来做上面的事情；</p><pre data-type="codeBlock" text="// 和上面的代码是完全等价的（语法糖）
for (const item of ddd) {
console.log(item);
}
"><code><span class="hljs-comment">// 和上面的代码是完全等价的（语法糖）</span>
<span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> item of ddd) {
console.<span class="hljs-built_in">log</span>(item);
}
</code></pre><p>遍历一个对象</p><pre data-type="codeBlock" text="const abc = {
  a: 1,
  b: 2,
  c: 3,
  Symbol.iterator {
    let i = 0;
    const keys = Object.keys(this);
    const values = Object.values(this);
    return {
      next() {
        const result = {
          value: {
            propsName: keys[i],
            propValue: values[i],
          },
          done: i &gt; keys.length,
        };
        i += 1;
        return result;
      },
    };
  },
};

// eslint-disable-next-line no-restricted-syntax
for (const item of abc) {
    console.log(item);
}
"><code>const abc <span class="hljs-operator">=</span> {
  a: <span class="hljs-number">1</span>,
  b: <span class="hljs-number">2</span>,
  c: <span class="hljs-number">3</span>,
  Symbol.iterator {
    let i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
    const keys <span class="hljs-operator">=</span> Object.keys(<span class="hljs-built_in">this</span>);
    const values <span class="hljs-operator">=</span> Object.values(<span class="hljs-built_in">this</span>);
    <span class="hljs-keyword">return</span> {
      next() {
        const result <span class="hljs-operator">=</span> {
          <span class="hljs-built_in">value</span>: {
            propsName: keys[i],
            propValue: values[i],
          },
          done: i <span class="hljs-operator">></span> keys.<span class="hljs-built_in">length</span>,
        };
        i <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
        <span class="hljs-keyword">return</span> result;
      },
    };
  },
};

<span class="hljs-comment">// eslint-disable-next-line no-restricted-syntax</span>
<span class="hljs-keyword">for</span> (const item of abc) {
    console.log(item);
}
</code></pre><p><strong>补充：</strong></p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">展开运算符与可迭代对象</h3><p>展开运算符可以用于可迭代对象，这样可以轻松的将可迭代对象转换为数组； 例如：接上面的代码</p><pre data-type="codeBlock" text="...
const arr = [...abc];
console.log(arr);
"><code>...
const arr <span class="hljs-operator">=</span> [...abc];
console.log(arr);
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">增强的数组功能</h2><ul><li><p><strong>新增的数组API</strong></p></li></ul><p>静态方法</p><ul><li><p>Array.of(...arg): 使用制定的数组项创建一个新的数组</p></li><li><p>Array.from(arg): 通过给定的类数组 或者可迭代的对象，创建一个新的数组。</p></li></ul><p>实例方法</p><ul><li><p>find(callback): 用于查找满足条件的第一个元素</p></li><li><p>findIndex(callback)：用于查找满足条件的第一个元素的下标</p></li><li><p>fill(data)：用指定的数据填充满数组所有的内容</p></li><li><p>copyWithin(target, start?, end?): 在数组内部完成复制</p></li></ul><pre data-type="codeBlock" text="const arr = [1, 2, 3, 4, 5, 6];
//从下标2开始，改变数组的数据，数据来自于下标0位置开始
// arr.copyWithin(2); // [1, 2, 1, 2, 3, 4]
// arr.copyWithin(2, 1); // [1, 2, 2, 3, 4, 5]

// arr.copyWithin(2, 1, 3); // [1, 2, 2, 3, 5, 6]
console.log(arr)
"><code>const arr <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>];
<span class="hljs-comment">//从下标2开始，改变数组的数据，数据来自于下标0位置开始</span>
<span class="hljs-comment">// arr.copyWithin(2); // [1, 2, 1, 2, 3, 4]</span>
<span class="hljs-comment">// arr.copyWithin(2, 1); // [1, 2, 2, 3, 4, 5]</span>

<span class="hljs-comment">// arr.copyWithin(2, 1, 3); // [1, 2, 2, 3, 5, 6]</span>
console.log(arr)
</code></pre><ul><li><p>includes(data)：判断数组中是否包含某个值，使用Object.is匹配 const arr = [45, 21, 356, 66 , 6, NaN, 723, 54]; console.log(arr.indexOf(66) &gt;= 0) console.log(arr.includes(NaN));</p></li></ul>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[react-router源码解析]]></title>
            <link>https://paragraph.com/@murraya/react-router</link>
            <guid>tzLTrAtRSA9NGc2iP4Qr</guid>
            <pubDate>Wed, 11 May 2022 16:14:59 GMT</pubDate>
            <description><![CDATA[history对象该对象提供了一些方法，用于控制或监听地址的变化。 该对象不是window.history，而是一个抽离的对象，它提供统一的API接口，封装了具体的实现createBrowserHistory 产生的控制浏览器真实地址的history对象createHashHistory 产生的控制浏览器hash的history对象createMemoryHistory 产生的控制内存中地址数组的history对象history对象共同的特点：维护了一个地址栈 第三方库：history 以下三个函数，虽然名称和参数不同，但返回的对象结构(history)完全一致history对象action：当前地址栈，最后一次操作的类型如果是通过createXXXHistory函数新创建的history对象，action固定为POP如果调用了history的push方法，action变为PUSH如果调用了history的replace方法，action变为REPLACEpush：向当前地址栈指针位置，入栈一个地址replace：替换指针指向的地址go：控制当前地址栈指针偏移，如果是0，地址不变...]]></description>
            <content:encoded><![CDATA[<h1 id="h-history" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">history对象</h1><p>该对象提供了一些方法，用于控制或监听地址的变化。</p><p>该对象<strong>不是</strong>window.history，而是一个抽离的对象，它提供统一的API接口，封装了具体的实现</p><ul><li><p>createBrowserHistory  产生的控制浏览器真实地址的history对象</p></li><li><p>createHashHistory  产生的控制浏览器hash的history对象</p></li><li><p>createMemoryHistory  产生的控制内存中地址数组的history对象</p></li></ul><p>history对象共同的特点：维护了一个地址栈</p><p>第三方库：history</p><p><strong>以下三个函数，虽然名称和参数不同，但返回的对象结构(history)完全一致</strong></p><h2 id="h-history" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">history对象</h2><ul><li><p>action：当前地址栈，最后一次操作的类型</p><ul><li><p>如果是通过createXXXHistory函数新创建的history对象，action固定为POP</p></li><li><p>如果调用了history的push方法，action变为PUSH</p></li><li><p>如果调用了history的replace方法，action变为REPLACE</p></li></ul></li><li><p>push：向当前地址栈指针位置，入栈一个地址</p></li><li><p>replace：替换指针指向的地址</p></li><li><p>go：控制当前地址栈指针偏移，如果是0，地址不变；如果是负数，则后退指定的步数；如果是正数，则前进指定的步数；</p></li><li><p>length：当前栈中的地址数量</p></li><li><p>goBack：相当于go(-1)</p></li><li><p>goForward：相当于go(1)</p></li><li><p>location：表达当前地址中的信息</p></li><li><p>listen：函数，用于监听地址栈指针的变化</p><ul><li><p>该函数接收一个函数作为参数，该参数表示地址变化后要做的事情</p><ul><li><p>参数函数接收两个参数</p></li><li><p>location：记录了新的地址</p></li><li><p>action：进入新地址的方式</p><ul><li><p>POP：指针移动，调用go、goBack、goForward、用户点击浏览器后退按钮</p></li><li><p>PUSH：调用history.push</p></li><li><p>REPLACE：调用history.replace</p></li></ul></li></ul></li><li><p>该函数有一个返回值，返回的是一个函数，用于取消监听</p></li></ul></li><li><p>block：用于设置一个阻塞，当页面发生跳转时，会将指定的消息传递到getUserConfirmation，并调用getUserConfirmation函数</p><ul><li><p>该函数接收一个字符串作为参数，表示消息内容，也可以接收一个函数作为参数，函数的返回值是消息内容</p></li><li><p>该函数返回一个取消函数，调用取消函数可以解除阻塞</p></li></ul></li><li><p>createHref：basename+url</p></li></ul><h2 id="h-createbrowserhistory" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">createBrowserHistory</h2><p>创建一个使用浏览器History Api的history对象</p><p>配置对象：</p><ul><li><p>basename：设置根路径</p></li><li><p>forceRefresh：地址改变时是否强制刷新页面</p></li><li><p>keyLength：location对象使用的key值长度</p><ul><li><p>地址栈中记录的并非字符串，而是一个location对象</p></li></ul></li><li><p>getUserConfirmation：一个函数，该函数当调用history对象block函数后，发生页面跳转时运行</p></li></ul><h2 id="h-createhashhistory" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">createHashHistory</h2><p>创建一个使用浏览器hash的history对象</p><p>配置对象：</p><ul><li><p>hashType：#号后给定的路径格式</p><ul><li><p>hashbang：被chrome弃用，#!路径</p></li><li><p>noslash：#a/b/c</p></li><li><p>slash：#/a/b/c</p></li></ul></li></ul><h2 id="h-creatememoryhistory" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">createMemoryHistory</h2><p>创建一个使用内存中的地址栈的history对象，一般用于没有地址栏的环境</p><p>配置对象：详见memoryHistory.js</p><h2 id="h-react-router" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">react-router中路由的匹配规则实现</h2><pre data-type="codeBlock" text="import pathToRegexp from &quot;path-to-regexp&quot;;

/**
 * 得到匹配结果（match对象），如果没有匹配，返回null
 * @param {*} path 路径规则
 * @param {*} pathname 真实的地址
 * @param {*} options 相关配置，该配置是一个对象，该对象中，可以出现：exact、sensitive、strict
 */
export default function matchPath(path, pathname, options) {
    const keys = [];//保存路径规则中的关键字
    const regExp = pathToRegexp(path, keys, getOptions(options));
    const result = regExp.exec(pathname); //匹配url地址
    if (!result) {
        return null; //没有匹配
    }
    //匹配了
    let groups = Array.from(result);
    groups = groups.slice(1); //得到匹配的分组结果
    const params = getParams(groups, keys);
    return {
        isExact: pathname === result[0],
        params,
        path,
        url: result[0]
    };
}

/**
 * 将传入的react-router配置，转换为path-to-regexp的配置
 * @param {*} options 
 */
function getOptions(options = {}) {
    const defaultOptions = {
        exact: false,
        sensitive: false,
        strict: false
    }
    const opts = { ...defaultOptions, ...options };
    return {
        sensitive: opts.sensitive,
        strict: opts.strict,
        end: opts.exact
    }
}

/**
 * 根据匹配的分组结果，得到一个params对象
 * @param {*} groups 
 * @param {*} keys 
 */
function getParams(groups, keys) {
    const obj = {};
    for (let i = 0; i &lt; groups.length; i++) {
        const value = groups[i];
        const name = keys[i].name;
        obj[name] = value;
    }
    return obj;
}
"><code><span class="hljs-keyword">import</span> <span class="hljs-title">pathToRegexp</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"path-to-regexp"</span>;

<span class="hljs-comment">/**
 * 得到匹配结果（match对象），如果没有匹配，返回null
 * @param {*} path 路径规则
 * @param {*} pathname 真实的地址
 * @param {*} options 相关配置，该配置是一个对象，该对象中，可以出现：exact、sensitive、strict
 */</span>
export default <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">matchPath</span>(<span class="hljs-params">path, pathname, options</span>) </span>{
    const keys <span class="hljs-operator">=</span> [];<span class="hljs-comment">//保存路径规则中的关键字</span>
    const regExp <span class="hljs-operator">=</span> pathToRegexp(path, keys, getOptions(options));
    const result <span class="hljs-operator">=</span> regExp.exec(pathname); <span class="hljs-comment">//匹配url地址</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>result) {
        <span class="hljs-keyword">return</span> null; <span class="hljs-comment">//没有匹配</span>
    }
    <span class="hljs-comment">//匹配了</span>
    let groups <span class="hljs-operator">=</span> Array.from(result);
    groups <span class="hljs-operator">=</span> groups.slice(<span class="hljs-number">1</span>); <span class="hljs-comment">//得到匹配的分组结果</span>
    const params <span class="hljs-operator">=</span> getParams(groups, keys);
    <span class="hljs-keyword">return</span> {
        isExact: pathname <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> result[<span class="hljs-number">0</span>],
        params,
        path,
        url: result[<span class="hljs-number">0</span>]
    };
}

<span class="hljs-comment">/**
 * 将传入的react-router配置，转换为path-to-regexp的配置
 * @param {*} options 
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOptions</span>(<span class="hljs-params">options = {}</span>) </span>{
    const defaultOptions <span class="hljs-operator">=</span> {
        exact: <span class="hljs-literal">false</span>,
        sensitive: <span class="hljs-literal">false</span>,
        strict: <span class="hljs-literal">false</span>
    }
    const opts <span class="hljs-operator">=</span> { ...defaultOptions, ...options };
    <span class="hljs-keyword">return</span> {
        sensitive: opts.sensitive,
        strict: opts.strict,
        end: opts.exact
    }
}

<span class="hljs-comment">/**
 * 根据匹配的分组结果，得到一个params对象
 * @param {*} groups 
 * @param {*} keys 
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getParams</span>(<span class="hljs-params">groups, keys</span>) </span>{
    const obj <span class="hljs-operator">=</span> {};
    <span class="hljs-keyword">for</span> (let i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> groups.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
        const value <span class="hljs-operator">=</span> groups[i];
        const name <span class="hljs-operator">=</span> keys[i].<span class="hljs-built_in">name</span>;
        obj[name] <span class="hljs-operator">=</span> value;
    }
    <span class="hljs-keyword">return</span> obj;
}
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[webpack-loader]]></title>
            <link>https://paragraph.com/@murraya/webpack-loader</link>
            <guid>3MAjt82EL94Gc2yyFS46</guid>
            <pubDate>Wed, 11 May 2022 16:10:05 GMT</pubDate>
            <description><![CDATA[webpack做的事情，仅仅是分析出各种模块的依赖关系，然后形成资源列表，最终打包生成到指定的文件中。 更多的功能需要借助webpack loaders和webpack plugins完成。webpack loader： loader本质上是一个函数，它的作用是将某个源码字符串转换成另一个源码字符串返回。loader函数的将在模块解析的过程中被调用，以得到最终的源码。 全流程：chunk中解析模块的流程：chunk中解析模块的更详细流程：处理loaders流程：loader配置： 完整配置module.exports = { module: { //针对模块的配置，目前版本只有两个配置，rules、noParse rules: [ //模块匹配规则，可以存在多个规则 { //每个规则是一个对象 test: /\.js$/, //匹配的模块正则 use: [ //匹配到后应用的规则模块 { //其中一个规则 loader: "模块路径", //loader模块的路径，该字符串会被放置到require中 options: { //向对应loader传递的额外参数 } } ] } ] ...]]></description>
            <content:encoded><![CDATA[<blockquote><p>webpack做的事情，仅仅是分析出各种模块的依赖关系，然后形成资源列表，最终打包生成到指定的文件中。 更多的功能需要借助webpack loaders和webpack plugins完成。</p></blockquote><p>webpack loader： loader本质上是一个函数，它的作用是将某个源码字符串转换成另一个源码字符串返回。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3d2cff94e5a9793f601484b02bfa12d42fb5f63b1e64ef1e783d6748fb263eea.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>loader函数的将在模块解析的过程中被调用，以得到最终的源码。</p><p><strong>全流程：</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e067741f3c7a4bc073837468b5e480d3806bb3cc02f73a20865a90eb43c4db0a.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>chunk中解析模块的流程：</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/45c41e8c2af7aa3248d1c0107f5116cf3a6d3d5f56d6165694ef23ce1bd84281.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>chunk中解析模块的更详细流程：</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f061465db043b5d9a02a2feb2fc69bac8eca2b159bc000ea2773db1dfc484bf6.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>处理loaders流程：</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/70d7608331556d7e84cc31810b25f7fe7e42f9849f44e9429fae8e887b51f45a.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>loader配置：</strong></p><p><strong>完整配置</strong></p><pre data-type="codeBlock" text="module.exports = {
    module: { //针对模块的配置，目前版本只有两个配置，rules、noParse
        rules: [ //模块匹配规则，可以存在多个规则
            { //每个规则是一个对象
                test: /\.js$/, //匹配的模块正则
                use: [ //匹配到后应用的规则模块
                    {  //其中一个规则
                        loader: &quot;模块路径&quot;, //loader模块的路径，该字符串会被放置到require中
                        options: { //向对应loader传递的额外参数
                          
                        }
                    }
                ]
            }
        ]
    }
}
"><code>module.exports <span class="hljs-operator">=</span> {
    module: { <span class="hljs-comment">//针对模块的配置，目前版本只有两个配置，rules、noParse</span>
        rules: [ <span class="hljs-comment">//模块匹配规则，可以存在多个规则</span>
            { <span class="hljs-comment">//每个规则是一个对象</span>
                test: <span class="hljs-operator">/</span>\.js$/, <span class="hljs-comment">//匹配的模块正则</span>
                use: [ <span class="hljs-comment">//匹配到后应用的规则模块</span>
                    {  <span class="hljs-comment">//其中一个规则</span>
                        loader: <span class="hljs-string">"模块路径"</span>, <span class="hljs-comment">//loader模块的路径，该字符串会被放置到require中</span>
                        options: { <span class="hljs-comment">//向对应loader传递的额外参数</span>
                          
                        }
                    }
                ]
            }
        ]
    }
}
</code></pre><p><strong>简化配置</strong></p><pre data-type="codeBlock" text="module.exports = {
    module: { //针对模块的配置，目前版本只有两个配置，rules、noParse
        rules: [ //模块匹配规则，可以存在多个规则
            { //每个规则是一个对象
                test: /\.js$/, //匹配的模块正则
                use: [&quot;模块路径1&quot;, &quot;模块路径2&quot;]//loader模块的路径，该字符串会被放置到require中
            }
        ]
    }
}
"><code>module.exports <span class="hljs-operator">=</span> {
    module: { <span class="hljs-comment">//针对模块的配置，目前版本只有两个配置，rules、noParse</span>
        rules: [ <span class="hljs-comment">//模块匹配规则，可以存在多个规则</span>
            { <span class="hljs-comment">//每个规则是一个对象</span>
                test: <span class="hljs-operator">/</span>\.js$/, <span class="hljs-comment">//匹配的模块正则</span>
                use: [<span class="hljs-string">"模块路径1"</span>, <span class="hljs-string">"模块路径2"</span>]<span class="hljs-comment">//loader模块的路径，该字符串会被放置到require中</span>
            }
        ]
    }
}
</code></pre><p>思考🤔：Loader中是否可以支持ES6 module？ 不可以，loader是在webpack打包过程中执行的，依赖Node.js的环境。（node并非不支持ES6的模块化，只是目前的生态环境使用的最多的还是ES6的模块化）；</p>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[webpack-plugin]]></title>
            <link>https://paragraph.com/@murraya/webpack-plugin</link>
            <guid>D7783TkTcm1PsNbCSbrA</guid>
            <pubDate>Wed, 11 May 2022 16:03:48 GMT</pubDate>
            <description><![CDATA[pluginloader的功能定位是转换代码，而一些其他的操作难以使用loader完成，比如：当webpack生成文件时，顺便多生成一个说明描述文件当webpack编译启动时，控制台输出一句话表示webpack启动了当xxxx时，xxxx这种类似的功能需要把功能嵌入到webpack的编译流程中，而这种事情的实现是依托于plugin的plugin的本质是一个带有apply方法的对象var plugin = { apply: function(compiler){ } } 通常，习惯上，我们会将该对象写成构造函数的模式class MyPlugin{ apply(compiler){ } } var plugin = new MyPlugin(); 要将插件应用到webpack，需要把插件对象配置到webpack的plugins数组中，如下：module.exports = { plugins:[ new MyPlugin() ] } apply函数会在初始化阶段，创建好Compiler对象后运行。 compiler对象是在初始化阶段构建的，整个webpack打包期间只有一个compi...]]></description>
            <content:encoded><![CDATA[<h2 id="h-plugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">plugin</h2><p>loader的功能定位是转换代码，而一些其他的操作难以使用loader完成，比如：</p><ul><li><p>当webpack生成文件时，顺便多生成一个说明描述文件</p></li><li><p>当webpack编译启动时，控制台输出一句话表示webpack启动了</p></li><li><p>当xxxx时，xxxx</p></li></ul><p>这种类似的功能需要把功能嵌入到webpack的编译流程中，而这种事情的实现是依托于plugin的</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/052108f04f9ad8925751f0b2031c2c4f1878ba94604fe2caf4be452a5b273aee.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>plugin的<strong>本质</strong>是一个带有apply方法的对象</p><pre data-type="codeBlock" text="var plugin = {
    apply: function(compiler){
        
    }
}
"><code><span class="hljs-keyword">var</span> plugin <span class="hljs-operator">=</span> {
    apply: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">compiler</span>)</span>{
        
    }
}
</code></pre><p>通常，习惯上，我们会将该对象写成构造函数的模式</p><pre data-type="codeBlock" text="class MyPlugin{
    apply(compiler){

    }
}

var plugin = new MyPlugin();
"><code><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyPlugin</span>{
    <span class="hljs-title function_">apply</span>(<span class="hljs-params">compiler</span>){

    }
}

<span class="hljs-keyword">var</span> plugin = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyPlugin</span>();
</code></pre><p>要将插件应用到webpack，需要把插件对象配置到webpack的plugins数组中，如下：</p><pre data-type="codeBlock" text="module.exports = {
    plugins:[
        new MyPlugin()
    ]
}
"><code>module.exports <span class="hljs-operator">=</span> {
    plugins:[
        <span class="hljs-keyword">new</span> MyPlugin()
    ]
}
</code></pre><p>apply函数会在初始化阶段，创建好Compiler对象后运行。</p><p>compiler对象是在初始化阶段构建的，整个webpack打包期间只有一个compiler对象，后续完成打包工作的是compiler对象内部创建的compilation</p><p>apply方法会在<strong>创建好compiler对象后调用</strong>，并向方法传入一个compiler对象</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/beadeee73a2b002c07f393ea7813508aa48303fe8be232a585c780364fbb3448.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>compiler对象提供了大量的钩子函数（hooks，可以理解为事件），plugin的开发者可以注册这些钩子函数，参与webpack编译和生成。</p><p>你可以在apply方法中使用下面的代码注册钩子函数:</p><pre data-type="codeBlock" text="class MyPlugin{
    apply(compiler){
        compiler.hooks.事件名称.事件类型(name, function(compilation){
            //事件处理函数
        })
    }
}
"><code>class MyPlugin{
    apply(compiler){
        compiler.hooks.事件名称.事件类型(name, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">compilation</span>)</span>{
            <span class="hljs-comment">//事件处理函数</span>
        })
    }
}
</code></pre><p><strong>事件名称</strong></p><p>即要监听的事件名，即钩子名，所有的钩子：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.webpackjs.com/api/compiler-hooks">https://www.webpackjs.com/api/compiler-hooks</a></p><p><strong>事件类型</strong></p><p>这一部分使用的是 Tapable API，这个小型的库是一个专门用于钩子函数监听的库。</p><p>它提供了一些事件类型：</p><ul><li><p>tap：注册一个同步的钩子函数，函数运行完毕则表示事件处理结束</p></li><li><p>tapAsync：注册一个基于回调的异步的钩子函数，函数通过调用一个回调表示事件处理结束</p></li><li><p>tapPromise：注册一个基于Promise的异步的钩子函数，函数通过返回的Promise进入已决状态表示事件处理结束</p></li></ul><p><strong>处理函数</strong></p><p>处理函数有一个事件参数<code>compilation</code></p>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[webpack性能优化]]></title>
            <link>https://paragraph.com/@murraya/webpack</link>
            <guid>Y548feXaVdqOsJ3NFK08</guid>
            <pubDate>Wed, 11 May 2022 16:00:28 GMT</pubDate>
            <description><![CDATA[概述这里所说的构建性能，是指在开发阶段的构建性能，而不是生产环境的构建性能 优化的目标，是降低从打包开始，到代码效果呈现所经过的时间 构建性能会影响开发效率。构建性能越高，开发过程中时间的浪费越少减少模块解析模块解析包括：抽象语法树分析、依赖分析、模块语法替换不做模块解析会怎样？如果某个模块不做解析，该模块经过loader处理后的代码就是最终代码。 如果没有loader对该模块进行处理，该模块的源码就是最终打包结果的代码。 如果不对某个模块进行解析，可以缩短构建时间哪些模块不需要解析？模块中无其他依赖：一些已经打包好的第三方库，比如jquery如何让某个模块不要解析？配置**module.noParse**，它是一个正则，被正则匹配到的模块不会解析优化loader性能进一步限制loader的应用范围思路是：对于某些库，不使用loader 例如：babel-loader可以转换ES6或更高版本的语法，可是有些库本身就是用ES5语法书写的，不需要转换，使用babel-loader反而会浪费构建时间 lodash就是这样的一个库lodash是在ES5之前出现的库，使用的是ES3语法通过...]]></description>
            <content:encoded><![CDATA[<p>概述</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8d7ced697cb323db6177af457205b0fc05696b13bbd7d5c4f15319361850aa27.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>这里所说的构建性能，是指在<strong>开发阶段的构建性能</strong>，而不是生产环境的构建性能</p><p>优化的目标，<strong>是降低从打包开始，到代码效果呈现所经过的时间</strong></p><p>构建性能会影响开发效率。构建性能越高，开发过程中时间的浪费越少</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">减少模块解析</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6904cbb89a14ed5b29f81df9e705993a5c78b0bb2dddb14655706ef12078442b.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>模块解析包括：抽象语法树分析、依赖分析、模块语法替换</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">不做模块解析会怎样？</h4><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cf4ce98e902b0a2adabd241eaa5aceab93fa67f858b937c682ee58ecb38c14b2.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>如果某个模块不做解析，该模块经过loader处理后的代码就是最终代码。</p><p>如果没有loader对该模块进行处理，该模块的源码就是最终打包结果的代码。</p><p>如果不对某个模块进行解析，可以缩短构建时间</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">哪些模块不需要解析？</h4><p>模块中无其他依赖：一些已经打包好的第三方库，比如jquery</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">如何让某个模块不要解析？</h4><p>配置<code>**module.noParse**</code>，它是一个正则，被正则匹配到的模块不会解析</p><h3 id="h-loader" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">优化loader性能</h3><h4 id="h-loader" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">进一步限制loader的应用范围</h4><p>思路是：对于某些库，不使用loader</p><p>例如：babel-loader可以转换ES6或更高版本的语法，可是有些库本身就是用ES5语法书写的，不需要转换，使用babel-loader反而会浪费构建时间</p><p>lodash就是这样的一个库</p><blockquote><p>lodash是在ES5之前出现的库，使用的是ES3语法</p></blockquote><p>通过<code>module.rule.exclude</code>或<code>module.rule.include</code>，排除或仅包含需要应用loader的场景</p><pre data-type="codeBlock" text="module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /lodash/,
                use: &quot;babel-loader&quot;
            }
        ]
    }
}
"><code>module.exports <span class="hljs-operator">=</span> {
    module: {
        rules: [
            {
                test: <span class="hljs-operator">/</span>\.js$/,
                exclude: <span class="hljs-operator">/</span>lodash<span class="hljs-operator">/</span>,
                use: <span class="hljs-string">"babel-loader"</span>
            }
        ]
    }
}
</code></pre><p>如果暴力一点，甚至可以排除掉<code>node_modules</code>目录中的模块，或仅转换<code>src</code>目录的模块</p><pre data-type="codeBlock" text="module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                //或
                // include: /src/,
                use: &quot;babel-loader&quot;
            }
        ]
    }
}
"><code>module.exports <span class="hljs-operator">=</span> {
    module: {
        rules: [
            {
                test: <span class="hljs-operator">/</span>\.js$/,
                exclude: <span class="hljs-operator">/</span>node_modules<span class="hljs-operator">/</span>,
                <span class="hljs-comment">//或</span>
                <span class="hljs-comment">// include: /src/,</span>
                use: <span class="hljs-string">"babel-loader"</span>
            }
        ]
    }
}
</code></pre><blockquote><p>这种做法是对loader的范围进行进一步的限制，和noParse不冲突，想想看，为什么不冲突</p></blockquote><h4 id="h-loader" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">缓存loader的结果</h4><p>我们可以基于一种假设：如果某个文件内容不变，经过相同的loader解析后，解析后的结果也不变</p><p>于是，可以将loader的解析结果保存下来，让后续的解析直接使用保存的结果</p><p><code>cache-loader</code>可以实现这样的功能</p><pre data-type="codeBlock" text="module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [&apos;cache-loader&apos;, ...loaders]
      },
    ],
  },
};
"><code>module.exports <span class="hljs-operator">=</span> {
  module: {
    rules: [
      {
        test: <span class="hljs-operator">/</span>\.js$/,
        use: [<span class="hljs-string">'cache-loader'</span>, ...loaders]
      },
    ],
  },
};
</code></pre><p>有趣的是，<code>cache-loader</code>放到最前面，却能够决定后续的loader是否运行</p><p>实际上，loader的运行过程中，还包含一个过程，即<code>pitch</code></p><p>!Unsupported embed</p><p><code>cache-loader</code>还可以实现各自自定义的配置，具体方式见文档</p><h4 id="h-loader" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">为loader的运行开启多线程</h4><p><code>thread-loader</code>会开启一个线程池，线程池中包含适量的线程</p><p>它会把后续的loader放到线程池的线程中运行，以提高构建效率</p><p>由于后续的loader会放到新的线程中，所以，后续的loader不能：</p><ul><li><p>使用 webpack api 生成文件</p></li><li><p>无法使用自定义的 plugin api</p></li><li><p>无法访问 webpack options</p></li></ul><blockquote><p>在实际的开发中，可以进行测试，来决定<code>thread-loader</code>放到什么位置</p></blockquote><p><strong>特别注意</strong>，开启和管理线程需要消耗时间，在小型项目中使用<code>thread-loader</code>反而会增加构建时间</p><h1 id="h-hmr-ignore" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">热替换 HMR {ignore}</h1><blockquote><p>热替换并不能降低构建时间（可能还会稍微增加），但可以降低代码改动到效果呈现的时间</p></blockquote><p>当使用<code>webpack-dev-server</code>时，考虑代码改动到效果呈现的过程</p><p>!Unsupported embed</p><p>而使用了热替换后，流程发生了变化</p><p>!Unsupported embed</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">使用和原理</h1><ol><li><p>更改配置</p></li></ol><pre data-type="codeBlock" text="module.exports = {
  devServer:{
    hot:true // 开启HMR
  },
  plugins:[ 
    // 可选
    new webpack.HotModuleReplacementPlugin()
  ]
}
"><code>module.exports <span class="hljs-operator">=</span> {
  devServer:{
    hot:<span class="hljs-literal">true</span> <span class="hljs-comment">// 开启HMR</span>
  },
  plugins:[ 
    <span class="hljs-comment">// 可选</span>
    <span class="hljs-keyword">new</span> webpack.HotModuleReplacementPlugin()
  ]
}
</code></pre><ol><li><p>更改代码</p></li></ol><pre data-type="codeBlock" text="// index.js

if(module.hot){ // 是否开启了热更新
  module.hot.accept() // 接受热更新
}
"><code><span class="hljs-comment">// index.js</span>

<span class="hljs-keyword">if</span>(module.hot){ <span class="hljs-comment">// 是否开启了热更新</span>
  module.hot.accept() <span class="hljs-comment">// 接受热更新</span>
}
</code></pre><p>首先，这段代码会参与最终运行！</p><p>当开启了热更新后，<code>webpack-dev-server</code>会向打包结果中注入<code>module.hot</code>属性</p><p>默认情况下，<code>webpack-dev-server</code>不管是否开启了热更新，当重新打包后，都会调用<code>location.reload</code>刷新页面</p><p>但如果运行了<code>module.hot.accept()</code>，将改变这一行为</p><p><code>module.hot.accept()</code>的作用是让<code>webpack-dev-server</code>通过<code>socket</code>管道，把服务器更新的内容发送到浏览器</p><p>!Unsupported embed</p><p>然后，将结果交给插件<code>HotModuleReplacementPlugin</code>注入的代码执行</p><p>插件<code>HotModuleReplacementPlugin</code>会根据覆盖原始代码，然后让代码重新执行</p><p><strong>所以，热替换发生在代码运行期</strong></p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">样式热替换</h1><p>对于样式也是可以使用热替换的，但需要使用<code>style-loader</code></p><p>因为热替换发生时，<code>HotModuleReplacementPlugin</code>只会简单的重新运行模块代码</p><p>因此<code>style-loader</code>的代码一运行，就会重新设置<code>style</code>元素中的样式</p><p>而<code>mini-css-extract-plugin</code>，由于它生成文件是在<strong>构建期间</strong>，运行期间并会也无法改动文件，因此它对于热替换是无效的</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">传输性能</h2><p>传输性能是指，打包后的JS代码传输到浏览器经过的时间</p><p>在优化传输性能时要考虑到：</p><ol><li><p>总传输量：所有需要传输的JS文件的内容加起来，就是总传输量，重复代码越少，总传输量越少</p></li><li><p>文件数量：当访问页面时，需要传输的JS文件数量，文件数量越多，http请求越多，响应速度越慢</p></li><li><p>浏览器缓存：JS文件会被浏览器缓存，被缓存的文件不会再进行传输</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">运行性能</h2><p>运行性能是指，JS代码在浏览器端的运行速度</p><p>它主要取决于我们如何书写高性能的代码</p><p><strong>永远不要过早的关注于性能</strong>，因为你在开发的时候，无法完全预知最终的运行性能，过早的关注性能会极大的降低开发效率</p><hr><p>性能优化主要从上面三个维度入手</p><p><strong>性能优化没有完美的解决方案，需要具体情况具体分析</strong></p>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[redux中间件以及redux-saga]]></title>
            <link>https://paragraph.com/@murraya/redux-redux-saga</link>
            <guid>IIQhinn4uUp0ZYg0izYf</guid>
            <pubDate>Wed, 11 May 2022 15:54:25 GMT</pubDate>
            <description><![CDATA[中间件：类似于插件，可以在不影响原本功能、并且不改动原本代码的基础上，对其功能进行增强。在Redux中，中间件主要用于增强dispatch函数。 实现Redux中间件的基本原理，是更改仓库中的dispatch函数。 Redux中间件书写：中间件本身是一个函数，该函数接收一个store参数，表示创建的仓库，该仓库并非一个完整的仓库对象，仅包含getState，dispatch。该函数运行的时间，是在仓库创建之后运行。由于创建仓库后需要自动运行设置的中间件函数，因此，需要在创建仓库时，告诉仓库有哪些中间件需要调用applyMiddleware函数，将函数的返回结果作为createStore的第二或第三个参数。中间件函数必须返回一个dispatch创建函数applyMiddleware函数，用于记录有哪些中间件，它会返回一个函数该函数用于记录创建仓库的方法，然后又返回一个函数redux-saga中文文档地址：https://redux-saga-in-chinese.js.org/纯净强大灵活在saga任务中，如果yield了一个普通数据，saga不作任何处理，仅仅将数据传递给yiel...]]></description>
            <content:encoded><![CDATA[<p>中间件：类似于插件，可以在不影响原本功能、并且不改动原本代码的基础上，对其功能进行增强。在Redux中，中间件主要用于增强dispatch函数。</p><p>实现Redux中间件的基本原理，是更改仓库中的dispatch函数。</p><p>Redux中间件书写：</p><ul><li><p>中间件本身是一个函数，该函数接收一个store参数，表示创建的仓库，该仓库并非一个完整的仓库对象，仅包含getState，dispatch。该函数运行的时间，是在仓库创建之后运行。</p></li><li><p>由于创建仓库后需要自动运行设置的中间件函数，因此，需要在创建仓库时，告诉仓库有哪些中间件</p></li><li><p>需要调用applyMiddleware函数，将函数的返回结果作为createStore的第二或第三个参数。</p></li><li><p>中间件函数必须返回一个dispatch创建函数</p></li><li><p>applyMiddleware函数，用于记录有哪些中间件，它会返回一个函数</p></li><li><p>该函数用于记录创建仓库的方法，然后又返回一个函数</p></li></ul><h1 id="h-redux-saga" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">redux-saga</h1><blockquote><p>中文文档地址：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://redux-saga-in-chinese.js.org/">https://redux-saga-in-chinese.js.org/</a></p></blockquote><ul><li><p>纯净</p></li><li><p>强大</p></li><li><p>灵活</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3d7083163db1229efc7aa1da4772c31ff6bb6150ac3e93bb34d01073726af766.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在saga任务中，如果yield了一个普通数据，saga不作任何处理，仅仅将数据传递给yield表达式（把得到的数据放到next的参数中），因此，在saga中，yield一个普通数据没什么意义。</p><p>saga需要你在yield后面放上一些合适的saga指令（saga effects），如果放的是指令，saga中间件会根据不同的指令进行特殊处理，以控制整个任务的流程。</p><p>每个指令本质上就是一个函数，该函数调用后，会返回一个指令对象，saga会接收到该指令对象，进行各种处理</p><p><strong>一旦saga任务完成（生成器函数运行完成），则saga中间件一定结束</strong></p><ul><li><p>take指令：【阻塞】监听某个action，如果action发生了，则会进行下一步处理，take指令仅监听一次。yield得到的是完整的action对象</p></li><li><p>all指令：【阻塞】该函数传入一个数组，数组中放入生成器，saga会等待所有的生成器全部完成后才会进一步处理</p></li><li><p>takeEvery指令：不断的监听某个action，当某个action到达之后，运行一个函数。takeEvery永远不会结束当前的生成器</p></li><li><p>delay指令：【阻塞】阻塞指定的毫秒数</p></li><li><p>put指令：用于重新触发action，相当于dispatch一个action</p></li><li><p>call指令：【可能阻塞】用于副作用（通常是异步）函数调用</p></li><li><p>apply指令：【可能阻塞】用于副作用（通常是异步）函数调用</p></li><li><p>select指令：用于得到当前仓库中的数据</p></li><li><p>cps指令：【可能阻塞】用于调用那些传统的回调方式的异步函数</p></li><li><p>fork：用于开启一个新的任务，该任务不会阻塞，该函数需要传递一个生成器函数，fork返回了一个对象，类型为Task</p></li><li><p>cancel：用于取消一个或多个任务，实际上，取消的实现原理，是利用generator.return。cancel可以不传递参数，如果不传递参数，则取消当前任务线。</p></li><li><p>takeLastest：功能和takeEvery一致，只不过，会自动取消掉之前开启的任务</p></li><li><p>cancelled：判断当前任务线是否被取消掉了</p></li><li><p>race：【阻塞】竞赛，可以传递多个指令，当其中任何一个指令结束后，会直接结束，与Promise.race类似。返回的结果，是最先完成的指令结果。并且，该函数会自动取消其他的任务</p></li></ul>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[浮点数精度问题]]></title>
            <link>https://paragraph.com/@murraya/x2PFXxxzb9Z8z0i2q9LE</link>
            <guid>x2PFXxxzb9Z8z0i2q9LE</guid>
            <pubDate>Wed, 11 May 2022 15:48:29 GMT</pubDate>
            <description><![CDATA[浮点数精度问题经典真题为什么 console.log(0.2+0.1==0.3) 得到的值为 false浮点数精度常见问题在 JavaScript 中整数和浮点数都属于 number 数据类型，所有数字都是以 64 位浮点数形式储存，即便整数也是如此。 所以我们在打印 1.00 这样的浮点数的结果是 1 而非 1.00 。 在一些特殊的数值表示中，例如金额，这样看上去有点别扭，但是至少值是正确了。 然而要命的是，当浮点数做数学运算的时候，你经常会发现一些问题，举几个例子： 场景一：进行浮点值运算结果的判断// 加法 console.log(0.1 + 0.2); // 0.30000000000000004 console.log(0.7 + 0.1); // 0.7999999999999999 console.log(0.2 + 0.4); // 0.6000000000000001 console.log(2.22 + 0.1); // 2.3200000000000003 // 减法 console.log(1.5 - 1.2); // 0.300000000000000...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">浮点数精度问题</h1><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">经典真题</h2><ul><li><p>为什么 <em>console.log(0.2+0.1==0.3)</em> 得到的值为 <em>false</em></p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">浮点数精度常见问题</h2><p>在 <em>JavaScript</em> 中整数和浮点数都属于 <em>number</em> 数据类型，所有数字都是以 <em>64</em> 位浮点数形式储存，即便整数也是如此。 所以我们在打印 <em>1.00</em> 这样的浮点数的结果是 <em>1</em> 而非 <em>1.00</em> 。</p><p>在一些特殊的数值表示中，例如金额，这样看上去有点别扭，但是至少值是正确了。</p><p>然而要命的是，当浮点数做数学运算的时候，你经常会发现一些问题，举几个例子：</p><p><strong>场景一</strong>：进行浮点值运算结果的判断</p><pre data-type="codeBlock" text="// 加法 
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.7 + 0.1); // 0.7999999999999999
console.log(0.2 + 0.4); // 0.6000000000000001
console.log(2.22 + 0.1); // 2.3200000000000003
 
// 减法
console.log(1.5 - 1.2); // 0.30000000000000004
console.log(0.3 - 0.2); // 0.09999999999999998
 
// 乘法 
console.log(19.9 * 100); // 1989.9999999999998
console.log(19.9 * 10 * 10); // 1990
console.log(9.7 * 100); // 969.9999999999999
console.log(39.7 * 100); // 3970.0000000000005
 
// 除法 
console.log(0.3 / 0.1); // 2.9999999999999996
console.log(0.69 / 10); // 0.06899999999999999
"><code><span class="hljs-comment">// 加法 </span>
console.log(<span class="hljs-number">0</span><span class="hljs-number">.1</span> <span class="hljs-operator">+</span> <span class="hljs-number">0</span><span class="hljs-number">.2</span>); <span class="hljs-comment">// 0.30000000000000004</span>
console.log(<span class="hljs-number">0</span><span class="hljs-number">.7</span> <span class="hljs-operator">+</span> <span class="hljs-number">0</span><span class="hljs-number">.1</span>); <span class="hljs-comment">// 0.7999999999999999</span>
console.log(<span class="hljs-number">0</span><span class="hljs-number">.2</span> <span class="hljs-operator">+</span> <span class="hljs-number">0</span><span class="hljs-number">.4</span>); <span class="hljs-comment">// 0.6000000000000001</span>
console.log(<span class="hljs-number">2.22</span> <span class="hljs-operator">+</span> <span class="hljs-number">0</span><span class="hljs-number">.1</span>); <span class="hljs-comment">// 2.3200000000000003</span>
 
<span class="hljs-comment">// 减法</span>
console.log(<span class="hljs-number">1.5</span> <span class="hljs-operator">-</span> <span class="hljs-number">1.2</span>); <span class="hljs-comment">// 0.30000000000000004</span>
console.log(<span class="hljs-number">0</span><span class="hljs-number">.3</span> <span class="hljs-operator">-</span> <span class="hljs-number">0</span><span class="hljs-number">.2</span>); <span class="hljs-comment">// 0.09999999999999998</span>
 
<span class="hljs-comment">// 乘法 </span>
console.log(<span class="hljs-number">19.9</span> <span class="hljs-operator">*</span> <span class="hljs-number">100</span>); <span class="hljs-comment">// 1989.9999999999998</span>
console.log(<span class="hljs-number">19.9</span> <span class="hljs-operator">*</span> <span class="hljs-number">10</span> <span class="hljs-operator">*</span> <span class="hljs-number">10</span>); <span class="hljs-comment">// 1990</span>
console.log(<span class="hljs-number">9.7</span> <span class="hljs-operator">*</span> <span class="hljs-number">100</span>); <span class="hljs-comment">// 969.9999999999999</span>
console.log(<span class="hljs-number">39.7</span> <span class="hljs-operator">*</span> <span class="hljs-number">100</span>); <span class="hljs-comment">// 3970.0000000000005</span>
 
<span class="hljs-comment">// 除法 </span>
console.log(<span class="hljs-number">0</span><span class="hljs-number">.3</span> <span class="hljs-operator">/</span> <span class="hljs-number">0</span><span class="hljs-number">.1</span>); <span class="hljs-comment">// 2.9999999999999996</span>
console.log(<span class="hljs-number">0</span><span class="hljs-number">.69</span> <span class="hljs-operator">/</span> <span class="hljs-number">10</span>); <span class="hljs-comment">// 0.06899999999999999</span>
</code></pre><p><strong>场景二</strong>：将小数乘以 <em>10</em> 的 <em>n</em> 次方取整</p><p>比如将钱币的单位，从元转化成分，经常写出来的是 <em>parseInt(yuan*100, 10)</em></p><pre data-type="codeBlock" text="console.log(parseInt(0.58 * 100, 10)); // 57
"><code>console.log(parseInt(<span class="hljs-number">0</span><span class="hljs-number">.58</span> <span class="hljs-operator">*</span> <span class="hljs-number">100</span>, <span class="hljs-number">10</span>)); <span class="hljs-comment">// 57</span>
</code></pre><p><strong>场景三</strong>：四舍五入保留 <em>n</em> 位小数</p><p>例如我们会写出 <em>(number).toFixed(2)</em>，但是看下面的例子：</p><pre data-type="codeBlock" text="console.log((1.335).toFixed(2)); // 1.33
"><code>console.log((<span class="hljs-number">1.335</span>).toFixed(<span class="hljs-number">2</span>)); <span class="hljs-comment">// 1.33</span>
</code></pre><p>在上面的例子中，我们得出的结果是 <em>1.33</em>，而不是预期结果 <em>1.34</em>。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">为什么会有这样的问题</h2><p>似乎是不可思议。小学生都会算的题目，<em>JavaScript</em> 不会？</p><p>我们来看看其真正的原因，到底为什么会产生精度丢失的问题呢？</p><p>计算机底层只有 <em>0</em> 和 <em>1</em>， 所以所有的运算最后实际上都是二进制运算。</p><p>十进制整数利用辗转相除的方法可以准确地转换为二进制数，但浮点数呢？</p><p><em>JavaScript</em> 里的数字是采用 <em>IEEE 754</em> 标准的 <em>64</em> 位双精度浮点数。</p><p>先看下面一张图：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ef5edc858bd4141f55f21425b9dbc02e09f707b33e929405a942b490b90fb473.png" alt="preview" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">preview</figcaption></figure><p>该规范定义了浮点数的格式，对于 <em>64</em> 位的浮点数在内存中的表示，最高的 <em>1</em> 位是符号位，接着的 <em>11</em> 位是指数，剩下的 <em>52</em> 位为有效数字，具体如下：</p><ul><li><p>符号位 <em>S</em>：第 <em>1</em> 位是正负数符号位（<em>sign</em>），<em>0</em> 代表正数，<em>1</em> 代表负数</p></li><li><p>指数位 <em>E</em>：中间的 <em>11</em> 位存储指数（<em>exponent</em>），用来表示次方数</p></li><li><p>尾数位 <em>M</em>：最后的 <em>52</em> 位是尾数（<em>mantissa</em>），储存小数部分，超出的部分自动进一舍零</p></li></ul><p>也就是说，浮点数最终在运算的时候实际上是一个符合该标准的二进制数</p><p>符号位决定了一个数的正负，指数部分决定了数值的大小，小数部分决定了数值的精度。</p><p><em>IEEE 754</em> 规定，有效数字第一位默认总是 <em>1</em>，不保存在 <em>64</em> 位浮点数之中。也就是说，有效数字总是 <em>1.xx…xx</em> 的形式，其中 <em>xx…xx</em> 的部分保存在 <em>64</em> 位浮点数之中，最长可能为 <em>52</em> 位。因此，<em>JavaScript</em> 提供的有效数字最长为 <em>53</em> 个二进制位（<em>64</em> 位浮点的后 <em>52</em> 位 + 有效数字第一位的 <em>1</em>）。</p><p>既然限定位数，必然有截断的可能。</p><p>我们可以看一个例子：</p><pre data-type="codeBlock" text="console.log(0.1 + 0.2); // 0.30000000000000004
"><code>console.log(<span class="hljs-number">0</span><span class="hljs-number">.1</span> <span class="hljs-operator">+</span> <span class="hljs-number">0</span><span class="hljs-number">.2</span>); <span class="hljs-comment">// 0.30000000000000004</span>
</code></pre><p>为了验证该例子，我们得先知道怎么将浮点数转换为二进制，整数我们可以用除 <em>2</em> 取余的方式，小数我们则可以用乘 <em>2</em> 取整的方式。</p><p><em>0.1</em> 转换为二进制：</p><p><em>0.1 * 2</em>，值为 <em>0.2</em>，小数部分 <em>0.2</em>，整数部分 <em>0</em></p><p><em>0.2 * 2</em>，值为 <em>0.4</em>，小数部分 <em>0.4</em>，整数部分 <em>0</em></p><p><em>0.4 * 2</em>，值为0.8，小数部分0.8，整数部分0</p><p><em>0.8 * 2</em>，值为 <em>1.6</em>，小数部分 <em>0.6</em>，整数部分 <em>1</em></p><p><em>0.6 * 2</em>，值为 <em>1.2</em>，小数部分 <em>0.2</em>，整数部分 <em>1</em></p><p><em>0.2 * 2</em>，值为 <em>0.4</em>，小数部分 <em>0.4</em>，整数部分 <em>0</em></p><p>从 <em>0.2</em> 开始循环</p><p><em>0.2</em> 转换为二进制可以直接参考上述，肯定最后也是一个循环的情况</p><p>所以最终我们能得到两个循环的二进制数：</p><p><em>0.1：0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1100 ...</em></p><p><em>0.2：0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 ...</em></p><p>这两个的和的二进制就是：</p><p><em>sum：0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 ...</em></p><p>最终我们只能得到和的近似值（按照 <em>IEEE 754</em> 标准保留 <em>52</em> 位，按 <em>0</em> 舍 <em>1</em> 入来取值），然后转换为十进制数变成：</p><p>sum ≈ 0.30000000000000004</p><p>再例如：</p><pre data-type="codeBlock" text="console.log((1.335).toFixed(2)); // 1.33
"><code>console.log((<span class="hljs-number">1.335</span>).toFixed(<span class="hljs-number">2</span>)); <span class="hljs-comment">// 1.33</span>
</code></pre><p>因为 <em>1.335</em> 其实是 <em>1.33499999999999996447286321199</em>，<em>toFixed</em> 虽然是四舍五入，但是是对 <em>1.33499999999999996447286321199</em> 进行四五入，所以得出 <em>1.33</em>。</p><p>在 <em>Javascript</em> 中，整数精度同样存在问题，先来看看问题：</p><pre data-type="codeBlock" text="console.log(19571992547450991); // 19571992547450990
console.log(19571992547450991===19571992547450992); // true
"><code>console.log(<span class="hljs-number">19571992547450991</span>); <span class="hljs-comment">// 19571992547450990</span>
console.log(<span class="hljs-number">19571992547450991</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-number">19571992547450992</span>); <span class="hljs-comment">// true</span>
</code></pre><p>同样的原因，在 <em>JavaScript</em> 中 <em>number</em> 类型统一按浮点数处理，整数是按最大 <em>54</em> 位来算，</p><ul><li><p>最大( <em>253 - 1</em>，<em>Number.MAX_SAFE_INTEGER</em>、<em>9007199254740991</em>)</p></li><li><p>最小( <em>-(253 - 1)</em>，<em>Number.MIN_SAFE_INTEGER</em>、<em>-9007199254740991</em>)</p></li></ul><p>所以只要超过这个范围，就会存在被舍去的精度问题。</p><p>当然这个问题并不只是在 <em>Javascript</em> 中才会出现，几乎所有的编程语言都采用了 <em>IEEE-754</em> 浮点数表示法，任何使用二进制浮点数的编程语言都会有这个问题。</p><p>只不过在很多其他语言中已经封装好了方法来避免精度的问题，而 <em>JavaScript</em> 是一门弱类型的语言，从设计思想上就没有对浮点数有个严格的数据类型，所以精度误差的问题就显得格外突出。</p><p>通常这种对精度要求高的计算都应该交给后端去计算和存储，因为后端有成熟的库来解决这种计算问题。</p><p>前端也有几个不错的类库：</p><p><strong><em>Math.js</em></strong></p><p><em>Math.js</em> 是专门为 <em>JavaScript</em> 和 <em>Node.js</em> 提供的一个广泛的数学库。它具有灵活的表达式解析器，支持符号计算，配有大量内置函数和常量，并提供集成解决方案来处理不同的数据类型。</p><p>像数字，大数字（超出安全数的数字），复数，分数，单位和矩阵。 功能强大，易于使用。</p><p><strong><em>decimal.js</em></strong></p><p>为 <em>JavaScript</em> 提供十进制类型的任意精度数值。</p><p><strong><em>big.js</em></strong></p><p>不仅能够支持处理 <em>Long</em> 类型的数据，也能够准确的处理小数的运算。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">真题解答</h2><ul><li><p>为什么 <em>console.log(0.2+0.1==0.3)</em> 得到的值为 <em>false</em></p></li></ul><blockquote><p>参考答案：</p><p>因为浮点数的计算存在 <em>round-off</em> 问题，也就是浮点数不能够进行精确的计算。并且：</p><ul><li><p>不仅 <em>JavaScript</em>，所有遵循 <em>IEEE 754</em> 规范的语言都是如此；</p></li><li><p>在 <em>JavaScript</em> 中，所有的 <em>Number</em> 都是以 <em>64-bit</em> 的双精度浮点数存储的；</p></li><li><p>双精度的浮点数在这 <em>64</em> 位上划分为 <em>3</em> 段，而这 <em>3</em> 段也就确定了一个浮点数的值，<em>64bit</em> 的划分是“<em>1-11-52</em>”的模式，具体来说：</p><ul><li><p>就是 <em>1</em> 位最高位（最左边那一位）表示符号位，<em>0</em> 表示正，<em>1</em> 表示负；</p></li><li><p><em>11</em> 位表示指数部分；</p></li><li><p><em>52</em> 位表示尾数部分，也就是有效域部分</p></li></ul></li></ul></blockquote><p>-<em>EOF</em>-</p>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[函数柯里化]]></title>
            <link>https://paragraph.com/@murraya/TIEeUJeSAUTisCGBQ2KI</link>
            <guid>TIEeUJeSAUTisCGBQ2KI</guid>
            <pubDate>Wed, 11 May 2022 15:44:45 GMT</pubDate>
            <description><![CDATA[函数柯里化什么是函数柯里化？什么是函数柯里化在计算机科学中，柯里化（英语：Currying），又译为卡瑞化或加里化，是把接受多个参数的函数变换成接受一个单一参数（最初函数的第一个参数）的函数，并且返回接受余下的参数而且返回结果的新函数的技术。 这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的，尽管它是 Moses Schönfinkel 和戈特洛布·弗雷格发明的。 在直觉上，柯里化声称如果你固定某些参数，你将得到接受余下参数的一个函数。 我们姑且叫它返回函数，在调用返回函数的时候，它将判断当前的参数和之前被柯里化函数固定的参数拼起来之后，是否达到了原本函数的参数个数。 如果是，则执行原本的函数，得到结果；如果没有达到，则要继续调用柯里化函数来固定目前的参数。 在理论计算机科学中，柯里化提供了在简单的理论模型中，比如：只接受一个单一参数的 lambda 演算中，研究带有多个参数的函数的方式。 函数柯里化的对偶是Uncurrying，一种使用匿名单参数函数来实现多参数函数的方法。柯里化快速入门接下来，我们来通过一个简单的示例，让大家快速体会函数柯里化。 假设我们有一个求取...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">函数柯里化</h1><ul><li><p>什么是函数柯里化？</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">什么是函数柯里化</h2><p>在计算机科学中，柯里化（英语：<em>Currying</em>），又译为卡瑞化或加里化，是把接受多个参数的函数变换成接受一个单一参数（最初函数的第一个参数）的函数，并且返回接受余下的参数而且返回结果的新函数的技术。</p><p>这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的，尽管它是 <em>Moses Schönfinkel</em> 和戈特洛布·弗雷格发明的。</p><p>在直觉上，柯里化声称如果你固定某些参数，你将得到接受余下参数的一个函数。</p><p>我们姑且叫它返回函数，在调用返回函数的时候，它将判断当前的参数和之前被柯里化函数固定的参数拼起来之后，是否达到了原本函数的参数个数。</p><p>如果是，则执行原本的函数，得到结果；如果没有达到，则要继续调用柯里化函数来固定目前的参数。</p><p>在理论计算机科学中，柯里化提供了在简单的理论模型中，比如：只接受一个单一参数的 <em>lambda</em> 演算中，研究带有多个参数的函数的方式。</p><p>函数柯里化的对偶是<em>Uncurrying</em>，一种使用匿名单参数函数来实现多参数函数的方法。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">柯里化快速入门</h2><p>接下来，我们来通过一个简单的示例，让大家快速体会函数柯里化。</p><p>假设我们有一个求取两个数之和的函数：</p><pre data-type="codeBlock" text="function add(x, y) {
    return x + y;
}
console.log(add(1, 2)); // 3
console.log(add(5, 7)); // 12
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params">x, y</span>) </span>{
    <span class="hljs-keyword">return</span> x <span class="hljs-operator">+</span> y;
}
console.log(add(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)); <span class="hljs-comment">// 3</span>
console.log(add(<span class="hljs-number">5</span>, <span class="hljs-number">7</span>)); <span class="hljs-comment">// 12</span>
</code></pre><p>在上面的示例中，我们有一个 <em>add</em> 函数，接收两个形参，返回两形参的和。</p><p>在调用的时候，我们每次也需要传递两个参数。</p><p>现在，我们对其进行柯里化，如下：</p><pre data-type="codeBlock" text="function add(x) {
    return function (y) {
        return x + y;
    }
}
console.log(add(1)(2)); // 3
console.log(add(5)(7)); // 3
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params">x</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">y</span>) </span>{
        <span class="hljs-keyword">return</span> x <span class="hljs-operator">+</span> y;
    }
}
console.log(add(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)); <span class="hljs-comment">// 3</span>
console.log(add(<span class="hljs-number">5</span>)(<span class="hljs-number">7</span>)); <span class="hljs-comment">// 3</span>
</code></pre><p>在上面的代码中，我们对 <em>add</em> 函数进行了柯里化改造，只接受一个参数，但是返回的也不是值了，而是返回一个函数，这个函数也接收一个参数，然后利用闭包的特性，可以访问到最开始传入的 <em>x</em> 的值，最终返回 <em>x</em> 和 <em>y</em> 的和。</p><p>所以，通过上面的这个示例，我们能够体会到前面所说的柯里化函数的特点：</p><p>一个柯里化的函数首先会接受一些参数，接受了这些参数之后，该函数并不会立即求值，而是继续返回另外一个函数，刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候，之前传入的所有参数都会被一次性用于求值。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">函数柯里化实际应用</h2><p>通过上面的例子，我们体验到了什么是柯里化函数。</p><p>但是问题来了，费这么大劲封装一层，到底有什么用处呢？</p><p>没有好处想让我们程序员多干事情是不可能滴，这辈子都不可能。</p><p>所以接下来我们就来看一下函数柯里化的一个实际应用。</p><p><strong>参数复用</strong></p><p>就是将相同的参数固定下来。</p><pre data-type="codeBlock" text="// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

// 即使是相同的正则表达式，也需要重新传递一次
console.log(check(/\d+/g, &apos;test1&apos;)); // true
console.log(check(/\d+/g, &apos;testtest&apos;)); // false
console.log(check(/[a-z]+/g, &apos;test&apos;)); // true

// Currying后
function curryingCheck(reg) {
    return function (txt) {
        return reg.test(txt)
    }
}

// 正则表达式通过闭包保存了起来
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

console.log(hasNumber(&apos;test1&apos;)); // true
console.log(hasNumber(&apos;testtest&apos;));  // false
console.log(hasLetter(&apos;21212&apos;)); // false
"><code><span class="hljs-comment">// 正常正则验证字符串 reg.test(txt)</span>

<span class="hljs-comment">// 函数封装后</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">check</span>(<span class="hljs-params">reg, txt</span>) </span>{
    <span class="hljs-keyword">return</span> reg.test(txt)
}

<span class="hljs-comment">// 即使是相同的正则表达式，也需要重新传递一次</span>
console.log(check(<span class="hljs-operator">/</span>\d<span class="hljs-operator">+</span><span class="hljs-operator">/</span>g, <span class="hljs-string">'test1'</span>)); <span class="hljs-comment">// true</span>
console.log(check(<span class="hljs-operator">/</span>\d<span class="hljs-operator">+</span><span class="hljs-operator">/</span>g, <span class="hljs-string">'testtest'</span>)); <span class="hljs-comment">// false</span>
console.log(check(<span class="hljs-operator">/</span>[a<span class="hljs-operator">-</span>z]<span class="hljs-operator">+</span><span class="hljs-operator">/</span>g, <span class="hljs-string">'test'</span>)); <span class="hljs-comment">// true</span>

<span class="hljs-comment">// Currying后</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">curryingCheck</span>(<span class="hljs-params">reg</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">txt</span>) </span>{
        <span class="hljs-keyword">return</span> reg.test(txt)
    }
}

<span class="hljs-comment">// 正则表达式通过闭包保存了起来</span>
<span class="hljs-keyword">var</span> hasNumber <span class="hljs-operator">=</span> curryingCheck(<span class="hljs-operator">/</span>\d<span class="hljs-operator">+</span><span class="hljs-operator">/</span>g)
<span class="hljs-keyword">var</span> hasLetter <span class="hljs-operator">=</span> curryingCheck(<span class="hljs-operator">/</span>[a<span class="hljs-operator">-</span>z]<span class="hljs-operator">+</span><span class="hljs-operator">/</span>g)

console.log(hasNumber(<span class="hljs-string">'test1'</span>)); <span class="hljs-comment">// true</span>
console.log(hasNumber(<span class="hljs-string">'testtest'</span>));  <span class="hljs-comment">// false</span>
console.log(hasLetter(<span class="hljs-string">'21212'</span>)); <span class="hljs-comment">// false</span>
</code></pre><p>上面的示例是一个正则的校验，正常来说直接调用 <em>check</em> 函数就可以了，但是如果我有很多地方都要校验是否有数字，其实就是需要将第一个参数 <em>reg</em> 进行复用，这样别的地方就能够直接调用 <em>hasNumber、hasLetter</em> 等函数，让参数能够复用，调用起来也更方便。</p><p><strong>提前确认</strong></p><pre data-type="codeBlock" text="/**
 * 
 * @param {要绑定事件的 DOM 元素} element 
 * @param {绑定什么事件} event 
 * @param {事件处理函数} handler 
 */
var on = function (element, event, handler) {
    if (document.addEventListener) {
        if (element &amp;&amp; event &amp;&amp; handler) {
            element.addEventListener(event, handler, false);
        }
    } else {
        if (element &amp;&amp; event &amp;&amp; handler) {
            element.attachEvent(&apos;on&apos; + event, handler);
        }
    }
}

on(div, &apos;click&apos;, function(){})


var on = (function () {
    if (document.addEventListener) {
        return function (element, event, handler) {
            if (element &amp;&amp; event &amp;&amp; handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function (element, event, handler) {
            if (element &amp;&amp; event &amp;&amp; handler) {
                element.attachEvent(&apos;on&apos; + event, handler);
            }
        };
    }
})();

on(div, &apos;click&apos;, function(){})

//换一种写法可能比较好理解一点，上面就是把 isSupport 这个参数给先确定下来了
var on = function (isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent(&apos;on&apos; + event, handler);
    }
}
on(true, div, &apos;click&apos;, function(){})
on(true, div, &apos;click&apos;, function(){})
on(true, div, &apos;click&apos;, function(){})
"><code><span class="hljs-comment">/**
 * 
 * @param {要绑定事件的 DOM 元素} element 
 * @param {绑定什么事件} event 
 * @param {事件处理函数} handler 
 */</span>
<span class="hljs-keyword">var</span> on <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">element, <span class="hljs-keyword">event</span>, handler</span>) </span>{
    <span class="hljs-keyword">if</span> (document.addEventListener) {
        <span class="hljs-keyword">if</span> (element <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-function"><span class="hljs-keyword">event</span> &#x26;&#x26; <span class="hljs-title">handler</span>) </span>{
            element.addEventListener(<span class="hljs-function"><span class="hljs-keyword">event</span>, <span class="hljs-title">handler</span>, <span class="hljs-title"><span class="hljs-literal">false</span></span>)</span>;
        }
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">if</span> (element <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-function"><span class="hljs-keyword">event</span> &#x26;&#x26; <span class="hljs-title">handler</span>) </span>{
            element.attachEvent(<span class="hljs-string">'on'</span> <span class="hljs-operator">+</span> <span class="hljs-function"><span class="hljs-keyword">event</span>, <span class="hljs-title">handler</span>)</span>;
        }
    }
}

on(div, <span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{})


<span class="hljs-keyword">var</span> on <span class="hljs-operator">=</span> (<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">if</span> (document.addEventListener) {
        <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">element, <span class="hljs-keyword">event</span>, handler</span>) </span>{
            <span class="hljs-keyword">if</span> (element <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-function"><span class="hljs-keyword">event</span> &#x26;&#x26; <span class="hljs-title">handler</span>) </span>{
                element.addEventListener(<span class="hljs-function"><span class="hljs-keyword">event</span>, <span class="hljs-title">handler</span>, <span class="hljs-title"><span class="hljs-literal">false</span></span>)</span>;
            }
        };
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">element, <span class="hljs-keyword">event</span>, handler</span>) </span>{
            <span class="hljs-keyword">if</span> (element <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-function"><span class="hljs-keyword">event</span> &#x26;&#x26; <span class="hljs-title">handler</span>) </span>{
                element.attachEvent(<span class="hljs-string">'on'</span> <span class="hljs-operator">+</span> <span class="hljs-function"><span class="hljs-keyword">event</span>, <span class="hljs-title">handler</span>)</span>;
            }
        };
    }
})();

on(div, <span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{})

<span class="hljs-comment">//换一种写法可能比较好理解一点，上面就是把 isSupport 这个参数给先确定下来了</span>
<span class="hljs-keyword">var</span> on <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">isSupport, element, <span class="hljs-keyword">event</span>, handler</span>) </span>{
    isSupport <span class="hljs-operator">=</span> isSupport <span class="hljs-operator">|</span><span class="hljs-operator">|</span> document.addEventListener;
    <span class="hljs-keyword">if</span> (isSupport) {
        <span class="hljs-keyword">return</span> element.addEventListener(<span class="hljs-function"><span class="hljs-keyword">event</span>, <span class="hljs-title">handler</span>, <span class="hljs-title"><span class="hljs-literal">false</span></span>)</span>;
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> element.attachEvent(<span class="hljs-string">'on'</span> <span class="hljs-operator">+</span> <span class="hljs-function"><span class="hljs-keyword">event</span>, <span class="hljs-title">handler</span>)</span>;
    }
}
on(<span class="hljs-literal">true</span>, div, <span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{})
on(<span class="hljs-literal">true</span>, div, <span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{})
on(<span class="hljs-literal">true</span>, div, <span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{})
</code></pre><p>我们在做项目的过程中，封装一些 <em>DOM</em> 操作可以说再常见不过，上面第一种写法也是比较常见，但是我们看看第二种写法，它相对于第一种写法就是自执行然后返回一个新的函数，这样其实就是提前确定了会走哪一个方法，避免每次都进行判断。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">封装通用柯里化函数</h2><p>接下来我们来封装一个通用的柯里化函数。</p><pre data-type="codeBlock" text="function curry() {
    var fn = arguments[0]; // 获取要执行的函数
    var args = [].slice.call(arguments, 1); // 获取传递的参数，构成一个参数数组
    // 如果传递的参数已经等于执行函数所需的参数数量
    if (args.length === fn.length) {
        return fn.apply(this, args)
    }
    // 参数不够向外界返回的函数
    function _curry(){
        // 推入之前判断
        // 将新接收到的参数推入到参数数组中
        args.push(...arguments);
        if(args.length === fn.length){
            return fn.apply(this, args)
        }
        return _curry;
    }
    return _curry;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">curry</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> fn <span class="hljs-operator">=</span> arguments[<span class="hljs-number">0</span>]; <span class="hljs-comment">// 获取要执行的函数</span>
    <span class="hljs-keyword">var</span> args <span class="hljs-operator">=</span> [].slice.<span class="hljs-built_in">call</span>(arguments, <span class="hljs-number">1</span>); <span class="hljs-comment">// 获取传递的参数，构成一个参数数组</span>
    <span class="hljs-comment">// 如果传递的参数已经等于执行函数所需的参数数量</span>
    <span class="hljs-keyword">if</span> (args.<span class="hljs-built_in">length</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> fn.<span class="hljs-built_in">length</span>) {
        <span class="hljs-keyword">return</span> fn.apply(<span class="hljs-built_in">this</span>, args)
    }
    <span class="hljs-comment">// 参数不够向外界返回的函数</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_curry</span>(<span class="hljs-params"></span>)</span>{
        <span class="hljs-comment">// 推入之前判断</span>
        <span class="hljs-comment">// 将新接收到的参数推入到参数数组中</span>
        args.<span class="hljs-built_in">push</span>(...arguments);
        <span class="hljs-keyword">if</span>(args.<span class="hljs-built_in">length</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> fn.<span class="hljs-built_in">length</span>){
            <span class="hljs-keyword">return</span> fn.apply(<span class="hljs-built_in">this</span>, args)
        }
        <span class="hljs-keyword">return</span> _curry;
    }
    <span class="hljs-keyword">return</span> _curry;
}
</code></pre><p>对上面的代码进行测试：</p><pre data-type="codeBlock" text="// 测试 1
function add(a, b, c) {
    return a + b + c;
}

console.log(curry(add)(1)(2)(3)); // 6
console.log(curry(add, 1)(2)(3)); // 6
console.log(curry(add, 1, 2, 3)); // 6
console.log(curry(add, 1)(3, 4)); // 8

var addCurrying = curry(add)(2);
console.log(addCurrying(7)(8)); // 17

// 测试 2
function check(reg, txt) {
    return reg.test(txt)
}
var hasNumber = curry(check)(/\d+/g);
console.log(hasNumber(&apos;test1&apos;));// true
"><code><span class="hljs-comment">// 测试 1</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params">a, b, c</span>) </span>{
    <span class="hljs-keyword">return</span> a <span class="hljs-operator">+</span> b <span class="hljs-operator">+</span> c;
}

console.log(curry(add)(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)(<span class="hljs-number">3</span>)); <span class="hljs-comment">// 6</span>
console.log(curry(add, <span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)(<span class="hljs-number">3</span>)); <span class="hljs-comment">// 6</span>
console.log(curry(add, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)); <span class="hljs-comment">// 6</span>
console.log(curry(add, <span class="hljs-number">1</span>)(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>)); <span class="hljs-comment">// 8</span>

<span class="hljs-keyword">var</span> addCurrying <span class="hljs-operator">=</span> curry(add)(<span class="hljs-number">2</span>);
console.log(addCurrying(<span class="hljs-number">7</span>)(<span class="hljs-number">8</span>)); <span class="hljs-comment">// 17</span>

<span class="hljs-comment">// 测试 2</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">check</span>(<span class="hljs-params">reg, txt</span>) </span>{
    <span class="hljs-keyword">return</span> reg.test(txt)
}
<span class="hljs-keyword">var</span> hasNumber <span class="hljs-operator">=</span> curry(check)(<span class="hljs-operator">/</span>\d<span class="hljs-operator">+</span><span class="hljs-operator">/</span>g);
console.log(hasNumber(<span class="hljs-string">'test1'</span>));<span class="hljs-comment">// true</span>
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">一道经典的柯里化面试题</h2><p>实现一个 <em>add</em> 方法，使计算结果能够满足如下预期：</p><pre data-type="codeBlock" text="add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
"><code><span class="hljs-built_in">add</span>(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)(<span class="hljs-number">3</span>) = <span class="hljs-number">6</span>;
<span class="hljs-built_in">add</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)(<span class="hljs-number">4</span>) = <span class="hljs-number">10</span>;
<span class="hljs-built_in">add</span>(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)(<span class="hljs-number">3</span>)(<span class="hljs-number">4</span>)(<span class="hljs-number">5</span>) = <span class="hljs-number">15</span>;
</code></pre><p>要完成上面的需求，我们就可以使用柯里化函数：</p><pre data-type="codeBlock" text="function add() {
    // 第一次执行时，定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数，利用闭包的特性保存 _args 并收集所有的参数值
    var _adder = function () {
        _args.push(...arguments);
        return _adder;
    };

    // 这个是最后输出的时候被调用的，return 后面如果是函数体，
    // 为了输出函数体字符串会自动调用 toString 方法
    // 利用 toString 隐式转换的特性，当最后执行时隐式转换，并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }

    // 这个 return 是第一次调用的时候返回上面的函数体，
    // 这样后面所有的括号再执行的时候就是执行 _adder 函数体
    return _adder;
}
console.log(add(1)(2)(3).toString()); // 6
console.log(add(1, 2, 3)(4).toString()); // 10
console.log(add(1)(2)(3)(4)(5).toString()); // 15
console.log(add(2, 6)(1).toString()); // 9
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// 第一次执行时，定义一个数组专门用来存储所有的参数</span>
    <span class="hljs-keyword">var</span> _args <span class="hljs-operator">=</span> Array.prototype.slice.<span class="hljs-built_in">call</span>(arguments);

    <span class="hljs-comment">// 在内部声明一个函数，利用闭包的特性保存 _args 并收集所有的参数值</span>
    <span class="hljs-keyword">var</span> _adder <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
        _args.<span class="hljs-built_in">push</span>(...arguments);
        <span class="hljs-keyword">return</span> _adder;
    };

    <span class="hljs-comment">// 这个是最后输出的时候被调用的，return 后面如果是函数体，</span>
    <span class="hljs-comment">// 为了输出函数体字符串会自动调用 toString 方法</span>
    <span class="hljs-comment">// 利用 toString 隐式转换的特性，当最后执行时隐式转换，并计算最终的值返回</span>
    _adder.toString <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">return</span> _args.reduce(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">a, b</span>) </span>{
            <span class="hljs-keyword">return</span> a <span class="hljs-operator">+</span> b;
        });
    }

    <span class="hljs-comment">// 这个 return 是第一次调用的时候返回上面的函数体，</span>
    <span class="hljs-comment">// 这样后面所有的括号再执行的时候就是执行 _adder 函数体</span>
    <span class="hljs-keyword">return</span> _adder;
}
console.log(add(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)(<span class="hljs-number">3</span>).toString()); <span class="hljs-comment">// 6</span>
console.log(add(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)(<span class="hljs-number">4</span>).toString()); <span class="hljs-comment">// 10</span>
console.log(add(<span class="hljs-number">1</span>)(<span class="hljs-number">2</span>)(<span class="hljs-number">3</span>)(<span class="hljs-number">4</span>)(<span class="hljs-number">5</span>).toString()); <span class="hljs-comment">// 15</span>
console.log(add(<span class="hljs-number">2</span>, <span class="hljs-number">6</span>)(<span class="hljs-number">1</span>).toString()); <span class="hljs-comment">// 9</span>
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[断点续传]]></title>
            <link>https://paragraph.com/@murraya/7ZvPm47pqwx3EQC4eLy9</link>
            <guid>7ZvPm47pqwx3EQC4eLy9</guid>
            <pubDate>Tue, 10 May 2022 12:28:23 GMT</pubDate>
            <description><![CDATA[下载若要实现下载时的断点续传，首先，服务器在响应时，要在头中加入下面的字段Accept-Ranges: bytes 这个字段是向客户端表明：我这个文件可以支持传输部分数据，你只需要告诉我你需要的是哪一部分的数据即可，单位是字节 此时，某些支持断点续传的客户端，比如迅雷，它就可以在请求时，告诉服务器需要的数据范围。具体做法是在请求头中加入下面的字段range: bytes=0-5000 客户端告诉服务器：请给我传递0-5000字节范围内的数据即可，无须传输全部数据 完整流程如下sequenceDiagram 客户端->>服务器: head请求，询问文件信息 服务器->>客户端: Content-Disposition,Accept-Ranges,Content-Length 客户端->>服务器: Range:bytes=0-500 服务器->>客户端: 0-500字节的数据 客户端->>服务器: Range:bytes=501-1000 服务器->>客户端: 501-1000字节的数据 客户端->>服务器: ... 服务器->>客户端: ... 客户端->>客户端: 将碎片信息组装...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"></h1><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9284990c8d42653a002b850a1ae2a55e8482e89bcc95049f9a28d7d6143d0eeb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">下载</h1><p>若要实现下载时的断点续传，首先，服务器在响应时，要在头中加入下面的字段</p><pre data-type="codeBlock" text="Accept-Ranges: bytes
"><code>Accept<span class="hljs-operator">-</span>Ranges: <span class="hljs-keyword">bytes</span>
</code></pre><p>这个字段是向客户端表明：我这个文件可以支持传输部分数据，你只需要告诉我你需要的是哪一部分的数据即可，单位是字节</p><p>此时，某些支持断点续传的客户端，比如迅雷，它就可以在请求时，告诉服务器需要的数据范围。具体做法是在请求头中加入下面的字段</p><pre data-type="codeBlock" text="range: bytes=0-5000
"><code>range: <span class="hljs-attr">bytes</span>=<span class="hljs-number">0</span>-<span class="hljs-number">5000</span>
</code></pre><p>客户端告诉服务器：请给我传递0-5000字节范围内的数据即可，无须传输全部数据</p><p>完整流程如下</p><pre data-type="codeBlock" text="sequenceDiagram
客户端-&gt;&gt;服务器: head请求，询问文件信息
服务器-&gt;&gt;客户端: Content-Disposition,Accept-Ranges,Content-Length
客户端-&gt;&gt;服务器: Range:bytes=0-500
服务器-&gt;&gt;客户端: 0-500字节的数据
客户端-&gt;&gt;服务器: Range:bytes=501-1000
服务器-&gt;&gt;客户端: 501-1000字节的数据
客户端-&gt;&gt;服务器: ...
服务器-&gt;&gt;客户端: ...
客户端-&gt;&gt;客户端: 将碎片信息组装成完整文件
"><code>sequenceDiagram
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>服务器: head请求，询问文件信息
服务器<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: Content<span class="hljs-operator">-</span>Disposition,Accept<span class="hljs-operator">-</span>Ranges,Content<span class="hljs-operator">-</span>Length
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>服务器: Range:<span class="hljs-keyword">bytes</span><span class="hljs-operator">=</span><span class="hljs-number">0</span><span class="hljs-number">-500</span>
服务器<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: <span class="hljs-number">0</span><span class="hljs-number">-500</span>字节的数据
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>服务器: Range:<span class="hljs-keyword">bytes</span><span class="hljs-operator">=</span><span class="hljs-number">501</span><span class="hljs-number">-1000</span>
服务器<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: <span class="hljs-number">501</span><span class="hljs-number">-1000</span>字节的数据
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>服务器: ...
服务器<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: ...
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: 将碎片信息组装成完整文件
</code></pre><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">上传</h1><p>整体来说，实现断点上传的主要思路就是把要上传的文件切分为多个小的数据块然后进行上传</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9284990c8d42653a002b850a1ae2a55e8482e89bcc95049f9a28d7d6143d0eeb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>虽然分片上传的整体思路一致，但它没有一个统一的、具体的标准，因此需要根据具体的业务场景制定自己的标准。</p><p>由于标准的不同，这也就意味着分片上传需要自行编写代码实现。</p><p>下面用一种极其简易的流程实现分片上传</p><pre data-type="codeBlock" text="sequenceDiagram
Note left of 客户端: 用户选择文件
Note left of 客户端: 将文件分割为多个分片
Note left of 客户端: 得到文件的MD5和每个分片的MD5
客户端-&gt;&gt;服务器: 文件MD5、后缀、分片顺序、每个分片MD5
Note right of 服务器: 文件还剩哪些分片没有上传？
服务器-&gt;&gt;客户端: 还需要上传的分片MD5
客户端-&gt;&gt;服务器: 上传一个分片
Note right of 服务器: 文件还剩哪些分片没有上传？
服务器-&gt;&gt;客户端: 还需要上传的分片MD5
客户端-&gt;&gt;服务器: 上传一个分片
Note right of 服务器: 文件还剩哪些分片没有上传？
服务器-&gt;&gt;客户端: 还需要上传的分片MD5
客户端-&gt;&gt;服务器: 上传一个分片
Note right of 服务器: 所有分片已上传
Note right of 服务器: 合并所有分片成完整的文件
服务器-&gt;&gt;客户端: 文件的访问地址
"><code>sequenceDiagram
Note left of 客户端: 用户选择文件
Note left of 客户端: 将文件分割为多个分片
Note left of 客户端: 得到文件的MD5和每个分片的MD5
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>服务器: 文件MD5、后缀、分片顺序、每个分片MD5
Note right of 服务器: 文件还剩哪些分片没有上传？
服务器<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: 还需要上传的分片MD5
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>服务器: 上传一个分片
Note right of 服务器: 文件还剩哪些分片没有上传？
服务器<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: 还需要上传的分片MD5
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>服务器: 上传一个分片
Note right of 服务器: 文件还剩哪些分片没有上传？
服务器<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: 还需要上传的分片MD5
客户端<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>服务器: 上传一个分片
Note right of 服务器: 所有分片已上传
Note right of 服务器: 合并所有分片成完整的文件
服务器<span class="hljs-operator">-</span><span class="hljs-operator">></span><span class="hljs-operator">></span>客户端: 文件的访问地址
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[Node 事件循环]]></title>
            <link>https://paragraph.com/@murraya/node</link>
            <guid>MPGmEufjZ2o0vJJ4ZrTE</guid>
            <pubDate>Tue, 10 May 2022 12:23:04 GMT</pubDate>
            <description><![CDATA[Node 事件循环经典真题请简述一下 Node.js 中的事件循环，和浏览器环境的事件循环有何不同？Node.js 与浏览器的事件循环有何区别？进程与线程我们经常说 JavaScript 是一门单线程语言，指的是一个进程里只有一个主线程，那到底什么是线程？什么是进程？ 首先需要把这个问题搞明白。 进程是 CPU 资源分配的最小单位，而线程是 CPU 调度的最小单位。举个例子，看下面的图：进程好比图中的工厂，有单独的专属自己的工厂资源。线程好比图中的工人，多个工人在一个工厂中协作工作，工厂与工人是 1:n 的关系。也就是说一个进程由一个或多个线程组成，线程是一个进程中代码的不同执行路线。工厂的空间是工人们共享的，这象征一个进程的内存空间是共享的，每个线程都可用这些共享内存。多个工厂之间独立存在。接下来我们回过头来看多进程和多线程的概念：多进程：在同一个时间里，同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的，比如你可以听歌的同时，打开编辑器敲代码，编辑器和听歌软件的进程之间丝毫不会相互干扰。多线程：程序中包含多个执行流，即在一个程序中可以同时运行...]]></description>
            <content:encoded><![CDATA[<h1 id="h-node" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><em>Node</em> 事件循环</h1><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">经典真题</h2><ul><li><p>请简述一下 <em>Node.js</em> 中的事件循环，和浏览器环境的事件循环有何不同？</p></li></ul><h2 id="h-nodejs" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><em>Node.js</em> 与浏览器的事件循环有何区别？</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">进程与线程</h3><p>我们经常说 <em>JavaScript</em> 是一门单线程语言，指的是一个进程里只有一个主线程，那到底什么是线程？什么是进程？</p><p>首先需要把这个问题搞明白。</p><p>进程是 <em>CPU</em> 资源分配的最小单位，而线程是 <em>CPU</em> 调度的最小单位。举个例子，看下面的图：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3bf0e04b5eeb77de974d5a8baf63f51b7ad9cc88b97b6a060a1b47994ca74ddb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><ul><li><p>进程好比图中的工厂，有单独的专属自己的工厂资源。</p></li><li><p>线程好比图中的工人，多个工人在一个工厂中协作工作，工厂与工人是 <em>1:n</em> 的关系。也就是说<strong>一个进程由一个或多个线程组成，线程是一个进程中代码的不同执行路线</strong>。</p></li><li><p>工厂的空间是工人们共享的，这象征<strong>一个进程的内存空间是共享的，每个线程都可用这些共享内存</strong>。</p></li><li><p>多个工厂之间独立存在。</p></li></ul><p>接下来我们回过头来看多进程和多线程的概念：</p><ul><li><p>多进程：在同一个时间里，同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的，比如你可以听歌的同时，打开编辑器敲代码，编辑器和听歌软件的进程之间丝毫不会相互干扰。</p></li><li><p>多线程：程序中包含多个执行流，即在一个程序中可以同时运行多个不同的线程来执行不同的任务，也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。</p></li></ul><p>以 <em>Chrome</em> 浏览器中为例，当你打开一个 <em>Tab</em> 页时，其实就是创建了一个进程。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cd2c2c486e63c21dfa7e337f66ebbaa84d1c1cfd1feda78673d81961e1a51e68.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>一个进程中可以有多个线程，比如渲染线程、<em>JS</em> 引擎线程、<em>HTTP</em> 请求线程等等。当你发起一个请求时，其实就是创建了一个线程，当请求结束后，该线程可能就会被销毁。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">浏览器内核</h3><p>简单来说浏览器内核是通过取得页面内容、整理信息（应用 <em>CSS</em> ）、计算和组合最终输出可视化的图像结果，通常也被称为渲染引擎。</p><p>浏览器内核是多线程，在内核控制下各线程相互配合以保持同步，一个浏览器通常由以下常驻线程组成：</p><ul><li><p><em>GUI</em> 渲染线程</p></li><li><p><em>JavaScript</em> 引擎线程</p></li><li><p>定时触发器线程</p></li><li><p>事件触发线程</p></li><li><p>异步 <em>http</em> 请求线程</p></li></ul><h4 id="h-gui" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><em>GUI</em> 渲染线程</h4><ul><li><p>主要负责页面的渲染，解析 <em>HTML</em>、<em>CSS</em>，构建 <em>DOM</em> 树，布局和绘制等。</p></li><li><p>当界面需要重绘或者由于某种操作引发回流时，将执行该线程。</p></li><li><p>该线程与 <em>JS</em> 引擎线程互斥，当执行 <em>JS</em> 引擎线程时，<em>GUI</em> 渲染会被挂起，当任务队列空闲时，主线程才会去执行 <em>GUI</em> 渲染。</p></li></ul><h4 id="h-javascript" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><em>JavaScript</em> 引擎线程</h4><ul><li><p>该线程当然是主要负责处理 <em>JavaScript</em> 脚本，执行代码。</p></li><li><p>也是主要负责执行准备好待执行的事件，即定时器计数结束，或者异步请求成功并正确返回时，将依次进入任务队列，等待 <em>JS</em> 引擎线程的执行。</p></li><li><p>当然，该线程与 <em>GUI</em> 渲染线程互斥，当 <em>JS</em> 引擎线程执行 <em>JavaScript</em> 脚本时间过长，将导致页面渲染的阻塞。</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">定时触发器线程</h4><ul><li><p>负责执行异步定时器一类的函数的线程，如：<em>setTimeout、setInterval</em>。</p></li><li><p>主线程依次执行代码时，遇到定时器，会将定时器交给该线程处理，当计数完毕后，事件触发线程会将计数完毕后的事件加入到任务队列的尾部，等待 <em>JS</em> 引擎线程执行。</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">事件触发线程</h4><ul><li><p>主要负责将准备好的事件交给 <em>JS</em> 引擎线程执行。</p></li></ul><p>比如 <em>setTimeout</em> 定时器计数结束， <em>ajax</em> 等异步请求成功并触发回调函数，或者用户触发点击事件时，该线程会将整装待发的事件依次加入到任务队列的队尾，等待 <em>JS</em> 引擎线程的执行。</p><h4 id="h-http" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">异步 <em>http</em> 请求线程</h4><ul><li><p>负责执行异步请求一类的函数的线程，如：<em>Promise、fetch、ajax</em> 等。</p></li><li><p>主线程依次执行代码时，遇到异步请求，会将函数交给该线程处理，当监听到状态码变更，如果有回调函数，事件触发线程会将回调函数加入到任务队列的尾部，等待 <em>JS</em> 引擎线程执行。</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">浏览器中的事件循环</h3><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">宏任务和微任务</h4><p>事件循环中的异步队列有两种：宏任务（ <em>macro</em> ）队列和微任务（ <em>micro</em> ）队列。</p><p><strong>宏任务队列有一个，微任务队列只有一个</strong>。</p><ul><li><p>常见的宏任务有：<em>setTimeout、setInterval、requestAnimationFrame、script</em>等。</p></li><li><p>常见的微任务有：<em>new Promise( ).then(回调)、MutationObserver</em> 等。</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">事件循环流程</h4><p>一个完整的事件循环过程，可以概括为以下阶段：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1fba977c8a2c07e1ce25c9f25f585c9b705d3261364b44e3fa825ecc78b1feec.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><ul><li><p>一开始执行栈空，我们可以把<strong>执行栈认为是一个存储函数调用的栈结构，遵循先进后出的原则</strong>。微任务队列空，宏任务队列里有且只有一个 <em>script</em> 脚本（整体代码）。</p></li><li><p>全局上下文（ <em>script</em> 标签）被推入执行栈，同步代码执行。在执行的过程中，会判断是同步任务还是异步任务，通过对一些接口的调用，可以产生新的宏任务与微任务，它们会分别被推入各自的任务队列里。同步代码执行完了，<em>script</em> 脚本会被移出宏任务队列，这个过程本质上是队列的宏任务的执行和出队的过程。</p></li><li><p>上一步我们出队的是一个宏任务，这一步我们处理的是微任务。但需要注意的是：当一个宏任务执行完毕后，会执行所有的微任务，也就是将整个微任务队列清空。</p></li><li><p>执行渲染操作，更新界面</p></li><li><p>检查是否存在 <em>Web worker</em> 任务，如果有，则对其进行处理</p></li><li><p>上述过程循环往复，直到两个队列都清空</p></li></ul><p>宏任务和微任务的执行流程，总结起来就是：</p><p><strong>当某个宏任务执行完后，会查看是否有微任务队列。如果有，先执行微任务队列中的所有任务，如果没有，会读取宏任务队列中排在最前的任务，执行宏任务的过程中，遇到微任务，依次加入微任务队列。栈空后，再次读取微任务队列里的任务，依次类推。</strong></p><p>执行流程如下图所示：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/963ec63a7709366d0da01de1f0aef9f0dc9b6573324eb11eb60de0192aa7c967.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>这里我们可以来看两道具体的代码加深理解：</p><pre data-type="codeBlock" text="console.log(&apos;script start&apos;);
setTimeout(function() {
    console.log(&apos;setTimeout&apos;);
}, 0);

Promise.resolve().then(function() {
    console.log(&apos;promise1&apos;);
}).then(function() {
    console.log(&apos;promise2&apos;);
});

console.log(&apos;script end&apos;);
"><code>console.log(<span class="hljs-string">'script start'</span>);
setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    console.log(<span class="hljs-string">'setTimeout'</span>);
}, <span class="hljs-number">0</span>);

Promise.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    console.log(<span class="hljs-string">'promise1'</span>);
}).then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    console.log(<span class="hljs-string">'promise2'</span>);
});

console.log(<span class="hljs-string">'script end'</span>);
</code></pre><p>上面的代码输出的结果为：</p><pre data-type="codeBlock" text="script start
script end
promise1
promise2
setTimeout
"><code>script start
script end
promise1
promise2
setTimeout
</code></pre><p>原因很简单，首先会执行同步的任务，输出 <em>script start</em> 以及 <em>script end</em>。接下来是处理异步任务，异步任务分为宏任务队列和微任务队列，在执行宏任务队列中的每个宏任务之前先把微任务清空一遍，由于 <em>promise</em> 是微任务，所以会先被执行，而 <em>setTimeout</em> 由于是一个宏任务，会在微任务队列被清空后再执行。</p><pre data-type="codeBlock" text="Promise.resolve().then(()=&gt;{
  console.log(&apos;Promise1&apos;)
  setTimeout(()=&gt;{
    console.log(&apos;setTimeout2&apos;)
  },0)
})
setTimeout(()=&gt;{
  console.log(&apos;setTimeout1&apos;)
  Promise.resolve().then(()=&gt;{
    console.log(&apos;Promise2&apos;)
  })
},0)
"><code>Promise.resolve().then(()<span class="hljs-operator">=</span><span class="hljs-operator">></span>{
  console.log(<span class="hljs-string">'Promise1'</span>)
  setTimeout(()<span class="hljs-operator">=</span><span class="hljs-operator">></span>{
    console.log(<span class="hljs-string">'setTimeout2'</span>)
  },<span class="hljs-number">0</span>)
})
setTimeout(()<span class="hljs-operator">=</span><span class="hljs-operator">></span>{
  console.log(<span class="hljs-string">'setTimeout1'</span>)
  Promise.resolve().then(()<span class="hljs-operator">=</span><span class="hljs-operator">></span>{
    console.log(<span class="hljs-string">'Promise2'</span>)
  })
},<span class="hljs-number">0</span>)
</code></pre><p>上面的代码输出的结果为：</p><pre data-type="codeBlock" text="Promise1
setTimeout1
Promise2
setTimeout2
"><code></code></pre><p>一开始执行栈的同步任务（这属于宏任务）执行完毕，会去查看是否有微任务队列，上题中存在（有且只有一个），然后执行微任务队列中的所有任务输出 <em>Promise1</em>，同时会生成一个宏任务 <em>setTimeout2</em>。</p><p>然后去查看宏任务队列，宏任务 <em>setTimeout1</em> 在 <em>setTimeout2</em> 之前，先执行宏任务 <em>setTimeout1</em>，输出 <em>setTimeout1</em>。在执行宏任务 <em>setTimeout1</em> 时会生成微任务 <em>Promise2</em> ，放入微任务队列中，接着先去清空微任务队列中的所有任务，输出 <em>Promise2</em>。</p><p>清空完微任务队列中的所有任务后，就又会去宏任务队列取一个，这回执行的是 <em>setTimeout2</em>。</p><h3 id="h-nodejs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><em>Node.js</em> 中的事件循环</h3><h4 id="h-nodejs" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><em>Node.js</em> 事件循环介绍</h4><p><em>Node.js</em> 中的事件循环和浏览器中的是完全不相同的东西。</p><p><em>Node.js</em> 采用 <em>V8</em> 作为 <em>JS</em> 的解析引擎，而 <em>I/O</em> 处理方面使用了自己设计的 <em>libuv</em>，<em>libuv</em> 是一个基于事件驱动的跨平台抽象层，封装了不同操作系统一些底层特性，对外提供统一的 <em>API</em>，事件循环机制也是它里面的实现。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/73d4f19b548fdf21aeb83a9081e806d323d24f739e2ae2ba7a470701015ea869.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>可以看出 <em>Node.JS</em> 的事件循环比浏览器端复杂很多。<em>Node.js</em> 的运行机制如下:</p><ul><li><p><em>V8</em> 引擎解析 <em>JavaScript</em> 脚本。</p></li><li><p>解析后的代码，调用 <em>Node API</em>。</p></li><li><p><em>libuv</em> 库负责 <em>Node API</em> 的执行。它将不同的任务分配给不同的线程，形成一个事件循环，以异步的方式将任务的执行结果返回给 <em>V8</em> 引擎。</p></li><li><p><em>V8</em> 引擎再将结果返回给用户。</p></li></ul><p>整个架构图如下所示：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9301736385034f37511252d453f8b4ccd7f445cd4af8829d70f89ba488e3ca34.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h4 id="h-6" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">事件循环的 <em>6</em> 个阶段</h4><p>其中 <em>libuv</em> 引擎中的事件循环分为 <em>6</em> 个阶段，它们会按照顺序反复运行。每当进入某一个阶段的时候，都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值，就会进入下一阶段。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5aaa9cf333f5f48e9cfc0ced9285c0b6f304918e33e9c998348f2d8a3ce0b76b.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>从上图中，大致看出 <em>Node.js</em> 中的事件循环的顺序：</p><p>外部输入数据 –-&gt; 轮询阶段（ <em>poll</em> ）-–&gt; 检查阶段（ <em>check</em> ）-–&gt; 关闭事件回调阶段（ <em>close callback</em> ）–-&gt; 定时器检测阶段（ <em>timer</em> ）–-&gt; <em>I/O</em> 事件回调阶段（ <em>I/O callbacks</em> ）-–&gt;闲置阶段（ <em>idle、prepare</em> ）–-&gt;轮询阶段（按照该顺序反复运行）...</p><p>以上 <em>6</em> 个阶段所做的事情如下：</p><ul><li><p><em>timers</em> 阶段：这个阶段执行 <em>timer</em>（ <em>setTimeout、setInterval</em> ）的回调</p></li><li><p><em>I/O callbacks</em> 阶段：处理一些上一轮循环中的少数未执行的 <em>I/O</em> 回调</p></li><li><p><em>idle、prepare</em> 阶段：仅 <em>Node.js</em> 内部使用</p></li><li><p><em>poll</em> 阶段：获取新的 <em>I/O</em> 事件, 适当的条件下 <em>Node.js</em> 将阻塞在这里</p></li><li><p><em>check</em> 阶段：执行 <em>setImmediate( )</em> 的回调</p></li><li><p><em>close callbacks</em> 阶段：执行 <em>socket</em> 的 <em>close</em> 事件回调</p></li></ul><p>注意：<strong>上面六个阶段都不包括 <em>process.nextTick( )</em></strong></p><p>接下去我们详细介绍 <em>timers、poll、check</em> 这 <em>3</em> 个阶段，因为日常开发中的绝大部分异步任务都是在这 <em>3</em> 个阶段处理的。</p><p><strong><em>timer</em> 阶段</strong></p><p><em>timers</em> 阶段会执行 <em>setTimeout</em> 和 <em>setInterval</em> 回调，并且是由 <em>poll</em> 阶段控制的。同样，<strong>在 <em>Node.js</em> 中定时器指定的时间也不是准确时间，只能是尽快执行</strong>。</p><p><strong><em>poll</em> 阶段</strong></p><p><em>poll</em> 是一个至关重要的阶段，这一阶段中，系统会做两件事情：</p><ul><li><p>回到 <em>timer</em> 阶段执行回调</p></li><li><p>执行 <em>I/O</em> 回调</p></li></ul><p>并且在进入该阶段时如果没有设定了 <em>timer</em> 的话，会发生以下两件事情：</p><ul><li><p>如果 <em>poll</em> 队列不为空，会遍历回调队列并同步执行，直到队列为空或者达到系统限制</p></li><li><p>如果 <em>poll</em> 队列为空时，会有两件事发生：</p><ul><li><p>如果有 <em>setImmediate</em> 回调需要执行，<em>poll</em> 阶段会停止并且进入到 <em>check</em> 阶段执行回调</p></li><li><p>如果没有 <em>setImmediate</em> 回调需要执行，会等待回调被加入到队列中并立即执行回调，这里同样会有个超时时间设置防止一直等待下去</p></li></ul></li></ul><p>当然设定了 <em>timer</em> 的话且 <em>poll</em> 队列为空，则会判断是否有 <em>timer</em> 超时，如果有的话会回到 <em>timer</em> 阶段执行回调。</p><p>假设 <em>poll</em> 被堵塞，那么即使 <em>timer</em> 已经到时间了也只能等着，这也是为什么上面说定时器指定的时间并不是准确的时间。例如：</p><pre data-type="codeBlock" text="const start = Date.now();
setTimeout(function f1() {
    console.log(&quot;setTimeout&quot;, Date.now() - start);
}, 200);

const fs = require(&apos;fs&apos;);

fs.readFile(&apos;./index.js&apos;, &apos;utf-8&apos;, function f2() {
    console.log(&apos;readFile&apos;);
    const start = Date.now();
    // 强行延时 500 毫秒
    while (Date.now() - start &lt; 500) { }
})
"><code>const start <span class="hljs-operator">=</span> Date.now();
setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f1</span>(<span class="hljs-params"></span>) </span>{
    console.log(<span class="hljs-string">"setTimeout"</span>, Date.now() <span class="hljs-operator">-</span> start);
}, <span class="hljs-number">200</span>);

const fs <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);

fs.readFile(<span class="hljs-string">'./index.js'</span>, <span class="hljs-string">'utf-8'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f2</span>(<span class="hljs-params"></span>) </span>{
    console.log(<span class="hljs-string">'readFile'</span>);
    const start <span class="hljs-operator">=</span> Date.now();
    <span class="hljs-comment">// 强行延时 500 毫秒</span>
    <span class="hljs-keyword">while</span> (Date.now() <span class="hljs-operator">-</span> start <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">500</span>) { }
})
</code></pre><p><strong><em>check</em> 阶段</strong></p><p><em>setImmediate( )</em> 的回调会被加入 <em>check</em> 队列中，从事件循环的阶段图可以知道，<em>check</em> 阶段的执行顺序在 <em>poll</em> 阶段之后。</p><p>我们先来看个例子：</p><pre data-type="codeBlock" text="console.log(&apos;start&apos;)
setTimeout(() =&gt; {
  console.log(&apos;timer1&apos;)
  Promise.resolve().then(function() {
    console.log(&apos;promise1&apos;)
  })
}, 0)
setTimeout(() =&gt; {
  console.log(&apos;timer2&apos;)
  Promise.resolve().then(function() {
    console.log(&apos;promise2&apos;)
  })
}, 0)
Promise.resolve().then(function() {
  console.log(&apos;promise3&apos;)
})
console.log(&apos;end&apos;)
// 输出结果：start =&gt; end =&gt; promise3 =&gt; timer1 =&gt; promise1 =&gt; timer2 =&gt; promise2
"><code>console.log(<span class="hljs-string">'start'</span>)
setTimeout(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  console.log(<span class="hljs-string">'timer1'</span>)
  Promise.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    console.log(<span class="hljs-string">'promise1'</span>)
  })
}, <span class="hljs-number">0</span>)
setTimeout(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  console.log(<span class="hljs-string">'timer2'</span>)
  Promise.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    console.log(<span class="hljs-string">'promise2'</span>)
  })
}, <span class="hljs-number">0</span>)
Promise.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
  console.log(<span class="hljs-string">'promise3'</span>)
})
console.log(<span class="hljs-string">'end'</span>)
<span class="hljs-comment">// 输出结果：start => end => promise3 => timer1 => promise1 => timer2 => promise2</span>
</code></pre><p>一开始执行同步任务，依次打印出 <em>start end</em>，并将 <em>2</em> 个 <em>timer</em> 依次放入 <em>timer</em> 队列，之后会立即执行微任务队列，所以打印出 <em>promise3</em>。</p><p>然后进入 <em>timers</em> 阶段，执行 <em>timer1</em> 的回调函数，打印 <em>timer1</em>，发现有一个 <em>promise.then</em> 回调将其加入到微任务队列并且立即执行，之后同样的步骤执行 <em>timer2</em>，打印 <em>timer2</em> 以及 <em>promise2</em>。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">一些注意点</h4><p><strong><em>setTimeout</em> 和 <em>setImmediate</em> 区别</strong></p><p>二者非常相似，区别主要在于调用时机不同。</p><ul><li><p><em>setImmediate</em> 设计在 <em>poll</em> 阶段完成时执行，即 <em>check</em> 阶段</p></li><li><p><em>setTimeout</em> 设计在 <em>poll</em> 阶段为空闲时，且设定时间到达后执行，但它在 <em>timer</em> 阶段执行</p></li></ul><p>来看一个具体的示例：</p><pre data-type="codeBlock" text="setTimeout(function timeout () {
  console.log(&apos;timeout&apos;);
},0);
setImmediate(function immediate () {
  console.log(&apos;immediate&apos;);
});
"><code>setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">timeout</span> (<span class="hljs-params"></span>) </span>{
  console.log(<span class="hljs-string">'timeout'</span>);
},<span class="hljs-number">0</span>);
setImmediate(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">immediate</span> (<span class="hljs-params"></span>) </span>{
  console.log(<span class="hljs-string">'immediate'</span>);
});
</code></pre><p>对于以上代码来说，<em>setTimeout</em> 可能执行在前，也可能执行在后。首先 <em>setTimeout(fn, 0) === setTimeout(fn, 1)</em>，这是由源码决定的，进入事件循环也是需要成本的，如果在准备时候花费了大于 <em>1ms</em> 的时间，那么在 <em>timer</em> 阶段就会直接执行 <em>setTimeout</em> 回调。如果准备时间花费小于 <em>1ms</em>，那么就是 <em>setImmediate</em> 回调先执行了。</p><p>但当二者在异步 <em>I/O callback</em> 内部调用时，总是先执行 <em>setImmediate</em>，再执行 <em>setTimeout</em>，例如：</p><pre data-type="codeBlock" text="const fs = require(&apos;fs&apos;)
fs.readFile(__filename, () =&gt; {
    setTimeout(() =&gt; {
        console.log(&apos;timeout&apos;);
    }, 0)
    setImmediate(() =&gt; {
        console.log(&apos;immediate&apos;)
    })
})
// immediate
// timeout
"><code>const fs <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
fs.readFile(__filename, () <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    setTimeout(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
        console.log(<span class="hljs-string">'timeout'</span>);
    }, <span class="hljs-number">0</span>)
    setImmediate(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
        console.log(<span class="hljs-string">'immediate'</span>)
    })
})
<span class="hljs-comment">// immediate</span>
<span class="hljs-comment">// timeout</span>
</code></pre><p>在上述代码中，<em>setImmediate</em> 永远先执行。因为两个代码写在 <em>I/O</em> 回调中，<em>I/O</em> 回调是在 <em>poll</em> 阶段执行，当回调执行完毕后队列为空，发现存在 <em>setImmediate</em> 回调，所以就直接跳转到 <em>check</em> 阶段去执行回调了。</p><p><strong><em>process.nextTick</em></strong></p><p>这个函数其实是独立于事件循环之外的，它有一个自己的队列。当每个阶段完成后，如果存在 <em>nextTick</em> 队列，就会清空队列中的所有回调函数，并且优先于其他 <em>microtask</em> 执行。</p><pre data-type="codeBlock" text="setTimeout(() =&gt; {
 console.log(&apos;timer1&apos;)
 Promise.resolve().then(function() {
   console.log(&apos;promise1&apos;)
 })
}, 0)
process.nextTick(() =&gt; {
 console.log(&apos;nextTick&apos;)
 process.nextTick(() =&gt; {
   console.log(&apos;nextTick&apos;)
   process.nextTick(() =&gt; {
     console.log(&apos;nextTick&apos;)
     process.nextTick(() =&gt; {
       console.log(&apos;nextTick&apos;)
     })
   })
 })
})
// nextTick =&gt; nextTick =&gt; nextTick =&gt; nextTick =&gt; timer1 =&gt; promise1
"><code>setTimeout(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
 console.log(<span class="hljs-string">'timer1'</span>)
 Promise.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
   console.log(<span class="hljs-string">'promise1'</span>)
 })
}, <span class="hljs-number">0</span>)
process.nextTick(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
 console.log(<span class="hljs-string">'nextTick'</span>)
 process.nextTick(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
   console.log(<span class="hljs-string">'nextTick'</span>)
   process.nextTick(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
     console.log(<span class="hljs-string">'nextTick'</span>)
     process.nextTick(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
       console.log(<span class="hljs-string">'nextTick'</span>)
     })
   })
 })
})
<span class="hljs-comment">// nextTick => nextTick => nextTick => nextTick => timer1 => promise1</span>
</code></pre><p><strong><em>Promise.then</em></strong></p><p><em>Promise.then</em> 也是独立于事件循环之外的，有一个自己的队列，但是优先级要比 <em>process.nextTick</em> 要低，所以当微任务中同时存在 <em>process.nextTick</em> 和 <em>Promise.then</em> 时，会优先执行前者。</p><pre data-type="codeBlock" text="setTimeout(()=&gt;{
    console.log(&apos;timer1&apos;)
    Promise.resolve().then(function() {
        console.log(&apos;promise1&apos;)
    })
    process.nextTick(() =&gt; {
        console.log(&apos;nexttick&apos;);
    })
}, 0)
setTimeout(()=&gt;{
    console.log(&apos;timer2&apos;)
    Promise.resolve().then(function() {
        console.log(&apos;promise2&apos;)
    })
}, 0)
// timer1、nexttick、promise1、timer2、promise2
"><code>setTimeout(()<span class="hljs-operator">=</span><span class="hljs-operator">></span>{
    console.log(<span class="hljs-string">'timer1'</span>)
    Promise.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        console.log(<span class="hljs-string">'promise1'</span>)
    })
    process.nextTick(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
        console.log(<span class="hljs-string">'nexttick'</span>);
    })
}, <span class="hljs-number">0</span>)
setTimeout(()<span class="hljs-operator">=</span><span class="hljs-operator">></span>{
    console.log(<span class="hljs-string">'timer2'</span>)
    Promise.resolve().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        console.log(<span class="hljs-string">'promise2'</span>)
    })
}, <span class="hljs-number">0</span>)
<span class="hljs-comment">// timer1、nexttick、promise1、timer2、promise2</span>
</code></pre><h4 id="h-nodejs" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><em>Node.js</em> 与浏览器的事件队列的差异</h4><p>浏览器环境下，就两个队列，一个宏任务队列，一个微任务队列。微任务的任务队列是每个宏任务执行完之后执行。</p><p>在 <em>Node.js</em> 中，每个任务队列的每个任务执行完毕之后，就会清空这个微任务队列。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/733e9d8567936e94c4ef32f1c5181fb73487bf9928934d2c7d5664e09a221598.png" alt="eventloop" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">eventloop</figcaption></figure>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[分享：水龙头合约]]></title>
            <link>https://paragraph.com/@murraya/pA0PzBfoqekFgHW6wVUR</link>
            <guid>pA0PzBfoqekFgHW6wVUR</guid>
            <pubDate>Sun, 08 May 2022 10:00:34 GMT</pubDate>
            <description><![CDATA[分享一个ERC20水龙头合约这个合约使用了一个关键词：abstract。 合约如果继承另一个合约，而没有实现它的构造方法，我们使用abstract标记；// SPDX-License-Identifier: MIT License pragma solidity ^0.8.4; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; abstract contract ERC20Faucet is ERC20, Ownable { uint256 public interval = 60; // 领取时间周期 uint256 public total = 1000000e18; // 可领取的总量 uint256 public amount; // 单次领取的数量 uint256 public minted; // 总共已经领取的数量 mapping(address => uint256) lastFaucetTime;...]]></description>
            <content:encoded><![CDATA[<h2 id="h-erc20" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">分享一个ERC20水龙头合约</h2><p>这个合约使用了一个关键词：abstract。</p><p>合约如果继承另一个合约，而没有实现它的构造方法，我们使用abstract标记；</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.4;

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;
import &quot;@openzeppelin/contracts/access/Ownable.sol&quot;;

abstract contract ERC20Faucet is ERC20, Ownable {

    uint256 public interval = 60; // 领取时间周期
    uint256 public total = 1000000e18; // 可领取的总量
    uint256 public amount; // 单次领取的数量
    uint256 public minted; // 总共已经领取的数量

    mapping(address =&gt; uint256) lastFaucetTime;

    constructor (uint256 _amount){
        amount = _amount;
    }

    function faucet() external {
        require(total &gt;= minted + amount, &quot;Faucet is running out now.&quot;);
        require(block.timestamp - lastFaucetTime[msg.sender] &gt; interval, &quot;Interval is not expired.&quot;);

        minted += amount;
        lastFaucetTime[msg.sender] = block.timestamp;
        _mint(msg.sender, amount);
    }

    function setFaucetAmount(uint256 _faucetAmount) external onlyOwner {
        amount = _faucetAmount;
    }

    function setInterval(uint256 _inverval) external onlyOwner {
        interval = _inverval;
    }

    function setFaucetTotal(uint256 _faucetTotal) external {
        total = _faucetTotal;
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT License</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.4;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/ERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ERC20Faucet</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC20</span>, <span class="hljs-title">Ownable</span> </span>{

    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> interval <span class="hljs-operator">=</span> <span class="hljs-number">60</span>; <span class="hljs-comment">// 领取时间周期</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> total <span class="hljs-operator">=</span> <span class="hljs-number">1000000e18</span>; <span class="hljs-comment">// 可领取的总量</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> amount; <span class="hljs-comment">// 单次领取的数量</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> minted; <span class="hljs-comment">// 总共已经领取的数量</span>

    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint256</span>) lastFaucetTime;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> _amount</span>)</span>{
        amount <span class="hljs-operator">=</span> _amount;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">faucet</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(total <span class="hljs-operator">></span><span class="hljs-operator">=</span> minted <span class="hljs-operator">+</span> amount, <span class="hljs-string">"Faucet is running out now."</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">-</span> lastFaucetTime[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">></span> interval, <span class="hljs-string">"Interval is not expired."</span>);

        minted <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amount;
        lastFaucetTime[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>;
        _mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setFaucetAmount</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _faucetAmount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        amount <span class="hljs-operator">=</span> _faucetAmount;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setInterval</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _inverval</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        interval <span class="hljs-operator">=</span> _inverval;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setFaucetTotal</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _faucetTotal</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        total <span class="hljs-operator">=</span> _faucetTotal;
    }
}
</code></pre><p>写好这个合约后，token创建合约可以继承这个合约</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.4;

import &quot;./ERC20Faucet.sol&quot;;

contract USDC is ERC20Faucet {

    constructor (uint256 _premint, uint256 _amount) ERC20(&quot;USDC&quot;, &quot;USDC&quot;) ERC20Faucet (_amount) {
        _mint(owner(), _premint);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT License</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.4;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"./ERC20Faucet.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">USDC</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC20Faucet</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">constructor</span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> _premint, <span class="hljs-keyword">uint256</span> _amount</span>) <span class="hljs-title">ERC20</span>(<span class="hljs-params"><span class="hljs-string">"USDC"</span>, <span class="hljs-string">"USDC"</span></span>) <span class="hljs-title">ERC20Faucet</span> (<span class="hljs-params">_amount</span>) </span>{
        _mint(owner(), _premint);
    }
}
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[智能合约-19.事件]]></title>
            <link>https://paragraph.com/@murraya/19</link>
            <guid>xX7ksktKdJXxslvYs2XC</guid>
            <pubDate>Sun, 08 May 2022 08:30:47 GMT</pubDate>
            <description><![CDATA[事件记录是记录当前智能合约运行状态的方法，他不会记录在区块链上（状态变量）；会体现在区块链浏览器上面或者log中； 栗子：// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract Event { event Log(string message, address addr); // 带索引的方式 event IndexLog (address indexed sender, uint val, uint val2, uint val3, uint val4); function example() external { emit Log("address", msg.sender); emit IndexLog(msg.sender, 123, 2,3, 8); } }]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">事件</h2><p>记录是记录当前智能合约运行状态的方法，他不会记录在区块链上（状态变量）；会体现在区块链浏览器上面或者log中；</p><p>栗子：</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract Event {

    event Log(string message, address addr);
        // 带索引的方式
    event IndexLog (address indexed sender, uint val, uint val2, uint val3, uint val4);

    function example() external {
        emit Log(&quot;address&quot;, msg.sender);
        emit IndexLog(msg.sender, 123, 2,3, 8);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.7;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Event</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Log</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message, <span class="hljs-keyword">address</span> addr</span>)</span>;
        <span class="hljs-comment">// 带索引的方式</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">IndexLog</span> (<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> sender, <span class="hljs-keyword">uint</span> val, <span class="hljs-keyword">uint</span> val2, <span class="hljs-keyword">uint</span> val3, <span class="hljs-keyword">uint</span> val4</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">example</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-keyword">emit</span> Log(<span class="hljs-string">"address"</span>, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
        <span class="hljs-keyword">emit</span> IndexLog(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-number">123</span>, <span class="hljs-number">2</span>,<span class="hljs-number">3</span>, <span class="hljs-number">8</span>);
    }
}
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bb00ed8e917bc79292a57d0da455918f6261077a3737d21219d24cc1a200b307.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[智能合约-18.Todolist-小案例]]></title>
            <link>https://paragraph.com/@murraya/18-todolist</link>
            <guid>P98j3y6DatJGhFc2DYjl</guid>
            <pubDate>Sun, 08 May 2022 08:26:59 GMT</pubDate>
            <description><![CDATA[Todolist-小案例注意：在 Solidity 0.8.0 中，只有数组、结构和映射类型可以指定数据位置。// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract TodoList { struct Todo { string text; bool completed; } Todo[] public todos; function create(string calldata _text) external { todos.push(Todo({ text: _text, completed: false })); } function updateText(uint _index, string calldata _text) external { require(_index &#x3C; todos.length, "undefind"); todos[_index].text = _text; } function get(uint _index) external view returns(s...]]></description>
            <content:encoded><![CDATA[<h2 id="h-todolist" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Todolist-小案例</h2><p><strong>注意</strong>：在 Solidity 0.8.0 中，只有数组、结构和映射类型可以指定数据位置。</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract TodoList {
    struct Todo {
        string text;
        bool completed;
    }

    Todo[] public todos;

    function create(string calldata _text) external {
        todos.push(Todo({
            text: _text,
            completed: false
        }));
    }

    function updateText(uint _index, string calldata _text) external {
        require(_index &lt; todos.length, &quot;undefind&quot;);
        todos[_index].text = _text;
    }

    function get(uint _index) external view returns(string memory, bool)  {
        Todo storage todo = todos[_index];
        return (todo.text, todo.completed);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.7;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">TodoList</span> </span>{
    <span class="hljs-keyword">struct</span> <span class="hljs-title">Todo</span> {
        <span class="hljs-keyword">string</span> text;
        <span class="hljs-keyword">bool</span> completed;
    }

    Todo[] <span class="hljs-keyword">public</span> todos;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">calldata</span> _text</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        todos.<span class="hljs-built_in">push</span>(Todo({
            text: _text,
            completed: <span class="hljs-literal">false</span>
        }));
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateText</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> _index, <span class="hljs-keyword">string</span> <span class="hljs-keyword">calldata</span> _text</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(_index <span class="hljs-operator">&#x3C;</span> todos.<span class="hljs-built_in">length</span>, <span class="hljs-string">"undefind"</span>);
        todos[_index].text <span class="hljs-operator">=</span> _text;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> _index</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span>, <span class="hljs-keyword">bool</span></span>)  </span>{
        Todo <span class="hljs-keyword">storage</span> todo <span class="hljs-operator">=</span> todos[_index];
        <span class="hljs-keyword">return</span> (todo.text, todo.completed);
    }
}
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[智能合约-17.数据的存储位置]]></title>
            <link>https://paragraph.com/@murraya/17</link>
            <guid>xgS08VnFs7fRQYv7q5Bw</guid>
            <pubDate>Sun, 08 May 2022 08:26:24 GMT</pubDate>
            <description><![CDATA[数据的存储位置智能合约中数据可以存储在storage、memory、calldata中storage：状态变量memory：局部变量calldata：函数的输入参数calldata和memory比较类似。下面这种情况可以使用calldatafunction a(uint calldata x) pure external returns(uint){ b(x); return a + 1; } function b(uint calldata x) pure external returns(uint) { return x + 1; } 解释：在函数a中调用了函数b，并把函数a中的变量局部变量作为参数传递给函数b，这种情况，如果b的参数不用calldata约定，a中的局部变量会先拷贝成memory类型，然后传递给b函数，这就会消耗过多gas。所以参数位置推荐使用calldata]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">数据的存储位置</h2><p>智能合约中数据可以存储在storage、memory、calldata中</p><ul><li><p>storage：状态变量</p></li><li><p>memory：局部变量</p></li><li><p>calldata：函数的输入参数</p></li></ul><p>calldata和memory比较类似。下面这种情况可以使用calldata</p><pre data-type="codeBlock" text="function a(uint calldata x) pure external returns(uint){
    b(x);
    return a + 1;
}

function b(uint calldata x) pure external returns(uint) {
    return x + 1;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">a</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> <span class="hljs-keyword">calldata</span> x</span>) <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint</span></span>)</span>{
    b(x);
    <span class="hljs-keyword">return</span> a <span class="hljs-operator">+</span> <span class="hljs-number">1</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">b</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> <span class="hljs-keyword">calldata</span> x</span>) <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint</span></span>) </span>{
    <span class="hljs-keyword">return</span> x <span class="hljs-operator">+</span> <span class="hljs-number">1</span>;
}
</code></pre><p>解释：在函数a中调用了函数b，并把函数a中的变量局部变量作为参数传递给函数b，这种情况，如果b的参数不用calldata约定，a中的局部变量会先拷贝成memory类型，然后传递给b函数，这就会消耗过多gas。所以参数位置推荐使用calldata</p>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[智能合约-16.合约部署]]></title>
            <link>https://paragraph.com/@murraya/16</link>
            <guid>qOcytu14iDm6V7T4fxBA</guid>
            <pubDate>Sun, 08 May 2022 08:25:38 GMT</pubDate>
            <description><![CDATA[合约的部署通过代理合约部署合约下面的这个例子，Proxy合约负责发布合约， 但是内部是写死的，如果以后要发布新的合约必须将代理合约重新部署一遍，这其实是没有意义的；// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract TestContract1 { address public owner = msg.sender; function setOwner(address _owner) public { require(msg.sender == owner, "not owner"); owner = _owner; } } contract Proxy { function deploy() external payable { new TestContract1(); } } 新的代理合约方案// TODO 待续...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">合约的部署</h2><ul><li><p>通过代理合约部署合约</p></li></ul><p>下面的这个例子，Proxy合约负责发布合约， 但是内部是写死的，如果以后要发布新的合约必须将代理合约重新部署一遍，这其实是没有意义的；</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract TestContract1 {
    address public owner = msg.sender;

    function setOwner(address _owner) public {
        require(msg.sender == owner, &quot;not owner&quot;);
        owner = _owner;
    }
}

contract Proxy {
    function deploy() external payable {
        new TestContract1();
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.7;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">TestContract1</span> </span>{
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setOwner</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _owner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> owner, <span class="hljs-string">"not owner"</span>);
        owner <span class="hljs-operator">=</span> _owner;
    }
}

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Proxy</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deploy</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-keyword">new</span> TestContract1();
    }
}
</code></pre><p>新的代理合约方案</p><pre data-type="codeBlock" text="// TODO
待续...
"><code><span class="hljs-comment">// TODO</span>
待续...
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[智能合约-15.枚举]]></title>
            <link>https://paragraph.com/@murraya/15</link>
            <guid>jarhlyhploIL91R9WVWI</guid>
            <pubDate>Sun, 08 May 2022 08:24:49 GMT</pubDate>
            <description><![CDATA[枚举// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract Enum { enum Status { Node, Pedding, Shipped, Completed, Rejected, Canceled } Status public status; struct Order { address buyer; Status status } Order[] public orders; function get() view returns (Status) { return status; } // 设置枚举类型 function set(Status _status) external { status = _status; } // 指定特定的枚举类型 function ship() external { status = Status.Shipped; } // 恢复为默认值 function reset() external { delete status; } }]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">枚举</h2><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract Enum {
    enum Status {
        Node,
        Pedding,
        Shipped,
        Completed,
        Rejected,
        Canceled
    }

    Status public status;

    struct Order {
        address buyer;
        Status status
    }

    Order[] public orders;

    function get() view returns (Status) {
        return status;
    }

    // 设置枚举类型
    function set(Status _status) external {
        status = _status;
    }

    // 指定特定的枚举类型
    function ship() external {
        status = Status.Shipped;
    }

    // 恢复为默认值
    function reset() external {
        delete status;
    }
 }
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.7;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Enum</span> </span>{
    <span class="hljs-keyword">enum</span> <span class="hljs-title">Status</span> {
        Node,
        Pedding,
        Shipped,
        Completed,
        Rejected,
        Canceled
    }

    Status <span class="hljs-keyword">public</span> status;

    <span class="hljs-keyword">struct</span> <span class="hljs-title">Order</span> {
        <span class="hljs-keyword">address</span> buyer;
        Status status
    }

    Order[] <span class="hljs-keyword">public</span> orders;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">Status</span>) </span>{
        <span class="hljs-keyword">return</span> status;
    }

    <span class="hljs-comment">// 设置枚举类型</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set</span>(<span class="hljs-params">Status _status</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        status <span class="hljs-operator">=</span> _status;
    }

    <span class="hljs-comment">// 指定特定的枚举类型</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ship</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        status <span class="hljs-operator">=</span> Status.Shipped;
    }

    <span class="hljs-comment">// 恢复为默认值</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reset</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-keyword">delete</span> status;
    }
 }
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
        <item>
            <title><![CDATA[智能合约-14.结构体]]></title>
            <link>https://paragraph.com/@murraya/14</link>
            <guid>QKcErnCyVw869pbozYcZ</guid>
            <pubDate>Sun, 08 May 2022 08:24:17 GMT</pubDate>
            <description><![CDATA[结构体// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract Structs { struct Car { // 结构体,有点像TS中的接口 string model; uint year; address owner; } Car public car; Car[] public cars; mapping(address => Car[]) public carsByOwner; function examples() external { Car memory toyota = Car("Toyota", 1990, msg.sender); Car memory lambo = Car({year: 1980, model: "Lamborghini", owner: msg.sender}); cars.push(toyota); cars.push(lambo); } }]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结构体</h2><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract Structs {
    struct Car { // 结构体,有点像TS中的接口
        string model;
        uint year;
        address owner;
    }

    Car public car;
    Car[] public cars;

    mapping(address =&gt; Car[]) public carsByOwner;

    function examples() external {
        Car memory toyota = Car(&quot;Toyota&quot;, 1990, msg.sender);
        Car memory lambo = Car({year: 1980, model: &quot;Lamborghini&quot;, owner: msg.sender});
        cars.push(toyota);
        cars.push(lambo);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.7;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Structs</span> </span>{
    <span class="hljs-keyword">struct</span> <span class="hljs-title">Car</span> { <span class="hljs-comment">// 结构体,有点像TS中的接口</span>
        <span class="hljs-keyword">string</span> model;
        <span class="hljs-keyword">uint</span> year;
        <span class="hljs-keyword">address</span> owner;
    }

    Car <span class="hljs-keyword">public</span> car;
    Car[] <span class="hljs-keyword">public</span> cars;

    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> Car[]) <span class="hljs-keyword">public</span> carsByOwner;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">examples</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        Car <span class="hljs-keyword">memory</span> toyota <span class="hljs-operator">=</span> Car(<span class="hljs-string">"Toyota"</span>, <span class="hljs-number">1990</span>, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
        Car <span class="hljs-keyword">memory</span> lambo <span class="hljs-operator">=</span> Car({year: <span class="hljs-number">1980</span>, model: <span class="hljs-string">"Lamborghini"</span>, owner: <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>});
        cars.<span class="hljs-built_in">push</span>(toyota);
        cars.<span class="hljs-built_in">push</span>(lambo);
    }
}
</code></pre>]]></content:encoded>
            <author>murraya@newsletter.paragraph.com (Murraya)</author>
        </item>
    </channel>
</rss>