<?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>MarkTang's Blog</title>
        <link>https://paragraph.com/@marktang-s-blog</link>
        <description>A Full-Stack engineer. I'm learning technique about web3.</description>
        <lastBuildDate>Tue, 11 Aug 2026 17:10:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>MarkTang's Blog</title>
            <url>https://storage.googleapis.com/papyrus_images/1c08230764babf090f240b570d21e863501fb35564b01148f6fcadcbb560305d.jpg</url>
            <link>https://paragraph.com/@marktang-s-blog</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[ENS技术解析(一) 注册流程]]></title>
            <link>https://paragraph.com/@marktang-s-blog/ens</link>
            <guid>05JrjyeYoEtFJQACcT7U</guid>
            <pubDate>Wed, 07 Sep 2022 09:00:36 GMT</pubDate>
            <description><![CDATA[ENS技术解析(一) 注册流程最近在弄关于ENS这类DID的东西，准备记录一下基础流程，为了记忆更深刻，顺便写一些文章。 文章会主要以前端视角对接合约的方式进行，从前端对接到合约代码，不是全合约代码解析。ENS介绍首先简单介绍一下ENS，ENS（Ethereum Name Service）是以太坊域名服务，是一个基于以太坊区块链的分布式、开放和可扩展的命名系统。ENS的主要作用就是将可读的域名如：marktang.eth 解析为可识别的标识符，比如钱包地址、元数据等。ENS还支持"反向解析"，也就可以让元数据和以太坊地址相关联成为可能。 ENS和DNS类似，但因为区块链的功能特点和限制条件，架构上有很大的不同。与DNS一样，ENS是一个层次结构的域名系统，不同层次域名之间以点作为分隔符，我们把层次的名称叫做域，一个域的所有者能够完全控制其子域。想了解更详细的可以去官网看看。ENS架构主要由注册表和解析器组成在ENS中解析一个域名需要两个步骤：首先，询问注册表是哪个解析器负责解析该域名，然后，向该解析器查询解析结果接下来正式开始流程说明注册注册大致可以分为3个步骤获取凭证提交凭证注...]]></description>
            <content:encoded><![CDATA[<h1 id="h-ens" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ENS技术解析(一) 注册流程</h1><p>最近在弄关于ENS这类DID的东西，准备记录一下基础流程，为了记忆更深刻，顺便写一些文章。</p><p>文章会主要以前端视角对接合约的方式进行，从前端对接到合约代码，不是全合约代码解析。</p><h3 id="h-ens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">ENS介绍</h3><p>首先简单介绍一下ENS，ENS（Ethereum Name Service）是以太坊域名服务，是一个基于以太坊区块链的分布式、开放和可扩展的命名系统。ENS的主要作用就是将可读的域名如：marktang.eth 解析为可识别的标识符，比如钱包地址、元数据等。ENS还支持&quot;反向解析&quot;，也就可以让元数据和以太坊地址相关联成为可能。</p><p>ENS和DNS类似，但因为区块链的功能特点和限制条件，架构上有很大的不同。与DNS一样，ENS是一个层次结构的域名系统，不同层次域名之间以点作为分隔符，我们把层次的名称叫做域，一个域的所有者能够完全控制其子域。想了解更详细的可以去官网看看。</p><h3 id="h-ens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">ENS架构</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">主要由注册表和解析器组成</figcaption></figure><p>在ENS中解析一个域名需要两个步骤：首先，询问注册表是哪个解析器负责解析该域名，然后，向该解析器查询解析结果</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>接下来正式开始流程说明</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">注册</h2><p>注册大致可以分为3个步骤</p><ol><li><p>获取凭证</p></li><li><p>提交凭证</p></li><li><p>注册</p></li></ol><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></li><li><p>获取minCommitmentAge，这个值在&lt;提交凭证&gt;时会用到，稍后说明</p></li><li><p>是否对Controller已Approve授权</p></li><li><p>生成 randomSecret，&lt;获取凭证&gt;时会用到</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">获取当前域名的预估注册费用</h4><p>调用RegistrarController的rentPrice方法来获取预估注册费用，需要传入两个参数，一个是当前需要注册的域名，另一个是时长。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>根据注册域名长度得到一个基础价格，然后通过ETH/USD Price Oracle得到当前的ETH价格算出最终的预估注册费用(ETH)</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h4 id="h-mincommitmentage" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">获取minCommitmentAge</h4><blockquote><p>获取最小时间间隔，这个比较简单，没什么可说明的</p></blockquote><h4 id="h-controllerapprove" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">是否对Controller已Approve授权</h4><p>这个也很简单，通过allowance方法判断一下，方便根据状态进行UI的一些操作更新</p><h4 id="h-randomsecret" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">生成randomSecret</h4><p>32随机字节，直接看代码</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">获取凭证</h3><p>通过调用RegistrarController合约的makeCommitment方法来获取凭证，需要传递以下参数：</p><ul><li><p>name 域名</p></li><li><p>owner 注册人地址</p></li><li><p>duration 时长</p></li><li><p>secret 之前在上一步&lt;初始化&gt;时生成的randomSecret</p></li><li><p>resolver 解析器合约地址</p></li><li><p>data 设置域名解析记录</p></li><li><p>reverseRecord 是否反解析</p></li><li><p>fuses 初始化的fuses</p></li><li><p>wrapperExpiry fuses过期时间</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>返回 abi encode后的 keccak256 hash作为凭证</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">提交凭证</h3><p>通过调用RegistrarController合约的commit方法来预提交，需要传入刚才&lt;获取凭证&gt;拿到的hash</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>然后来看合约代码，这里先对重复提交进行了验证，时间如果未超过最大时间间隔就不需要重新更新时间戳。通过验证则记录本次transaction的时间戳。</p><blockquote><p>预提交的目的</p></blockquote><p>现在来说明一下这个预提交的目的是什么，为什么不是直接就注册了？预提交的目的是为了防止抢跑，如果没有这部分，直接就是注册操作的话，在MEV里就可以监听注册域名的tx，然后用gasPrice去抢跑，这样就会造成一些有价值的域名被机器人抢走。有了预提交步骤后，机器人要抢跑域名注册就需要监听commit方法，但这个方法的参数是解读不出tx是要注册哪个域名的，从而防止了抢跑操作。</p><p>在commit之后必须要等待最小间隔时间后才能进行注册操作，当前是60秒。这里需要setTimeout 一个&lt;初始化&gt;时获取的minCommitmentAge * 1000 后再开始进行注册操作。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">注册</h3><p>在等待最小间隔时间后通过RegistrarController合约的register方法来注册，方法需要传入的参数同&lt;获取凭证&gt;的makeCommitment的方法一样。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>然后一步一步来看看合约</p><p>1. 首先验证账户是否有足够币来支付注册费用</p><p>2. 消费commitment， 这里会检查是否满足最小间隔时间和是否超过最大间隔时间，验证当前域名是否可用、删除commitment，返还gas、时长不能小于最小时间(28天)</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>3. 获取registrar expiry 新名称在.eth注册商的到期时间</p><p>4. setRecords 设置域名解析记录，这里会对检查namehash做检查</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>5. 是否设置反向解析，反向解析就是用地址反向解析出ENS域名</p><p>6. 触发 NameRegistered Event，客户端可以监听该Event来实现一些后续操作</p><p>7. 扣钱，注册完成</p><blockquote><p>Note: ENS域名也是ERC721的NFT，可以在各大市场进行交易，只是没有metadata。如果在市场交易成功后，新的owner需要调用一下reclaim方法重新认领才能管理自己的ENS域名。在域名过期后，设有90天的保护期，在保护期内owner是可以直接调用renew方法来续期的，过了保护期后就要重新commit去进行注册</p></blockquote><p>目前先对注册流程进行了一个大体的整理，有空后下一篇会说明 地址/域名搜索、我拥有的域名列表、域名详情信息的流程。不知道具体会更新几篇，取决于之后我所了解到ENS功能的范围吧。</p><p>之后计划今年还会更新几篇最近在研究的前端可视化方面的文章、Web3项目前端主流技术栈中的一些package介绍、全栈写完一个完整的Web3项目(合约、前端、后端)能够完整体验整个流程，具体项目还在构思中，需要简单一点不会写太久又能体现每个部分的特点的。</p><p>Bye, All the best.</p>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/bbe692c3d73964a2766b4208789c1b78d137b5f65779de50e444b385955bef6a.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Ethereum升级和Layer2相关基本概念]]></title>
            <link>https://paragraph.com/@marktang-s-blog/ethereum-layer2</link>
            <guid>LuitTv5TAn3UuZYmpHSG</guid>
            <pubDate>Sun, 28 Aug 2022 04:26:08 GMT</pubDate>
            <description><![CDATA[Ethereum升级和Layer2相关基本概念1. 什么是Layer1?Layer1就是基础公链，现在大家所知的以太坊、比特币就是Layer1公链，现在我们看到的市面上的Layer2的网络，都是以Layer1为基础的Layer1的问题拥堵交易费贵不可能三角问题 传统货币理论中存在的"不可能三角"是一个国家无法同时实现货币政策的独立性、汇率稳定与资本自由流动，最多只能同时满足其中两个目标，而做取舍放弃另一个目标。 区块链技术的"不可能三角"主要是指无法同时达到可扩展性(Scalability) 、去中心化(Decentralization) 、安全(Security) ，只能同时满足其中两个目标。 问题的解决方案:建立Layer2升级Layer1下面会讲一下这两种解决方案：2.什么是Layer2?Layer2是一个概括性的词语去描述以太坊的扩容解决方案。Layer1和Layer2是分开的，Layer2是独立的区块链，它扩展了以太坊并且继承了以太坊的安全性两种roll up技术什么是roll up? Rollups是目前扩容以太坊的首选Layer2解决方案。通过使用Rollups，用...]]></description>
            <content:encoded><![CDATA[<h2 id="h-ethereumlayer2" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ethereum升级和Layer2相关基本概念</h2><h2 id="h-1-layer1" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">1. 什么是Layer1?</h2><p>Layer1就是基础公链，现在大家所知的以太坊、比特币就是Layer1公链，现在我们看到的市面上的Layer2的网络，都是以Layer1为基础的</p><h4 id="h-layer1" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Layer1的问题</h4><ol><li><p>拥堵</p></li><li><p>交易费贵</p></li></ol><p><strong>不可能三角问题</strong></p><p>传统货币理论中存在的&quot;不可能三角&quot;是一个国家无法同时实现货币政策的独立性、汇率稳定与资本自由流动，最多只能同时满足其中两个目标，而做取舍放弃另一个目标。</p><p>区块链技术的&quot;不可能三角&quot;主要是指无法同时达到<strong>可扩展性(Scalability)</strong> 、<strong>去中心化(Decentralization)</strong> 、<strong>安全(Security)</strong> ，只能同时满足其中两个目标。</p><p><strong>问题的解决方案:</strong></p><ol><li><p>建立Layer2</p></li><li><p>升级Layer1</p></li></ol><p>下面会讲一下这两种解决方案：</p><h2 id="h-2layer2" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">2.什么是Layer2?</h2><p>Layer2是一个概括性的词语去描述以太坊的扩容解决方案。Layer1和Layer2是分开的，Layer2是独立的区块链，它扩展了以太坊并且继承了以太坊的安全性</p><h4 id="h-roll-up" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">两种roll up技术</h4><p><strong>什么是roll up?</strong></p><p>Rollups是目前扩容以太坊的首选Layer2解决方案。通过使用Rollups，用户可以比Layer1减少高达100倍的gas费。</p><p>Rollups把在Layer1上的几百个交易都打包捆绑在一起变成一个交易，现在的Ethereum主网每秒只能处理15个交易，以目前Ethereum的用户量来看，这个量是完全不够的，从而导致拥堵和交易费高昂。而Rollups打包后的1个交易的交易费是平摊的，用户所需要支付的gas费更低。Rollups打包处理这些交易的时候是在Layer1之外进行的，但是交易数据会被发回到Layer1上，通过这个方式，Rollups继承了Ethereum的安全性。</p><p>目前有两种不同的Rollups技术, 一种叫Optimistic rollups，一种叫 Zero-knowledge rollups，它们的区别主要在于交易数据如何被发布到Layer1，这次先不详细展开讲了，下一次会专门一篇文章介绍两种技术。目前Layer2的公链：Polygon、Loopring、OMG Network、Skale、ZKSwap、Arbitrum One、Optimism、Boba Network.</p><p>接下来讲一下另一个解决方案: 升级Layer1 -&gt; ETH2.0(有些朋友以为Layer2就是以太坊2.0，实际两者是没有关系的)</p><h2 id="h-3-eth20" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">3. ETH2.0</h2><p>以太坊2.0升级的三个部分:</p><ol><li><p>beacon chain(is live)</p></li><li><p>Merge (2022第三季度)</p></li><li><p>Sharding (2023-2024)</p></li></ol><p>接下来简单介绍一下各个部分的特点:</p><h4 id="h-the-beacon-chain" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">The Beacon Chain</h4><blockquote><p>目前已经在运行中</p></blockquote><ul><li><p>Beacon Chain不会影响当前使用以太坊的任何东西</p></li><li><p>它将POS引入了以太坊</p></li><li><p>它将协调网络，充当共识层</p></li><li><p>它是未来扩展升级前的重要步骤</p></li></ul><h4 id="h-the-merge" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">The Merge</h4><blockquote><p>2022第三季度执行</p></blockquote><ul><li><p>以太坊主网和Beacon Chain POS系统合并</p></li><li><p>以太坊POW的结束，向POS的全面过渡</p></li><li><p>为未来的扩展升级(Sharding)创造了条件</p></li><li><p>合并将使以太坊的能源消耗减少大约99.95%(共识算法改变的关系)</p></li></ul><p>不了解POW和POS的可以查看：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/0xD5B12730F08472C471172B0a4DC8912965036b8d/ot9OyZjYYxEYqqZFyEmB6TPMVdcRvNOZYfYkBlZtZ3w">https://mirror.xyz/0xD5B12730F08472C471172B0a4DC8912965036b8d/ot9OyZjYYxEYqqZFyEmB6TPMVdcRvNOZYfYkBlZtZ3w</a></p><h4 id="h-sharding" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Sharding</h4><blockquote><p>2024年之前执行</p></blockquote><ul><li><p>分片是一个多阶段的升级，以提高Ethereum的可扩展性和容量</p></li><li><p>分片提供了数据存储需求的安全分布，使rollups的成本更低，并使节点更容易操作</p></li><li><p>使Layer2解决方案能够提供低交易费用，同时利用以太坊的安全性</p></li><li><p>该升级计划在主网与Beacon Chain合并后进行</p></li></ul>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[Ethereum]]></title>
            <link>https://paragraph.com/@marktang-s-blog/ethereum</link>
            <guid>87ZYu9DN9ZNyOcIwM7Z2</guid>
            <pubDate>Sat, 27 Aug 2022 04:50:54 GMT</pubDate>
            <description><![CDATA[Ethereum世界状态 (world state)Ether(以太币)记载在 世界状态(world state) 里世界状态 = 每个人账户的状态的总集合A535612C-7A82-4925-B6DB-67DB140FB644余额变化Ether(以太币)余额因交易而变化以太坊既可以看作静态的区块的串联， 也可以看作一个状态机， 随着用户不断地发起交易，促成这个状态机进行状态转移2101E30D-ED11-428F-BBDB-DAF09E23B0A824E6C1E9-869E-48F1-9BDA-1C90F8FA2BAC账户状态58E89057-1EEF-4B26-828A-8E92771106A6已执行交易总数该值会随着用户不断发送交易而递增，保障用户发出的交易是按照顺序被收纳入最终的区块链。 因为在同一个账户中，已执行交易总数不可以在区块链中再次出现。 当用户创建智能合约时，要指定合约地址，该地址是由用户账户的已执行交易总数和用户账户地址联合计算而得出的。61A41C87-C5B2-437A-B93D-519C490282B8代码区的哈希值该值为 智能合约独有，外部账户不包含...]]></description>
            <content:encoded><![CDATA[<h1 id="h-ethereum" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ethereum</h1><h3 id="h-world-state" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">世界状态 (world state)</h3><p>Ether(以太币)记载在 <strong>世界状态(world state)</strong> 里</p><blockquote><p>世界状态 = 每个人账户的状态的总集合</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fd87ad3c0bb968f0f070227982a8e0fe8cdf5e70f7869c1a0410baa242fe6cfd.jpg" alt="A535612C-7A82-4925-B6DB-67DB140FB644" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">A535612C-7A82-4925-B6DB-67DB140FB644</figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">余额变化</h3><p>Ether(以太币)余额因交易而变化</p><blockquote><p>以太坊既可以看作静态的区块的串联， 也可以看作一个状态机， 随着用户不断地发起交易，促成这个状态机进行状态转移</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/748d18022f03ab67f464c77a09b505235c1b3f4b193f89d58bc501c22f98fc81.jpg" alt="2101E30D-ED11-428F-BBDB-DAF09E23B0A8" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">2101E30D-ED11-428F-BBDB-DAF09E23B0A8</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a310d1563afb17ea3621f4c09ef4505cd33df49eaed63d7d318384a94a74da0b.jpg" alt="24E6C1E9-869E-48F1-9BDA-1C90F8FA2BAC" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">24E6C1E9-869E-48F1-9BDA-1C90F8FA2BAC</figcaption></figure><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/bd556191bceb9a8cba2239bea242debb30d1ebca3a8d2d9b5b6f5f21ad7bf083.jpg" alt="58E89057-1EEF-4B26-828A-8E92771106A6" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">58E89057-1EEF-4B26-828A-8E92771106A6</figcaption></figure><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/4195b83cae4d2bdb0d5a1dfab157a9628b6e6e22f91367bf2b4dea7394ae7ce1.jpg" alt="61A41C87-C5B2-437A-B93D-519C490282B8" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">61A41C87-C5B2-437A-B93D-519C490282B8</figcaption></figure><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">代码区的哈希值</h4><p>该值为 <strong>智能合约独有</strong>，外部账户不包含该值。 代码区即为智能合约代码本身。 在合约的生命周期中，该区域的内容是不可更改的 <strong>只读状态</strong>。 代码区存放于以太坊网络节点的硬盘中，当运行时被读入虚拟机执行。代码区的内容通过散列函数得出校验哈希值，该值即为代码区的哈希值。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9601fc3ab2c8bd4987e9c34f66e4690050f5c51f35c6f17ee50e12e7e22866d7.jpg" alt="EEA3E3B9-0F2F-4277-9CD6-32296AC118B1" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">EEA3E3B9-0F2F-4277-9CD6-32296AC118B1</figcaption></figure><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">公开地址从私钥派生逻辑</h4><ul><li><p>32字节私钥生成的长度为65字节的公钥</p></li><li><p>删除为首的一个字节0x04，还剩64字节</p></li><li><p>将其放入keccak256哈希算法，生成一个256位的哈希值</p></li><li><p>截取哈希值的最后20字节，既为所求的公开地址</p></li><li><p>(可选)辅以0x的开头装饰，表明这是一个16进制的书面记录形式</p></li></ul><h3 id="h-eip-55" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">EIP-55格式的账户地址</h3><blockquote><p>为了解决转账地址填错的问题，该提议按照一定逻辑，将地址中的部分字母大写，与剩余的小写字母来形成校验和，让地址拥有自校验的能力，具体的代码参考下方</p></blockquote><pre data-type="codeBlock" text="const createKeccakHash = require(&apos;keccak&apos;)
​
function toChecksumAddress (address) {
  address = address.toLowerCase().replace(&apos;0x&apos;, &apos;&apos;)
  var hash = createKeccakHash(&apos;keccak256&apos;).update(address).digest(&apos;hex&apos;)
  var ret = &apos;0x&apos;
​
  for (var i = 0; i &lt; address.length; i++) {
    if (parseInt(hash[i], 16) &gt;= 8) {
      ret += address[i].toUpperCase()
    } else {
      ret += address[i]
    }
  }
​
  return ret
}
"><code>const createKeccakHash <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">'keccak'</span>)
​
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toChecksumAddress</span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
  <span class="hljs-keyword">address</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>.toLowerCase().replace(<span class="hljs-string">'0x'</span>, <span class="hljs-string">''</span>)
  <span class="hljs-keyword">var</span> hash <span class="hljs-operator">=</span> createKeccakHash(<span class="hljs-string">'keccak256'</span>).update(<span class="hljs-keyword">address</span>).digest(<span class="hljs-string">'hex'</span>)
  <span class="hljs-keyword">var</span> ret <span class="hljs-operator">=</span> <span class="hljs-string">'0x'</span>
​
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> <span class="hljs-keyword">address</span>.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
    <span class="hljs-keyword">if</span> (parseInt(hash[i], <span class="hljs-number">16</span>) <span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">8</span>) {
      ret <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>[i].toUpperCase()
    } <span class="hljs-keyword">else</span> {
      ret <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>[i]
    }
  }
​
  <span class="hljs-keyword">return</span> ret
}
</code></pre><h3 id="h-transaction" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">交易(Transaction)</h3><p>交易活动步骤：</p><ol><li><p>用户签名打包一条或多条消息，组装成一笔交易，发送到以太坊网络上</p></li><li><p>该交易被矿工捕获，并收纳入某一个区块</p></li><li><p>区块形成后，被矿工广播到节点网络中，最终加入区块链</p></li></ol><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">交易与消息的区别</h4><blockquote><p>消息在账户与账户之间传递数据(data)与价值(value)，消息的具体表现形式如下：</p></blockquote><ul><li><p>数据：一个账户向另一个账户请求函数调用</p></li><li><p>价值：一个账户向另一个账户发送以太币</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">交易的特性</h4><p>&quot;原子性&quot;</p><blockquote><p>要么完全执行，要么完全不执行</p></blockquote><p>&quot;串行&quot;执行</p><blockquote><p>不会同时并行执行</p></blockquote><p>&quot;进入区块链的顺序不确定&quot;</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">交易的结构</h4><p>Web3.js代发的交易结构</p><pre data-type="codeBlock" text="{
  nonce: web3.toHex(10),
  GasPrice: web3.toHex(100000000000),
  Gas: web3.toHex(140000),
  from: &apos;0x633296baebc20f33ac2e1c1b105d7cd1f6a0718b&apos;,
  to: &apos;0xD1E1cdbCE15f1009B5A7874053E09C728Df91d47&apos;,
  value: web3.toHex(0),
  data: &apos;0xcc9ab24952616d6100000000000000000000000000000000000000000000000000000000&apos;
}
"><code>{
  nonce: web3.toHex(<span class="hljs-number">10</span>),
  GasPrice: web3.toHex(<span class="hljs-number">100000000000</span>),
  Gas: web3.toHex(<span class="hljs-number">140000</span>),
  <span class="hljs-keyword">from</span>: <span class="hljs-string">'0x633296baebc20f33ac2e1c1b105d7cd1f6a0718b'</span>,
  to: <span class="hljs-string">'0xD1E1cdbCE15f1009B5A7874053E09C728Df91d47'</span>,
  <span class="hljs-built_in">value</span>: web3.toHex(<span class="hljs-number">0</span>),
  data: <span class="hljs-string">'0xcc9ab24952616d6100000000000000000000000000000000000000000000000000000000'</span>
}
</code></pre><p>字段解释：</p><ul><li><p>nonce 该账户已发送交易数量，一个正整数</p></li><li><p>GasPrice 和 Gas 与现实生活中的汽油费类似，用以支付交易费(<strong>交易费 = Gas x GasPrice</strong>)</p></li></ul><blockquote><p>汽油数量 *<strong><em>Gas</em></strong> *由用户指定，汽油单价 *<strong><em>GasPrice</em></strong> *由市场价格决定。运行交易后多余的 *<strong><em>Gas</em></strong> *会退还给用户(惩罚性虚拟机指令assert例外，会扣除所有的剩余未花费的 *<strong><em>Gas</em></strong> *) 。 *<strong><em>Gas</em></strong> *的数值人工预测并不准确，可以让客户端软件在签发交易时代为预测，无须自己手动填写。</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8a45353b2a2113c897de87a1c049b6e8cd9e2d85af798de44e69fc2cbf765940.jpg" alt="4D895A73-6DE9-4A2C-BCD5-7611B795F437" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">4D895A73-6DE9-4A2C-BCD5-7611B795F437</figcaption></figure><ul><li><p>From 该笔交易的发送方</p></li><li><p>To 该笔交易的接收方</p></li></ul><blockquote><p>若该交易为普通以太币转账交易，则接收方为受益人账户地址；若该交易为调用智能合约的交易，则接收方为智能合约的地址；若该交易为创建智能合约，则接收方地址可空缺。</p></blockquote><ul><li><p>Value具体转移多少以太币到接收方</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3539742fb4d582f8008f85b7fe680636a11ca9741dabaa0f1905a3ed1dbb5457.jpg" alt="7744E408-B41A-48A1-B8B8-77D91CD72925" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">7744E408-B41A-48A1-B8B8-77D91CD72925</figcaption></figure><ul><li><p>Data数据字段</p></li></ul><blockquote><p>当若进行以太币纯转账交易时，该字段可空缺；若进行为智能合约调用，则该值包含编码后的函数名和参数的字节码；若为进行合约创建，则该值包含初始化合约的字节码。</p></blockquote><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/a3ac7e49591a79a3a26ea99acde4c9eb5aba89b4e19ebe42af533fd56ea17f53.jpg" alt="9E47CF7F-D1A8-41D9-9FA7-88756A3D93D1" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">9E47CF7F-D1A8-41D9-9FA7-88756A3D93D1</figcaption></figure><ul><li><p>客户端软件在收集完交易信息，组织成相应的结构体，需要使用用户的私钥来签名该交易。</p></li><li><p>交易后编码为一个公开消息，通过节点网络发出并逐渐扩散到网络中各个节点。</p></li><li><p>挖矿节点和众多其他普通节点同时收到该消息，矿工将其暂时缓存起来。</p></li><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/c3f9404b9c007742cdccf21b5ed5d6f5a4880cd7c4f57f24bf810119e9c25b04.jpg" alt="9A0CBA3C-75ED-4848-AD8E-6EB26CDE2EED" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">9A0CBA3C-75ED-4848-AD8E-6EB26CDE2EED</figcaption></figure><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/55eaf361e5f206c4bd8ab51cbb6908fde845d7d870e85d6083658f761e004e69.jpg" alt="67F24F14-7BE8-456F-A371-C6810983CD0C" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">67F24F14-7BE8-456F-A371-C6810983CD0C</figcaption></figure><p>以太坊上最常见的交易：</p><ul><li><p>以太币转账</p></li><li><p>智能合约调用</p></li><li><p>智能合约创建</p></li></ul><blockquote><p>这三种交易在交易发送时经历的步骤是 *<strong><em>一模一样</em></strong> *的，区别仅在于填写交易时选择传递数据 <strong>data</strong> 还是传递价值 <strong>value</strong> 。 传递数据的即为合约相关操作，传递价值即为转账操作。</p></blockquote><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">共识与工作量证明</h3><blockquote><p>分布式系统的问题就在于如何协调各个节点步调一致、共同进退。这称之为“拜占庭将军问题”。</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/69e8b6e7820470b236662f31677444e6d16cf6402f171e84f9a2791721a349b1.jpg" alt="F58617CF-891B-4255-A960-E91493A60E85" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">F58617CF-891B-4255-A960-E91493A60E85</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/747c784842186624de94f71d7909202a0c08c4f5a21e089e7e244db56caabf59.jpg" alt="A5BE7A2B-2826-42DA-890C-B70B2AAA6EF8" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">A5BE7A2B-2826-42DA-890C-B70B2AAA6EF8</figcaption></figure><p>中心化支付系统与中心化支付系统的拓扑结构</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">数据结构</h3><p><strong>Radix树</strong></p><p><strong>Merkle树和Merkle证明</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cae4ce092b28407775191fa13468793087a8976d5a4cd5a8953f1c7e16999616.jpg" alt="62E564ED-5E8E-4E9E-9F86-A68FCC9D3252" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">62E564ED-5E8E-4E9E-9F86-A68FCC9D3252</figcaption></figure><blockquote><p>Merkle树是一个在密码学中常使用的树形结构，适用于解决数据完整性问题。 它的结构功能异常简单，通常我们用二叉树的形式来表示一棵 Merkle树， 它存在的作用是数据分组并保证数据在存储中的完整性。 例如一棵包含了数字 1 到 8 的 Merkle二叉树</p></blockquote><p>如果碰上基础数据集不是偶数个元素（例如仅有7个数据），则复制一个元素，让它与自己的双胞胎做哈希运算</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c38549b89a61370376b4c9dd125412f49d1f69b310612f641457b0b495691663.jpg" alt="94EC25C4-FA30-4B5E-9EB8-D9F085CDB6FB" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">94EC25C4-FA30-4B5E-9EB8-D9F085CDB6FB</figcaption></figure><p><strong>Merkle Patricia树</strong></p><p>以太坊团队开创性地融合了Radix树和Merkle树的结构，并进一步优化该结构的存储性能，形成了实用的Merkle Patricia树</p><blockquote><p>Merkle Patiricia树 (以下简称MPT树) 是一个可以存储键值对 (key-value pair) 的高效数据结构，键、值分别可以是任意的序列化过的字符串。它具有Merkle树的密码学安全校验功能，提供树中的数据完整性与存在性的 Merkle 证明。它具有强大的互动能力，在log(N)级别的时间内完成插入、删除、查询等操作。我们来循序渐进地查看如何构建一棵 MPT树。</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e7ca518ad7a70bbdfefdcc3cf01e410ec1055128409db1745ce3768c0d3f3714.jpg" alt="EC5A7CA1-D13B-42AB-B6B6-0D1376C807BF" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">EC5A7CA1-D13B-42AB-B6B6-0D1376C807BF</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ad3b000052573cb979efefd1e435608f6d4e4fd59c5f38e949a01650fc66f228.jpg" alt="0701E504-E6D5-497C-A97F-7221224AA236" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">0701E504-E6D5-497C-A97F-7221224AA236</figcaption></figure><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/8598555a7a9cf4ed4ffc1fc8af5ed64950c9c0565950d98f71cf05c779c086c2.jpg" alt="D7724404-B3A9-4E71-B486-7060DA827EEA" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">D7724404-B3A9-4E71-B486-7060DA827EEA</figcaption></figure><p>存储树，账户状态，世界状态的构成关系</p><blockquote><p>根据以太坊黄皮书，账户若是一个智能合约账户，则必定包含了 *<strong><em>存储树</em></strong> *（storageRoot）和 *<strong><em>代码存储</em></strong> *（codeHash）</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d0ddf2ed2b44e1cf31904127397c69669dbdf4e9982a6605f421f69eb410d687.jpg" alt="D2C0E1A4-CCF0-4B4B-8CB0-8B750D33EBFF" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">D2C0E1A4-CCF0-4B4B-8CB0-8B750D33EBFF</figcaption></figure><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">交易树</h4><blockquote><p>每个区块都有一棵独立的 <strong>交易树</strong> (transactionsRoot)</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6548d1616190c7dff8bf49de0e2a7ffef573bc4587d1c435a388c61af08c0f33.jpg" alt="BDAF1C6E-AAB5-4BC5-87D4-88A7B7D00BBF" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">BDAF1C6E-AAB5-4BC5-87D4-88A7B7D00BBF</figcaption></figure><p>单笔交易和交易树的构成</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">收据树</h4><blockquote><p>每个区块都有一棵独立的 <strong>收据树</strong> (receiptsRoot)</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0d52ed4be5f1c3122a9fa6d7f7f08ca14528c2bb44f97213ff746c59a8c6256a.jpg" alt="8B22696F-F576-4427-A778-3D52CC132113" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">8B22696F-F576-4427-A778-3D52CC132113</figcaption></figure><p>从左到右：日志、交易收据、收据树的构成</p><p><strong>区块</strong></p><p>*<strong><em>区块头</em></strong> *较为轻量级，包含了一系列的数值、引用的数值以及哈希值, 总长度在 508Byte 左右；</p><p>*<strong><em>区块体</em></strong> *较为重量级，包含了该区块收纳的交易列表和叔块列表，长度视包含的交易多寡而定，目前在 20KB 左右 。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a91d7de31a7268b7c3322305d190fe642bceb88b8ff807ccf49335ff7d4fa563.jpg" alt="EC7E92C9-A0B2-4028-BDDF-DC75D2F66091" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">EC7E92C9-A0B2-4028-BDDF-DC75D2F66091</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/61e7faad23ed5a5e925cb64bd481b36fd7de88f359533041cbb9c03daabf8de7.jpg" alt="6E415B49-3A3C-479F-8366-BB1633A9D3F2" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">6E415B49-3A3C-479F-8366-BB1633A9D3F2</figcaption></figure><p>一个以太坊区块的示意图</p>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[Ethers.js入门]]></title>
            <link>https://paragraph.com/@marktang-s-blog/ethers-js</link>
            <guid>mvgqAF1JsqW2tl0E7pjX</guid>
            <pubDate>Fri, 26 Aug 2022 16:28:04 GMT</pubDate>
            <description><![CDATA[Ethers.js入门术语解释Provider: Provider 是一个为连接到以太坊网络提供抽象的类，提供对blockchain及其状态的只读访问 Signer: Signer是一个类，通常以某种方式直接或间接地接触到一个私钥，它可以签署消息和交易，以授权网络向你的账户收取以太坊来执行操作 Contract: Contract是一个抽象概念，它代表了与以太坊网络上特定合约的连接，因此，应用程序可以像普通的JavaScript对象一样使用它连接方式及操作Connecting to Ethereum: MetaMask在Ethereum上进行实验和开始开发的最快速和最简单的方法是使用MetaMask，它是一个浏览器扩展// Web3Provider包装了一个标准的Web3 provider，MetaMask为每个页面注入了window.ethereum const provider = new ethers.providers.Web3Provider(window.ethereum); // MetaMask Plugin 还允许签名transactions 来发送ether和...]]></description>
            <content:encoded><![CDATA[<h2 id="h-ethersjs" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ethers.js入门</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">术语解释</h3><p><strong>Provider</strong>: Provider 是一个为连接到以太坊网络提供抽象的类，提供对blockchain及其状态的只读访问</p><p><strong>Signer</strong>: Signer是一个类，通常以某种方式直接或间接地接触到一个私钥，它可以签署消息和交易，以授权网络向你的账户收取以太坊来执行操作</p><p><strong>Contract</strong>: Contract是一个抽象概念，它代表了与以太坊网络上特定合约的连接，因此，应用程序可以像普通的JavaScript对象一样使用它</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">连接方式及操作</h3><h4 id="h-connecting-to-ethereum-metamask" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Connecting to Ethereum: MetaMask</h4><blockquote><p>在Ethereum上进行实验和开始开发的最快速和最简单的方法是使用MetaMask，它是一个浏览器扩展</p></blockquote><pre data-type="codeBlock" text="// Web3Provider包装了一个标准的Web3 provider，MetaMask为每个页面注入了window.ethereum
const provider = new ethers.providers.Web3Provider(window.ethereum);

// MetaMask Plugin 还允许签名transactions 来发送ether和支付以改变链上的状态，所以需要 signer
const signer = provider.getSigner();
"><code><span class="hljs-comment">// Web3Provider包装了一个标准的Web3 provider，MetaMask为每个页面注入了window.ethereum</span>
const provider <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.providers.Web3Provider(window.ethereum);

<span class="hljs-comment">// MetaMask Plugin 还允许签名transactions 来发送ether和支付以改变链上的状态，所以需要 signer</span>
const signer <span class="hljs-operator">=</span> provider.getSigner();
</code></pre><h4 id="h-connecting-to-ethereum-rpc" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Connecting to Ethereum: RPC</h4><blockquote><p>JSON-RPC API是另一种常用的与Ethereum交互的方式，在所有主要的以太坊节点实现(Geth, Parity)以及许多第三方网络服务(Infura)中都有</p></blockquote><pre data-type="codeBlock" text="// 如果你不指定一个 url地址，会默认为: http://localhost:8545
const provider = new ethers.providers.JsonRpcProvider();

// 也允许签名transactions
const signer = provider.getSigner();
"><code><span class="hljs-comment">// 如果你不指定一个 url地址，会默认为: http://localhost:8545</span>
const provider <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.providers.JsonRpcProvider();

<span class="hljs-comment">// 也允许签名transactions</span>
const signer <span class="hljs-operator">=</span> provider.getSigner();
</code></pre><h4 id="h-querying-the-blockchain" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Querying the Blockchain</h4><blockquote><p>一旦你有了Provider，你就有了与Blockchain的只读连接，可以用它来查询当前状态，获取历史日志，查询部署的代码等等</p></blockquote><pre data-type="codeBlock" text="// Look up the current block number
await provider.getBlockNumber(); // 137223

// Get the balance of an account (by address or ENS name, if supported by network)
balance = await provider.getBalance(&quot;ethers.eth&quot;); 
// { BigNumber: &quot;2337132817842795605&quot; }

ethers.utils.formatEther(balance);
// &apos;2.337132817842795605&apos;

ethers.utils.parseEther(&quot;1.0&quot;);
// { BigNumber: &quot;1000000000000000000&quot; }
"><code><span class="hljs-comment">// Look up the current block number</span>
await provider.getBlockNumber(); <span class="hljs-comment">// 137223</span>

<span class="hljs-comment">// Get the balance of an account (by address or ENS name, if supported by network)</span>
balance <span class="hljs-operator">=</span> await provider.getBalance(<span class="hljs-string">"ethers.eth"</span>); 
<span class="hljs-comment">// { BigNumber: "2337132817842795605" }</span>

ethers.utils.formatEther(balance);
<span class="hljs-comment">// '2.337132817842795605'</span>

ethers.utils.parseEther(<span class="hljs-string">"1.0"</span>);
<span class="hljs-comment">// { BigNumber: "1000000000000000000" }</span>
</code></pre><blockquote><p>Note: ENS(Ethereum Name Service)之于以太坊就像是DNS对于因特网，ENS允许用户使用个性化的以太坊域名为自己注册，比如用“vitalik.eth”来替代密码地址‘0x32b724f073ec346edd64b0cc67757e4f6fe42950’。ENS一直在迭代开发中，ENS不仅仅用于以太坊地址，未来ENS将覆盖以太坊的各个方面</p></blockquote><h4 id="h-writing-to-the-blockchain" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Writing to the Blockchain</h4><pre data-type="codeBlock" text="// Send 1 ether to an ens name.
const tx = signer.sendTransaction({
    to: &quot;marktang.firefly.eth&quot;,
    value: ethers.utils.parseEther(&quot;1.0&quot;)
});
"><code><span class="hljs-comment">// Send 1 ether to an ens name.</span>
const <span class="hljs-built_in">tx</span> <span class="hljs-operator">=</span> signer.sendTransaction({
    to: <span class="hljs-string">"marktang.firefly.eth"</span>,
    <span class="hljs-built_in">value</span>: ethers.utils.parseEther(<span class="hljs-string">"1.0"</span>)
});
</code></pre><h4 id="h-contracts" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Contracts</h4><blockquote><p>合约是在Ethereum blockchain上运行的代码的一种抽象</p></blockquote><p>Contract类为了与链上合约进行通信，需要知道有哪些方法可以调用，这需要传入ABI文件</p><p>这个类是一个meta-class，在运行时构造的，传入ABI给构造函数时，它用ABI来决定要添加哪些方法</p><p>ABI由 Solidity或Vyper编译成，但你可以在代码中使用人类可读的ABI，如：</p><pre data-type="codeBlock" text="// You can also use an ENS name for the contract address
const daiAddress = &quot;dai.tokens.ethers.eth&quot;;

// The ERC-20 Contract ABI, which is a common contract interface
// for tokens (this is the Human-Readable ABI format)
const daiAbi = [
  // Some details about the token
  &quot;function name() view returns (string)&quot;,
  &quot;function symbol() view returns (string)&quot;,

  // Get the account balance
  &quot;function balanceOf(address) view returns (uint)&quot;,

  // Send some of your tokens to someone else
  &quot;function transfer(address to, uint amount)&quot;,

  // An event triggered whenever anyone transfers to someone else
  &quot;event Transfer(address indexed from, address indexed to, uint amount)&quot;
];

// The Contract object
const daiContract = new ethers.Contract(daiAddress, daiAbi, provider);
"><code><span class="hljs-comment">// You can also use an ENS name for the contract address</span>
const daiAddress <span class="hljs-operator">=</span> <span class="hljs-string">"dai.tokens.ethers.eth"</span>;

<span class="hljs-comment">// The ERC-20 Contract ABI, which is a common contract interface</span>
<span class="hljs-comment">// for tokens (this is the Human-Readable ABI format)</span>
const daiAbi <span class="hljs-operator">=</span> [
  <span class="hljs-comment">// Some details about the token</span>
  <span class="hljs-string">"function name() view returns (string)"</span>,
  <span class="hljs-string">"function symbol() view returns (string)"</span>,

  <span class="hljs-comment">// Get the account balance</span>
  <span class="hljs-string">"function balanceOf(address) view returns (uint)"</span>,

  <span class="hljs-comment">// Send some of your tokens to someone else</span>
  <span class="hljs-string">"function transfer(address to, uint amount)"</span>,

  <span class="hljs-comment">// An event triggered whenever anyone transfers to someone else</span>
  <span class="hljs-string">"event Transfer(address indexed from, address indexed to, uint amount)"</span>
];

<span class="hljs-comment">// The Contract object</span>
const daiContract <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.Contract(daiAddress, daiAbi, provider);
</code></pre><h4 id="h-read-only-methods" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Read-Only Methods</h4><pre data-type="codeBlock" text="// Get the ERC-20 token name
await daiContract.name()
// &apos;Dai Stablecoin&apos;

// Get the ERC-20 token symbol (for tickers and UIs)
await daiContract.symbol()
// &apos;DAI&apos;

// Get the balance of an address
balance = await daiContract.balanceOf(&quot;ricmoo.firefly.eth&quot;)
// { BigNumber: &quot;18190624174838529547383&quot; }

// Format the DAI for displaying to the user
ethers.utils.formatUnits(balance, 18)
// &apos;18190.624174838529547383&apos;
"><code><span class="hljs-comment">// Get the ERC-20 token name</span>
await daiContract.<span class="hljs-built_in">name</span>()
<span class="hljs-comment">// 'Dai Stablecoin'</span>

<span class="hljs-comment">// Get the ERC-20 token symbol (for tickers and UIs)</span>
await daiContract.symbol()
<span class="hljs-comment">// 'DAI'</span>

<span class="hljs-comment">// Get the balance of an address</span>
balance <span class="hljs-operator">=</span> await daiContract.balanceOf(<span class="hljs-string">"ricmoo.firefly.eth"</span>)
<span class="hljs-comment">// { BigNumber: "18190624174838529547383" }</span>

<span class="hljs-comment">// Format the DAI for displaying to the user</span>
ethers.utils.formatUnits(balance, <span class="hljs-number">18</span>)
<span class="hljs-comment">// '18190.624174838529547383'</span>
</code></pre><h4 id="h-state-changing-methods" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">State Changing Methods</h4><pre data-type="codeBlock" text="// DAI 合约目前连接到Provider，但是Provider是 read-only的
// 需要连接到一个Signer，这样就可以付费发送一些改变状态的交易
const daiWithSigner = contract.connect(signer);

// Each DAI has 18 decimal places
const dai = ethers.utils.parseUnits(&quot;1.0&quot;, 18);

// Send 1 DAI to &quot;ricmoo.firefly.eth&quot;
tx = daiWithSigner.transfer(&quot;ricmoo.firefly.eth&quot;, dai);
"><code><span class="hljs-comment">// DAI 合约目前连接到Provider，但是Provider是 read-only的</span>
<span class="hljs-comment">// 需要连接到一个Signer，这样就可以付费发送一些改变状态的交易</span>
const daiWithSigner <span class="hljs-operator">=</span> <span class="hljs-keyword">contract</span>.connect(signer);

<span class="hljs-comment">// Each DAI has 18 decimal places</span>
const dai <span class="hljs-operator">=</span> ethers.utils.parseUnits(<span class="hljs-string">"1.0"</span>, <span class="hljs-number">18</span>);

<span class="hljs-comment">// Send 1 DAI to "ricmoo.firefly.eth"</span>
<span class="hljs-built_in">tx</span> <span class="hljs-operator">=</span> daiWithSigner.<span class="hljs-built_in">transfer</span>(<span class="hljs-string">"ricmoo.firefly.eth"</span>, dai);
</code></pre><h4 id="h-listening-to-events" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Listening to Events</h4><pre data-type="codeBlock" text="// Receive an event when ANY transfer occurs
daiContract.on(&quot;Transfer&quot;, (from, to, amount, event) =&gt; {
    console.log(`${ from } sent ${ formatEther(amount) } to ${ to}`);
    // 事件对象包含逐字记录数据、EventFragment和获取块、交易和接收的函数以及事件函数
});

// @Todo: I don&apos;t know if Can I get all transactions here.
// A filter for when a specific address receives tokens
myAddress = &quot;0x8ba1f109551bD432803012645Ac136ddd64DBA72&quot;;
filter = daiContract.filters.Transfer(null, myAddress)
// {
//   address: &apos;dai.tokens.ethers.eth&apos;,
//   topics: [
//     &apos;0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef&apos;,
//     null,
//     &apos;0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72&apos;
//   ]
// }

// Receive an event when that filter occurs
daiContract.on(filter, (from, to, amount, event) =&gt; {
    // The to will always be &quot;address&quot;
    console.log(`I got ${ formatEther(amount) } from ${ from }.`);
});
"><code><span class="hljs-comment">// Receive an event when ANY transfer occurs</span>
daiContract.on(<span class="hljs-string">"Transfer"</span>, (<span class="hljs-keyword">from</span>, to, amount, <span class="hljs-function"><span class="hljs-keyword">event</span>) => </span>{
    console.log(`${ <span class="hljs-keyword">from</span> } sent ${ formatEther(amount) } to ${ to}`);
    <span class="hljs-comment">// 事件对象包含逐字记录数据、EventFragment和获取块、交易和接收的函数以及事件函数</span>
});

<span class="hljs-comment">// @Todo: I don't know if Can I get all transactions here.</span>
<span class="hljs-comment">// A filter for when a specific address receives tokens</span>
myAddress <span class="hljs-operator">=</span> <span class="hljs-string">"0x8ba1f109551bD432803012645Ac136ddd64DBA72"</span>;
filter <span class="hljs-operator">=</span> daiContract.filters.Transfer(null, myAddress)
<span class="hljs-comment">// {</span>
<span class="hljs-comment">//   address: 'dai.tokens.ethers.eth',</span>
<span class="hljs-comment">//   topics: [</span>
<span class="hljs-comment">//     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',</span>
<span class="hljs-comment">//     null,</span>
<span class="hljs-comment">//     '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'</span>
<span class="hljs-comment">//   ]</span>
<span class="hljs-comment">// }</span>

<span class="hljs-comment">// Receive an event when that filter occurs</span>
daiContract.on(filter, (<span class="hljs-keyword">from</span>, to, amount, <span class="hljs-function"><span class="hljs-keyword">event</span>) => </span>{
    <span class="hljs-comment">// The to will always be "address"</span>
    console.log(`I got ${ formatEther(amount) } <span class="hljs-keyword">from</span> ${ <span class="hljs-keyword">from</span> }.`);
});
</code></pre><h4 id="h-query-historic-events" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Query Historic Events</h4><pre data-type="codeBlock" text="// Get the address of the Signer
myAddress = await signer.getAddress()
// &apos;0x8ba1f109551bD432803012645Ac136ddd64DBA72&apos;

// Filter for all token transfers from me
filterFrom = daiContract.filters.Transfer(myAddress, null);
// {
//   address: &apos;dai.tokens.ethers.eth&apos;,
//   topics: [
//     &apos;0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef&apos;,
//     &apos;0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72&apos;
//   ]
// }

// Filter for all token transfers to me
filterTo = daiContract.filters.Transfer(null, myAddress);
// {
//   address: &apos;dai.tokens.ethers.eth&apos;,
//   topics: [
//     &apos;0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef&apos;,
//     null,
//     &apos;0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72&apos;
//   ]
// }

// List all transfers sent from me a specific block range
await daiContract.queryFilter(filterFrom, 9843470, 9843480)
// [
//   {
//     address: &apos;0x6B175474E89094C44Da98b954EedeAC495271d0F&apos;,
//     args: [
//       &apos;0x8ba1f109551bD432803012645Ac136ddd64DBA72&apos;,
//       &apos;0x8B3765eDA5207fB21690874B722ae276B96260E0&apos;,
//       { BigNumber: &quot;4750000000000000000&quot; },
//       amount: { BigNumber: &quot;4750000000000000000&quot; },
//       from: &apos;0x8ba1f109551bD432803012645Ac136ddd64DBA72&apos;,
//       to: &apos;0x8B3765eDA5207fB21690874B722ae276B96260E0&apos;
//     ],
//     blockHash: &apos;0x8462eb2fbcef5aa4861266f59ad5f47b9aa6525d767d713920fdbdfb6b0c0b78&apos;,
//     blockNumber: 9843476,
//     data: &apos;0x00000000000000000000000000000000000000000000000041eb63d55b1b0000&apos;,
//     decode: [Function],
//     event: &apos;Transfer&apos;,
//     eventSignature: &apos;Transfer(address,address,uint256)&apos;,
//     getBlock: [Function],
//     getTransaction: [Function],
//     getTransactionReceipt: [Function],
//     logIndex: 69,
//     removeListener: [Function],
//     removed: false,
//     topics: [
//       &apos;0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef&apos;,
//       &apos;0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72&apos;,
//       &apos;0x0000000000000000000000008b3765eda5207fb21690874b722ae276b96260e0&apos;
//     ],
//     transactionHash: &apos;0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1&apos;,
//     transactionIndex: 81
//   },
//   {
//     address: &apos;0x6B175474E89094C44Da98b954EedeAC495271d0F&apos;,
//     args: [
//       &apos;0x8ba1f109551bD432803012645Ac136ddd64DBA72&apos;,
//       &apos;0x00De4B13153673BCAE2616b67bf822500d325Fc3&apos;,
//       { BigNumber: &quot;250000000000000000&quot; },
//       amount: { BigNumber: &quot;250000000000000000&quot; },
//       from: &apos;0x8ba1f109551bD432803012645Ac136ddd64DBA72&apos;,
//       to: &apos;0x00De4B13153673BCAE2616b67bf822500d325Fc3&apos;
//     ],
//     blockHash: &apos;0x8462eb2fbcef5aa4861266f59ad5f47b9aa6525d767d713920fdbdfb6b0c0b78&apos;,
//     blockNumber: 9843476,
//     data: &apos;0x00000000000000000000000000000000000000000000000003782dace9d90000&apos;,
//     decode: [Function],
//     event: &apos;Transfer&apos;,
//     eventSignature: &apos;Transfer(address,address,uint256)&apos;,
//     getBlock: [Function],
//     getTransaction: [Function],
//     getTransactionReceipt: [Function],
//     logIndex: 70,
//     removeListener: [Function],
//     removed: false,
//     topics: [
//       &apos;0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef&apos;,
//       &apos;0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72&apos;,
//       &apos;0x00000000000000000000000000de4b13153673bcae2616b67bf822500d325fc3&apos;
//     ],
//     transactionHash: &apos;0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1&apos;,
//     transactionIndex: 81
//   }
// ]

// List all transfers sent in the last 10,000 blocks
await daiContract.queryFilter(filterFrom, -10000)

// List all transfers ever sent to me
await daiContract.queryFilter(filterTo)
"><code><span class="hljs-comment">// Get the address of the Signer</span>
myAddress <span class="hljs-operator">=</span> await signer.getAddress()
<span class="hljs-comment">// '0x8ba1f109551bD432803012645Ac136ddd64DBA72'</span>

<span class="hljs-comment">// Filter for all token transfers from me</span>
filterFrom <span class="hljs-operator">=</span> daiContract.filters.Transfer(myAddress, null);
<span class="hljs-comment">// {</span>
<span class="hljs-comment">//   address: 'dai.tokens.ethers.eth',</span>
<span class="hljs-comment">//   topics: [</span>
<span class="hljs-comment">//     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',</span>
<span class="hljs-comment">//     '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'</span>
<span class="hljs-comment">//   ]</span>
<span class="hljs-comment">// }</span>

<span class="hljs-comment">// Filter for all token transfers to me</span>
filterTo <span class="hljs-operator">=</span> daiContract.filters.Transfer(null, myAddress);
<span class="hljs-comment">// {</span>
<span class="hljs-comment">//   address: 'dai.tokens.ethers.eth',</span>
<span class="hljs-comment">//   topics: [</span>
<span class="hljs-comment">//     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',</span>
<span class="hljs-comment">//     null,</span>
<span class="hljs-comment">//     '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'</span>
<span class="hljs-comment">//   ]</span>
<span class="hljs-comment">// }</span>

<span class="hljs-comment">// List all transfers sent from me a specific block range</span>
await daiContract.queryFilter(filterFrom, <span class="hljs-number">9843470</span>, <span class="hljs-number">9843480</span>)
<span class="hljs-comment">// [</span>
<span class="hljs-comment">//   {</span>
<span class="hljs-comment">//     address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',</span>
<span class="hljs-comment">//     args: [</span>
<span class="hljs-comment">//       '0x8ba1f109551bD432803012645Ac136ddd64DBA72',</span>
<span class="hljs-comment">//       '0x8B3765eDA5207fB21690874B722ae276B96260E0',</span>
<span class="hljs-comment">//       { BigNumber: "4750000000000000000" },</span>
<span class="hljs-comment">//       amount: { BigNumber: "4750000000000000000" },</span>
<span class="hljs-comment">//       from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',</span>
<span class="hljs-comment">//       to: '0x8B3765eDA5207fB21690874B722ae276B96260E0'</span>
<span class="hljs-comment">//     ],</span>
<span class="hljs-comment">//     blockHash: '0x8462eb2fbcef5aa4861266f59ad5f47b9aa6525d767d713920fdbdfb6b0c0b78',</span>
<span class="hljs-comment">//     blockNumber: 9843476,</span>
<span class="hljs-comment">//     data: '0x00000000000000000000000000000000000000000000000041eb63d55b1b0000',</span>
<span class="hljs-comment">//     decode: [Function],</span>
<span class="hljs-comment">//     event: 'Transfer',</span>
<span class="hljs-comment">//     eventSignature: 'Transfer(address,address,uint256)',</span>
<span class="hljs-comment">//     getBlock: [Function],</span>
<span class="hljs-comment">//     getTransaction: [Function],</span>
<span class="hljs-comment">//     getTransactionReceipt: [Function],</span>
<span class="hljs-comment">//     logIndex: 69,</span>
<span class="hljs-comment">//     removeListener: [Function],</span>
<span class="hljs-comment">//     removed: false,</span>
<span class="hljs-comment">//     topics: [</span>
<span class="hljs-comment">//       '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',</span>
<span class="hljs-comment">//       '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72',</span>
<span class="hljs-comment">//       '0x0000000000000000000000008b3765eda5207fb21690874b722ae276b96260e0'</span>
<span class="hljs-comment">//     ],</span>
<span class="hljs-comment">//     transactionHash: '0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1',</span>
<span class="hljs-comment">//     transactionIndex: 81</span>
<span class="hljs-comment">//   },</span>
<span class="hljs-comment">//   {</span>
<span class="hljs-comment">//     address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',</span>
<span class="hljs-comment">//     args: [</span>
<span class="hljs-comment">//       '0x8ba1f109551bD432803012645Ac136ddd64DBA72',</span>
<span class="hljs-comment">//       '0x00De4B13153673BCAE2616b67bf822500d325Fc3',</span>
<span class="hljs-comment">//       { BigNumber: "250000000000000000" },</span>
<span class="hljs-comment">//       amount: { BigNumber: "250000000000000000" },</span>
<span class="hljs-comment">//       from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',</span>
<span class="hljs-comment">//       to: '0x00De4B13153673BCAE2616b67bf822500d325Fc3'</span>
<span class="hljs-comment">//     ],</span>
<span class="hljs-comment">//     blockHash: '0x8462eb2fbcef5aa4861266f59ad5f47b9aa6525d767d713920fdbdfb6b0c0b78',</span>
<span class="hljs-comment">//     blockNumber: 9843476,</span>
<span class="hljs-comment">//     data: '0x00000000000000000000000000000000000000000000000003782dace9d90000',</span>
<span class="hljs-comment">//     decode: [Function],</span>
<span class="hljs-comment">//     event: 'Transfer',</span>
<span class="hljs-comment">//     eventSignature: 'Transfer(address,address,uint256)',</span>
<span class="hljs-comment">//     getBlock: [Function],</span>
<span class="hljs-comment">//     getTransaction: [Function],</span>
<span class="hljs-comment">//     getTransactionReceipt: [Function],</span>
<span class="hljs-comment">//     logIndex: 70,</span>
<span class="hljs-comment">//     removeListener: [Function],</span>
<span class="hljs-comment">//     removed: false,</span>
<span class="hljs-comment">//     topics: [</span>
<span class="hljs-comment">//       '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',</span>
<span class="hljs-comment">//       '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72',</span>
<span class="hljs-comment">//       '0x00000000000000000000000000de4b13153673bcae2616b67bf822500d325fc3'</span>
<span class="hljs-comment">//     ],</span>
<span class="hljs-comment">//     transactionHash: '0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1',</span>
<span class="hljs-comment">//     transactionIndex: 81</span>
<span class="hljs-comment">//   }</span>
<span class="hljs-comment">// ]</span>

<span class="hljs-comment">// List all transfers sent in the last 10,000 blocks</span>
await daiContract.queryFilter(filterFrom, <span class="hljs-number">-10000</span>)

<span class="hljs-comment">// List all transfers ever sent to me</span>
await daiContract.queryFilter(filterTo)
</code></pre><h4 id="h-signing-messages" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Signing Messages</h4><pre data-type="codeBlock" text="// To sign a simple string, which are used for
// logging into a service, such as CryptoKitties,
// pass the string in.
signature = await signer.signMessage(&quot;Hello World&quot;);
// &apos;0x5a77beb84677b221d7110b08605a2658dd6c1e88a2ba9293436e587dbf6479d4798d0ca34ba2113bdb51ad97cd831975ccaccaf5f6fbd5d566fe662f11e2ca411b&apos;

//
// A common case is also signing a hash, which is 32
// bytes. It is important to note, that to sign binary
// data it MUST be an Array (or TypedArray)
//

// This string is 66 characters long
message = &quot;0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef&quot;

// This array representation is 32 bytes long
messageBytes = ethers.utils.arrayify(message);
// Uint8Array [ 221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149, 43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239 ]

// To sign a hash, you most often want to sign the bytes
signature = await signer.signMessage(messageBytes)
// &apos;0xe099cce5e80dec1d8464d00c4156855d1c14cc4f83473deee7ab8e60be770f4b5ea04e90e7b7102ac5b493f7822b0ad10dc26bfda0761530a58e5f461f90b2fd1b&apos;
"><code><span class="hljs-comment">// To sign a simple string, which are used for</span>
<span class="hljs-comment">// logging into a service, such as CryptoKitties,</span>
<span class="hljs-comment">// pass the string in.</span>
signature <span class="hljs-operator">=</span> await signer.signMessage(<span class="hljs-string">"Hello World"</span>);
<span class="hljs-comment">// '0x5a77beb84677b221d7110b08605a2658dd6c1e88a2ba9293436e587dbf6479d4798d0ca34ba2113bdb51ad97cd831975ccaccaf5f6fbd5d566fe662f11e2ca411b'</span>

<span class="hljs-comment">//</span>
<span class="hljs-comment">// A common case is also signing a hash, which is 32</span>
<span class="hljs-comment">// bytes. It is important to note, that to sign binary</span>
<span class="hljs-comment">// data it MUST be an Array (or TypedArray)</span>
<span class="hljs-comment">//</span>

<span class="hljs-comment">// This string is 66 characters long</span>
message <span class="hljs-operator">=</span> <span class="hljs-string">"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"</span>

<span class="hljs-comment">// This array representation is 32 bytes long</span>
messageBytes <span class="hljs-operator">=</span> ethers.utils.arrayify(message);
<span class="hljs-comment">// Uint8Array [ 221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149, 43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239 ]</span>

<span class="hljs-comment">// To sign a hash, you most often want to sign the bytes</span>
signature <span class="hljs-operator">=</span> await signer.signMessage(messageBytes)
<span class="hljs-comment">// '0xe099cce5e80dec1d8464d00c4156855d1c14cc4f83473deee7ab8e60be770f4b5ea04e90e7b7102ac5b493f7822b0ad10dc26bfda0761530a58e5f461f90b2fd1b'</span>
</code></pre>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[Dapp Meeting Learning Notes]]></title>
            <link>https://paragraph.com/@marktang-s-blog/dapp-meeting-learning-notes</link>
            <guid>qoMfiPmPHUG5LExX7VED</guid>
            <pubDate>Fri, 26 Aug 2022 16:25:23 GMT</pubDate>
            <description><![CDATA[从DC网络到nym网络匿名网络Tor 洋葱网络 - 暗网主流方案Nym (继承了匿名网络的同时，引入了区块链技术，解决了节点的激烈问题)Elixxer (继承了匿名网络的同时，引入了区块链技术，解决了节点的激烈问题)76BE4401-A1E8-4C1C-A190-760D7CA5D1A5区块链共识和扩容方案E8E53409-9DBB-45A5-80F4-AA2CB5E38141CACD94B3-7BA0-4FF2-8240-4D10309FD94561D9C9F4-AB0B-45E8-866B-E1F67717453901CA959A-254C-4717-A2F6-85F1606B7A7EE4E2F273-BE09-4F25-A3C8-4F5F0CD1486BC24BDE2D-00C0-4FF4-8453-3B557A2BD1F5D87CE9E0-22D5-4714-AC7B-A534AC157A3F37133F4C-B145-4760-88EC-3EF6F116F766F392339A-DC21-464C-B80A-BE5B5E0296C87806F7E5-CAB4-45BE-...]]></description>
            <content:encoded><![CDATA[<h2 id="h-dcnym" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">从DC网络到nym网络</h2><ul><li><p>匿名网络</p></li><li><p>Tor 洋葱网络 - 暗网主流方案</p></li><li><p>Nym (继承了匿名网络的同时，引入了区块链技术，解决了节点的激烈问题)</p></li><li><p>Elixxer (继承了匿名网络的同时，引入了区块链技术，解决了节点的激烈问题)</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">76BE4401-A1E8-4C1C-A190-760D7CA5D1A5</figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">区块链共识和扩容方案</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">E8E53409-9DBB-45A5-80F4-AA2CB5E38141</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">CACD94B3-7BA0-4FF2-8240-4D10309FD945</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">61D9C9F4-AB0B-45E8-866B-E1F677174539</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">01CA959A-254C-4717-A2F6-85F1606B7A7E</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">E4E2F273-BE09-4F25-A3C8-4F5F0CD1486B</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">C24BDE2D-00C0-4FF4-8453-3B557A2BD1F5</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">D87CE9E0-22D5-4714-AC7B-A534AC157A3F</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">37133F4C-B145-4760-88EC-3EF6F116F766</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">F392339A-DC21-464C-B80A-BE5B5E0296C8</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">7806F7E5-CAB4-45BE-AA48-5FCCE6BE041E</figcaption></figure>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[初探Solidity]]></title>
            <link>https://paragraph.com/@marktang-s-blog/solidity</link>
            <guid>S9393SRq9ZA5p0Y0oq9O</guid>
            <pubDate>Fri, 26 Aug 2022 16:19:30 GMT</pubDate>
            <description><![CDATA[初探Solidity基础概念没有浮点数运算为了弥补这个缺陷，如何表示带小数的数字呢？以太坊将自身的数切分为最小18位，我们称为wei，任何数字都是wei的整数倍44A06DF3-DABB-4BD0-86EE-86A0726C014C变量类型智能合约的变量分为两种，存在区块链上的和不存在区块链上的。存在区块链上的我们称为状态变量（state variable）。这类变量将永久记录在区块链上，写入读取它们就仿佛操作一个数据库一样，修改和赋值都会造成巨额的开销。不存在于区块链上的变量则是程序中的内存变量（memory variable），程序运行完毕就从内存中释放，相对开销较小internal/external修饰符internal修饰符可以让合约继承后子合约访问该函数external 修饰符让该函数只能被外部调用者调用Solidity的函数也有修饰符，称为modifiers，这标明了函数可能对区块链状态有无修改/读取的标记。一般都会标记该值让编译器帮我们执行代码的优化672641FC-3F0D-45C1-921E-05293A6D99FD内置函数keccak256 散列函数算法，可以...]]></description>
            <content:encoded><![CDATA[<h1 id="h-solidity" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">初探Solidity</h1><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>为了弥补这个缺陷，如何表示带小数的数字呢？以太坊将自身的数切分为最小18位，我们称为wei，任何数字都是wei的整数倍</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">44A06DF3-DABB-4BD0-86EE-86A0726C014C</figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">变量类型</h3><p>智能合约的变量分为两种，存在区块链上的和不存在区块链上的。存在区块链上的我们称为状态变量（state variable）。这类变量将永久记录在区块链上，写入读取它们就仿佛操作一个数据库一样，修改和赋值都会造成巨额的开销。不存在于区块链上的变量则是程序中的内存变量（memory variable），程序运行完毕就从内存中释放，相对开销较小</p><h4 id="h-internalexternal" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">internal/external修饰符</h4><ul><li><p>internal修饰符可以让合约继承后子合约访问该函数</p></li><li><p>external 修饰符让该函数只能被外部调用者调用</p></li></ul><p><strong>Solidity的函数也有修饰符，称为modifiers，这标明了函数可能对区块链状态有无修改/读取的标记。一般都会标记该值让编译器帮我们执行代码的优化</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">672641FC-3F0D-45C1-921E-05293A6D99FD</figcaption></figure><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">内置函数</h4><ul><li><p>keccak256 散列函数算法，可以根据任意长度的明文产生固定长度256位的哈希值</p></li></ul><h4 id="h-map" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">数据结构：Map</h4><p>映射数据结构：mapping</p><blockquote><p>例如我们可以存储账号地址以及它对应的合约内的token数量的关系(假设是一个代币合约)</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">1CC08B0E-986A-4DDA-9732-C7A7DAB23454</figcaption></figure><h4 id="h-msgsender" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">环境变量：msg.sender</h4><blockquote><p>当前合约调用者的地址</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">FD0D5090-B4DB-40B3-AE34-36C17DB7A106</figcaption></figure><h4 id="h-require-assert" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">require / assert</h4><blockquote><p>在合约中进行权限检查或者条件满足检查</p></blockquote><ul><li><p>require 条件检查语句如果不通过，则扣除运行到当前语句时，程序执行所话费的gas，终止程序执行，并返回</p></li><li><p>assert 条件检查语句如果不通过，则视为严重错误，扣除所有的gas，终止程序执行，并返回</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">6CFC6B7B-5EAF-4AE4-8D89-4514B8C65B9E</figcaption></figure><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">继承和引入</h4><blockquote><p>智能合约的代码可以来源于自身项目内，也可以来源于外部早已部署完毕的链上合约</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">CDE78801-35DE-4599-BBA1-7F536A4F5C6A</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">C77EB3A9-9CD3-463A-9A1A-787056410F6F</figcaption></figure><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">内存变量</h4><blockquote><p>不同的存储类型，花费的gas数额不同。它们的最终存储地方也不同。有时候为了省钱，我们会把临时变量留在内存里，而不是保存在区块链上。随着程序执行，内存里的变量会消亡，而区块链上的会永存。由于没有改变区块链状态，内存变量(memory)的花费会比状态变量(storage)的花费少很多</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">9047DF90-02CA-48D2-B48D-CE2B597A5BD9</figcaption></figure><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">接口与合约调用</h4><blockquote><p>合约的接口就是合约的抽象。我们可以通过定义合约接口，并指定合约地址，来调用另外一个在以太坊上早已经部署好的合约</p></blockquote><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">ECC942AC-4156-445B-9542-F6379B490E37</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">8CDE7C2D-CB8D-41E3-833F-1446E07D5FDE</figcaption></figure><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;"><figcaption HTMLAttributes="[object Object]" class="">09363FC1-2333-472A-8116-1ABDB5BF929F</figcaption></figure><h4 id="h-ownable" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Ownable控制</h4><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">753431E0-28EA-4C7B-9C26-DDE534A6B4AE</figcaption></figure><h4 id="h-pausable" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Pausable控制</h4><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">367FDA31-4F30-4E7F-8E41-BF0F9190CF36</figcaption></figure><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;"><figcaption HTMLAttributes="[object Object]" class="">86C34667-D059-4B79-8AB4-6F6ABA3AAD58</figcaption></figure><p>这里now和5 minutes都属于时间表示，我们甚至可以直接将时间单位赋值给uint类型的变量。以下的语句是完全合法的。</p><pre data-type="codeBlock" text="uint lastUpdated = now;
"><code>uint <span class="hljs-attr">lastUpdated</span> = now<span class="hljs-comment">;</span>
</code></pre><pre data-type="codeBlock" text="  uint one_day = 1 days;
  uint five_minutes = 5 minutes;
  uint now_time = now;
  uint two_hours = 2 hours;
"><code>  uint <span class="hljs-attr">one_day</span> = <span class="hljs-number">1</span> days<span class="hljs-comment">;</span>
  uint <span class="hljs-attr">five_minutes</span> = <span class="hljs-number">5</span> minutes<span class="hljs-comment">;</span>
  uint <span class="hljs-attr">now_time</span> = now<span class="hljs-comment">;</span>
  uint <span class="hljs-attr">two_hours</span> = <span class="hljs-number">2</span> hours<span class="hljs-comment">;</span>
</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="// ID 和年龄的映射
mapping (uint =&gt; uint) public age;

// 一个函数修饰
modifier olderThan(uint _age, uint _userId) {
  require(age[_userId] &gt;= _age);
  _;
}

// 限制年龄大于16岁才能开车
function driveCar(uint _userId) public olderThan(16, _userId) {

}
"><code><span class="hljs-comment">// ID 和年龄的映射</span>
<span class="hljs-keyword">mapping</span> (<span class="hljs-keyword">uint</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint</span>) <span class="hljs-keyword">public</span> age;

<span class="hljs-comment">// 一个函数修饰</span>
<span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">olderThan</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> _age, <span class="hljs-keyword">uint</span> _userId</span>) </span>{
  <span class="hljs-built_in">require</span>(age[_userId] <span class="hljs-operator">></span><span class="hljs-operator">=</span> _age);
  <span class="hljs-keyword">_</span>;
}

<span class="hljs-comment">// 限制年龄大于16岁才能开车</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">driveCar</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> _userId</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">olderThan</span>(<span class="hljs-params"><span class="hljs-number">16</span>, _userId</span>) </span>{

}
</code></pre><h4 id="h-for" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">for循环</h4><p>环在智能合约中使用范围不是很明显。因为循环消耗大量的计算步骤，智能合约编纂者都尽量减少循环次数，甚至从根本上避免循环的产生</p><pre data-type="codeBlock" text="function getOdds() pure external returns(uint[]) {
  uint[] memory odds = new uintUnsupported embed;

  uint counter = 0;

  for (uint i = 1; i &lt;= 10; i++) {
    if (i % 2 != 0) {
      odds[counter] = i;
      counter++;
    }
  }
  return odds;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOdds</span>(<span class="hljs-params"></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">uint</span>[] <span class="hljs-keyword">memory</span> odds <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> uintUnsupported embed;

  <span class="hljs-keyword">uint</span> counter <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">1</span>; i <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
    <span class="hljs-keyword">if</span> (i <span class="hljs-operator">%</span> <span class="hljs-number">2</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
      odds[counter] <span class="hljs-operator">=</span> i;
      counter<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
    }
  }
  <span class="hljs-keyword">return</span> odds;
}
</code></pre><h4 id="h-payable" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">合约收款：payable修饰符</h4><pre data-type="codeBlock" text="contract OnlineStore {
  function buySomething() external payable {
    // 查看付款金额
    require(msg.value == 0.001 ether);
    // 将对应金额的物品转移给调用方
    transferSomething(msg.sender);
  }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">OnlineStore</span> </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">buySomething</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-comment">// 查看付款金额</span>
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.001</span> <span class="hljs-literal">ether</span>);
    <span class="hljs-comment">// 将对应金额的物品转移给调用方</span>
    transferSomething(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
  }
}
</code></pre><p>上面多了一个关键字payable和两个环境变量：msg.value和msg.sender。payable表示调用者可以在调用该方法时附上想发送的以太币数量；msg.value则表示实际使用中调用者支付的以太币，这里要和gas费用相区别，gas费用是付给矿工来执行合约的，而 msg.value则是直接付给智能合约的费用。在智能合约收到以太币后，可以直接调用其他函数进行等价交换。这种函数经常在ICO的代币发行中使用，让广大用户可以直接通过合约交换以太坊为代币，无需人工干预，公平、公正、公开</p><h4 id="h-transfer" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">支付费用：transfer方法</h4><p>智能合约的作者往往能获得报酬。怎么获得呢？就是通过用户不断向智能合约打入以太币，并积累在智能合约的balance余额中。当智能合约所有者想提取的时候，调用一个函数就可以执行</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">A984B0D7-FBB5-4CA0-9FC7-DB47FB2BC256</figcaption></figure><h4 id="h-truffle" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Truffle</h4><p>Truffle内置了一个默认的测试底层网络实现 Ganache。Ganache 原名 Test-RPC，是以太坊开源项目中采用JavaScript编写的模拟区块链运行的节点。它本身并不联网产生区块，而是一个测试环境中的高度仿真模拟器。它的Web3命令支持丰富，紧跟社区的最新提案和开发进度，因为没有实际挖矿（仅仅模拟挖矿行为），所以它的运行速度是惊人的，非常利于测试环境下的高速使用，不需要等实际区块链的出块时间间隔</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/95abda582280d0a341ba7ae798ab4ea29e9a7a7a4f296f6e8006049c34eae620.png" alt="EFD042A4-D6FD-4DD6-A83C-15F52C7E3FF8.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">EFD042A4-D6FD-4DD6-A83C-15F52C7E3FF8.png</figcaption></figure><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;"><figcaption HTMLAttributes="[object Object]" class="">91F22119-838F-4434-9836-83983AAB103D</figcaption></figure><pre data-type="codeBlock" text="$ mkdir truffle-project
$ cd truffle-project
$ truffle unbox metacoin
"><code><span class="hljs-meta prompt_">$ </span><span class="bash"><span class="hljs-built_in">mkdir</span> truffle-project</span>
<span class="hljs-meta prompt_">$ </span><span class="bash"><span class="hljs-built_in">cd</span> truffle-project</span>
<span class="hljs-meta prompt_">$ </span><span class="bash">truffle unbox metacoin</span>
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">2E0AF910-243F-4C8F-8013-A6376F777513</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">453E6230-9C76-4E60-9078-95117039817D</figcaption></figure><p>每个目录具体解释如下：</p><ul><li><p>contracts目录包含了主要的合约代码，示例中包含了MetaCoin.sol（主要合约），ConvertLib.sol （库文件）和部署时记录地址的合约 Migration.sol</p></li><li><p>migrations目录包含了两个部署文件，先会部署Migration.sol, 再会链接ConvertLib.sol与MetaCoin.sol部署到链上，两者顺序不可颠倒。代码中aritfacts.require()函数相当于node.js中的require()函数，指明了我们希望引用哪个合约的抽象。这个引用名必须与合约中的类名相同</p></li><li><p>test目录包含了两种不同的测试文件，智能合约可以通过sol结尾的合约进行测试写作，也可以通过js文件写作，两者功能几乎一致，笔者在开发组多时，考虑到语言的通用性与灵活程度，平时一直使用js的格式书写测试。Truffle的测试都是Mocha测试框架格式的语法</p></li><li><p>truffle-config.js和truffle.js都保存了控制truffle行为定义的全局变量，当想调换测试网络时，可以编辑truffle.js指定网络和端口</p></li></ul><h4 id="h-erc20" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">ERC20合约</h4><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">3C20E549-C0C0-4A14-9142-6E565F46A871</figcaption></figure>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[Node.js Notes]]></title>
            <link>https://paragraph.com/@marktang-s-blog/node-js-notes</link>
            <guid>3OYcBh04IsQIVRCR9Ma6</guid>
            <pubDate>Fri, 26 Aug 2022 16:14:08 GMT</pubDate>
            <description><![CDATA[Node.js NotesNode.js是一个基于Chrome V8引擎的Javascript运行环境Node.js使用了一个事件驱动、非阻塞式I/O的模型，使其轻量又高效Node.js适合的服务Web服务中间层(搜索引擎优化 + 首屏速度优化 = 服务端渲染)构建工作流 - Gulp、Webpack开发工具 - Visual Studio Code游戏 - wayward(可扩展性 - 大型应用需要给使用者自定义模块的能力)客户端应用 - twitch.tv(用Node.js客户端技术(Electron)实现，最大限度复用现有工程)CommonJS模块规范commonJS模块被引用后，如果没有导出，默认是个空对象 {}导出的引用与模块内部的exports引入是同一个，因此外部也可以修改模块的引用exports = module.exportsNode.js内置模块BD7E57F3-E8C1-4F11-BF2A-4DCA5C45641A利用EventEmitter实现观察者模式事件循环(Event Loop)libuv这个库提供两个最重要的东西是事件循环和线程池，两者共同构建了异...]]></description>
            <content:encoded><![CDATA[<h1 id="h-nodejs-notes" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Node.js Notes</h1><ul><li><p>Node.js是一个基于Chrome V8引擎的Javascript运行环境</p></li><li><p>Node.js使用了一个事件驱动、非阻塞式I/O的模型，使其轻量又高效</p></li></ul><h3 id="h-nodejs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Node.js适合的服务</h3><ul><li><p>Web服务中间层(搜索引擎优化 + 首屏速度优化 = 服务端渲染)</p></li><li><p>构建工作流 - Gulp、Webpack</p></li><li><p>开发工具 - Visual Studio Code</p></li><li><p>游戏 - wayward(可扩展性 - 大型应用需要给使用者自定义模块的能力)</p></li><li><p>客户端应用 - <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://twitch.tv">twitch.tv</a>(用Node.js客户端技术(Electron)实现，最大限度复用现有工程)</p></li></ul><h3 id="h-commonjs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">CommonJS模块规范</h3><ul><li><p>commonJS模块被引用后，如果没有导出，默认是个空对象 {}</p></li><li><p>导出的引用与模块内部的exports引入是同一个，因此外部也可以修改模块的引用</p></li><li><p>exports = module.exports</p></li></ul><h3 id="h-nodejs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Node.js内置模块</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">BD7E57F3-E8C1-4F11-BF2A-4DCA5C45641A</figcaption></figure><blockquote><p>利用EventEmitter实现观察者模式</p></blockquote><h3 id="h-event-loop" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">事件循环(Event Loop)</h3><blockquote><p>libuv这个库提供两个最重要的东西是事件循环和线程池，两者共同构建了异步非阻塞I/O模型，而事件驱动只是在整个非阻塞I/O模型当中线程池通知事件循环它已经完成I/O操作的这样一种机制而已</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;"><figcaption HTMLAttributes="[object Object]" class="">176F38E6-94BD-4B18-9280-2CCE95370AEF</figcaption></figure><h4 id="h-micro-task-and-macro-task" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Micro-Task and Macro-Task</h4><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">39918200-597C-4F96-9679-876FB55533C2</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">69702BD3-D76C-49C9-ACD9-79EAC1A47F84</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">D19ED4DF-C81F-4874-BE19-61CDD6C9A424</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">EA3767A5-7DBF-4A0A-BD56-5D88012F240A</figcaption></figure><h3 id="h-promise" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Promise</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">C6EAB6DA-12CA-4BA5-9506-A082BA75EC09</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">D2B5E5EA-E5B7-432D-A99C-51E465E1F7DD</figcaption></figure><h3 id="h-asyncawait" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Async/Await</h3><ul><li><p>Async function 是Promise的语法糖封装</p></li><li><p>异步编程的终极方案 - 以同步的方式写异步</p></li><li><p>await 关键字可以 “暂停” async function 的执行</p><ul><li><p>await 关键字可以以同步的写法获取Promise的执行结果</p></li><li><p>try-catch 可以获取await 所得到的错误</p></li></ul></li></ul><h3 id="h-express" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Express</h3><p>核心功能</p><ul><li><p>路由</p><ul><li><p>request/response</p><ul><li><p>request: pathname, query等</p></li><li><p>response: send(), json(), jsonp()等</p></li></ul></li></ul></li><li><p>中间件 - 洋葱模型</p></li></ul><blockquote><p>Express洋葱模型不完善，有设计缺陷，所以作者新写了Koa</p></blockquote><h3 id="h-koa" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Koa</h3><p>核心功能</p><ul><li><p>中间件 - 洋葱模型</p></li><li><p>Context - 将request和response整合进了context，使其更符合语义</p></li><li><p>中间件支持异步函数，可以使用 await next(); 阻塞住，不会出现express的异步调用缺陷</p></li><li><p>Express vs Koa</p><ul><li><p>Express 门槛更低，koa更强大优雅</p></li><li><p>Express 封装更多东西，开发更快速，koa可定制型更高</p></li></ul></li></ul><h3 id="h-rpc" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">RPC调用</h3><ul><li><p>Remote Procedure Call (远程过程调用)</p></li><li><p>和Ajax的相同点</p><ul><li><p>都是两个计算机之间的网络通信</p></li><li><p>需要双方约定一个数据格式</p></li></ul></li><li><p>和Ajax的不同点</p><ul><li><p>不一定使用DNS作为寻址服务</p></li><li><p>应用层协议一般不使用HTTP</p></li><li><p>基于TCP或UDP协议</p></li></ul></li><li><p>寻址/负载均衡</p><ul><li><p>Ajax: 使用DNS进行寻址</p></li><li><p>RPC: 使用特有服务进行寻址</p></li></ul></li><li><p>TCP通信方式</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-buffer" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Buffer编解码二进制数据包</h3><ul><li><p>Protocol buffer协议 Google提供</p></li></ul><h3 id="h-nodejs-net" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Node.js net模块</h3><ul><li><p>全双工的通信通道搭建</p><ul><li><p>关键在于应用层协议需要有标记包号的字段</p></li><li><p>处理以下情况，需要有标记包长的字段</p><ul><li><p>粘包</p></li><li><p>不完整包</p></li></ul></li><li><p>错误处理</p></li></ul></li></ul><h3 id="h-api-restful" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">API服务 - Restful</h3><ul><li><p>简单易懂</p></li><li><p>可以快速搭建</p></li><li><p>在数据的聚合方面有很大劣势</p></li></ul><h3 id="h-api-graphql" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">API服务 - GraphQL</h3><ul><li><p>专注数据聚合，前端要什么就返回什么</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">压测工具</h3><ul><li><p>Ab (apache bench)</p></li><li><p>webbench</p></li><li><p>找到性能瓶颈所在地</p><ul><li><p>top</p></li><li><p>iostat</p></li></ul></li></ul><h3 id="h-nodejs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Node.js性能分析工具</strong></h3><ul><li><p>Chrome devtools</p></li></ul><h3 id="h-nodejs-cluster" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Node.js Cluster</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><figcaption HTMLAttributes="[object Object]" class="">D02BFAA4-1917-4A37-8632-141CAC10ACF7</figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">动静分离</h3><ul><li><p>静态内容</p><ul><li><p>基本不会变动，也不会因为请求参数不同而变化</p></li><li><p>-&gt; CDN分发，HTTP缓存等</p></li></ul></li><li><p>动态内容</p><ul><li><p>各种因为请求参数不同而变动，且变种的数量几乎不可枚举</p></li><li><p>-&gt; 用大量的源站机器承载，结合反向代理进行负载均衡</p></li></ul></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">设计模式</h3><blockquote><p>学习设计模式最主要的不是学习模式，而是理解设计模式的原则，理解要怎么样才能做出架构优秀的程序</p></blockquote><ul><li><p>观察者模式</p><ul><li><p>EventEmitter</p></li><li><p>DOM addEventListener</p></li></ul></li><li><p>外观模式</p><ul><li><p>jQuery</p></li></ul></li><li><p>设计模式六大法则</p><ul><li><p>单一职责原则</p><ul><li><p>每个模块/类只做一件事情</p></li><li><p>微服务</p></li><li><p>方便重用</p></li><li><p>解耦</p></li></ul></li><li><p>里氏替换原则</p></li><li><p>依赖倒转原则</p></li><li><p>接口隔离</p></li><li><p>最小知晓原则</p></li><li><p>开闭原则</p><ul><li><p>对扩展开放，对修改关闭</p></li><li><p>Webpack loader设计</p></li><li><p>每一个模块都有明确的功能</p></li><li><p>新增功能只需要新增模块，不需要修改原有部分</p></li></ul></li></ul></li></ul><h3 id="h-serverless" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Serverless</h3><ul><li><p>云函数</p><ul><li><p>不用再因为运维、架构的事情操心</p><ul><li><p>缩短业务上线周期</p></li><li><p>减少出错的概率</p></li><li><p>业务开发的上手难度更低</p></li></ul></li><li><p>渐进式</p></li></ul></li><li><p>less</p></li><li><p>把能在多个业务复用的东西下沉，屏蔽细节</p></li></ul>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[油画笔记]]></title>
            <link>https://paragraph.com/@marktang-s-blog/BnpRJdVHdiamjcD1V0X3</link>
            <guid>BnpRJdVHdiamjcD1V0X3</guid>
            <pubDate>Fri, 26 Aug 2022 06:12:40 GMT</pubDate>
            <description><![CDATA[油画历史时期1. 史前壁画画画的模式还是出于本能，本能地去画画、涂抹，这个阶段成为“像儿童一样画画”2. 古希腊与古罗马这两个时期的绘画就有些像刚刚学成的专业绘画者画的。古希腊关键词：尊重自然、信奉众神、黄金比例、公式化 古罗马关键词：比较务实、实用性很强3. 中世纪刻意模仿儿童画画时期 关键词：异教徒、基督教、圣经、镶嵌画、湿壁画、蛋彩、宗教、宗教内的神迹显现、以神为名义的征战4. 文艺复兴关键词：乔托、人文精神的复兴、技法的复兴、达芬奇、米开朗基罗、拉斐尔、巴洛克(胜利、权利和控制 人体的动作和姿势生动大胆、色彩明快、强调光影变化)、洛可可(奢华、浮夸、贝壳形的花纹、抽象的火焰形状、不对称的花边、生动、神奇、不纯洁、不道德)5. 新古典主义与浪漫主义关键词：画风细腻、过渡平和、颜色鲜艳和细节处理到位、强烈的感情、浪漫主义的创作重心是追求理想，爱好奇幻型的艺术形象，具有强烈的主观抒情色彩6. 印象派和后印象派关键词：不完整、更关注现实的光和色彩、印象派不刻意地追求象征意义，而是追求描绘自然的现代色彩写生、自我感受和主观感情7. 20世纪后关键词：野兽派、表现主义、立体主义、未来...]]></description>
            <content:encoded><![CDATA[<h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">油画历史时期</h1><h3 id="h-1" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">1. 史前壁画</h3><p>画画的模式还是出于本能，本能地去画画、涂抹，这个阶段成为“像儿童一样画画”</p><h3 id="h-2" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2. 古希腊与古罗马</h3><p>这两个时期的绘画就有些像刚刚学成的专业绘画者画的。古希腊关键词：尊重自然、信奉众神、黄金比例、公式化</p><p>古罗马关键词：比较务实、实用性很强</p><h3 id="h-3" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">3. 中世纪</h3><p>刻意模仿儿童画画时期 关键词：异教徒、基督教、圣经、镶嵌画、湿壁画、蛋彩、宗教、宗教内的神迹显现、以神为名义的征战</p><h3 id="h-4" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">4. 文艺复兴</h3><p>关键词：乔托、人文精神的复兴、技法的复兴、达芬奇、米开朗基罗、拉斐尔、巴洛克(胜利、权利和控制 人体的动作和姿势生动大胆、色彩明快、强调光影变化)、洛可可(奢华、浮夸、贝壳形的花纹、抽象的火焰形状、不对称的花边、生动、神奇、不纯洁、不道德)</p><h3 id="h-5" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">5. 新古典主义与浪漫主义</h3><p>关键词：画风细腻、过渡平和、颜色鲜艳和细节处理到位、强烈的感情、浪漫主义的创作重心是追求理想，爱好奇幻型的艺术形象，具有强烈的主观抒情色彩</p><h3 id="h-6" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">6. 印象派和后印象派</h3><p>关键词：不完整、更关注现实的光和色彩、印象派不刻意地追求象征意义，而是追求描绘自然的现代色彩写生、自我感受和主观感情</p><h3 id="h-7-20" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">7. 20世纪后</h3><p>关键词：野兽派、表现主义、立体主义、未来主义、达达主义、超现实主义、抽象主义、抽象性和表现性、从写实发展到写意、艺术家的自我意识的发展</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/78a52f8cccdd16303f09d35df48472ffa4470d366f907509d18b4533fd32456d.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>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/7e1bfd6f659e79a00a4747927d2261328676332a709875176df855ac4b4af297.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Javascript一些基础面试题]]></title>
            <link>https://paragraph.com/@marktang-s-blog/javascript</link>
            <guid>FRJU8FVsVjBeWecZ2TYu</guid>
            <pubDate>Fri, 26 Aug 2022 05:49:07 GMT</pubDate>
            <description><![CDATA[JS面试一些使Javascript更加简洁的小技巧清空或截断数组 const arr = [11, 22, 33, 44, 55, 66]; // truncanting arr.length = 3; console.log(arr); // => [11, 22, 33] //clearing arr.length = 0; console.log(arr); // => [] console.log(arr[2]); // => undefined 几个面试题JS中使用 typeof 能得到的哪些类型？ 考点：JS变量类型何时使用 === 何时使用 == ? 考点：强制类型转换window.onload和DOMContentLoaded的区别? 考点：浏览器渲染过程用JS创建10个标签，点击的时候弹出来对应的序号 考点：作用域简述如何实现一个模块加载器，实现类似require.js的基本功能 考点：JS模块化实现数组的随机排序 考点：JS基础算法变量类型和计算题目JS中使用 typeof 能得到的哪些类型？ 考点：JS变量类型何时使用 === 何时使用 == ? 考点：强制...]]></description>
            <content:encoded><![CDATA[<h2 id="h-js" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">JS面试</h2><h3 id="h-javascript" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">一些使Javascript更加简洁的小技巧</h3><blockquote><p>清空或截断数组</p></blockquote><pre data-type="codeBlock" text="
const arr = [11, 22, 33, 44, 55, 66];

// truncanting

arr.length = 3;

console.log(arr); // =&gt; [11, 22, 33]

//clearing

arr.length = 0;

console.log(arr); // =&gt; []

console.log(arr[2]); // =&gt; undefined
"><code>
const arr <span class="hljs-operator">=</span> [<span class="hljs-number">11</span>, <span class="hljs-number">22</span>, <span class="hljs-number">33</span>, <span class="hljs-number">44</span>, <span class="hljs-number">55</span>, <span class="hljs-number">66</span>];

<span class="hljs-comment">// truncanting</span>

arr.<span class="hljs-built_in">length</span> <span class="hljs-operator">=</span> <span class="hljs-number">3</span>;

console.log(arr); <span class="hljs-comment">// => [11, 22, 33]</span>

<span class="hljs-comment">//clearing</span>

arr.<span class="hljs-built_in">length</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;

console.log(arr); <span class="hljs-comment">// => []</span>

console.log(arr[<span class="hljs-number">2</span>]); <span class="hljs-comment">// => undefined</span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">几个面试题</h3><ol><li><p>JS中使用 typeof 能得到的哪些类型？ 考点：JS变量类型</p></li><li><p>何时使用 === 何时使用 == ? 考点：强制类型转换</p></li><li><p>window.onload和DOMContentLoaded的区别? 考点：浏览器渲染过程</p></li><li><p>用JS创建10个标签，点击的时候弹出来对应的序号 考点：作用域</p><ul><li><p>简述如何实现一个模块加载器，实现类似require.js的基本功能 考点：JS模块化</p></li><li><p>实现数组的随机排序 考点：JS基础算法</p></li></ul></li><li><p>变量类型和计算</p><blockquote><p>题目</p></blockquote><ol><li><p>JS中使用 typeof 能得到的哪些类型？ 考点：JS变量类型</p></li><li><p>何时使用 === 何时使用 == ? 考点：强制类型转换</p></li><li><p>JS中有哪些内置函数</p></li><li><p>JS变量按照存储方式区分为哪些类型，并描述其特点</p></li><li><p>如何理解JSON</p></li></ol><blockquote><p>解答</p></blockquote><ol><li><p>undefined、string、number、boolean、object、function</p></li><li><p>obj.a == null 除了判断null和undefined外，都需要使用全等符</p></li><li><p>数据封装类对象- Object、Array、Boolean、Number、String、Function、Date、RegExp、Error</p></li><li><p>值类型、引用类型</p></li><li><p>JSON 只不过是一个 JS 对象而已, 也是一个数据格式</p></li></ol><blockquote><p>知识点</p></blockquote><ol><li><p>变量类型</p></li><li><p>变量计算</p></li></ol><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">变量类型</h4><ul><li><p>值类型 vs 引用类型</p></li><li><p>typeof 运算符详解</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>字符串拼接</p></li><li><p>== 运算符</p></li><li><p>if语句</p></li><li><p>逻辑运算</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">原型和原型链</h3><blockquote><p>题目</p></blockquote><ul><li><p>如何准确判断一个变量是数组类型</p></li><li><p>写一个原型链继承的例子</p></li><li><p>描述new一个对象的过程</p></li><li><p>zepto(或其他框架) 源码中如何使用原型链</p></li></ul><blockquote><p>解答</p></blockquote><ul><li><p>arr instanceof Array</p></li></ul><pre data-type="codeBlock" text="
// 动物

function Animal() {

this.eat = function () {

console.log(&apos;animal eat&apos;)

}

}

// 狗

function Dog() {

this.bark = function () {

console.log(&apos;dog bark&apos;)

}

}

Dog.prototype = new Animal()

// 哈士奇

var hashiqi = new Dog()

// 接下来代码演示时，会推荐更加贴近实战的原型继承示例！
"><code>
<span class="hljs-comment">// 动物</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Animal</span>(<span class="hljs-params"></span>) </span>{

<span class="hljs-built_in">this</span>.eat <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

console.log(<span class="hljs-string">'animal eat'</span>)

}

}

<span class="hljs-comment">// 狗</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dog</span>(<span class="hljs-params"></span>) </span>{

<span class="hljs-built_in">this</span>.bark <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

console.log(<span class="hljs-string">'dog bark'</span>)

}

}

Dog.prototype <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Animal()

<span class="hljs-comment">// 哈士奇</span>

<span class="hljs-keyword">var</span> hashiqi <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Dog()

<span class="hljs-comment">// 接下来代码演示时，会推荐更加贴近实战的原型继承示例！</span>
</code></pre><blockquote><p>描述new 一个对象的过程</p></blockquote><ol><li><p>创建一个新对象</p></li><li><p>this 指向这个新对象</p></li><li><p>执行代码，即对this 赋值</p></li><li><p>返回 this</p></li></ol><blockquote><p>zepto (或其他框架) 源码中如何使用原型链</p></blockquote><ul><li><p>阅读源码是高效提高技能的方式</p></li><li><p>但不能 &quot;埋头苦钻&quot; 有技巧在其中</p></li><li><p>慕课网搜索 &quot;zepto设计和源码分析&quot;</p></li></ul><blockquote><p>知识点</p></blockquote><ul><li><p>构造函数</p></li><li><p>构造函数 - 扩展</p></li><li><p>原型规则和示例</p></li><li><p>原型链</p></li><li><p>instanceof</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>所有的引用类型(数组、对象、函数)，都具有对象特性，即可自由扩展属性(除了 &quot;null&quot; 以外)</p></li><li><p>所有的引用(数组、对象、函数)，都有一个__proto__ (隐式原型)属性,属性值是一个普通的对象</p></li><li><p>所有的函数，都有一个 prototype (显式原型) 属性，属性值也是一个普通对象</p></li><li><p>所有的引用类型(数组、对象、函数), __ proto __ 属性值指向它的构造函数的 &quot;prototype&quot; 属性值</p></li></ul><pre data-type="codeBlock" text="
var obj = {}; obj.a = 100;

var arr = []; arr.a = 100;

function fn () {}

fn.a = 100;

console.log(obj.__proto__);

console.log(arr.__proto__);

console.log(fn.__proto__);

console.log(fn.prototype)

console.log(obj.__proto__ === Object.prototype)
"><code>
<span class="hljs-keyword">var</span> obj <span class="hljs-operator">=</span> {}; obj.a <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;

<span class="hljs-keyword">var</span> arr <span class="hljs-operator">=</span> []; arr.a <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fn</span> (<span class="hljs-params"></span>) </span>{}

fn.a <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;

console.log(obj.__proto__);

console.log(arr.__proto__);

console.log(fn.__proto__);

console.log(fn.prototype)

console.log(obj.__proto__ <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> Object.prototype)
</code></pre><ul><li><p>当试图得到一个对象的某个属性时，如果这个对象本身没有这个属性，那么会去它的__ proto __ (即它的构造函数的 prototype) 中寻找。</p></li></ul><pre data-type="codeBlock" text="
// 构造函数

function Foo(name, age) {

this.name = name;

}

Foo.prototype.alertName = function () {

alert(this.name);

}

// 创建示例

var f = new Foo(&apos;zhangsan&apos;)

f.printName = function () {

console.log(this.name);

}

// 测试

f.printName(); // zhangsan

f.alertName(); // zhangsan

var item;

for (item in f) {

//高级浏览器已经在 for in 中屏蔽了来自原型的属性

//但是这里建议大家还是加上这个判断，保证程序的健壮性

if (f.hasOwnProperty(item)) {

console.log(item)

}

}
"><code>
<span class="hljs-comment">// 构造函数</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Foo</span>(<span class="hljs-params">name, age</span>) </span>{

<span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span> <span class="hljs-operator">=</span> name;

}

Foo.prototype.alertName <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

alert(<span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span>);

}

<span class="hljs-comment">// 创建示例</span>

<span class="hljs-keyword">var</span> f <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Foo(<span class="hljs-string">'zhangsan'</span>)

f.printName <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

console.log(<span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span>);

}

<span class="hljs-comment">// 测试</span>

f.printName(); <span class="hljs-comment">// zhangsan</span>

f.alertName(); <span class="hljs-comment">// zhangsan</span>

<span class="hljs-keyword">var</span> item;

<span class="hljs-keyword">for</span> (item in f) {

<span class="hljs-comment">//高级浏览器已经在 for in 中屏蔽了来自原型的属性</span>

<span class="hljs-comment">//但是这里建议大家还是加上这个判断，保证程序的健壮性</span>

<span class="hljs-keyword">if</span> (f.hasOwnProperty(item)) {

console.log(item)

}

}
</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="
//构造函数

function Foo(name, age) {

this.name = name;

}

Foo.prototype.alertName = function () {

alert(this.name);

}

// 创建示例

var f = new Foo(&apos;zhangsan&apos;)

f.printName = function () {

console.log(this.name)

}

// 测试

f.printName()

f.alertName()

f.toString() // 要去 f.__proto__.__proto__中查找
"><code>
<span class="hljs-comment">//构造函数</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Foo</span>(<span class="hljs-params">name, age</span>) </span>{

<span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span> <span class="hljs-operator">=</span> name;

}

Foo.prototype.alertName <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

alert(<span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span>);

}

<span class="hljs-comment">// 创建示例</span>

<span class="hljs-keyword">var</span> f <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Foo(<span class="hljs-string">'zhangsan'</span>)

f.printName <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

console.log(<span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span>)

}

