mirror of
https://github.com/DefectingCat/DefectingCat.github.io
synced 2025-07-16 01:01:38 +00:00
160 lines
36 KiB
HTML
160 lines
36 KiB
HTML
<!DOCTYPE html><html lang="zh-CN" data-default-color-scheme=""auto""><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="/"> <strong>🍭Defectink</strong> </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"> <i class="iconfont icon-search"></i> </a></li><li class="nav-item" id="color-toggle-btn"><a class="nav-link" href="javascript:"> <i class="iconfont icon-dark" id="color-toggle-icon"></i> </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 < arr.length; i++) {
|
||
<span class="hljs-built_in">console</span>.log(arr[i]);
|
||
}</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) {
|
||
<span class="hljs-built_in">console</span>.log(arr[i]);
|
||
}</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> =></span> {
|
||
<span class="hljs-built_in">console</span>.log(element);
|
||
});</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) {
|
||
<span class="hljs-built_in">console</span>.log(arr[i]);
|
||
<span class="hljs-comment">// 1, 2, 3, ,4, 5</span>
|
||
}
|
||
|
||
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i <span class="hljs-keyword">in</span> arr) {
|
||
<span class="hljs-built_in">console</span>.log(i);
|
||
<span class="hljs-comment">// 0, 1, 2, 3, ,4</span>
|
||
}</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>{};
|
||
|
||
<span class="hljs-built_in">Object</span>.defineProperty(<span class="hljs-built_in">Array</span>.prototype, <span class="hljs-string">'push'</span>, {
|
||
enumerable: <span class="hljs-literal">true</span>
|
||
})
|
||
|
||
<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) {
|
||
<span class="hljs-built_in">console</span>.log(arr[i]);
|
||
}</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 = {
|
||
start: <span class="hljs-number">1</span>,
|
||
end: <span class="hljs-number">5</span>
|
||
};
|
||
|
||
<span class="hljs-keyword">for</span> (num <span class="hljs-keyword">of</span> obj) {
|
||
<span class="hljs-built_in">console</span>.log(num);
|
||
}</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>{done: Boolean, value: any}</code>,当 <code>done=true</code> 时,表示迭代结束,否则 <code>value</code> 是下一个值。</li></ol><pre><code class="hljs js"><span class="hljs-keyword">let</span> obj = {
|
||
start: <span class="hljs-number">1</span>,
|
||
end: <span class="hljs-number">5</span>
|
||
};
|
||
|
||
<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>{
|
||
<span class="hljs-keyword">return</span> {
|
||
<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> {
|
||
<span class="hljs-comment">// Symbol.iterator返回的是一个对象,this不会多级指向,所以这里用到了刚刚定义的属性</span>
|
||
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.current <= <span class="hljs-built_in">this</span>.last) {
|
||
<span class="hljs-keyword">return</span> {
|
||
value: <span class="hljs-built_in">this</span>.current++,
|
||
done: <span class="hljs-literal">false</span>
|
||
}
|
||
} <span class="hljs-keyword">else</span> {
|
||
<span class="hljs-keyword">return</span> {
|
||
done: <span class="hljs-literal">true</span>
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
<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) {
|
||
<span class="hljs-built_in">console</span>.log(num);
|
||
};</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 = {
|
||
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]() {
|
||
<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>;
|
||
},
|
||
<span class="hljs-function"><span class="hljs-title">next</span>(<span class="hljs-params"></span>)</span> {
|
||
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.count <= <span class="hljs-built_in">this</span>.end) {
|
||
<span class="hljs-keyword">return</span> {
|
||
value: <span class="hljs-built_in">this</span>.count++,
|
||
done: <span class="hljs-literal">false</span>
|
||
}
|
||
} <span class="hljs-keyword">else</span> {
|
||
<span class="hljs-keyword">return</span> {
|
||
done: <span class="hljs-literal">true</span>
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> num <span class="hljs-keyword">of</span> obj) {
|
||
<span class="hljs-built_in">console</span>.log(num);
|
||
};</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>{name: "mdn"}</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">'xfy'</span>;
|
||
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < str.length; i++) {
|
||
<span class="hljs-built_in">console</span>.log(str[i]);
|
||
}
|
||
<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">'xfy'</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">'xfy'</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> {
|
||
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);
|
||
}</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 = {
|
||
<span class="hljs-number">0</span>: <span class="hljs-string">'x'</span>,
|
||
<span class="hljs-number">1</span>: <span class="hljs-string">'f'</span>,
|
||
<span class="hljs-number">2</span>: <span class="hljs-string">'y'</span>,
|
||
length: <span class="hljs-number">3</span>
|
||
}
|
||
<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">'xfy'</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 = {
|
||
<span class="hljs-number">0</span>: <span class="hljs-string">'x'</span>,
|
||
<span class="hljs-number">1</span>: <span class="hljs-string">'f'</span>,
|
||
<span class="hljs-number">2</span>: <span class="hljs-string">'y'</span>,
|
||
length: <span class="hljs-number">3</span>
|
||
};
|
||
|
||
<span class="hljs-keyword">let</span> arr = <span class="hljs-built_in">Array</span>.from(obj);
|
||
|
||
arr.push(<span class="hljs-string">'嘤嘤嘤'</span>);</code></pre><p>Array.from 方法接受对象,检查它是一个可迭代对象或类数组对象,然后创建一个新数组,并将该对象的所有元素浅拷贝到这个新数组。可迭代的对象也是同理。</p><pre><code class="hljs js"><span class="hljs-keyword">let</span> obj = {
|
||
<span class="hljs-number">0</span>: <span class="hljs-string">'x'</span>,
|
||
<span class="hljs-number">1</span>: <span class="hljs-string">'f'</span>,
|
||
[<span class="hljs-built_in">Symbol</span>.iterator]() {
|
||
<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>;
|
||
},
|
||
<span class="hljs-function"><span class="hljs-title">next</span>(<span class="hljs-params"></span>)</span> {
|
||
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.sw) {
|
||
<span class="hljs-built_in">this</span>.sw = <span class="hljs-literal">false</span>;
|
||
<span class="hljs-keyword">return</span> {
|
||
value: <span class="hljs-built_in">Object</span>.keys(<span class="hljs-built_in">this</span>),
|
||
done: <span class="hljs-literal">false</span>
|
||
}
|
||
} <span class="hljs-keyword">else</span> {
|
||
<span class="hljs-keyword">return</span> {
|
||
done: <span class="hljs-literal">true</span>
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
<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> =></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">'𝒳😂𩷶'</span>;
|
||
|
||
<span class="hljs-built_in">console</span>.log(str);
|
||
<span class="hljs-comment">// "𝒳😂𩷶"</span>
|
||
str.slice(<span class="hljs-number">1</span>,<span class="hljs-number">3</span>)
|
||
<span class="hljs-comment">// "<EFBFBD><EFBFBD>"</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">'𝒳😂𩷶'</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>{
|
||
<span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.from(arr).slice(star, end).join(<span class="hljs-string">''</span>);
|
||
}</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>{done: Boolean, value: any}</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="参考&推荐"></a>参考&推荐</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> 目录</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">×</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 "],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> |