Files
DefectingCat.github.io/defect/javascript-iterable-object-and-for-of.html
DefectingCat 8c7085f18f
2020-11-02 02:17:54 +00:00

160 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html><html lang="zh-CN" data-default-color-scheme="&#34;auto&#34;"><head><meta charset="UTF-8"><link rel="apple-touch-icon" sizes="76x76" href="/images/img/apple-touch-icon.webp"><link rel="icon" type="image/png" href="/images/img/favicon.webp"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no"><meta http-equiv="x-ua-compatible" content="ie=edge"><meta name="theme-color" content="#9DC8C8"><meta name="description" content=""><meta name="author" content="Defectink"><meta name="keywords" content=""><title>JavaScript-可迭代对象与for-of - 🍭Defectink</title><link rel="stylesheet" href="https://cdn.defectink.com/static/twitter-bootstrap/4.5.3/css/bootstrap.min.css"><link rel="stylesheet" href="https://cdn.defectink.com/static/github-markdown-css/4.0.0/github-markdown.min.css"><link rel="stylesheet" href="/lib/hint/hint.min.css"><link rel="stylesheet" href="https://cdn.defectink.com/static/highlight.js/10.0.0/styles/github-gist.min.css"><link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_ba1fz6golrf.css"><link rel="stylesheet" href="https://cdn.defectink.com/static/t/font_1736178_kmeydafke9r.css"><link rel="stylesheet" href="/css/main.css"><link rel="stylesheet" href="/css/xfy.css"><script src="/js/utils.js"></script><script src="/js/color-schema.js"></script><meta name="generator" content="Hexo 5.2.0"><link rel="alternate" href="/xml/atom.xml" title="🍭Defectink" type="application/atom+xml"><link rel="alternate" href="/xml/rss.xml" title="🍭Defectink" type="application/rss+xml"></head><body><header style="height:75vh"><nav id="navbar" class="navbar fixed-top navbar-expand-lg navbar-dark scrolling-navbar"><div class="container"><a class="navbar-brand" href="/">&nbsp;<strong>🍭Defectink</strong>&nbsp;</a> <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><div class="animated-icon"><span></span><span></span><span></span></div></button><div class="collapse navbar-collapse" id="navbarSupportedContent"><ul class="navbar-nav ml-auto text-center"><li class="nav-item"><a class="nav-link" href="/">🏠 首页</a></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">📕 索引</a><div class="dropdown-menu" aria-labelledby="navbarDropdown"><a class="dropdown-item" href="/categories/">🎁 分类</a> <a class="dropdown-item" href="/tags/">🎐 标签</a></div></li><li class="nav-item"><a class="nav-link" href="/archives/">📂 归档</a></li><li class="nav-item"><a class="nav-link" href="/about/">🎃 关于</a></li><li class="nav-item"><a class="nav-link" href="/links/">🙆‍♀️ 小伙伴</a></li><li class="nav-item"><a class="nav-link" href="/pgp/">🔐 PGP</a></li><li class="nav-item" id="search-btn"><a class="nav-link" data-toggle="modal" data-target="#modalSearch">&nbsp;<i class="iconfont icon-search"></i>&nbsp;</a></li><li class="nav-item" id="color-toggle-btn"><a class="nav-link" href="javascript:">&nbsp;<i class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a></li></ul></div></div></nav><div class="banner intro-2" id="background" parallax="true" style="background:url(/images/img/post.webp) no-repeat center center;background-size:cover"><div class="full-bg-img"><div class="mask flex-center" style="background-color:rgba(0,0,0,.3)"><div class="container page-header text-center fade-in-up"><span class="h2" id="subtitle"></span><div class="mt-3"><span class="post-meta mr-2"><i class="iconfont icon-author" aria-hidden="true"></i> Defectink</span><span class="post-meta"><i class="iconfont icon-date-fill" aria-hidden="true"></i> <time datetime="2020-10-29 17:15" pubdate>2020年10月29日 下午</time></span></div><div class="mt-1"><span class="post-meta mr-2"><i class="iconfont icon-chart"></i> 3.5k 字</span><span class="post-meta mr-2"><i class="iconfont icon-clock-fill"></i> 41 分钟</span></div></div></div></div></div></header><main><div class="container-fluid"><div class="row"><div class="d-none d-lg-block col-lg-2"></div><div class="col-lg-8 nopadding-md"><div class="container nopadding-md" id="board-ctn"><div class="py-5" id="board"><article class="post-content mx-auto" id="post"><h1 style="display:none">JavaScript-可迭代对象与for-of</h1><p class="note note-info">本文最后水于2020年11月2日 凌晨</p><div class="markdown-body" id="post-body"><h2 id="Iterable-object可迭代对象"><a href="#Iterable-object可迭代对象" class="headerlink" title="Iterable object可迭代对象"></a>Iterable object可迭代对象</h2><p>可迭代Iterable 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在<code>for...of</code>循环中使用的对象。数组是可迭代的。但不仅仅是数组,很多其他的内建对象也是可迭代的。例如字符串就是可迭代的。</p><h2 id="总最早开始"><a href="#总最早开始" class="headerlink" title="总最早开始"></a>总最早开始</h2><p>可能十年前或者更加久远的年代,我们遍历一个数组需要这样:</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> arr = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; arr.length; i++) &#123;
<span class="hljs-built_in">console</span>.log(arr[i]);
&#125;</code></pre><p>或许也不是很久在我最初学习js的时候就是这样去尝试理解for循环的。</p><p>后来我们发现这样写或许太复杂了,于是有了<code>for...in</code>。我们遍历一个数组就变成了这样:</p><pre><code class="hljs js"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i <span class="hljs-keyword">in</span> arr) &#123;
<span class="hljs-built_in">console</span>.log(arr[i]);
&#125;</code></pre><p>是不是和for循环有点类似<code>for...in</code>便是循环遍历对象的一个方式。</p><p>ES6也给了我们一个专门操作遍历数组的方法<code>forEach()</code></p><pre><code class="hljs js">arr.forEach(<span class="hljs-function"><span class="hljs-params">element</span> =&gt;</span> &#123;
<span class="hljs-built_in">console</span>.log(element);
&#125;);</code></pre><p>与其他的方法不同的是,<code>forEach()</code>同数组的<code>push()</code><code>pop()</code>等方法一样是在Array对象的原型上的也就是<code>Array.prototype.forEach()</code>。并且它除了抛出异常以外,没有办法中止或跳出<code>forEach()</code>循环。如果我们需要中止或跳出循环<code>forEach()</code>方法不是应当使用的工具。</p><h3 id="弥补不足"><a href="#弥补不足" class="headerlink" title="弥补不足"></a>弥补不足</h3><p>我们有多种可以轻松遍历数组的方法,不过他们各有各的不足之处。<code>for...of</code>便是代替<code>for...in</code>来循环数组而诞生的。</p><p>首先来看看<code>for...in</code>对数组的小问题:</p><ol><li><code>for...in</code>是为对象设计的它遍历的是key而不是value。</li><li><code>for...in</code>会一直查找可枚举的属性,直至原型链顶端。</li></ol><p>先看第一条,<code>for...in</code>和直接for循环遍历数组类似他们循环的是数组的key需要使用数组的标准访问写法才能得到值。</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> arr = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i <span class="hljs-keyword">in</span> arr) &#123;
<span class="hljs-built_in">console</span>.log(arr[i]);
<span class="hljs-comment">// 1, 2, 3, ,4, 5</span>
&#125;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i <span class="hljs-keyword">in</span> arr) &#123;
<span class="hljs-built_in">console</span>.log(i);
<span class="hljs-comment">// 0, 1, 2, 3, ,4</span>
&#125;</code></pre><p>不过这看上去无伤大雅,第二条的问题就不像这么温柔了。在当前数组的原型链上的所有的可枚举的属性都会被遍历出来。</p><pre><code class="hljs js"><span class="hljs-built_in">Array</span>.prototype.arrTest = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>(<span class="hljs-params"></span>) </span>&#123;&#125;;
<span class="hljs-built_in">Object</span>.defineProperty(<span class="hljs-built_in">Array</span>.prototype, <span class="hljs-string">&#x27;push&#x27;</span>, &#123;
enumerable: <span class="hljs-literal">true</span>
&#125;)
<span class="hljs-keyword">let</span> arr = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i <span class="hljs-keyword">in</span> arr) &#123;
<span class="hljs-built_in">console</span>.log(arr[i]);
&#125;</code></pre><p>无论是我们自定义的函数,还是修改属性为可枚举,<code>for...in</code>一条都不会放过。</p><p><img src="../images/JavaScript-%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1%E4%B8%8Efor-of/2020-10-27-15-01-39.webp" srcset="/images/img/loading.gif"></p><h2 id="迭代协议"><a href="#迭代协议" class="headerlink" title="迭代协议"></a>迭代协议</h2><p>通常的对象是不可迭代的,它不是数组。通过自己创建一个不可迭代的对象,我们就可以轻松地掌握可迭代的概念。</p><p>首先来看一个最基本的对象,我们尝试使用<code>for...of</code>去遍历它,会得到一个其不是可迭代对象的是错误:</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> obj = &#123;
start: <span class="hljs-number">1</span>,
end: <span class="hljs-number">5</span>
&#125;;
<span class="hljs-keyword">for</span> (num <span class="hljs-keyword">of</span> obj) &#123;
<span class="hljs-built_in">console</span>.log(num);
&#125;</code></pre><pre><code class="hljs angelscript">VM155:<span class="hljs-number">6</span> Uncaught TypeError: obj <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> iterable</code></pre><p>这是因为我们的Object对象不是可迭代的对象。而迭代协议可以使其成为一个可迭代的对象。</p><p>迭代协议作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。</p><h3 id="迭代器"><a href="#迭代器" class="headerlink" title="迭代器"></a>迭代器</h3><p>为了让<code>obj</code>对象可迭代(也就让<code>for..of</code>可以运行)我们需要为对象添加一个名为<code>Symbol.iterator</code>的方法一个专门用于使对象可迭代的内置symbol</p><ol><li><code>for..of</code> 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 <strong>迭代器iterator</strong> —— 一个有 <code>next</code> 方法的对象。</li><li>从此开始,<code>for..of</code> <strong>仅适用于这个被返回的对象</strong></li><li><code>for..of</code> 循环希望取得下一个数值,它就调用这个对象的 <code>next()</code> 方法。</li><li><code>next()</code> 方法返回的结果的格式必须是 <code>&#123;done: Boolean, value: any&#125;</code>,当 <code>done=true</code> 时,表示迭代结束,否则 <code>value</code> 是下一个值。</li></ol><pre><code class="hljs js"><span class="hljs-keyword">let</span> obj = &#123;
start: <span class="hljs-number">1</span>,
end: <span class="hljs-number">5</span>
&#125;;
<span class="hljs-comment">// for..of 调用首先会调用这个:</span>
obj[<span class="hljs-built_in">Symbol</span>.iterator] = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>&#123;
<span class="hljs-keyword">return</span> &#123;
<span class="hljs-comment">// 这个function还是属于obj所以this指向obj。</span>
<span class="hljs-comment">//接下来for..of 仅与此迭代器一起工作,要求它提供下一个值</span>
current: <span class="hljs-built_in">this</span>.start,
last: <span class="hljs-built_in">this</span>.end,
<span class="hljs-comment">// next() 在 for..of 的每一轮循环迭代中被调用</span>
<span class="hljs-comment">// 所以通常next都带有一个判断语句</span>
<span class="hljs-function"><span class="hljs-title">next</span>(<span class="hljs-params"></span>)</span> &#123;
<span class="hljs-comment">// Symbol.iterator返回的是一个对象this不会多级指向所以这里用到了刚刚定义的属性</span>
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.current &lt;= <span class="hljs-built_in">this</span>.last) &#123;
<span class="hljs-keyword">return</span> &#123;
value: <span class="hljs-built_in">this</span>.current++,
done: <span class="hljs-literal">false</span>
&#125;
&#125; <span class="hljs-keyword">else</span> &#123;
<span class="hljs-keyword">return</span> &#123;
done: <span class="hljs-literal">true</span>
&#125;
&#125;
&#125;
&#125;
&#125;;
<span class="hljs-comment">// 可以迭代啦</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> num <span class="hljs-keyword">of</span> obj) &#123;
<span class="hljs-built_in">console</span>.log(num);
&#125;;</code></pre><p>第一次见到迭代器的时候感觉它还是挺复杂的但仔细研究过后就会发现其实它大部分还都是固定搭配的。不过这里的this还是比较容易浑人的。</p><p>仔细观察下其核心的功能,发现迭代器是通过一个名为<code>Symbol.iterator</code>的方法返回的对象中的:</p><ol><li><code>obj</code> 自身没有 <code>next()</code> 方法。</li><li>相反,是通过调用 <code>obj[Symbol.iterator]()</code> 创建了另一个对象,即所谓的“迭代器”对象,并且它的 <code>next</code> 会为迭代生成值</li></ol><p>那么,既然都是对象,所以迭代器应该是可以放在<code>obj</code>自身的。</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> obj = &#123;
start: <span class="hljs-number">1</span>,
end: <span class="hljs-number">5</span>,
<span class="hljs-comment">// Symbol.iterator负责返回一个对象其对象中包含next方法这里直接返回this在obj中定义一个next方法</span>
<span class="hljs-comment">// this.count用于计数</span>
[<span class="hljs-built_in">Symbol</span>.iterator]() &#123;
<span class="hljs-built_in">this</span>.count = <span class="hljs-built_in">this</span>.start;
<span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
&#125;,
<span class="hljs-function"><span class="hljs-title">next</span>(<span class="hljs-params"></span>)</span> &#123;
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.count &lt;= <span class="hljs-built_in">this</span>.end) &#123;
<span class="hljs-keyword">return</span> &#123;
value: <span class="hljs-built_in">this</span>.count++,
done: <span class="hljs-literal">false</span>
&#125;
&#125; <span class="hljs-keyword">else</span> &#123;
<span class="hljs-keyword">return</span> &#123;
done: <span class="hljs-literal">true</span>
&#125;
&#125;
&#125;
&#125;;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> num <span class="hljs-keyword">of</span> obj) &#123;
<span class="hljs-built_in">console</span>.log(num);
&#125;;</code></pre><p>这里的<code>[Symbol.iterator]()</code>定义为<code>obj</code>的一个属性,同时<code>[Symbol.iterator]()</code>需要返回一个带有<code>next()</code>方法的对象。所以直接将<code>next()</code>方法定义在<code>obj</code>身上,<code>[Symbol.iterator]()</code>通过返回this来返回这个对象。</p><p>这样的写法会比在外部定义<code>[Symbol.iterator]()</code>方法更加简洁this指向也更加清晰。但是在<code>[Symbol.iterator]()</code>方法中定义的属性会被添加到<code>obj</code>上。</p><pre><code class="hljs js">obj.count <span class="hljs-comment">// 6</span></code></pre><p>并且迭代器只用一个,现在不能在该对象上同时运行多个<code>for...of</code>循环了,它们将共享迭代状态,因为只有一个迭代器,即对象本身。但是两个并行的 for..of 是很罕见的,即使在异步情况下。</p><blockquote><p>无穷迭代器<br>无穷迭代器也是可能的。例如,将<code>obj</code>设置为<code>obj.to = Infinity</code>,这时<code>obj</code>则成为了无穷迭代器。或者我们可以创建一个可迭代对象,它生成一个无穷伪随机数序列。也是可能的。<br><code>next</code>没有什么限制,它可以返回越来越多的值,这是正常的。<br>当然,迭代这种对象的<code>for..of</code>循环将不会停止。但是我们可以通过使用<code>break</code>来停止它。</p></blockquote><h3 id="展开语法"><a href="#展开语法" class="headerlink" title="展开语法"></a>展开语法</h3><p>展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。</p><p>字面量也就是常见的<code>[1, 2, 3]</code>或者<code>&#123;name: &quot;mdn&quot;&#125;</code>这种简洁的构造方式。</p><p>展开语法与<code>for...of</code>及其相似,无法迭代的对象也无法使用展开语法。错误信息也是一样:</p><p><img src="../images/JavaScript-%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1%E4%B8%8Efor-of/2020-10-29-10-17-49.webp" srcset="/images/img/loading.gif"></p><p>展开语法不仅仅只是和<code>for...of</code>行为比较像,它还有更多的用法。不过在此赘述也是没有多少意义了。</p><h2 id="可迭代的字符串"><a href="#可迭代的字符串" class="headerlink" title="可迭代的字符串"></a>可迭代的字符串</h2><p>在我最早学习js的基本类型的时候就被告知字符串可以被循环处理。类似于这样</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> str = <span class="hljs-string">&#x27;xfy&#x27;</span>;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; str.length; i++) &#123;
<span class="hljs-built_in">console</span>.log(str[i]);
&#125;
<span class="hljs-comment">// x, f, y;</span></code></pre><p>虽然无法理解是什么一回事,但当时就感觉字符串和数组很相似,非常神奇。</p><p>根据包装对象的原理,很容易就联想到字符串可迭代是因为其构造函数<code>String</code>可迭代当然也有length属性。要验证这非常简单只需要找下<code>String</code>上有没有迭代器必备的<code>[Symbol.iterator]()</code>方法就可以了。</p><p>虽然包装对象的过程我们无法看到但是我们可以对一个字符串的原型链向上寻找就ok了。直接调用其原型链上的方法便会触发包装对象就像调用<code>toString()</code>一样,</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> str = <span class="hljs-string">&#x27;xfy&#x27;</span>;
str.__proto__[<span class="hljs-built_in">Symbol</span>.iterator];</code></pre><p>直接访问原型链上的<code>[Symbol.iterator]()</code>方法,就会发现有这个方法存在,正是有它的存在,字符串才是可迭代的。</p><h3 id="显式调用迭代器"><a href="#显式调用迭代器" class="headerlink" title="显式调用迭代器"></a>显式调用迭代器</h3><p>为了能够更加深入的了解迭代器的工作,我们可以不使用<code>for...of</code>,反而使用显式的去操作迭代器:</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> str = <span class="hljs-string">&#x27;xfy&#x27;</span>;
<span class="hljs-comment">// 接收迭代器</span>
<span class="hljs-keyword">let</span> iterator = str[<span class="hljs-built_in">Symbol</span>.iterator]();
<span class="hljs-keyword">let</span> res;
<span class="hljs-function"><span class="hljs-title">while</span>(<span class="hljs-params"><span class="hljs-literal">true</span></span>)</span> &#123;
res = iterator.next();
<span class="hljs-keyword">if</span> (res.done) <span class="hljs-keyword">break</span>;
<span class="hljs-built_in">console</span>.log(res.value);
&#125;</code></pre><p>只要弄弄清楚了迭代器的工作方式,就能很轻松的理解显式调用。最终我们根据<code>next()</code>方法返回的固定格式的值来判断什么适合需要跳出循环以及取值。</p><p>正常情况下我们不需要显式的去迭代一个对象,但是这样做比<code>for...of</code>给了我们更多的控制权。我们可以拆分迭代的步骤,并在中途做一些其他的事情。</p><h2 id="可迭代与类数组"><a href="#可迭代与类数组" class="headerlink" title="可迭代与类数组"></a>可迭代与类数组</h2><p>可迭代对象与类数组很相似,但他们是两种不同的对象,有着不同的正式术语:</p><ul><li><strong>Iterable</strong> 如上所述,是实现了 <code>Symbol.iterator</code> 方法的对象。</li><li><strong>Array-like</strong> 是有索引和 <code>length</code> 属性的对象,所以它们看起来很像数组。</li></ul><p>当然也有两种特性都有的对象,例如字符串就是可迭代同时也是类数组(有数值索引和 <code>length</code> 属性)。</p><p>当光是类数组的对象是无法迭代的</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> obj = &#123;
<span class="hljs-number">0</span>: <span class="hljs-string">&#x27;x&#x27;</span>,
<span class="hljs-number">1</span>: <span class="hljs-string">&#x27;f&#x27;</span>,
<span class="hljs-number">2</span>: <span class="hljs-string">&#x27;y&#x27;</span>,
length: <span class="hljs-number">3</span>
&#125;
<span class="hljs-comment">// Uncaught TypeError: object is not iterable</span>
[...obj];</code></pre><p>可迭代对象和类数组对象通常都<strong>不是数组</strong>,他们也没有数组的一些方法。不过出了字符串以外,我们手动创建的类数组可以使用<code>call</code>来改变数组方法的指向,从而使其能够使用一些数组的方法:</p><pre><code class="hljs js"><span class="hljs-built_in">Array</span>.prototype.push.call(obj, <span class="hljs-number">1</span>);
obj[<span class="hljs-number">3</span>];
<span class="hljs-comment">// 1</span></code></pre><p>而包装后的字符串其<code>length</code>属性是只读的,所以我们无法通过数组的方法去操作它:</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> str = <span class="hljs-string">&#x27;xfy&#x27;</span>;
<span class="hljs-comment">// Uncaught TypeError: Cannot assign to read only</span>
<span class="hljs-built_in">Array</span>.prototype.push.call(str, <span class="hljs-number">1</span>);</code></pre><h3 id="Array-from"><a href="#Array-from" class="headerlink" title="Array.from"></a>Array.from</h3><p><code>Array.from</code>可以从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。通过创建一个浅拷贝的数组,就可以对其使用数组的方法了。</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> obj = &#123;
<span class="hljs-number">0</span>: <span class="hljs-string">&#x27;x&#x27;</span>,
<span class="hljs-number">1</span>: <span class="hljs-string">&#x27;f&#x27;</span>,
<span class="hljs-number">2</span>: <span class="hljs-string">&#x27;y&#x27;</span>,
length: <span class="hljs-number">3</span>
&#125;;
<span class="hljs-keyword">let</span> arr = <span class="hljs-built_in">Array</span>.from(obj);
arr.push(<span class="hljs-string">&#x27;嘤嘤嘤&#x27;</span>);</code></pre><p>Array.from 方法接受对象,检查它是一个可迭代对象或类数组对象,然后创建一个新数组,并将该对象的所有元素浅拷贝到这个新数组。可迭代的对象也是同理。</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> obj = &#123;
<span class="hljs-number">0</span>: <span class="hljs-string">&#x27;x&#x27;</span>,
<span class="hljs-number">1</span>: <span class="hljs-string">&#x27;f&#x27;</span>,
[<span class="hljs-built_in">Symbol</span>.iterator]() &#123;
<span class="hljs-built_in">this</span>.sw = <span class="hljs-literal">true</span>;
<span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
&#125;,
<span class="hljs-function"><span class="hljs-title">next</span>(<span class="hljs-params"></span>)</span> &#123;
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.sw) &#123;
<span class="hljs-built_in">this</span>.sw = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">return</span> &#123;
value: <span class="hljs-built_in">Object</span>.keys(<span class="hljs-built_in">this</span>),
done: <span class="hljs-literal">false</span>
&#125;
&#125; <span class="hljs-keyword">else</span> &#123;
<span class="hljs-keyword">return</span> &#123;
done: <span class="hljs-literal">true</span>
&#125;
&#125;
&#125;
&#125;
<span class="hljs-keyword">let</span> arr = <span class="hljs-built_in">Array</span>.from(obj);</code></pre><p><code>Array.from</code>还有一个可选的参数,提供了类似于<code>forEach</code>的参数选项。</p><pre><code class="hljs js"><span class="hljs-built_in">Array</span>.from(obj[, mapFn, thisArg])</code></pre><p>可选的第二个参数 <code>mapFn</code> 可以是一个函数,该函数会在对象中的元素被添加到数组前,被应用于每个元素,此外 <code>thisArg</code> 允许我们为该函数设置 <code>this</code></p><pre><code class="hljs js"><span class="hljs-built_in">Array</span>.from([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>], <span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x + x);
<span class="hljs-comment">// [2, 4, 6]</span></code></pre><h3 id="可用于代理对"><a href="#可用于代理对" class="headerlink" title="可用于代理对"></a>可用于代理对</h3><p>对于代理对surrogate pairs UTF-16 的扩展字符),<code>Array.from</code>也可以正常识别并拷贝为数组。对于普通的字符串,虽然能够使用<code>slice()</code>方法,但是对于代理对的操作会导致乱码,两个不同 UTF-16 扩展字符碎片拼接的结果。</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> str = <span class="hljs-string">&#x27;𝒳😂𩷶&#x27;</span>;
<span class="hljs-built_in">console</span>.log(str);
<span class="hljs-comment">// &quot;𝒳😂𩷶&quot;</span>
str.slice(<span class="hljs-number">1</span>,<span class="hljs-number">3</span>)
<span class="hljs-comment">// &quot;<EFBFBD><EFBFBD>&quot;</span></code></pre><p>我们可以利用<code>Array.from</code>对代理对的正确操作特性来重写创建代理感知surrogate-aware<code>slice</code>方法。</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> str = <span class="hljs-string">&#x27;𝒳😂𩷶&#x27;</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">aSlice</span>(<span class="hljs-params">arr, star, end</span>) </span>&#123;
<span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.from(arr).slice(star, end).join(<span class="hljs-string">&#x27;&#x27;</span>);
&#125;</code></pre><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>可以应用 <code>for..of</code> 的对象被称为 <strong>可迭代的</strong></p><ul><li>技术上来说,可迭代对象必须实现 <code>Symbol.iterator</code>方法。<ul><li><code>obj[Symbol.iterator]</code> 的结果被称为 <strong>迭代器iterator</strong>。由它处理进一步的迭代过程。</li><li>一个迭代器必须有 <code>next()</code> 方法,它返回一个 <code>&#123;done: Boolean, value: any&#125;</code> 对象,这里 <code>done:true</code> 表明迭代结束,否则 <code>value</code> 就是下一个值。</li></ul></li><li><code>Symbol.iterator</code> 方法会被 <code>for..of</code> 自动调用,但我们也可以直接调用它。</li><li>展开语法的操作结果与<code>for..of</code>类似。</li><li>内置的可迭代对象例如字符串和数组,都实现了 <code>Symbol.iterator</code></li><li>字符串迭代器能够识别代理对surrogate pair</li></ul><p>有索引属性和 <code>length</code> 属性的对象被称为 <strong>类数组对象</strong>。这种对象可能还具有其他属性和方法,但是没有数组的内建方法。</p><h2 id="参考-amp-推荐"><a href="#参考-amp-推荐" class="headerlink" title="参考&amp;推荐"></a>参考&amp;推荐</h2><ul><li><a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols">迭代协议</a></li><li><a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_Generators">迭代器和生成器</a></li><li><a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*">yield*</a></li><li><a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax">展开语法</a></li><li><a target="_blank" rel="noopener" href="https://zh.javascript.info/iterable">Iterable object可迭代对象</a></li></ul></div><hr><div><div class="post-metas mb-3"><div class="post-meta mr-3"><i class="iconfont icon-category"></i> <a class="hover-with-bg" href="/categories/%E7%AC%94%E8%AE%B0/">笔记</a></div><div class="post-meta"><i class="iconfont icon-tags"></i> <a class="hover-with-bg" href="/tags/JavaScript/">JavaScript</a></div></div><p class="note note-warning"><a target="_blank" href="https://zh.wikipedia.org/wiki/Wikipedia:CC_BY-SA_3.0%E5%8D%8F%E8%AE%AE%E6%96%87%E6%9C%AC" rel="nofollow noopener noopener">CC BY-SA 3.0❤</a></p><div class="post-prevnext row"><article class="post-prev col-6"><a href="/defect/response-data-in-Vue3.html"><i class="iconfont icon-arrowleft"></i> <span class="hidden-mobile">Vue3中的响应数据</span> <span class="visible-mobile">上一篇</span></a></article><article class="post-next col-6"><a href="/defect/Vue-js-get-started.html"><span class="hidden-mobile">Vue.js-起步!</span> <span class="visible-mobile">下一篇</span><i class="iconfont icon-arrowright"></i></a></article></div></div><article class="comments" id="comments"><div id="vcomments"></div><script type="text/javascript">function loadValine(){addScript("https://cdn.defectink.com/static/valine/1.4.14/Valine.min.js",function(){new Valine({el:"#vcomments",app_id:"dD9t7mcIBVzJWag5ez6GPy2v-MdYXbMMI",app_key:"bWG6pmKsEscrH4JjrpNNAAy6",placeholder:"嘤嘤嘤???",path:window.location.pathname,avatar:"retro",meta:["nick","mail","link"],pageSize:"10",lang:"zh-CN",highlight:!0,recordIP:!1,serverURLs:""})})}waitElementVisible("vcomments",loadValine)</script><noscript>Please enable JavaScript to view the <a target="_blank" href="https://valine.js.org" rel="nofollow noopener noopener">comments powered by Valine.</a></noscript></article></article></div></div></div><div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn"><div id="toc"><p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p><div id="tocbot"></div></div></div></div></div></main><a id="scroll-top-button" href="#" role="button"><i class="iconfont icon-arrowup" aria-hidden="true"></i></a><div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel" aria-hidden="true"><div class="modal-dialog modal-dialog-scrollable modal-lg" role="document"><div class="modal-content"><div class="modal-header text-center"><h4 class="modal-title w-100 font-weight-bold">搜索</h4><button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><div class="modal-body mx-3"><div class="md-form mb-5"><input type="text" id="local-search-input" class="form-control validate"> <label data-error="x" data-success="v" for="local-search-input">关键词</label></div><div class="list-group" id="local-search-result"></div></div></div></div></div><footer class="text-center mt-5 py-3"><div class="footer-content"><a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a><i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a></div><div class="beian"><a href="http://beian.miit.gov.cn/" target="_blank" rel="nofollow noopener">皖ICP备17017808号</a></div></footer><script src="https://cdn.defectink.com/static/jquery/3.4.1/jquery.min.js"></script><script src="https://cdn.defectink.com/static/twitter-bootstrap/4.5.3/js/bootstrap.min.js"></script><script src="/js/debouncer.js"></script><script src="/js/main.js"></script><script src="/js/lazyload.js"></script><script defer="defer" src="https://cdn.defectink.com/static/clipboard.js/2.0.6/clipboard.min.js"></script><script src="/js/clipboard-use.js"></script><script src="/js/xfy.js"></script><script src="https://cdn.defectink.com/static/tocbot/4.11.1/tocbot.min.js"></script><script>$(document).ready(function(){var t=$("#board-ctn").offset().top;tocbot.init({tocSelector:"#tocbot",contentSelector:"#post-body",headingSelector:"h1,h2,h3,h4,h5,h6",linkClass:"tocbot-link",activeLinkClass:"tocbot-active-link",listClass:"tocbot-list",isCollapsedClass:"tocbot-is-collapsed",collapsibleClass:"tocbot-is-collapsible",collapseDepth:3,scrollSmooth:!0,headingsOffset:-t}),0<$(".toc-list-item").length&&$("#toc").css("visibility","visible")})</script><script src="https://cdn.defectink.com/static/typed.js/2.0.11/typed.min.js"></script><script>var typed=new Typed("#subtitle",{strings:[" ","JavaScript-可迭代对象与for-of&nbsp;"],cursorChar:"❤",typeSpeed:70,loop:!1});typed.stop(),$(document).ready(function(){$(".typed-cursor").addClass("h2"),typed.start()})</script><script src="/js/local-search.js"></script><script>var path="/xml/local-search.xml",inputArea=document.querySelector("#local-search-input");inputArea.onclick=function(){searchFunc(path,"local-search-input","local-search-result"),this.onclick=null}</script><script src="https://cdn.defectink.com/static/fancybox/3.5.7/jquery.fancybox.min.js"></script><link rel="stylesheet" href="https://cdn.defectink.com/static/fancybox/3.5.7/jquery.fancybox.min.css"><script>$("#post img:not(.no-zoom img, img[no-zoom]), img[zoom]").each(function(){var t=document.createElement("a");$(t).attr("data-fancybox","images"),$(t).attr("href",$(this).attr("src")),$(this).wrap(t)})</script><script src="https://cdn.defectink.com/static/mermaid/8.5.0/mermaid.min.js"></script><script>window.mermaid&&mermaid.initialize({theme:"default"})</script></body></html>