<span class="hljs-comment">// 测试</span>

f.printName()

f.alertName()

f.toString() <span class="hljs-comment">// 要去 f.__proto__.__proto__中查找</span>
</code></pre><blockquote><p>instanceof</p></blockquote><ul><li><p>用于判断 引用类型 属于哪个 构造函数 的方法</p></li><li><p>f instanceof Foo 的判断逻辑是：</p></li><li><p>f的 __ proto __ 一层一层往上，能否对应到 Foo.prototype</p></li><li><p>再试着判断 f instanceof Object</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">作用域和闭包-执行上下文</h3><blockquote><p>题目</p></blockquote><ol><li><p>说一下对变量提升的理解</p></li></ol><ul><li><p>变量定义</p></li><li><p>函数声明(注意和函数表达式的区别)</p></li></ul><ol><li><p>说明 this 几种不同的使用场景</p></li></ol><ul><li><p>作为构造函数执行</p></li><li><p>作为对象属性执行</p></li><li><p>作为普通函数执行</p></li><li><p>call apply bind</p></li></ul><ol><li><p>创建10个标签，点击的时候弹出来对应的序号</p></li><li><p>
// 这是一个错误的写法!!!

var i,a

for (i = 0; i &lt; 10; i++) {

a = document.createElement(&apos;a&apos;)

a.innerHTML = i + &apos;&lt;br&gt;&apos;

a.addEventListener(&apos;click&apos;, function (e) {

e.preventDefault()

alert(i)

})

document.body.appendChild(a)

}

// 这是正确的写法!!!

var i

for (i = 0; i &lt; 10; i++) {

(function(i){

var a = document.createElement(&apos;a&apos;)

a.innerHTML = i + &apos;&lt;br&gt;&apos;

a.addEventListener(&apos;click&apos;, function (e) {

e.preventDefault()

alert(i)

})

document.body.appendChild(a)

})(i)

}</p><ol><li><p>如何理解作用域</p></li></ol><ul><li><p>自由变量</p></li><li><p>作用域链，即自由变量的查找</p></li><li><p>闭包的两个场景</p></li></ul><ol><li><p>实际开发中闭包的应用</p></li></ol><pre data-type="codeBlock" text="
// 闭包实际应用中主要用于封装变量，收敛权限

function isFirstLoad() {

var _list = []

return function (id) {

if (_list.indexOf(id) &gt;= 0) {

return false

} else {

_list.push(id)

return true

}

}

}

// 使用

var firstLoad = isFirstLoad()

firstLoad(10) // true

firstLoad(10) // false

firstLoad(20) // true
"><code>
<span class="hljs-comment">// 闭包实际应用中主要用于封装变量，收敛权限</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isFirstLoad</span>(<span class="hljs-params"></span>) </span>{

<span class="hljs-keyword">var</span> _list <span class="hljs-operator">=</span> []

<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">id</span>) </span>{

<span class="hljs-keyword">if</span> (_list.indexOf(id) <span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {

<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>

} <span class="hljs-keyword">else</span> {

_list.<span class="hljs-built_in">push</span>(id)

<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>

}

}

}

<span class="hljs-comment">// 使用</span>

<span class="hljs-keyword">var</span> firstLoad <span class="hljs-operator">=</span> isFirstLoad()

firstLoad(<span class="hljs-number">10</span>) <span class="hljs-comment">// true</span>

firstLoad(<span class="hljs-number">10</span>) <span class="hljs-comment">// false</span>

firstLoad(<span class="hljs-number">20</span>) <span class="hljs-comment">// true</span>
</code></pre><blockquote><p>知识点</p></blockquote><ul><li><p>执行上下文</p></li><li><p>this</p></li><li><p>作用域</p></li><li><p>作用域链</p></li><li><p>闭包</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">执行上下文</h4><pre data-type="codeBlock" text="
console.log(a) // undefined

var a = 100

fn(&apos;zhangsan&apos;) // &apos;zhangsan&apos; 20

function fn(name) {

age = 20

console.log(name, age)

var age

}
"><code>
console.log(a) <span class="hljs-comment">// undefined</span>

<span class="hljs-keyword">var</span> a <span class="hljs-operator">=</span> <span class="hljs-number">100</span>

fn(<span class="hljs-string">'zhangsan'</span>) <span class="hljs-comment">// 'zhangsan' 20</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fn</span>(<span class="hljs-params">name</span>) </span>{

age <span class="hljs-operator">=</span> <span class="hljs-number">20</span>

console.log(name, age)

<span class="hljs-keyword">var</span> age

}
</code></pre><ul><li><p>范围:一段</p><ul><li><p>全局: 变量定义、函数声明 ==一段</p><ul><li><p>函数: 变量定义、函数声明、this、arguments ==函数==</p></li><li><p>PS：注意&quot;函数声明&quot;和&quot;函数表达式&quot;的区别</p></li></ul></li></ul><h4 id="h-this" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">this</h4><ul><li><p>this 要在执行时才能确认值，定义时无法确认</p></li></ul><pre data-type="codeBlock" text="
var a = {

name: &apos;A&apos;,

fn: function () {

console.log(this.name)

}

}

a.fn() // this === a

a.fn.call({name: &apos;B&apos;}) // this === {name: &apos;B&apos;}

var fn1 = a.fn

fn1() // this === window
"><code>
<span class="hljs-keyword">var</span> a <span class="hljs-operator">=</span> {

name: <span class="hljs-string">'A'</span>,

fn: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

console.log(<span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span>)

}

}

a.fn() <span class="hljs-comment">// this === a</span>

a.fn.<span class="hljs-built_in">call</span>({name: <span class="hljs-string">'B'</span>}) <span class="hljs-comment">// this === {name: 'B'}</span>

<span class="hljs-keyword">var</span> fn1 <span class="hljs-operator">=</span> a.fn

fn1() <span class="hljs-comment">// this === window</span>
</code></pre><ul><li><p>作为构造函数执行</p></li><li><p>作为对象属性执行</p></li><li><p>作为普通函数执行</p></li><li><p>call apply bind</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>没有块级作用域</p></li><li><p>只有函数和全局作用域</p></li></ul><pre data-type="codeBlock" text="
// 无块级作用域

if (true) {

var name = &apos;zhangsan&apos;

}

console.log(name)

// 函数和全局作用域

var a = 100

function fn() {

var a = 200

console.log(&apos;fn&apos;, a)

}

console.log(&apos;global&apos;, a)

fn()
"><code>
<span class="hljs-comment">// 无块级作用域</span>

<span class="hljs-keyword">if</span> (<span class="hljs-literal">true</span>) {

<span class="hljs-keyword">var</span> name <span class="hljs-operator">=</span> <span class="hljs-string">'zhangsan'</span>

}

console.log(name)

<span class="hljs-comment">// 函数和全局作用域</span>

<span class="hljs-keyword">var</span> a <span class="hljs-operator">=</span> <span class="hljs-number">100</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fn</span>(<span class="hljs-params"></span>) </span>{

<span class="hljs-keyword">var</span> a <span class="hljs-operator">=</span> <span class="hljs-number">200</span>

console.log(<span class="hljs-string">'fn'</span>, a)

}

console.log(<span class="hljs-string">'global'</span>, a)

fn()
</code></pre><pre data-type="codeBlock" text="
//作用域链

var a = 100

function fn() {

var b = 200

// 当前作用域没有定义的变量，即 &quot;自由变量&quot;

console.log(a)

console.log(b)

}

fn()
"><code>
<span class="hljs-comment">//作用域链</span>

<span class="hljs-keyword">var</span> a <span class="hljs-operator">=</span> <span class="hljs-number">100</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fn</span>(<span class="hljs-params"></span>) </span>{

<span class="hljs-keyword">var</span> b <span class="hljs-operator">=</span> <span class="hljs-number">200</span>

<span class="hljs-comment">// 当前作用域没有定义的变量，即 "自由变量"</span>

console.log(a)

console.log(b)

}

fn()
</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="
function F1() {

var a = 100

// 返回一个函数 (函数作为返回值)

return function () {

console.log(a)

}

}

// f1 得到一个函数

var f1 = F1()

var a = 200

f1()
"><code>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">F1</span>(<span class="hljs-params"></span>) </span>{

<span class="hljs-keyword">var</span> a <span class="hljs-operator">=</span> <span class="hljs-number">100</span>

<span class="hljs-comment">// 返回一个函数 (函数作为返回值)</span>

<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

console.log(a)

}

}

<span class="hljs-comment">// f1 得到一个函数</span>

<span class="hljs-keyword">var</span> f1 <span class="hljs-operator">=</span> F1()

<span class="hljs-keyword">var</span> a <span class="hljs-operator">=</span> <span class="hljs-number">200</span>

f1()
</code></pre><ul><li><p>函数作为返回值</p></li><li><p>函数作为参数传递</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><blockquote><p>题目</p></blockquote><ol><li><p>同步和异步的区别是什么？分别举一个同步和异步的例子</p></li></ol><ul><li><p>同步会阻塞代码执行，而异步不会</p></li><li><p>alert是同步，setTimeout是异步</p></li></ul><ol><li><p>一个关于setTimeout的笔试题</p></li></ol><pre data-type="codeBlock" text="
console.log(1)

setTimeout(function () {

console.log(2)

}, 0)

console.log(3)

setTimeout(function () {

console.log(4)

}, 1000)

console.log(5)
"><code>
console.log(<span class="hljs-number">1</span>)

setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

console.log(<span class="hljs-number">2</span>)

}, <span class="hljs-number">0</span>)

console.log(<span class="hljs-number">3</span>)

setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

console.log(<span class="hljs-number">4</span>)

}, <span class="hljs-number">1000</span>)

console.log(<span class="hljs-number">5</span>)
</code></pre><ol><li><p>前端使用异步的场景有哪些</p></li></ol><ul><li><p>定时任务：setTimeout, setInverval</p></li><li><p>网络请求：ajax请求，动态加载</p></li><li><p>事件绑定</p></li></ul><blockquote><p>知识点</p></blockquote><ul><li><p>什么是异步(对比同步)</p></li><li><p>前端使用异步的场景</p></li><li><p>异步和单线程</p></li></ul><pre data-type="codeBlock" text="
// 异步-不阻塞

console.log(100)

setTimeout(function() {

console.log(200)

}, 1000)

console.log(300)

// 同步-阻塞

console.log(100)

alert(200)

console.log(300)
"><code>
<span class="hljs-comment">// 异步-不阻塞</span>

console.log(<span class="hljs-number">100</span>)

setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{

console.log(<span class="hljs-number">200</span>)

}, <span class="hljs-number">1000</span>)

console.log(<span class="hljs-number">300</span>)

<span class="hljs-comment">// 同步-阻塞</span>

console.log(<span class="hljs-number">100</span>)

alert(<span class="hljs-number">200</span>)

console.log(<span class="hljs-number">300</span>)
</code></pre><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">何时需要异步</h4><ul><li><p>在可能发生等待的情况</p></li><li><p>等待过程中不能像 alert 一样阻塞程序运行</p></li><li><p>因此，所有的 &quot;等待的情况&quot; 都需要异步</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>定时任务：setTimeout, setInverval</p></li><li><p>网络请求：ajax请求，动态加载</p></li><li><p>事件绑定</p></li></ul><pre data-type="codeBlock" text="
// ajax请求代码示例

console.log(&apos;start&apos;)

$.get(&apos;./data1.json&apos;, function(data1) {

console.log(data1)

})

console.log(&apos;end&apos;)

// &lt;img&gt;加载示例

console.log(&apos;start&apos;)

var img = document.createElement(&apos;img&apos;)

img.onload = function(){

console.log(&apos;loaded&apos;)

}

img.src = &apos;/xxx.png&apos;

console.log(&apos;end&apos;)

// 事件绑定示例

console.log(&apos;start&apos;)

document.getElementById(&apos;btn1&apos;).addEventListener(&apos;click&apos;, function() {

alert(&apos;clicked&apos;)

})

console.log(&apos;end&apos;)
"><code>
<span class="hljs-comment">// ajax请求代码示例</span>

console.log(<span class="hljs-string">'start'</span>)

$.get(<span class="hljs-string">'./data1.json'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">data1</span>) </span>{

console.log(data1)

})

console.log(<span class="hljs-string">'end'</span>)

<span class="hljs-comment">// &#x3C;img>加载示例</span>

console.log(<span class="hljs-string">'start'</span>)

<span class="hljs-keyword">var</span> img <span class="hljs-operator">=</span> document.createElement(<span class="hljs-string">'img'</span>)

img.onload <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{

console.log(<span class="hljs-string">'loaded'</span>)

}

img.src <span class="hljs-operator">=</span> <span class="hljs-string">'/xxx.png'</span>

console.log(<span class="hljs-string">'end'</span>)

<span class="hljs-comment">// 事件绑定示例</span>

console.log(<span class="hljs-string">'start'</span>)

document.getElementById(<span class="hljs-string">'btn1'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{

alert(<span class="hljs-string">'clicked'</span>)

})

console.log(<span class="hljs-string">'end'</span>)
</code></pre><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">单线程</h4><ul><li><p>执行第一行，打印100</p></li><li><p>执行setTimeout后，传入setTimeout的函数会被暂存起来，不会立即执行(单线程的特点,不能同时干两件事)</p></li><li><p>执行最后一行，打印300</p></li><li><p>待所有程序执行完，处于空闲状态时，会立马看有没有暂存起来的要执行。</p></li><li><p>发现暂存起来的setTimeout中的函数无需等待时间，就立即拿过来执行</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">其他知识</h3><blockquote><p>题目</p></blockquote><ol><li><p>获取 2017-06-10 格式的日期</p></li></ol><pre data-type="codeBlock" text="
function formatDate(dt) {

if (!dt) {

dt = new Date()

}

var year = dt.getFullYear()

var month = dt.getMonth() + 1

var date = dt.getDate()

if (month &lt; 10) {

// 强制类型转换

month = &apos;0&apos; + month

}

if (date &lt; 10) {

// 强制类型转换

date = &apos;0&apos; + date

}

// 强制类型转换

return year + &apos;-&apos; + month + &apos;-&apos; + date

}

var dt = new Date()

var formatDate = formatDate(dt)

console.log(formatDate)
"><code>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatDate</span>(<span class="hljs-params">dt</span>) </span>{

<span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>dt) {

dt <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Date()

}

<span class="hljs-keyword">var</span> year <span class="hljs-operator">=</span> dt.getFullYear()

<span class="hljs-keyword">var</span> month <span class="hljs-operator">=</span> dt.getMonth() <span class="hljs-operator">+</span> <span class="hljs-number">1</span>

<span class="hljs-keyword">var</span> date <span class="hljs-operator">=</span> dt.getDate()

<span class="hljs-keyword">if</span> (month <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">10</span>) {

<span class="hljs-comment">// 强制类型转换</span>

month <span class="hljs-operator">=</span> <span class="hljs-string">'0'</span> <span class="hljs-operator">+</span> month

}

<span class="hljs-keyword">if</span> (date <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">10</span>) {

<span class="hljs-comment">// 强制类型转换</span>

date <span class="hljs-operator">=</span> <span class="hljs-string">'0'</span> <span class="hljs-operator">+</span> date

}

<span class="hljs-comment">// 强制类型转换</span>

<span class="hljs-keyword">return</span> year <span class="hljs-operator">+</span> <span class="hljs-string">'-'</span> <span class="hljs-operator">+</span> month <span class="hljs-operator">+</span> <span class="hljs-string">'-'</span> <span class="hljs-operator">+</span> date

}

<span class="hljs-keyword">var</span> dt <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Date()

<span class="hljs-keyword">var</span> formatDate <span class="hljs-operator">=</span> formatDate(dt)

console.log(formatDate)
</code></pre><ol><li><p>获取随机数，要求是长度一致的字符串格式</p></li></ol><pre data-type="codeBlock" text="
var random = Math.random()

var random = random + &apos;0000000000&apos; // 后面加上10个零

var random = random.slice(0, 10)

console.log(random)
"><code>
<span class="hljs-keyword">var</span> random <span class="hljs-operator">=</span> Math.random()

<span class="hljs-keyword">var</span> random <span class="hljs-operator">=</span> random <span class="hljs-operator">+</span> <span class="hljs-string">'0000000000'</span> <span class="hljs-comment">// 后面加上10个零</span>

<span class="hljs-keyword">var</span> random <span class="hljs-operator">=</span> random.slice(<span class="hljs-number">0</span>, <span class="hljs-number">10</span>)

console.log(random)
</code></pre><ol><li><p>写一个能遍历对象和数组的通用forEach函数</p></li></ol><pre data-type="codeBlock" text="
function forEach(obj, fn) {

var key

if (obj instanceof Array) {

// 准确判断是不是数组

obj.forEach(function (item, index) {

fn(index, item)

})

} else {

// 不是数组就是对象

for (key in obj) {

fn(key, obj[key])

}

}

}

var arr = [1,2,3]

// 注意，这里参数的顺序换了，为了和对象的遍历格式一致

forEach(arr, function (index, item) {

console.log(index, item)

})

var obj = {x: 100, y: 200}

forEach(obj, function (key, value) {

console.log(key, value)

})
"><code>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">forEach</span>(<span class="hljs-params">obj, fn</span>) </span>{

<span class="hljs-keyword">var</span> key

<span class="hljs-keyword">if</span> (obj instanceof Array) {

<span class="hljs-comment">// 准确判断是不是数组</span>

obj.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">item, index</span>) </span>{

fn(index, item)

})

} <span class="hljs-keyword">else</span> {

<span class="hljs-comment">// 不是数组就是对象</span>

<span class="hljs-keyword">for</span> (key in obj) {

fn(key, obj[key])

}

}

}

<span class="hljs-keyword">var</span> 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-comment">// 注意，这里参数的顺序换了，为了和对象的遍历格式一致</span>

forEach(arr, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">index, item</span>) </span>{

console.log(index, item)

})

<span class="hljs-keyword">var</span> obj <span class="hljs-operator">=</span> {x: <span class="hljs-number">100</span>, y: <span class="hljs-number">200</span>}

forEach(obj, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">key, value</span>) </span>{

console.log(key, value)

})
</code></pre><blockquote><p>知识点</p></blockquote><ul><li><p>日期</p></li><li><p>Math</p></li><li><p>数组API</p></li><li><p>对象API</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">日期</h4><pre data-type="codeBlock" text="
Date.now() // 获取当前时间毫秒数

var dt = new Date()

dt.getTime() // 获取毫秒数

dt.getFullYear() //年

dt.getMonth // 月 (0-11)

dt.getDate() // 日 (0-31)

dt.getHours() // 小时 (0-23)

dt.getMinutes() // 分钟 (0-59)

dt.getSeconds() // 秒 (0-59)
"><code>
Date.now() <span class="hljs-comment">// 获取当前时间毫秒数</span>

<span class="hljs-keyword">var</span> dt <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Date()

dt.getTime() <span class="hljs-comment">// 获取毫秒数</span>

dt.getFullYear() <span class="hljs-comment">//年</span>

dt.getMonth <span class="hljs-comment">// 月 (0-11)</span>

dt.getDate() <span class="hljs-comment">// 日 (0-31)</span>

dt.getHours() <span class="hljs-comment">// 小时 (0-23)</span>

dt.getMinutes() <span class="hljs-comment">// 分钟 (0-59)</span>

dt.getSeconds() <span class="hljs-comment">// 秒 (0-59)</span>
</code></pre><h4 id="h-math" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Math</h4><ul><li><p>获取随机数 Math.random()</p></li></ul><h4 id="h-api" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">数组API</h4><ul><li><p>forEach 遍历所有元素</p></li><li><p>every 判断所有元素是否都符合条件</p></li><li><p>some 判断是否有至少一个元素符合条件</p></li><li><p>sort 排序</p></li><li><p>map 对元素重新组装，生成新数组</p></li><li><p>filter 过滤符合条件的元素</p></li></ul><h4 id="h-foreach" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">forEach</h4><pre data-type="codeBlock" text="
var arr = [1,2,3]

arr.forEach(function (item, index) {

// 遍历数组的所有元素

console.log(index, item)

})
"><code>
<span class="hljs-keyword">var</span> arr <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>]

arr.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">item, index</span>) </span>{

<span class="hljs-comment">// 遍历数组的所有元素</span>

console.log(index, item)

})
</code></pre><h4 id="h-every" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">every</h4><pre data-type="codeBlock" text="
var arr = [1,2,3]

var result = arr.every(function (item, index) {

// 用来判断所有的数组元素，都满足一个条件

if (item &lt; 4) {

return true

}

})

console.log(result)
"><code>
<span class="hljs-keyword">var</span> 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-keyword">var</span> result <span class="hljs-operator">=</span> arr.every(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">item, index</span>) </span>{

<span class="hljs-comment">// 用来判断所有的数组元素，都满足一个条件</span>

<span class="hljs-keyword">if</span> (item <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">4</span>) {

<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>

}

})

console.log(result)
</code></pre><h4 id="h-some" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">some</h4><pre data-type="codeBlock" text="
var arr = [1,2,3]

var result = arr.some(function (item, index) {

// 用来判断所有的数组元素，只要有一个满足条件即可

if (item &lt; 2) {

return true

}

})

console.log(result)
"><code>
<span class="hljs-keyword">var</span> 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-keyword">var</span> result <span class="hljs-operator">=</span> arr.some(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">item, index</span>) </span>{

<span class="hljs-comment">// 用来判断所有的数组元素，只要有一个满足条件即可</span>

<span class="hljs-keyword">if</span> (item <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">2</span>) {

<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>

}

})

console.log(result)
</code></pre><h4 id="h-sort" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">sort</h4><pre data-type="codeBlock" text="
var arr = [1,4,2,3,5]

var arr2 = arr.sort(function(a, b) {

// 从小到大排序

return a - b

// 从大到小排序

// return b - a

})

console.log(arr2)
"><code>
<span class="hljs-keyword">var</span> arr <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">4</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">5</span>]

<span class="hljs-keyword">var</span> arr2 <span class="hljs-operator">=</span> arr.sort(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">a, b</span>) </span>{

<span class="hljs-comment">// 从小到大排序</span>

<span class="hljs-keyword">return</span> a <span class="hljs-operator">-</span> b

<span class="hljs-comment">// 从大到小排序</span>

<span class="hljs-comment">// return b - a</span>

})

console.log(arr2)
</code></pre><h4 id="h-map" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">map</h4><pre data-type="codeBlock" text="
var arr = [1,2,3,4]

var arr2 = arr.map(function(item, index) {

// 将元素重新组装，并返回

return &apos;&lt;b&gt;&apos; + item + &apos;&lt;/b&gt;&apos;

})

console.log(arr2)
"><code>
<span class="hljs-keyword">var</span> 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-keyword">var</span> arr2 <span class="hljs-operator">=</span> arr.map(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">item, index</span>) </span>{

<span class="hljs-comment">// 将元素重新组装，并返回</span>

<span class="hljs-keyword">return</span> <span class="hljs-string">'&#x3C;b>'</span> <span class="hljs-operator">+</span> item <span class="hljs-operator">+</span> <span class="hljs-string">'&#x3C;/b>'</span>

})

console.log(arr2)
</code></pre><h4 id="h-filter" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">filter</h4><pre data-type="codeBlock" text="
var arr = [1,2,3,4]

var arr2 = arr.filter(function(item, index) {

// 通过某一个条件过滤数组

if (item &gt;= 2) {

return true

}

})

console.log(arr2)
"><code>
<span class="hljs-keyword">var</span> 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-keyword">var</span> arr2 <span class="hljs-operator">=</span> arr.filter(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">item, index</span>) </span>{

<span class="hljs-comment">// 通过某一个条件过滤数组</span>

<span class="hljs-keyword">if</span> (item <span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">2</span>) {

<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>

}

})

console.log(arr2)
</code></pre><h4 id="h-api" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">对象API</h4><pre data-type="codeBlock" text="
var obj = {

x: 100,

y: 200,

z: 300

}

var key

for (key in obj) {

// 注意这里的 hasOwnProperty, 再讲原型链时候讲过了

if (obj.hasOwnProperty(key)) {

console.log(key, obj[key])

}

}
"><code>
var obj = {

<span class="hljs-symbol">x:</span> <span class="hljs-number">100</span>,

<span class="hljs-symbol">y:</span> <span class="hljs-number">200</span>,

<span class="hljs-symbol">z:</span> <span class="hljs-number">300</span>

}

var <span class="hljs-keyword">key</span>

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">key</span> <span class="hljs-keyword">in</span> obj) {

// 注意这里的 hasOwnProperty, 再讲原型链时候讲过了

<span class="hljs-keyword">if</span> (obj.hasOwnProperty(<span class="hljs-keyword">key</span>)) {

console.log(<span class="hljs-keyword">key</span>, obj[<span class="hljs-keyword">key</span>])

}

}
</code></pre><h3 id="h-js-web-api" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">JS-Web-API</h3><ul><li><p>JS基础知识: ECMA 262 标准</p></li><li><p>JS-Web-API: W3C 标准</p></li><li><p>W3C标准中关于JS的规定有：</p></li><li><p>DOM操作</p></li><li><p>BOM操作</p></li><li><p>事件绑定</p></li><li><p>ajax请求(包括http协议)</p></li><li><p>存储</p></li></ul><h3 id="h-dom" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">DOM操作</h3><blockquote><p>题目</p></blockquote><ol><li><p>DOM 是哪种基本的数据结构？</p></li></ol><ul><li><p>树</p></li></ul><ol><li><p>DOM 操作的常用API有哪些</p></li></ol><ul><li><p>获取DOM节点，以及节点的property和Attribute</p></li><li><p>获取父节点，</p></li><li><p>新增节点，删除节点</p></li></ul><ol><li><p>DOM 节点的Attribute 和 property有何区别</p></li></ol><ul><li><p>property 只是一个JS对象的属性的修改</p></li><li><p>Attribute 是对html标签属性的修改</p></li></ul><blockquote><p>知识点</p></blockquote><ul><li><p>DOM本质</p></li><li><p>DOM节点操作</p></li><li><p>DOM结构操作</p></li></ul><h4 id="h-dom" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">DOM节点操作</h4><ul><li><p>获取DOM节点</p></li><li><p>property</p></li><li><p>Attribute</p></li></ul><pre data-type="codeBlock" text="
var div1 = document.getElementById(&apos;div1&apos;) // 元素

var divList = document.getElementsByTagName(&apos;div&apos;) // 集合

console.log(divList.length)

console.log(divList[0])

var containerList = document.getElementsByClassName(&apos;.container&apos;) // 集合

var pList = document.querySelectorAll(&apos;p&apos;) // 集合
"><code>
<span class="hljs-keyword">var</span> div1 <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'div1'</span>) <span class="hljs-comment">// 元素</span>

<span class="hljs-keyword">var</span> divList <span class="hljs-operator">=</span> document.getElementsByTagName(<span class="hljs-string">'div'</span>) <span class="hljs-comment">// 集合</span>

console.log(divList.<span class="hljs-built_in">length</span>)

console.log(divList[<span class="hljs-number">0</span>])

<span class="hljs-keyword">var</span> containerList <span class="hljs-operator">=</span> document.getElementsByClassName(<span class="hljs-string">'.container'</span>) <span class="hljs-comment">// 集合</span>

<span class="hljs-keyword">var</span> pList <span class="hljs-operator">=</span> document.querySelectorAll(<span class="hljs-string">'p'</span>) <span class="hljs-comment">// 集合</span>
</code></pre><pre data-type="codeBlock" text="
// property

var pList = document.querySelectorAll(&apos;p&apos;)

var p = pList[0]

console.log(p.style.width) // 获取样式

p.style.width = &apos;100px&apos;

console.log(p.className)

p.className = &apos;p1&apos;

// 获取 nodeName 和 nodeType

console.log(p.nodeName)

console.log(p.nodeType)
"><code>
<span class="hljs-comment">// property</span>

<span class="hljs-keyword">var</span> pList <span class="hljs-operator">=</span> document.querySelectorAll(<span class="hljs-string">'p'</span>)

<span class="hljs-keyword">var</span> p <span class="hljs-operator">=</span> pList[<span class="hljs-number">0</span>]

console.log(p.style.width) <span class="hljs-comment">// 获取样式</span>

p.style.width <span class="hljs-operator">=</span> <span class="hljs-string">'100px'</span>

console.log(p.className)

p.className <span class="hljs-operator">=</span> <span class="hljs-string">'p1'</span>

<span class="hljs-comment">// 获取 nodeName 和 nodeType</span>

console.log(p.nodeName)

console.log(p.nodeType)
</code></pre><pre data-type="codeBlock" text="
// Attribute

var pList = document.querySelectorAll(&apos;p&apos;)

var p = pList[0]

p.getAttribute(&apos;data-name&apos;)

p.setAttribute(&apos;data-name&apos;, &apos;sss&apos;)

p.getAttribute(&apos;style&apos;)

p.setAttribute(&apos;style&apos;, &apos;font-size:28px;&apos;)
"><code>
<span class="hljs-comment">// Attribute</span>

<span class="hljs-keyword">var</span> pList <span class="hljs-operator">=</span> document.querySelectorAll(<span class="hljs-string">'p'</span>)

<span class="hljs-keyword">var</span> p <span class="hljs-operator">=</span> pList[<span class="hljs-number">0</span>]

p.getAttribute(<span class="hljs-string">'data-name'</span>)

p.setAttribute(<span class="hljs-string">'data-name'</span>, <span class="hljs-string">'sss'</span>)

p.getAttribute(<span class="hljs-string">'style'</span>)

p.setAttribute(<span class="hljs-string">'style'</span>, <span class="hljs-string">'font-size:28px;'</span>)
</code></pre><h4 id="h-dom" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">DOM结构操作</h4><ul><li><p>新增节点</p></li><li><p>获取父元素</p></li><li><p>获取子元素</p></li><li><p>删除节点</p></li></ul><pre data-type="codeBlock" text="
// 新增节点

var div1 = document.getElementById(&apos;div1&apos;)

// 添加新节点

var p1 = document.createElement(&apos;p&apos;)

p1.innerHtml = &apos;this is p1&apos;

div1.appendChild(p1) // 添加新创建元素

// 移动已有节点

var p2 = document.getElementById(&apos;p2&apos;)

div1.appendChild(p2)
"><code>
<span class="hljs-comment">// 新增节点</span>

<span class="hljs-keyword">var</span> div1 <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'div1'</span>)

<span class="hljs-comment">// 添加新节点</span>

<span class="hljs-keyword">var</span> p1 <span class="hljs-operator">=</span> document.createElement(<span class="hljs-string">'p'</span>)

p1.innerHtml <span class="hljs-operator">=</span> <span class="hljs-string">'this is p1'</span>

div1.appendChild(p1) <span class="hljs-comment">// 添加新创建元素</span>

<span class="hljs-comment">// 移动已有节点</span>

<span class="hljs-keyword">var</span> p2 <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'p2'</span>)

div1.appendChild(p2)
</code></pre><pre data-type="codeBlock" text="
// 获取父元素和子元素

var div1 = document.getElementById(&apos;div1&apos;)

var parent = div1.parentElement

var child = div1.childNodes

div1.removeChild(child[0])
"><code>
<span class="hljs-comment">// 获取父元素和子元素</span>

<span class="hljs-keyword">var</span> div1 <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'div1'</span>)

<span class="hljs-keyword">var</span> parent <span class="hljs-operator">=</span> div1.parentElement

<span class="hljs-keyword">var</span> child <span class="hljs-operator">=</span> div1.childNodes

div1.removeChild(child[<span class="hljs-number">0</span>])
</code></pre><h4 id="h-bom" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">BOM操作</h4><blockquote><p>题目</p></blockquote><ol><li><p>如何检测浏览器的类型</p></li></ol><pre data-type="codeBlock" text="
var ua = navigator.userAgent

var isChrome = ua.indexOf(&apos;Chrome&apos;)

console.log(isChrome)
"><code>
<span class="hljs-keyword">var</span> ua <span class="hljs-operator">=</span> navigator.userAgent

<span class="hljs-keyword">var</span> isChrome <span class="hljs-operator">=</span> ua.indexOf(<span class="hljs-string">'Chrome'</span>)

console.log(isChrome)
</code></pre><ol><li><p>拆解url的各部分</p></li></ol><pre data-type="codeBlock" text="
location.href

location.protocol

location.pathname

location.search

location.hash
"><code>
location.href

location.protocol

location.pathname

location.search

location.hash
</code></pre><blockquote><p>知识点</p></blockquote><ul><li><p>navigator</p></li><li><p>screen</p></li><li><p>location</p></li><li><p>history</p></li></ul><pre data-type="codeBlock" text="
// navigator

var ua = navigator.userAgent

var isChrome = ua.indexOf(&apos;Chrome&apos;)

console.log(isChrome)

// screen

console.log(screen.width)

console.log(screen.height)
"><code>
<span class="hljs-comment">// navigator</span>

<span class="hljs-keyword">var</span> ua <span class="hljs-operator">=</span> navigator.userAgent

<span class="hljs-keyword">var</span> isChrome <span class="hljs-operator">=</span> ua.indexOf(<span class="hljs-string">'Chrome'</span>)

console.log(isChrome)

<span class="hljs-comment">// screen</span>

console.log(screen.width)

console.log(screen.height)
</code></pre><pre data-type="codeBlock" text="
// location

console.log(location.href)

console.log(location.protocol) // &apos;http:&apos; &apos;https:&apos;

console.log(location.pathname) // user/api

console.log(location.search)

console.log(location.hash)

// history

history.back()

history.forward()
"><code>
<span class="hljs-comment">// location</span>

console.log(location.href)

console.log(location.protocol) <span class="hljs-comment">// 'http:' 'https:'</span>

console.log(location.pathname) <span class="hljs-comment">// user/api</span>

console.log(location.search)

console.log(location.hash)

<span class="hljs-comment">// history</span>

history.back()

history.forward()
</code></pre><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">事件</h4><blockquote><p>题目</p></blockquote><ol><li><p>编写一个通用的事件监听函数</p></li><li><p>描述事件冒泡流程</p></li><li><p>对于一个无限下拉加载图片的页面，如何给每个图片绑定事件</p></li></ol><blockquote><p>知识点</p></blockquote><ul><li><p>通用事件绑定</p></li><li><p>事件冒泡</p></li><li><p>代理</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">通用事件绑定</h4><pre data-type="codeBlock" text="
var btn = document.getElementById(&apos;btn1&apos;)

btn.addEventListener(&apos;click&apos;, function (event) {

console.log(&apos;clicked&apos;)

})

function bindEvent(elem, type, fn) {

elem.addEventListener(type, fn)

}

var a = document.getElementById(&apos;link1&apos;)

bindEvent(a, &apos;click&apos;, function(e) {

e.preventDefault() // 阻止默认行为

alert(&apos;clicked&apos;)

})
"><code>
<span class="hljs-keyword">var</span> btn <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'btn1'</span>)

btn.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"><span class="hljs-keyword">event</span></span>) </span>{

console.log(<span class="hljs-string">'clicked'</span>)

})

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bindEvent</span>(<span class="hljs-params">elem, <span class="hljs-keyword">type</span>, fn</span>) </span>{

elem.addEventListener(<span class="hljs-keyword">type</span>, fn)

}

<span class="hljs-keyword">var</span> a <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'link1'</span>)

bindEvent(a, <span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) </span>{

e.preventDefault() <span class="hljs-comment">// 阻止默认行为</span>

alert(<span class="hljs-string">'clicked'</span>)

})
</code></pre><h4 id="h-ie" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">关于IE低版本的兼容性</h4><ul><li><p>IE低版本使用 attachEvent绑定事件，和W3C标准不一样</p></li><li><p>IE低版本使用量以非常少，很多网站都早已不支持</p></li><li><p>建议对IE低版本的兼容性: 了解即可，无需深究</p></li><li><p>如果遇到对IE低版本要求苛刻的面试，果断放弃</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">代理</h4><pre data-type="codeBlock" text="
var div1 = document.getElementById(&apos;div1&apos;)

div1.addEventListener(&apos;click&apos;, function(e) {

var target = e.target

if (target.nodeName === &apos;A&apos;) {

alert(target.innerHTML)

}

})
"><code>
<span class="hljs-keyword">var</span> div1 <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'div1'</span>)

div1.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) </span>{

<span class="hljs-keyword">var</span> target <span class="hljs-operator">=</span> e.target

<span class="hljs-keyword">if</span> (target.nodeName <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">'A'</span>) {

alert(target.innerHTML)

}

})
</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="
function bindEvent(elem, type, selector, fn) {

if (fn == null) {

fn = selector

selector = null

}

elem.addEventListener(type, function (e) {

var target

if (selector) {

target = e.target

if (target.matches(selector)) {

fn.call(target, e)

}

} else {

fn(e)

}

})

}
"><code>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bindEvent</span>(<span class="hljs-params">elem, <span class="hljs-keyword">type</span>, selector, fn</span>) </span>{

<span class="hljs-keyword">if</span> (fn <span class="hljs-operator">=</span><span class="hljs-operator">=</span> null) {

fn <span class="hljs-operator">=</span> selector

selector <span class="hljs-operator">=</span> null

}

elem.addEventListener(<span class="hljs-keyword">type</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">e</span>) </span>{

<span class="hljs-keyword">var</span> target

<span class="hljs-keyword">if</span> (selector) {

target <span class="hljs-operator">=</span> e.target

<span class="hljs-keyword">if</span> (target.matches(selector)) {

fn.<span class="hljs-built_in">call</span>(target, e)

}

} <span class="hljs-keyword">else</span> {

fn(e)

}

})

}
</code></pre><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">代理的好处</h4><ul><li><p>代码简洁</p></li><li><p>减少浏览器内存占用</p></li></ul><h4 id="h-ajax" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Ajax</h4><blockquote><p>题目</p></blockquote><ol><li><p>手动编写一个ajax, 不依赖第三方库</p></li><li><p>跨域的几种实现方式</p></li></ol><blockquote><p>知识点</p></blockquote><ul><li><p>XMLHttpRequest</p></li><li><p>状态码说明</p></li><li><p>跨域</p></li></ul><h4 id="h-xmlhttprequest" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">XMLHttpRequest</h4><pre data-type="codeBlock" text="
var xhr = new XMLHttpRequest()

xhr.open(&quot;GET&quot;, &quot;/api&quot;, false)

xhr.onreadystatechange = function () {

// 这里的函数异步执行，可参考之前JS基础中的异步模块

if (xhr.readyState == 4) {

if (xhr.status == 200) {

alert(xhr.responseText)

}

}

}

xhr.send(null)
"><code>
<span class="hljs-keyword">var</span> xhr <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> XMLHttpRequest()

xhr.open(<span class="hljs-string">"GET"</span>, <span class="hljs-string">"/api"</span>, <span class="hljs-literal">false</span>)

xhr.onreadystatechange <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

<span class="hljs-comment">// 这里的函数异步执行，可参考之前JS基础中的异步模块</span>

<span class="hljs-keyword">if</span> (xhr.readyState <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">4</span>) {

<span class="hljs-keyword">if</span> (xhr.status <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">200</span>) {

alert(xhr.responseText)

}

}

}

xhr.<span class="hljs-built_in">send</span>(null)
</code></pre><h4 id="h-ie" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">IE兼容性问题</h4><ul><li><p>IE低版本使用 ActiveXObject, 和W3C标准不一样</p></li><li><p>IE低版本使用量以非常少，很多网站都早已不支持</p></li><li><p>建议对IE低版本的兼容性: 了解即可，无需深究</p></li><li><p>如果遇到对IE低版本要求苛刻的面试，果断放弃</p></li></ul><h4 id="h-readystate" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">readyState</h4><ul><li><p>0 - (未初始化)还没有调用send()方法</p></li><li><p>1 - (载入)已调用send()方法，正在发送请求</p></li><li><p>2 - (载入完成)send()方法执行完成，已经接收到全部响应内容</p></li><li><p>3 - (交互) 正在解析响应内容</p></li><li><p>4 - (完成) 响应内容解析完成，可以在客户端调用了</p></li></ul><h4 id="h-status" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">status</h4><ul><li><p>2xx - 表示成功处理请求。如200</p></li><li><p>3xx - 需要重定向，浏览器直接跳转</p></li><li><p>4xx - 客户端请求错误，如404</p></li><li><p>5xx - 服务器端错误</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>什么是跨域</p></li><li><p>JSONP</p></li><li><p>服务器端设置 http header (cors)</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>浏览器有同源策略，不允许ajax访问其他域接口</p></li><li><p>跨域条件：协议、域名、端口、有一个不同就算跨域</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>但是有三个标签允许跨域加载资源</p></li><li><br></li><li><br></li><li><br></li><li><p>三个标签的场景</p><ul><li><p>用于打点统计，统计网站可能是其他域</p></li><li><p>跨域注意事项</p><ul><li><p>所有的跨域请求都必须经过信息提供方允许</p></li><li><p>如果未经允许即可获取，那是浏览器同源策略出现漏洞</p></li></ul><h4 id="h-jsonp" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">JSONP实现原理</h4><ul><li><p>加载 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://coding.m.test.com/classindex.html">http://coding.m.test.com/classindex.html</a></p></li><li><p>不一定服务器端真正有一个 classindex.html 文件</p></li><li><p>服务器可以根据请求，动态生成一个文件，返回</p></li><li><p>同理于</p></li><li><p>存储</p><blockquote><p>题目</p></blockquote><ol><li><p>请描述一下 cookie, sessionStorage 和 localStorage的区别？</p></li></ol><ul><li><p>容量</p></li><li><p>是否会携带到ajax中</p></li><li><p>API易用性</p></li></ul><blockquote><p>知识点</p></blockquote><ul><li><p>cookie</p></li><li><p>localStorage 和 sessionStorage</p></li></ul><h4 id="h-cookie" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">cookie</h4><ul><li><p>本身用于客户端和服务器端通信</p></li><li><p>但是它有本地存储的功能，于是就被 &quot;借用&quot;</p></li><li><p>使用 document.cookie = .... 获取和修改即可</p></li></ul><h4 id="h-cookie" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">cookie 用于存储的缺点</h4><ul><li><p>存储量太小，只有4kb</p></li><li><p>所有http请求都带着，会影响获取资源的效率</p></li><li><p>API简单，需要封装才能用 document.cookie = ....</p></li></ul><h4 id="h-localstorage-sessionstorage" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">localStorage 和 sessionStorage</h4><ul><li><p>HTML5专门为存储而设计，最大容量5M</p></li><li><p>API简单易用</p></li><li><p>localStorage.setItem(key, value); localStorage.getItem(key);</p></li><li><p>IOS safari 隐藏模式下</p></li><li><p>localStorage.getItem 会报错</p></li><li><p>建议统一使用 try-catch 封装</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>面试官想通过开发环境了解面试者的经验</p></li><li><p>开发环境，最能提现工作产出的效率</p></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">模块化</h4><blockquote><p>知识点</p></blockquote><ul><li><p>不使用模块化的情况</p></li><li><p>使用模块化</p></li><li><p>AMD</p></li><li><p>CommonJS</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><blockquote><p>题目</p></blockquote><ol><li><p>从输入url到得到html的详细过程</p></li><li><p>window.onload 和 DOMContentLoaded的区别</p></li></ol><blockquote><p>知识点</p></blockquote><ul><li><p>加载资源的形式</p></li><li><p>加载一个资源的过程</p></li><li><p>浏览器渲染页面的过程</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>输入url(或跳转页面)加载html</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://coding.m.test.com">http://coding.m.test.com</a></p></li><li><p>加载html中的静态资源</p></li><li><br></li></ul><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">加载一个资源的过程</h4><ol><li><p>浏览器根据DNS服务器得到域名的IP地址</p></li><li><p>向这个IP的机器发送http 请求</p></li><li><p>服务器收到、处理并返回http 请求</p></li><li><p>浏览器得到返回内容</p></li></ol><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">浏览器渲染页面的过程</h4><p>答案</p><ol><li><p>根据HTML结构生成DOM Tree</p></li><li><p>根据CSS生成 CSSOM</p></li><li><p>将DOM和CSSOM整合形成RenderTree</p></li><li><p>根据 RenderTree 开始渲染和展示</p></li><li><p>遇到</p></li><li><p>
window.onload // 页面的全部资源加载完才会执行，包括图片、视频等

DOMContentLoaded // DOM渲染完即可执行，此时图片、视频还可能没有加载完</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">性能优化</h4><blockquote><p>原则</p></blockquote><ul><li><p>多使用内存、缓存或者其他方法</p></li><li><p>减少CPU计算、减少网络</p></li></ul><blockquote><p>从哪里入手</p></blockquote><ul><li><p>加载页面和静态资源</p></li><li><p>页面渲染</p></li></ul><blockquote><p>加载资源优化</p></blockquote><ul><li><p>静态资源的压缩合并</p></li><li><p>静态资源缓存</p></li><li><p>使用CDN让资源加载更快</p></li><li><p>使用SSR后端渲染，数据直接输出到HTML中</p></li></ul><blockquote><p>渲染优化</p></blockquote><ul><li><p>CSS放前面，JS放后面</p></li><li><p>懒加载(图片懒加载、下拉加载更多)</p></li><li><p>减少DOM查询，对DOM查询做缓存</p></li><li><p>减少DOM操作，多个操作尽量合并在一起执行</p></li><li><p>事件节流</p></li><li><p>尽早执行操作(如DOMContentLoaded)</p></li></ul><h4 id="h-dom" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">合并DOM插入</h4><pre data-type="codeBlock" text="
//合并DOM插入

var listNode = document.getElementById(&apos;list&apos;)

// 要插入10个 li 标签

var frag = document.createDocumentFragment() // 不触发DOM操作

var x, li;

for (x = 0; x &lt; 10; x++) {

li = document.createElement(&quot;li&quot;)

li.innerHTML = &quot;list item &quot; + x

frag.appendChild(li)

}

listNode.appendChild(frag) //触发DOM操作
"><code>
<span class="hljs-comment">//合并DOM插入</span>

<span class="hljs-keyword">var</span> listNode <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'list'</span>)

<span class="hljs-comment">// 要插入10个 li 标签</span>

<span class="hljs-keyword">var</span> frag <span class="hljs-operator">=</span> document.createDocumentFragment() <span class="hljs-comment">// 不触发DOM操作</span>

<span class="hljs-keyword">var</span> x, li;

<span class="hljs-keyword">for</span> (x <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; x <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">10</span>; x<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {

li <span class="hljs-operator">=</span> document.createElement(<span class="hljs-string">"li"</span>)

li.innerHTML <span class="hljs-operator">=</span> <span class="hljs-string">"list item "</span> <span class="hljs-operator">+</span> x

frag.appendChild(li)

}

listNode.appendChild(frag) <span class="hljs-comment">//触发DOM操作</span>
</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="
var textarea = document.getElementById(&apos;text&apos;)

var timeoutId

textarea.addEventListener(&apos;keyup&apos;, function () {

if (timeoutId) {

clearTimeout(timeoutId)

}

timeoutId = setTimeout(function () {

// 触发 change 事件

}, 100)

})
"><code>
<span class="hljs-keyword">var</span> textarea <span class="hljs-operator">=</span> document.getElementById(<span class="hljs-string">'text'</span>)

<span class="hljs-keyword">var</span> timeoutId

textarea.addEventListener(<span class="hljs-string">'keyup'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

<span class="hljs-keyword">if</span> (timeoutId) {

clearTimeout(timeoutId)

}

timeoutId <span class="hljs-operator">=</span> setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

<span class="hljs-comment">// 触发 change 事件</span>

}, <span class="hljs-number">100</span>)

})
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">安全性</h3><blockquote><p>知识点</p></blockquote><ul><li><p>XSS跨站请求攻击</p></li><li><p>XSRF 跨站请求伪造</p></li></ul><h4 id="h-xss" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">XSS</h4><ul><li><p>在新浪博客写一篇文章，同时偷偷插入一段</p><ul><li><p>攻击代码中，获取cookie, 发送自己的服务器</p></li><li><p>发布博客，有人查看博客内容</p></li><li><p>会把查看者的cookie发送到攻击者的服务器</p></li></ul></li><li><p>预防</p><ul><li><p>前端替换关键字，例如替换 为&gt;</p><ul><li><p>后端替换</p></li></ul></li><li><p>XSRF</p><ul><li><p>你已登录一个购物网站，正在浏览器商品</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://xn--xxx-er9dp4usp0ab8dbn6cfoi8u5a7bd.com/pay?id=100">该网站付费接口是xxx.com/pay?id=100</a> 但是没有任何验证</p></li><li><p>然后你收到一封邮件，隐藏着 src=xxx.com/pay?id=100</p></li><li><p>&gt;</p></li></ul></li><li><p>你查看邮件的时候，就悄悄付费。</p></li></ul><blockquote><p>预防</p></blockquote><ul><li><p>增加验证流程，如输入指纹、密码、短信验证码</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>GUI渲染线程</p></li><li><p>javascript引擎线程</p></li><li><p>HTTP请求线程</p></li><li><p>浏览器事件触发线程</p></li></ul><h4 id="h-api" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">异步API</h4><ul><li><p>DOM Events</p></li><li><p>XMLHttpRequest(ajax)</p></li><li><p>fetch</p></li><li><p>WebSockets</p></li><li><p>Service Worker</p></li><li><p>Timer</p></li></ul><h4 id="h-promise" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Promise</h4><ul><li><p>异步处理方案</p></li><li><p>异步数据的链式处理</p></li><li><p>异步代码同步化</p></li><li><p>catch 获取异常</p></li><li><p>then 正常结束</p></li></ul><h4 id="h-event-loop" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">event-loop</h4><blockquote><p>事件轮巡</p></blockquote><ul><li><p>异步队列</p></li></ul><h4 id="h-jquery-deferred" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">jquery-deferred</h4><ul><li><p>dtd的API可分成两类，用意不同</p></li><li><p>第一类：dtd.resolve dtd.reject</p></li><li><p>第二类：dtd.then dtd.done dtd.fail</p></li><li><p>这两类应该分开，否则后果严重</p></li><li><p>使用 返回dtd.promise() 解决随意在监听范围调用reject</p></li></ul><h4 id="h-asyncawait" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">async/await</h4><blockquote><p>ES7提案</p></blockquote><ul><li><p>then 只是将 callback拆分了</p></li><li><p>async/await 是最直接的同步写法</p></li></ul><blockquote><p>用法</p></blockquote><ul><li><p>使用 await , 函数必须用async标识</p></li><li><p>await 后面跟的是一个Promise实例</p></li><li><p>需要babel-polyfill</p></li></ul><h4 id="h-dom-virtual-dom" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">虚拟dom Virtual Dom</h4><blockquote><p>问题</p></blockquote><ul><li><p>v-dom 是什么？</p></li><li><p>v-dom 如何运用？</p></li><li><p>介绍Diff算法</p></li></ul><blockquote><p>v-dom 是什么？</p></blockquote><ul><li><p>用js模拟dom结构</p></li><li><p>如果dom有变化，对比出变化的地方，只修改这个变化的地方</p></li><li><p>提高重绘性能</p></li><li><p>JS是前端中唯一:图灵完备语言</p></li><li><p>将DOM对比操作放在JS层，提高效率</p></li></ul><blockquote><p>遇到的问题</p></blockquote><ul><li><p>DOM操作是 &quot;昂贵&quot;的，js运行效率高</p></li><li><p>尽量减少DOM操作，而不是 &quot;推倒重来&quot;</p></li><li><p>项目越复杂，影响越严重</p></li><li><p>vdom即可解决这个问题</p></li></ul><h4 id="h-snabbdom" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">snabbdom</h4><ul><li><p>实现vdom的开源库</p></li><li><p>vue2.0借用了snabbdom</p></li><li><p>h函数、patch函数</p></li><li><p>h函数返回v-node</p></li><li><p>patch函数将vnode patch到一个真实的dom容器中</p></li></ul><h4 id="h-diff" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Diff算法</h4><blockquote><p>问题</p></blockquote><ul><li><p>什么是diff算法?</p></li><li><p>vdom为何用diff算法?</p></li><li><p>diff算法的实现过程</p></li></ul><blockquote><p>vdom为何使用diff算法</p></blockquote><p>答案:</p><ul><li><p>DOM操作是 &quot;昂贵&quot;的，因此尽量减少DOM操作</p></li><li><p>找出本次DOM必须更新的节点来更新，其他的不更新</p></li><li><p>这个 &quot;找出&quot; 的过程，就需要diff算法</p></li></ul><blockquote><p>diff算法的实现过程</p></blockquote><ul><li><p>patch(container, vnode)</p></li><li><p>patch(vnode, newVnode)</p></li></ul><blockquote><p>什么是diff算法？</p></blockquote><ul><li><p>是linux的基础命令 diff git diff</p></li></ul><pre data-type="codeBlock" text="
{

tag: &apos;ul&apos;,

attrs: {

id: &apos;list&apos;

},

children: [

{

tag: &apos;li&apos;,

attrs: {

className: &apos;item&apos;

},

children: [&apos;Item 1&apos;]

}

]

}
"><code>
{

<span class="hljs-symbol">tag:</span> <span class="hljs-comment">'ul',</span>

<span class="hljs-symbol">attrs:</span> {

<span class="hljs-symbol">id:</span> <span class="hljs-comment">'list'</span>

},

<span class="hljs-symbol">children:</span> [

{

<span class="hljs-symbol">tag:</span> <span class="hljs-comment">'li',</span>

<span class="hljs-symbol">attrs:</span> {

<span class="hljs-symbol">className:</span> <span class="hljs-comment">'item'</span>

},

<span class="hljs-symbol">children:</span> [<span class="hljs-comment">'Item 1']</span>

}

]

}
</code></pre><h4 id="h-mvvm" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">MVVM</h4><blockquote><p>问题</p></blockquote><ul><li><p>如何理解MVVM？</p></li><li><p>如何实现MVVM？</p></li><li><p>是否解读过vue的源码?</p></li></ul><blockquote><p>题目</p></blockquote><ul><li><p>说一下使用jQuery和使用框架的区别？</p></li><li><p>说一下对MVVM的理解？</p></li><li><p>vue中如何实现响应式？</p></li><li><p>vue中如何解析模板？</p></li><li><p>vue的整个实现流程？</p></li></ul><blockquote><p>从jQuery到框架</p></blockquote><ul><li><p>数据和视图的分离,解耦(开放封闭原则)</p></li><li><p>以数据驱动视图，只关心数据变化，DOM操作被封装</p></li></ul><blockquote><p>如何理解 MVVM</p></blockquote><ul><li><p>MVVM - Model View ViewModel</p></li><li><p>三者之间的联系，以及如何对应到各段代码</p></li><li><p>ViewModel的理解，联系View和Model</p></li></ul><h3 id="h-vue" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">vue三要素</h3><ul><li><p>响应式：vue如何监听到data的每个属性变化？</p></li><li><p>模板引擎：vue的模板如何被解析，指令如何处理？</p></li><li><p>渲染：vue的模板如何被渲染成html?以及渲染过程</p></li></ul><h4 id="h-vue" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">vue中如何实现响应式</h4><ul><li><p>什么是响应式</p></li><li><p>Object.defineProperty</p></li><li><p>模拟</p></li></ul><blockquote><p>什么是响应式</p></blockquote><ul><li><p>修改data属性之后，vue立刻监听到</p></li><li><p>data属性被代理到vm上</p></li></ul><blockquote><p>问题解答</p></blockquote><ul><li><p>关键是理解 Object.defineProperty</p></li><li><p>将data的属性代理到vm上</p></li></ul><h4 id="h-vue" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">vue中如何解析模板</h4><ul><li><p>模板是什么</p></li><li><p>render函数</p></li><li><p>render函数与vdom</p></li></ul><blockquote><p>模板是什么？</p></blockquote><ul><li><p>本质：字符串</p></li><li><p>有逻辑，如v-if v-for等</p></li><li><p>与html格式很像，但有很大区别</p></li><li><p>最终还要转换为html来显示</p></li><li><p>模板最终必须转换成JS代码，因为：</p></li><li><p>有逻辑(v-if v-for),必须用JS才能实现(图灵完备)</p></li><li><p>转换为html渲染页面，必须用JS才能实现</p></li><li><p>因此，模板最终要转换成一个JS函数(render函数)</p></li></ul><blockquote><p>render 函数 - with的用法</p></blockquote><pre data-type="codeBlock" text="
var obj = {

name: &apos;zhangsan&apos;,

age: 20,

getAddress: function () {

alert(&apos;beijing&apos;)

}

}

function fn() {

with(obj){

console.log(name)

console.log(age)

getAddress()

}

}
"><code>
<span class="hljs-keyword">var</span> obj <span class="hljs-operator">=</span> {

name: <span class="hljs-string">'zhangsan'</span>,

age: <span class="hljs-number">20</span>,

getAddress: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

alert(<span class="hljs-string">'beijing'</span>)

}

}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fn</span>(<span class="hljs-params"></span>) </span>{

with(obj){

console.log(name)

console.log(age)

getAddress()

}

}
</code></pre><ul><li><p>从哪里可以看到render函数？</p></li><li><p>复杂一点的例子，render函数是什么样子的？</p></li><li><p>v-if v-for v-on 都是怎么处理的？</p></li><li><p>vm._c其实就是snabbvdom的h</p></li></ul><blockquote><p>解答</p></blockquote><ul><li><p>模板：字符串，有逻辑，嵌入JS变量.....</p></li><li><p>模板必须转换为JS代码(有逻辑、渲染html、JS变量)</p></li><li><p>render函数是什么样子的</p></li><li><p>render函数执行是返回vnode</p></li><li><p>updateComponent</p></li><li><p>模板中的所有信息都被render函数包含</p></li></ul><h4 id="h-vue" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">vue的整个实现流程</h4><ul><li><p>第一步：解析模板成render函数</p></li><li><p>第二步：响应式开始监听</p></li><li><p>第三步：首次渲染，显示页面，且绑定依赖</p></li></ul><ol><li><p>初次渲染，执行updateComponent,执行vm._render()</p></li><li><p>执行render函数，会访问到vm.list和vm.title</p></li><li><p>会被响应式的get方法监听到</p></li><li><p>执行updateComponent，会走到vdom的patch方法</p></li><li><p>patch将vnode渲染成DOM，初次渲染完成</p></li><li><p>为何要监听get，直接监听set不行吗？</p></li><li><p>data中有很多属性，有些被用到，有些可能不被用到</p></li><li><p>被用到的会走到get,没用到就不会走get</p></li></ol><ul><li><p>第四步：data属性变化，触发re-render</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">组件化</h3><blockquote><p>题目</p></blockquote><ul><li><p>说一下对组件化的理解</p></li><li><p>JSX本质是什么？</p></li><li><p>JSX和vdom的关系</p></li><li><p>说一下setState的过程</p></li><li><p>阐述自己对React和Vue的认识</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>组件的封装</p></li><li><p>组件的复用</p></li></ul><blockquote><p>组件的封装</p></blockquote><ul><li><p>视图</p></li><li><p>数据</p></li><li><p>变化逻辑(数据驱动视图变化)</p></li></ul><blockquote><p>组件的复用</p></blockquote><ul><li><p>props传递</p></li></ul><blockquote><p>解答</p></blockquote><ul><li><p>组件的封装: 封装视图、数据、变化逻辑</p></li><li><p>组件的复用： props传递、复用</p></li></ul><h4 id="h-jsx" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">JSX本质是什么？</h4><ul><li><p>JSX语法</p></li><li><p>JSX解析成JS</p></li><li><p>独立的标准</p></li></ul><blockquote><p>JSX语法</p></blockquote><ul><li><p>html形式</p></li><li><p>引入JS变量和表达式</p></li><li><p>if...else</p></li><li><p>循环</p></li><li><p>style和className</p></li><li><p>事件</p></li><li><p>JSX语法根本无法被浏览器所解析</p></li><li><p>那么它如何在浏览器运行？</p></li></ul><blockquote><p>JSX 解析</p></blockquote><ul><li><p>JSX其实是语法糖</p></li><li><p>开发环境会将JSX编译成JS代码</p></li><li><p>JSX的写法大大降低了学习成本和编码工作量</p></li><li><p>同时，JSX也会增加debug成本</p></li></ul><blockquote><p>JSX独立的标准</p></blockquote><ul><li><p>JSX是React引入的，但不是React独有的</p></li><li><p>React已经将它作为一个独立标准开放，其他项目也可用</p></li><li><p>React.createElement 是可以自定义修改的</p></li><li><p>说明：本身功能已经完备；和其他标准兼容和扩展性没问题</p></li><li><p>另：有机会录制&gt;,就用JSX标准</p></li><li><p>问题解答</p><ul><li><p>JSX语法(标签、JS表达式、判断、循环、事件绑定)</p></li><li><p>JSX是语法糖，需被解析成JS才能运行</p></li><li><p>JSX是独立的标准，可被其他项目使用</p></li></ul><h4 id="h-jsxvdom" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">JSX和vdom的关系</h4><ul><li><p>分析：为何需要vdom</p></li><li><p>React.createElement和h</p></li><li><p>何时patch?</p></li><li><p>自定义组件的解析</p></li></ul><blockquote><p>为何需要vdom</p></blockquote><ul><li><p>vdom 是 React初次推广开来的，结合JSX</p></li><li><p>JSX就是模板，最终要渲染成html</p></li><li><p>初次渲染 + 修改 state 后的 re-render</p></li><li><p>正好符号vdom的应用场景</p></li></ul><blockquote><p>React.createElement和h</p></blockquote><ul><li><p>初次渲染 - ReactDOM.render(,container)</p></li><li><p>会触发patch(container,vnode)</p></li><li><p>re-render - setState</p></li><li><p>会触发patch(vnode,newVnode)</p></li></ul><blockquote><p>自定义组件的解析</p></blockquote><ul><li><p>&apos;div&apos; - 直接渲染</p><p>即可,vdom可以做到</p><ul><li><p>Input和List,是自定义组件(class),vdom默认不认识</p></li><li><p>因此Input和List定义的时候必须声明render函数</p></li><li><p>根据props初始化实例，然后执行实例的render函数</p></li><li><p>render函数返回的还是vnode对象</p></li></ul></li><li><p>问题解答</p><ul><li><p>为何需要vdom: JSX需要渲染成html,数据驱动视图</p></li><li><p>React.createElement和h,都生成vnode</p></li><li><p>何时patch: ReactDOM.render(...) 和 setState</p></li><li><p>自定义组件的解析：初始化实例，然后执行render</p></li></ul><h4 id="h-react-setstate" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">说一下 React setState 的过程</h4><ul><li><p>setState的异步</p></li><li><p>vue修改属性也是异步</p></li><li><p>setState的过程</p></li></ul><blockquote><p>setState的异步</p></blockquote><pre data-type="codeBlock" text="
addTitle(title) {

const currentList = this.state.list

console.log(this.state.list) // [&apos;a&apos;, &apos;b&apos;]

this.setState({

list:currentList.concat(title) // &apos;c&apos;

})

console.log(this.state.list) // [&apos;a&apos;, &apos;b&apos;]

}
"><code>
addTitle(title) {

const currentList <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.state.list

console.log(<span class="hljs-built_in">this</span>.state.list) <span class="hljs-comment">// ['a', 'b']</span>

<span class="hljs-built_in">this</span>.setState({

list:currentList.concat(title) <span class="hljs-comment">// 'c'</span>

})

console.log(<span class="hljs-built_in">this</span>.state.list) <span class="hljs-comment">// ['a', 'b']</span>

}
</code></pre><blockquote><p>setState 为何需要异步？</p></blockquote><ul><li><p>可能会一次执行多次 setState</p></li><li><p>你无法规定、限制用户如何使用setState</p></li><li><p>没必要每次setState 都重新渲染，考虑性能</p></li><li><p>即便是每次重新渲染，用户也看不到中间的效果</p></li></ul><blockquote><p>vue修改属性也是异步</p></blockquote><ul><li><p>效果、原因和setState一样</p></li></ul><blockquote><p>setState的过程</p></blockquote><p>答案:</p><ul><li><p>每个组件实例，都有renderComponent方法</p></li><li><p>执行renderComponent会重新执行实例的render</p></li><li><p>render函数返回newVnode,然后拿到preVnode</p></li><li><p>执行patch(preVnode,newVnode)</p></li></ul><blockquote><p>问题解答</p></blockquote><ul><li><p>setState的异步：效果、原因</p></li><li><p>vue修改属性也是异步：效果、原因</p></li><li><p>setState的过程：最终走到patch(preVnode,newVnode)</p></li></ul><h4 id="h-react-vs-vue" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">React vs Vue</h4><ul><li><p>两者的本质区别</p></li><li><p>看模板和组件化的区别</p></li><li><p>两者共同点</p></li><li><p>总结问题答案</p></li></ul><blockquote><p>前言</p></blockquote><ul><li><p>文无第一武无第二，技术选型没有绝对的对与错</p></li><li><p>技术选型要考虑的因素非常多</p></li><li><p>作为面试者，要有自己的主见</p></li><li><p>和面试官的观点不一致没关系，只要能说出理由</p></li></ul><blockquote><p>两者的本质区别</p></blockquote><ul><li><p>vue - 本质是MVVM框架，由MVC发展而来</p></li><li><p>React - 本质是前端组件化框架，由后端组件化发展而来</p></li><li><p>但这并不妨碍他们两者都能实现相同的功能</p></li></ul><blockquote><p>模板的区别</p></blockquote><ul><li><p>vue - 使用模板 (最初由 angular 提出)</p></li><li><p>React - 使用JSX</p></li><li><p>模板语法上，我更加倾向于JSX</p></li><li><p>模板分离上，我更加倾向于vue</p></li></ul><blockquote><p>组件化的区别</p></blockquote><ul><li><p>React本身就是组件化，没有组件化就不是React</p></li><li><p>vue也支持组件化，不过是在MVVM上的扩展</p></li><li><p>查阅vue组件化的文档，洋洋洒洒很多(侧面反映)</p></li><li><p>对于组件化，我更加倾向于React,更加清晰</p></li></ul><blockquote><p>两者共同点</p></blockquote><ul><li><p>都支持组件化</p></li><li><p>都是数据驱动视图</p></li></ul><blockquote><p>问题解答</p></blockquote><ul><li><p>国内使用，首推vue。文档更易读、易学、社区够大</p></li><li><p>如果团队水平较高，推荐使用React。组件化和JSX</p></li></ul><h3 id="h-hybrid" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">hybrid</h3><ul><li><p>移动端占大部分流量，已经远远超过PC</p></li><li><p>一线互联网公司都有自己的App</p></li><li><p>这些App中有很大比例的前端代码(不要惊讶)</p></li><li><p>拿微信举例，你每天浏览微信的内容，多少是前端？</p></li></ul><blockquote><p>题目</p></blockquote><ul><li><p>hybrid是什么，为何用hybrid？</p></li><li><p>介绍一下hybrid更新和上线的流程？</p></li><li><p>hybrid和h5的主要区别</p></li><li><p>前端JS和客户端如何通讯？</p></li></ul><h4 id="h-hybridhybrid" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">hybrid是什么，为何会用hybrid?</h4><ul><li><p>hybrid 文字解释</p></li><li><p>存在价值，为何会用hybrid</p></li><li><p>webview</p></li><li><p>file:// 协议</p></li><li><p>hybrid实现流程</p></li></ul><blockquote><p>hybrid 文字解释</p></blockquote><ul><li><p>hybrid 即 &quot;混合&quot;，即前端和客户端的混合开发</p></li><li><p>需前端开发人员和客户端开发人员配合完成</p></li><li><p>某些环节也可能涉及到server端</p></li><li><p>PS:不要以为自己是前端就可以不理会客户端的知识</p></li></ul><blockquote><p>hybrid存在价值</p></blockquote><ul><li><p>可以快速迭代更新「关键」(无需app审核，思考为何？)</p></li><li><p>体验流畅(和NA的体验基本类似)</p></li><li><p>减少开发和沟通成本，双端公用一套代码</p></li></ul><blockquote><p>webview</p></blockquote><ul><li><p>是app中的一个组件(app 可以有webview,也可以没有)</p></li><li><p>用于加载H5页面，即一个小型的浏览器内核</p></li></ul><blockquote><p>file:// 协议</p></blockquote><ul><li><p>其实在一开始接触html开发，就已经使用了file协议</p></li><li><p>只不过你当时没有 &quot;协议&quot; &quot;标准&quot;等这些概念</p></li><li><p>再次强调 &quot;协议&quot; &quot;标准&quot;的重要性！！！</p></li><li><p>file协议: 本地文件，快</p></li><li><p>http(s)协议: 网络加载，慢</p></li></ul><blockquote><p>hrbrid 具体实现</p></blockquote><ul><li><p>不是所有场景都适合使用hybrid:</p></li><li><p>使用NA: 体验要求极致，变化不频繁 (如头条的首页)</p></li><li><p>使用hybrid: 体验要求高，变化频繁 (如头条的新闻详情页)</p></li><li><p>使用h5: 体验无要求， 不常用 (如举报、反馈等页面)</p></li></ul><blockquote><p>实现</p></blockquote><ul><li><p>前端做好静态页面 (html js css), 将文件交给客户端</p></li><li><p>客户端拿到前端静态页面，以文件形式存储在app中</p></li><li><p>客户端在一个webview中，通过file协议加载前端文件(html js css)</p></li></ul><blockquote><p>具体实现 - 遗留问题</p></blockquote><ul><li><p>app发布之后，静态文件如何实时更新？</p></li><li><p>静态页面如何获取内容？</p></li></ul><blockquote><p>问题解答</p></blockquote><ul><li><p>hybrid 是客户端和前端的混合开发</p></li><li><p>hybrid 存在的核心意义在于快速迭代，无需审核</p></li><li><p>hybrid实现流程(图)，以及webview和file协议</p></li></ul><h4 id="h-hybrid" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">hybrid 更新上线流程</h4><blockquote><p>思考 (目的，可行途径)</p></blockquote><ul><li><p>要替换每个客户端的静态文件</p></li><li><p>只能客户端来做 (客户端是我们开发的)</p></li><li><p>客户端去server下载最新的静态文件</p></li><li><p>我们维护server的静态文件</p></li></ul><blockquote><p>完整流程</p></blockquote><ul><li><p>分版本，有版本号， 如201803211015</p></li><li><p>将静态文件压缩成zip包，上传到服务端</p></li><li><p>客户端每次启动，都去服务端检查版本号</p></li><li><p>如果服务端版本号大于客户端版本号，就去下载最新的zip包</p></li><li><p>下载完之后解压包，然后将现有文件覆盖</p></li></ul><blockquote><p>问题解答</p></blockquote><ul><li><p>掌握流程图</p></li><li><p>要点1：服务端的版本和zip包维护</p></li><li><p>要点2：更新zip包之前，先对比版本号</p></li><li><p>要点3：zip下载解压和覆盖</p></li></ul><h4 id="h-hybridh5" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">hybrid和h5的比较</h4><blockquote><p>优点</p></blockquote><ul><li><p>体验更好，跟NA体验基本一致</p></li><li><p>可快速迭代，无需审核</p></li></ul><blockquote><p>缺点</p></blockquote><ul><li><p>开发成本高。联调、测试、查bug都比较麻烦</p></li><li><p>运维成本高。参考此前讲过的更新上线的流程</p></li></ul><blockquote><p>适用的场景</p></blockquote><ul><li><p>hybrid: 产品的稳定功能，体验要求高，迭代频繁</p></li><li><p>h5: 单次的运营活动(如 xx 红包) 或不常用功能</p></li></ul><blockquote><p>问题解答</p></blockquote><ul><li><p>优点：体验好，可快速迭代</p></li><li><p>缺点：开发成本高，运维成本高</p></li><li><p>适用的场景：hybrid适合产品型，h5适合运营型</p></li></ul><h4 id="h-js" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">JS和客户端通讯</h4><ul><li><p>回顾之前遗留的问题</p></li><li><p>JS和客户端通讯的基本形式</p></li><li><p>schema协议简介和使用</p></li><li><p>schema使用的封装</p></li><li><p>内置上线</p></li></ul><blockquote><p>之前遗留的问题</p></blockquote><ul><li><p>新闻详情页适用hybrid,前端如何获取新闻内容？</p></li><li><p>不能用ajax获取。第一 跨域，第二 速度慢</p></li><li><p>客户端获取新闻内容，然后JS通讯拿到内容，再渲染</p></li></ul><blockquote><p>JS和客户端通讯的基本形式</p></blockquote><ul><li><p>JS访问客户端能力，传递参数和回调函数</p></li><li><p>客户端通过回调函数返回内容</p></li></ul><h4 id="h-schema" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">schema 协议简介和使用</h4><ul><li><p>之前介绍了http(s) 和 file协议</p></li><li><p>schema协议 - 前端和客户端通讯的约定</p></li></ul><pre data-type="codeBlock" text="
/* 网上搜的微信的部分 schema 协议 */

weixin://dl/scan 扫一扫

weixin://dl/feedback 反馈

weixin://dl/moments 朋友圈

weixin://dl/settings 设置
"><code>
/* 网上搜的微信的部分 schema 协议 *<span class="hljs-regexp">/

weixin:/</span><span class="hljs-regexp">/dl/s</span>can 扫一扫

weixin:<span class="hljs-regexp">//dl</span><span class="hljs-regexp">/feedback 反馈

weixin:/</span><span class="hljs-regexp">/dl/m</span>oments 朋友圈

weixin:<span class="hljs-regexp">//dl</span>/settings 设置
</code></pre><blockquote><p>内置上线</p></blockquote><ul><li><p>将以上封装的代码打包，叫做invoke.js 内置到客户端</p></li><li><p>客户端每次启动webview,都默认执行invoke.js</p></li><li><p>本地加载，免去网络加载的时间，更快。</p></li><li><p>本地加载，没有网络请求，黑客看不到schema协议，更安全</p></li></ul><blockquote><p>问题解答</p></blockquote><ul><li><p>通讯的基本形式：调用能力，传递参数，监听回调</p></li><li><p>对schema协议的理解和使用</p></li><li><p>调用schema代码的封装</p></li><li><p>内置上线的好处：更快、更安全</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">如何热爱编程？</h3><ul><li><p>热爱！</p></li><li><p>怎么证明？</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>看书</p></li><li><p>写博客</p></li><li><p>做开源</p></li></ul><blockquote><p>看书 - 手下不离书</p></blockquote><ul><li><p>构建知识体系的最好方式</p></li><li><p>自己买书，不要借书</p></li><li><p>看书有技巧</p></li></ul><blockquote><p>博客 - 合格程序员的必备</p></blockquote><blockquote><p>开源 - github 的 star 是硬通货</p></blockquote></li></ul></li></ul></li></ul></li></ol></li></ul></li></ul></li></ul></li></ul></li></ol></li></ol>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[OpenSSL双向证书]]></title>
            <link>https://paragraph.com/@marktang-s-blog/openssl</link>
            <guid>FiGoMD6efDcw8Ucjj4P1</guid>
            <pubDate>Fri, 26 Aug 2022 05:44:28 GMT</pubDate>
            <description><![CDATA[时间戳鉴权加密方式加密密钥: 123456 (存在本地加密) 加密方式: AES 模式: CBC 填充补全码: Pkcs7 密文编码: Base64 iv偏移量: 123456 (跟密钥相同) token请求头字段名: Access-Token加密流程获取服务器当前时间时间戳时间戳转成字节数组分组密钥获取密钥块长度，利用需要加密的内容的字节数组+密钥块长度 Pkcs7方式生成补全码利用 分组密钥+iv偏移量 创建CBC加密模式创建补全码长度的 字节数组用于待会儿保存加密后字节数组利用补全码进行CBC加密，加密字节保存到上方创建的字节数组中对字节数组密文进行base64编码，将该字符串存入请求头(Headers)的Access-Token中例:生成根证书私钥 root-key.key生成根证书请求文件 根据root-key.key -> root-req.csr自签根证书 根据root-req.csr、root-key.key -> root-cert.cer (设置过期时间365)生成p12格式根证书 根据root-cert.cer、root-key.key -> root.p1...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">时间戳鉴权加密方式</h2><p>加密密钥: 123456 (存在本地加密)</p><p>加密方式: AES</p><p>模式: CBC</p><p>填充补全码: Pkcs7</p><p>密文编码: Base64</p><p>iv偏移量: 123456 (跟密钥相同)</p><p>token请求头字段名: Access-Token</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">加密流程</h4><ol><li><p>获取服务器当前时间时间戳</p></li><li><p>时间戳转成字节数组</p></li><li><p>分组密钥</p></li><li><p>获取密钥块长度，利用需要加密的内容的字节数组+密钥块长度 Pkcs7方式生成补全码</p></li><li><p>利用 分组密钥+iv偏移量 创建CBC加密模式</p></li><li><p>创建补全码长度的 字节数组用于待会儿保存加密后字节数组</p></li><li><p>利用补全码进行CBC加密，加密字节保存到上方创建的字节数组中</p></li><li><p>对字节数组密文进行base64编码，将该字符串存入请求头(Headers)的Access-Token中</p></li></ol><p><strong>例:</strong></p><ol><li><p>生成根证书私钥 root-key.key</p></li><li><p>生成根证书请求文件 根据root-key.key -&gt; root-req.csr</p></li><li><p>自签根证书 根据root-req.csr、root-key.key -&gt; root-cert.cer (设置过期时间365)</p></li><li><p>生成p12格式根证书 根据root-cert.cer、root-key.key -&gt; root.p12</p></li><li><p>生成服务端证书私钥 server-key.key</p></li><li><p>生成服务端证书请求文件 根据server-key.key -&gt; server-req.csr (绑定服务端域名)</p></li><li><p>生成服务端证书 (root证书(root-cert.cer)、rootKey(root-key.key)、服务端Key(server-key.key)、服务端请求文件(server-req.csr)，用这4个文件生成服务端证书) -&gt; server-cert.cer</p></li><li><p>生成客户端证书私钥 client-key.key</p></li><li><p>生成客户端证书请求文件 根据client-key.key -&gt; client-req.csr</p></li><li><p>生成客户端证书 (root证书(root-cert.cer)、rootKey(root-key.key)、客户端Key(client-key.key)、客户端请求文件(client-req.csr), 用这4个文件生成客户端证书) -&gt; client-cert.cer</p></li><li><p>生成客户端p12格式根证书 根据client-cert.cer、client-key.key -&gt; client.p12</p></li></ol>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[Vim Notes]]></title>
            <link>https://paragraph.com/@marktang-s-blog/vim-notes</link>
            <guid>6ukT3j5Wiv2LHzV4JKXR</guid>
            <pubDate>Fri, 26 Aug 2022 03:46:25 GMT</pubDate>
            <description><![CDATA[Vim Notes80C33DEC-ECA0-4EE2-BAE0-F398E6C464E1.webp目前写代码的主力编辑器是NeoVim和SpaceMacs，VsCode也主要是用Vim插件。整理的命令做一下记录，方便查漏补缺基本命令行命令":q!": 退出vim":wq": 存盘退出":s": 执行替换":!": 执行外部命令":edit"(缩写":e"): 编辑文件":w": 写文件":r": 读文件":help": 查看帮助使用Ctrl-D 和 Tab (选项卡) 来进行命令行补全30347DF5-DC14-42EC-B4D3-E7CEA151F860.png0CBC1FD3-4AEF-4491-A09E-B899655A27E0.pnga i w搭配使用演示6E365975-F9BA-4EC4-98F6-EEB7E8C53C03.pngE66DFAE7-439D-4A55-8F78-0D98B7C98DFC.pngForward Backward (落后) (落后) 半翻页 返回到上一个位置 前进到下一个位置 zz 将当前光标画面移到屏幕中央 数字+G 跳转到指定行 数字...]]></description>
            <content:encoded><![CDATA[<h1 id="h-vim-notes" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Vim Notes</h1><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fdc85a95077a4000056383b773aaf82b838110497cb10c5b9925859dec599498.webp" alt="80C33DEC-ECA0-4EE2-BAE0-F398E6C464E1.webp" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">80C33DEC-ECA0-4EE2-BAE0-F398E6C464E1.webp</figcaption></figure><blockquote><p>目前写代码的主力编辑器是NeoVim和SpaceMacs，VsCode也主要是用Vim插件。整理的命令做一下记录，方便查漏补缺</p></blockquote><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">基本命令行命令</h2><ul><li><p>&quot;:q!&quot;: 退出vim</p></li><li><p>&quot;:wq&quot;: 存盘退出</p></li><li><p>&quot;:s&quot;: 执行替换</p></li><li><p>&quot;:!&quot;: 执行外部命令</p></li><li><p>&quot;:edit&quot;(缩写&quot;:e&quot;): 编辑文件</p></li><li><p>&quot;:w&quot;: 写文件</p></li><li><p>&quot;:r&quot;: 读文件</p></li><li><p>&quot;:help&quot;: 查看帮助</p></li><li><p>使用Ctrl-D 和 Tab (选项卡) 来进行命令行补全</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bb3a22f67e03b91eac095dae8a81b84df7fe95d8de574044b280466ca34e3131.png" alt="30347DF5-DC14-42EC-B4D3-E7CEA151F860.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">30347DF5-DC14-42EC-B4D3-E7CEA151F860.png</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a06c2ac322b336984f3f02accfecfe85b4121738f59f1509697299aaca496ab0.png" alt="0CBC1FD3-4AEF-4491-A09E-B899655A27E0.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">0CBC1FD3-4AEF-4491-A09E-B899655A27E0.png</figcaption></figure><h3 id="h-a-i-w" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">a i w搭配使用演示</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/87b5570f7d1e549574e00c00155f70bba0b22976330350b506440e744b1983ea.png" alt="6E365975-F9BA-4EC4-98F6-EEB7E8C53C03.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">6E365975-F9BA-4EC4-98F6-EEB7E8C53C03.png</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d7f11d00b2d050ecad29ee3d1928018809870acd58df3639e242f30e673eb9f2.png" alt="E66DFAE7-439D-4A55-8F78-0D98B7C98DFC.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">E66DFAE7-439D-4A55-8F78-0D98B7C98DFC.png</figcaption></figure><p>Forward                                      Backward           (落后)                             (落后)                  
半翻页 返回到上一个位置 前进到下一个位置 zz 将当前光标画面移到屏幕中央 数字+G 跳转到指定行 数字+| 跳转到指定列 H(High) 移动到当前屏幕的顶部位置 M(middle) 移动到当前屏幕的中部位置 L(low) 移动到当前屏幕的底部位置 移动屏幕，不移动光标 zt、zz、zb 移动当前行到顶部、中部、底部 05C5F9D0-2703-4017-BA14-5D9FEDC23A4A.png VIM删除操作大全 :%s/r//g                    删除DOS方式的回车^M 
:%s= *$==                   删除行尾空白 
​
:%s/^(.*)n1/1$/               删除重复行 
​
:%s/^.{-}pdf/new.pdf/         只是删除第一个pdf 
​
:%s/&lt;!--_.{-}--&gt;//           又是删除多行注释（咦？为什么要说「又」呢？） 
​
:g/s*^$/d                    删除所有空行 ：这个好用有没有人用过还有其他的方法吗？
:g!/^dd/d                     删除不含字符串&apos;dd&apos;的行 
:v/^dd/d                     同上 （译释：v ==&amp;nbspg!，就是不匹配！） 
​
:g/str1/,/str2/d              删除所有第一个含str1到第一个含str2之间的行 
​
​
:v/./.,/./-1join                压缩空行 
:g/^$/,/./-j                  压缩空行 
​
ndw 或&amp;nbspndW              删除光标处开始及其后的&amp;nbspn-1 个字符。 
d0                          删至行首。 
d$                          删至行尾。 
ndd                         删除当前行及其后&amp;nbspn-1 行。 
x 或&amp;nbspX                  删除一个字符。 
Ctrl+u                      删除输入方式下所输入的文本。 
^R                          恢复u的操作 
J                           把下一行合并到当前行尾 
V                           选择一行 
^V                          按下^V后即可进行矩形的选择了 
aw                          选择单词 
iw                          内部单词(无空格) 
as                          选择句子 
is                          选择句子(无空格) 
ap                          选择段落 
ip                          选择段落(无空格) 
D                           删除到行尾 
x,y                         删除与复制包含高亮区 
​
dl                          删除当前字符（与x命令功能相同） 
d0                          删除到某一行的开始位置 
d^                          删除到某一行的第一个字符位置（不包括空格或TAB字符） 
dw                          删除到某个单词的结尾位置 
d3w                         删除到第三个单词的结尾位置 
db                          删除到某个单词的开始位置 
dW                          删除到某个以空格作为分隔符的单词的结尾位置 
dB                          删除到某个以空格作为分隔符的单词的开始位置 
d7B                         删除到前面7个以空格作为分隔符的单词的开始位置 
d）                         删除到某个语句的结尾位置 
d4）                        删除到第四个语句的结尾位置 
d（                         删除到某个语句的开始位置 
d）                         删除到某个段落的结尾位置 
d{                          删除到某个段落的开始位置 
d7{                         删除到当前段落起始位置之前的第7个段落位置 
dd                         删除当前行 
d/text                     删除从文本中出现“text”中所指定字样的位置， 一直向前直到下一个该字样所出现的位置（但不包括该字样）之间的内容 
dfc                        删除从文本中出现字符“c”的位置，一直向前直到下一个该字符所出现的位置（包括该字符）之间的内容 
dtc                        删除当前行直到下一个字符“c”所出现位置之间的内容 
D                          删除到某一行的结尾 
d$                         删除到某一行的结尾 
5dd                        删除从当前行所开始的5行内容 
dL                         删除直到屏幕上最后一行的内容 
dH                         删除直到屏幕上第一行的内容 
dG                         删除直到工作缓存区结尾的内容 
d1G                        删除直到工作缓存区开始的内容  
多文件操作 13F418FB-98CC-407F-BFED-85A401BD2184 31F41D6E-8CA5-482C-B3B5-8D9172AFF54B 缓冲区 1BA24765-CA60-460E-AA5B-9D2C4FF14863 多窗口编辑 3955D74D-F4C8-4DC8-A905-C98E64D2CC0A 双窗口比较 788B4619-C3CA-44E5-ABBF-933C6CF07E0C 多标签页 952CA83A-5243-477D-9B3B-BB47DC6A9C3A 6A6B0536-C2B1-4226-B221-4AAEF07FCBF9 NERDTree F403123E-0A03-4F39-9593-88CE719D4D60 正则表达式搜索 B4BBDF1B-B711-4045-960B-7C8B525FB70E F90DBE74-28D8-4098-83C0-31D4BD10CD66 DD1D8DA3-9560-4A51-A8F6-4D2F0867639A</p>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
        <item>
            <title><![CDATA[Mac安装Rust]]></title>
            <link>https://paragraph.com/@marktang-s-blog/mac-rust</link>
            <guid>rbWF8vYqCDUCQmrxblwg</guid>
            <pubDate>Thu, 25 Aug 2022 09:29:38 GMT</pubDate>
            <description><![CDATA[Mac安装Rust最近被周围人安利Rust比较有趣，想玩儿一玩儿，去官网看了一下环境的安装方法image-20200717203801007一行命令就搞定了curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 由于众所周知的原因，网络情况不好的话，可能会一直卡在这个画面20分钟都没什么反应(可以试一下手机热点，一般能成功)image-20200717204141592然后一种安装方式是用brew来安装，几乎都能成功然后试了一下HelloWorld，完全没问题，可以很好运行。但是有一个问题，这样安装的话是不带 rustup的 后来找到了一个方式，brew 安装，也带rustupbrew install rustup-init // 安装完成后执行 rustup-init 大功告成!]]></description>
            <content:encoded><![CDATA[<h2 id="h-macrust" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Mac安装Rust</h2><p>最近被周围人安利Rust比较有趣，想玩儿一玩儿，去官网看了一下环境的安装方法</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/dca0ea4d97e336b7c1e9ccd973bf74d62ba250efba30aff19ae5b12ba8e7e396.jpg" alt="image-20200717203801007" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image-20200717203801007</figcaption></figure><p>一行命令就搞定了</p><pre data-type="codeBlock" text="curl --proto &apos;=https&apos; --tlsv1.2 -sSf https://sh.rustup.rs | sh
"><code>curl <span class="hljs-operator">-</span><span class="hljs-operator">-</span>proto <span class="hljs-string">'=https'</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span>tlsv1<span class="hljs-number">.2</span> <span class="hljs-operator">-</span>sSf https:<span class="hljs-comment">//sh.rustup.rs | sh</span>
</code></pre><p>由于众所周知的原因，网络情况不好的话，可能会一直卡在这个画面20分钟都没什么反应(可以试一下手机热点，一般能成功)</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/90c4dd935115a940a3e3503aa0c77a6e09c39d93175b0d7668a44b79e4f60f2f.jpg" alt="image-20200717204141592" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image-20200717204141592</figcaption></figure><p>然后一种安装方式是用brew来安装，几乎都能成功</p><pre data-type="codeBlock" text="brew install rust
"><code></code></pre><p>然后试了一下HelloWorld，完全没问题，可以很好运行。但是有一个问题，这样安装的话是不带 rustup的</p><p>后来找到了一个方式，brew 安装，也带rustup</p><pre data-type="codeBlock" text="brew install rustup-init
// 安装完成后执行
rustup-init
"><code>brew install rustup-<span class="hljs-keyword">init</span>
<span class="hljs-comment">// 安装完成后执行</span>
rustup-<span class="hljs-keyword">init</span>
</code></pre><p>大功告成!</p>]]></content:encoded>
            <author>marktang-s-blog@newsletter.paragraph.com (MarkTang's Blog)</author>
        </item>
    </channel>
</rss>