前面的話
10余年的華容網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。營銷型網(wǎng)站建設(shè)的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整華容建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)從事“華容網(wǎng)站設(shè)計(jì)”,“華容網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
用循環(huán)語句迭代數(shù)據(jù)時(shí),必須要初始化一個(gè)變量來記錄每一次迭代在數(shù)據(jù)集合中的位置,而在許多編程語言中,已經(jīng)開始通過程序化的方式用迭代器對象返回迭代過程中集合的每一個(gè)元素
迭代器的使用可以極大地簡化數(shù)據(jù)操作,于是ES6也向JS中添加了這個(gè)迭代器特性。新的數(shù)組方法和新的集合類型(如Set集合與Map集合)都依賴迭代器的實(shí)現(xiàn),這個(gè)新特性對于高效的數(shù)據(jù)處理而言是不可或缺的,在語言的其他特性中也都有迭代器的身影:新的for-of循環(huán)、展開運(yùn)算符(...),甚至連異步編程都可以使用迭代器
本文將詳細(xì)介紹ES6中的迭代器(Iterator)和生成器(Generator)
引入
下面是一段標(biāo)準(zhǔn)的for循環(huán)代碼,通過變量i來跟蹤colors數(shù)組的索引,循環(huán)每次執(zhí)行時(shí),如果i小于數(shù)組長度len則加1,并執(zhí)行下一次循環(huán)
var colors = ["red", "green", "blue"]; for (var i = 0, len = colors.length; i < len; i++) { console.log(colors[i]); }
雖然循環(huán)語句語法簡單,但如果將多個(gè)循環(huán)嵌套則需要追蹤多個(gè)變量,代碼復(fù)雜度會(huì)大大增加,一不小心就錯(cuò)誤使用了其他for循環(huán)的跟蹤變量,從而導(dǎo)致程序出錯(cuò)。迭代器的出現(xiàn)旨在消除這種復(fù)雜性并減少循環(huán)中的錯(cuò)誤
迭代器
迭代器是一種特殊對象,它具有一些專門為迭代過程設(shè)計(jì)的專有接口,所有的迭代器對象都有一個(gè)next()方法,每次調(diào)用都返回一個(gè)結(jié)果對象。結(jié)果對象有兩個(gè)屬性:一個(gè)是value,表示下一個(gè)將要返回的值;另一個(gè)是done,它是一個(gè)布爾類型的值,當(dāng)沒有更多可返回?cái)?shù)據(jù)時(shí)返回true。迭代器還會(huì)保存一個(gè)內(nèi)部指針,用來指向當(dāng)前集合中值的位置,每調(diào)用一次next()方法,都會(huì)返回下一個(gè)可用的值
如果在最后一個(gè)值返回后再調(diào)用next()方法,那么返回的對象中屬性done的值為true,屬性value則包含迭代器最終返回的值,這個(gè)返回值不是數(shù)據(jù)集的一部分,它與函數(shù)的返回值類似,是函數(shù)調(diào)用過程中最后一次給調(diào)用者傳遞信息的方法,如果沒有相關(guān)數(shù)據(jù)則返回undefined
下面用ES5的語法創(chuàng)建一個(gè)迭代器
function createIterator(items) { var i = 0; return { next: function() { var done = (i >= items.length); var value = !done ? items[i++] : undefined; return { done: done, value: value }; } }; } var iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }" // 之后的所有調(diào)用 console.log(iterator.next()); // "{ value: undefined, done: true }"
在上面這段代碼中,createIterator()方法返回的對象有一個(gè)next()方法,每次調(diào)用時(shí),items數(shù)組的下一個(gè)值會(huì)作為value返回。當(dāng)i為3時(shí),done變?yōu)閠rue;此時(shí)三元表達(dá)式會(huì)將value的值設(shè)置為undefined。最后兩次調(diào)用的結(jié)果與ES6迭代器的最終返回機(jī)制類似,當(dāng)數(shù)據(jù)集被用盡后會(huì)返回最終的內(nèi)容
上面這個(gè)示例很復(fù)雜,而在ES6中,迭代器的編寫規(guī)則也同樣復(fù)雜,但ES6同時(shí)還引入了一個(gè)生成器對象,它可以讓創(chuàng)建迭代器對象的過程變得更簡單
生成器
生成器是一種返回迭代器的函數(shù),通過function關(guān)鍵字后的星號(hào)(*)來表示,函數(shù)中會(huì)用到新的關(guān)鍵字yield。星號(hào)可以緊挨著function關(guān)鍵字,也可以在中間添加一個(gè)空格
// 生成器 function *createIterator() { yield 1; yield 2; yield 3; } // 生成器能像正規(guī)函數(shù)那樣被調(diào)用,但會(huì)返回一個(gè)迭代器 let iterator = createIterator(); console.log(iterator.next().value); // 1 console.log(iterator.next().value); // 2 console.log(iterator.next().value); // 3
在這個(gè)示例中,createlterator()前的星號(hào)表明它是一個(gè)生成器;yield關(guān)鍵字也是ES6的新特性,可以通過它來指定調(diào)用迭代器的next()方法時(shí)的返回值及返回順序。生成迭代器后,連續(xù)3次調(diào)用它的next()方法返回3個(gè)不同的值,分別是1、2和3。生成器的調(diào)用過程與其他函數(shù)一樣,最終返回的是創(chuàng)建好的迭代器
生成器函數(shù)最有趣的部分是,每當(dāng)執(zhí)行完一條yield語句后函數(shù)就會(huì)自動(dòng)停止執(zhí)行。舉個(gè)例子,在上面這段代碼中,執(zhí)行完語句yield 1之后,函數(shù)便不再執(zhí)行其他任何語句,直到再次調(diào)用迭代器的next()方法才會(huì)繼續(xù)執(zhí)行yield 2語句。生成器函數(shù)的這種中止函數(shù)執(zhí)行的能力有很多有趣的應(yīng)用
使用yield關(guān)鍵字可以返回任何值或表達(dá)式,所以可以通過生成器函數(shù)批量地給迭代器添加元素。例如,可以在循環(huán)中使用yield關(guān)鍵字
function *createIterator(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } } let iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }" // 之后的所有調(diào)用 console.log(iterator.next()); // "{ value: undefined, done: true }"
在此示例中,給生成器函數(shù)createlterator()傳入一個(gè)items數(shù)組,而在函數(shù)內(nèi)部,for循環(huán)不斷從數(shù)組中生成新的元素放入迭代器中,每遇到一個(gè)yield語句循環(huán)都會(huì)停止;每次調(diào)用迭代器的next()方法,循環(huán)會(huì)繼續(xù)運(yùn)行并執(zhí)行下一條yield語句
生成器函數(shù)是ES6中的一個(gè)重要特性,可以將其用于所有支持函數(shù)使用的地方
【使用限制】
yield關(guān)鍵字只可在生成器內(nèi)部使用,在其他地方使用會(huì)導(dǎo)致程序拋出錯(cuò)誤
function *createIterator(items) { items.forEach(function(item) { // 語法錯(cuò)誤 yield item + 1; }); }
從字面上看,yield關(guān)鍵字確實(shí)在createlterator()函數(shù)內(nèi)部,但是它與return關(guān)鍵字一樣,二者都不能穿透函數(shù)邊界。嵌套函數(shù)中的return語句不能用作外部函數(shù)的返回語句,而此處嵌套函數(shù)中的yield語句會(huì)導(dǎo)致程序拋出語法錯(cuò)誤
【生成器函數(shù)表達(dá)式】
也可以通過函數(shù)表達(dá)式來創(chuàng)建生成器,只需在function關(guān)鍵字和小括號(hào)中間添加一個(gè)星號(hào)(*)即可
let createIterator = function *(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } }; let iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }" // 之后的所有調(diào)用 console.log(iterator.next()); // "{ value: undefined, done: true }"
在這段代碼中,createlterator()是一個(gè)生成器函數(shù)表達(dá)式,而不是一個(gè)函數(shù)聲明。由于函數(shù)表達(dá)式是匿名的,因此星號(hào)直接放在function關(guān)鍵字和小括號(hào)之間。此外,這個(gè)示例基本與前例相同,使用的也是for循環(huán)
[注意]不能用箭頭函數(shù)來創(chuàng)建生成器
【生成器對象的方法】
由于生成器本身就是函數(shù),因而可以將它們添加到對象中。例如,在ES5風(fēng)格的對象字面量中,可以通過函數(shù)表達(dá)式來創(chuàng)建生成器
var o = { createIterator: function *(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } } }; let iterator = o.createIterator([1, 2, 3]);
也可以用ES6的函數(shù)方法的簡寫方式來創(chuàng)建生成器,只需在函數(shù)名前添加一個(gè)星號(hào)(*)
var o = { *createIterator(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } } }; let iterator = o.createIterator([1, 2, 3]);
這些示例使用了不同于之前的語法,但它們的功能實(shí)際上是等價(jià)的。在簡寫版本中,由于不使用function關(guān)鍵字來定義createlterator()方法,因此盡管可以在星號(hào)和方法名之間留白,但還是將星號(hào)緊貼在方法名之前
【狀態(tài)機(jī)】
生成器的一個(gè)常用功能是生成狀態(tài)機(jī)
let state = function*(){ while(1){ yield 'A'; yield 'B'; yield 'C'; } } let status = state(); console.log(status.next().value);//'A' console.log(status.next().value);//'B' console.log(status.next().value);//'C' console.log(status.next().value);//'A' console.log(status.next().value);//'B'
可迭代對象
可迭代對象具有Symbol.iterator屬性,是一種與迭代器密切相關(guān)的對象。Symbol.iterator通過指定的函數(shù)可以返回一個(gè)作用于附屬對象的迭代器。在ES6中,所有的集合對象(數(shù)組、Set集合及Map集合)和字符串都是可迭代對象,這些對象中都有默認(rèn)的迭代器。ES6中新加入的特性for-of循環(huán)需要用到可迭代對象的這些功能
[注意]由于生成器默認(rèn)會(huì)為Symbol.iterator屬性賦值,因此所有通過生成器創(chuàng)建的迭代器都是可迭代對象
一開始,我們曾提到過循環(huán)內(nèi)部索引跟蹤的相關(guān)問題,要解決這個(gè)問題,需要兩個(gè)工具:一個(gè)是迭代器,另一個(gè)是for-of循環(huán)。如此一來,便不需要再跟蹤整個(gè)集合的索引,只需關(guān)注集合中要處理的內(nèi)容
for-of循環(huán)每執(zhí)行一次都會(huì)調(diào)用可迭代對象的next()方法,并將迭代器返回的結(jié)果對象的value屬性存儲(chǔ)在一個(gè)變量中,循環(huán)將持續(xù)執(zhí)行這一過程直到返回對象的done屬性的值為true。這里有個(gè)示例
let values = [1, 2, 3]; for (let num of values) { //1 //2 //3 console.log(num); }
這段for-of循環(huán)的代碼通過調(diào)用values數(shù)組的Symbol.iterator方法來獲取迭代器,這一過程是在JS引擎背后完成的。隨后迭代器的next()方法被多次調(diào)用,從其返回對象的value屬性讀取值并存儲(chǔ)在變量num中,依次為1、2和3,當(dāng)結(jié)果對象的done屬性值為true時(shí)循環(huán)退出,所以num不會(huì)被賦值為undefined
如果只需迭代數(shù)組或集合中的值,用for-of循環(huán)代替for循環(huán)是個(gè)不錯(cuò)的選擇。相比傳統(tǒng)的for循環(huán),for-of循環(huán)的控制條件更簡單,不需要追蹤復(fù)雜的條件,所以更少出錯(cuò)
[注意]如果將for-of語句用于不可迭代對象、null或undefined將會(huì)導(dǎo)致程序拋出錯(cuò)誤
【訪問默認(rèn)迭代器】
可以通過Symbol.iterator來訪問對象默認(rèn)的迭代器
let values = [1, 2, 3]; let iterator = values[Symbol.iterator](); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
在這段代碼中,通過Symbol.iterator獲取了數(shù)組values的默認(rèn)迭代器,并用它遍歷數(shù)組中的元素。在JS引擎中執(zhí)行for-of循環(huán)語句時(shí)也會(huì)有類似的處理過程
由于具有Symbol.iterator屬性的對象都有默認(rèn)的迭代器,因此可以用它來檢測對象是否為可迭代對象
function isIterable(object) { return typeof object[Symbol.iterator] === "function"; } console.log(isIterable([1, 2, 3])); // true console.log(isIterable("Hello")); // true console.log(isIterable(new Map())); // true console.log(isIterable(new Set())); // true console.log(isIterable(new WeakMap())); // false console.log(isIterable(new WeakSet())); // false
這里的islterable()函數(shù)可以檢查指定對象中是否存在默認(rèn)的函數(shù)類型迭代器,而for-of循環(huán)在執(zhí)行前也會(huì)做相似的檢查
除了使用內(nèi)建的可迭代對象類型的Symbol.iterator,也可以使用Symbol.iterator來創(chuàng)建屬于自己的迭代器
【創(chuàng)建可迭代對象】
默認(rèn)情況下,開發(fā)者定義的對象都是不可迭代對象,但如果給Symbol.iterator屬性添加一個(gè)生成器,則可以將其變?yōu)榭傻鷮ο?/p>
let collection = { items: [], *[Symbol.iterator]() { for (let item of this.items) { yield item; } } }; collection.items.push(1); collection.items.push(2); collection.items.push(3); for (let x of collection) { //1 //2 //3 console.log(x); }
在這個(gè)示例中,先創(chuàng)建一個(gè)生成器(注意,星號(hào)仍然在屬性名前)并將其賦值給對象的Symbol.iterator屬性來創(chuàng)建默認(rèn)的迭代器;而在生成器中,通過for-of循環(huán)迭代this.items并用yield返回每一個(gè)值。collection對象默認(rèn)迭代器的返回值由迭代器this.items自動(dòng)生成,而非手動(dòng)遍歷來定義返回值
【展開運(yùn)算符和非數(shù)組可迭代對象】
通過展開運(yùn)算符(...)可以把Set集合轉(zhuǎn)換成一個(gè)數(shù)組
let set = new Set([1, 2, 3, 3, 3, 4, 5]), array = [...set]; console.log(array); // [1,2,3,4,5]
這段代碼中的展開運(yùn)算符把Set集合的所有值填充到了一個(gè)數(shù)組字面量里,它可以操作所有可迭代對象,并根據(jù)默認(rèn)迭代器來選取要引用的值,從迭代器讀取所有值。然后按照返回順序?qū)⑺鼈円来尾迦氲綌?shù)組中。Set集合是一個(gè)可迭代對象,展開運(yùn)算符也可以用于其他可迭代對象
let map = new Map([ ["name", "huochai"], ["age", 25]]), array = [...map]; console.log(array); // [ ["name", "huochai"], ["age", 25]]
展開運(yùn)算符把Map集合轉(zhuǎn)換成包含多個(gè)數(shù)組的數(shù)組,Map集合的默認(rèn)迭代器返回的是多組鍵值對,所以結(jié)果數(shù)組與執(zhí)行new Map()時(shí)傳入的數(shù)組看起來一樣
在數(shù)組字面量中可以多次使用展開運(yùn)算符,將可迭代對象中的多個(gè)元素依次插入新數(shù)組中,替換原先展開運(yùn)算符所在的位置
let smallNumbers = [1, 2, 3], bigNumbers = [100, 101, 102], allNumbers = [0, ...smallNumbers, ...bigNumbers]; console.log(allNumbers.length); // 7 console.log(allNumbers); // [0, 1, 2, 3, 100, 101, 102]
創(chuàng)建一個(gè)變量allNumbers,用展開運(yùn)算符將smallNumbers和bigNumbers里的值依次添加到allNumbers中。首先存入0,然后存入small中的值,最后存入bigNumbers中的值。當(dāng)然,原始數(shù)組中的值只是被復(fù)制到allNumbers中,它們本身并未改變
由于展開運(yùn)算符可以作用于任意可迭代對象,因此如果想將可迭代對象轉(zhuǎn)換為數(shù)組,這是最簡單的方法。既可以將字符串中的每一個(gè)字符(不是編碼單元)存入新數(shù)組中,也可以將瀏覽器中NodeList對象中的每一個(gè)節(jié)點(diǎn)存入新的數(shù)組中
內(nèi)建迭代器
迭代器是ES6的一個(gè)重要組成部分,在ES6中,已經(jīng)默認(rèn)為許多內(nèi)建類型提供了內(nèi)建迭代器,只有當(dāng)這些內(nèi)建迭代器無法實(shí)現(xiàn)目標(biāo)時(shí)才需要自己創(chuàng)建。通常來說當(dāng)定義自己的對象和類時(shí)才會(huì)遇到這種情況,否則,完全可以依靠內(nèi)建的迭代器完成工作,而最常使用的可能是集合的那些迭代器
【集合對象迭代器】
在ES6中有3種類型的集合對象:數(shù)組、Map集合與Set集合
為了更好地訪問對象中的內(nèi)容,這3種對象都內(nèi)建了以下三種迭代器
entries() 返回一個(gè)迭代器,其值為多個(gè)鍵值對
values() 返回一個(gè)迭代器,其值為集合的值
keys() 返回一個(gè)迭代器,其值為集合中的所有鍵名
調(diào)用以上3個(gè)方法都可以訪問集合的迭代器
entries()迭代器
每次調(diào)用next()方法時(shí),entries()迭代器都會(huì)返回一個(gè)數(shù)組,數(shù)組中的兩個(gè)元素分別表示集合中每個(gè)元素的鍵與值。如果被遍歷的對象是數(shù)組,則第一個(gè)元素是數(shù)字類型的索引;如果是Set集合,則第一個(gè)元素與第二個(gè)元素都是值(Set集合中的值被同時(shí)作為鍵與值使用);如果是Map集合,則第一個(gè)元素為鍵名
let colors = [ "red", "green", "blue" ]; let tracking = new Set([1234, 5678, 9012]); let data = new Map(); data.set("title", "Understanding ES6"); data.set("format", "ebook"); for (let entry of colors.entries()) { console.log(entry); } for (let entry of tracking.entries()) { console.log(entry); } for (let entry of data.entries()) { console.log(entry); }
調(diào)用console.log()方法后輸出以下內(nèi)容
[0, "red"] [1, "green"] [2, "blue"] [1234, 1234] [5678, 5678] [9012, 9012] ["title", "Understanding ES6"] ["format", "ebook"]
在這段代碼中,調(diào)用每個(gè)集合的entries()方法獲取一個(gè)迭代器,并使用for-of循環(huán)來遍歷元素,且通過console將每一個(gè)對象的鍵值對輸出出來
values()迭代器
調(diào)用values()迭代器時(shí)會(huì)返回集合中所存的所有值
let colors = [ "red", "green", "blue" ]; let tracking = new Set([1234, 5678, 9012]); let data = new Map(); data.set("title", "Understanding ES6"); data.set("format", "ebook"); for (let value of colors.values()) { console.log(value); } for (let value of tracking.values()) { console.log(value); } for (let value of data.values()) { console.log(value); }
調(diào)用console.log()方法后輸出以下內(nèi)容
"red" "green" "blue" 1234 5678 9012 "Understanding ES6" "ebook"
如上所示,調(diào)用values()迭代器后,返回的是每個(gè)集合中包含的真正數(shù)據(jù),而不包含數(shù)據(jù)在集合中的位置信息
keys()迭代器
keys()迭代器會(huì)返回集合中存在的每一個(gè)鍵。如果遍歷的是數(shù)組,則會(huì)返回?cái)?shù)字類型的鍵,數(shù)組本身的其他屬性不會(huì)被返回;如果是Set集合,由于鍵與值是相同的,因此keys()和values()返回的也是相同的迭代器;如果是Map集合,則keys()迭代器會(huì)返回每個(gè)獨(dú)立的鍵
let colors = [ "red", "green", "blue" ]; let tracking = new Set([1234, 5678, 9012]); let data = new Map(); data.set("title", "Understanding ES6"); data.set("format", "ebook"); for (let key of colors.keys()) { console.log(key); } for (let key of tracking.keys()) { console.log(key); } for (let key of data.keys()) { console.log(key); }
調(diào)用console.log()方法后輸出以下內(nèi)容
0 1 2 1234 5678 9012 "title" "format"
keys()迭代器會(huì)獲取colors、tracking和data這3個(gè)集合中的每一個(gè)鍵,而且分別在3個(gè)for-of循環(huán)內(nèi)部將這些鍵名打印出來。對于數(shù)組對象來說,無論是否為數(shù)組添加命名屬性,打印出來的都是數(shù)字類型的索引;而for-in循環(huán)迭代的是數(shù)組屬性而不是數(shù)字類型的索引
不同集合類型的默認(rèn)迭代器
每個(gè)集合類型都有一個(gè)默認(rèn)的迭代器,在for-of循環(huán)中,如果沒有顯式指定則使用默認(rèn)的迭代器。數(shù)組和Set集合的默認(rèn)迭代器是values()方法,Map集合的默認(rèn)迭代器是entries()方法。有了這些默認(rèn)的迭代器,可以更輕松地在for-of循環(huán)中使用集合對象
let colors = [ "red", "green", "blue" ]; let tracking = new Set([1234, 5678, 9012]); let data = new Map(); data.set("title", "Understanding ES6"); data.set("format", "print"); // 與使用 colors.values() 相同 for (let value of colors) { console.log(value); } // 與使用 tracking.values() 相同 for (let num of tracking) { console.log(num); } // 與使用 data.entries() 相同 for (let entry of data) { console.log(entry); }
上述代碼未指定迭代器,所以將使用默認(rèn)的迭代器。數(shù)組、Set集合及Map集合的默認(rèn)迭代器也會(huì)反應(yīng)出這些對象的初始化過程,所以這段代碼會(huì)輸出以下內(nèi)容
"red" "green" "blue" 1234 5678 9012 ["title", "Understanding ES6"] ["format", "print"]
默認(rèn)情況下,如果是數(shù)組和Set集合,會(huì)逐一返回集合中所有的值。如果是Map集合,則按照Map構(gòu)造函數(shù)參數(shù)的格式返回相同的數(shù)組內(nèi)容。而WeakSet集合與WeakMap集合就沒有內(nèi)建的迭代器,由于要管理弱引用,因而無法確切地知道集合中存在的值,也就無法迭代這些集合了
【字符串迭代器】
自ES5發(fā)布以后,JS字符串慢慢變得更像數(shù)組了,例如,ES5正式規(guī)定可以通過方括號(hào)訪問字符串中的字符(也就是說,text[0]可以獲取字符串text的第一個(gè)字符,并以此類推)。由于方括號(hào)操作的是編碼單元而非字符,因此無法正確訪問雙字節(jié)字符
var message = "A 𠮷 B" ; for (let i=0; i < message.length; i++) { console.log(message[i]); }
在這段代碼中,訪問message的length屬性獲取索引值,并通過方括號(hào)訪問來迭代并打印一個(gè)單字符字符串,但是輸出的結(jié)果卻與預(yù)期不符
A
B
由于雙字節(jié)字符被視作兩個(gè)獨(dú)立的編碼單元,從而最終在A與B之間打印出4個(gè)空行
所幸,ES6的目標(biāo)是全面支持Unicode,并且我們可以通過改變字符串的默認(rèn)迭代器來解決這個(gè)問題,使其操作字符而不是編碼單元。現(xiàn)在,修改前一個(gè)示例中字符串的默認(rèn)迭代器,讓for-of循環(huán)輸出正確的內(nèi)容
var message = "A 𠮷 B" ; for (let c of message) { console.log(c); }
這段代碼輸出以下內(nèi)容
A
𠮷
B
這個(gè)結(jié)果更符合預(yù)期,通過循環(huán)語句可以直接操作字符并成功打印出Unicode字符
【NodeList迭代器】
DOM標(biāo)準(zhǔn)中有一個(gè)NodeList類型,document對象中的所有元素都用這個(gè)類型來表示。對于編寫Web瀏覽器環(huán)境中的JS開發(fā)者來說,需要花點(diǎn)兒功夫去理解NodeList對象和數(shù)組之間的差異。二者都使用length屬性來表示集合中元素的數(shù)量,都可以通過方括號(hào)來訪問集合中的獨(dú)立元素。而在內(nèi)部實(shí)現(xiàn)中,二者的表現(xiàn)非常不一致,因而會(huì)造成很多困擾
自從ES6添加了默認(rèn)迭代器后,DOM定義中的NodeList類型(定義在HTML標(biāo)準(zhǔn)而不是ES6標(biāo)準(zhǔn)中)也擁有了默認(rèn)迭代器,其行為與數(shù)組的默認(rèn)迭代器完全一致。所以可以將NodeList應(yīng)用于for-of循環(huán)及其他支持對象默認(rèn)迭代器的地方
var divs = document.getElementsByTagName("div"); for (let div of divs) { console.log(div.id); }
在這段代碼中,通過調(diào)用getElementsByTagName()方法獲取到document對象中所有div元素的列表,在for-of循環(huán)中遍歷列表中的每一個(gè)元素并輸出元素ID,實(shí)際上是按照處理數(shù)組的方式來處理NodeList的
高級迭代器
迭代器的基礎(chǔ)功能可以輔助完成很多任務(wù),通過生成器創(chuàng)建迭代器的過程也很便捷,除了這些簡單的集合遍歷任務(wù)之外,迭代器也可以被用于完成一些復(fù)雜的任務(wù)
【給迭代器傳遞參數(shù)】
迭代器既可以用迭代器的next()方法返回值,也可以在生成器內(nèi)部使用yield關(guān)鍵字來生成值。如果給迭代器的next()方法傳遞參數(shù),則這個(gè)參數(shù)的值就會(huì)替代生成器內(nèi)部上條yield語句的返回值。而如果要實(shí)現(xiàn)更多像異步編程這樣的高級功能,那么這種給迭代器傳值的能力就變得至關(guān)重要
function *createIterator() { let first = yield 1; let second = yield first + 2; // 4 + 2 yield second + 3; // 5 + 3 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.next(5)); // "{ value: 8, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
第一次調(diào)用next()方法時(shí)無論傳入什么參數(shù)都會(huì)被丟棄。由于傳給next()方法的參數(shù)會(huì)替代上一次yield的返回值,而在第一次調(diào)用next()方法前不會(huì)執(zhí)行任何yield語句,因此在第一次調(diào)用next()方法時(shí)傳遞參數(shù)是毫無意義的
第二次調(diào)用next()方法傳入數(shù)值4作為參數(shù),它最后被賦值給生成器函數(shù)內(nèi)部的變量first。在一個(gè)含參yield語句中,表達(dá)式右側(cè)等價(jià)于第一次調(diào)用next()方法后的下一個(gè)返回值,表達(dá)式左側(cè)等價(jià)于第二次調(diào)用next()方法后,在函數(shù)繼續(xù)執(zhí)行前得到的返回值。第二次調(diào)用next()方法傳入的值為4,它會(huì)被賦值給變量first,函數(shù)則繼續(xù)執(zhí)行。第二條yield語句在第一次yield的結(jié)果上加了2,最終的返回值為6
第三次調(diào)用next()方法時(shí),傳入數(shù)值5,這個(gè)值被賦值給second,最后用于第三條yield語句并最終返回?cái)?shù)值8
【在迭代器中拋出錯(cuò)誤】
除了給迭代器傳遞數(shù)據(jù)外,還可以給它傳遞錯(cuò)誤條件。通過throw()方法,當(dāng)?shù)骰謴?fù)執(zhí)行時(shí)可令其拋出一個(gè)錯(cuò)誤。這種主動(dòng)拋出錯(cuò)誤的能力對于異步編程而言至關(guān)重要,也能提供模擬結(jié)束函數(shù)執(zhí)行的兩種方法(返回值或拋出錯(cuò)誤),從而增強(qiáng)生成器內(nèi)部的編程彈性。將錯(cuò)誤對象傳給throw()方法后,在迭代器繼續(xù)執(zhí)行時(shí)其會(huì)被拋出
function *createIterator() { let first = yield 1; let second = yield first + 2; // yield 4 + 2 ,然后拋出錯(cuò)誤 yield second + 3; // 永不會(huì)被執(zhí)行 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.throw(new Error("Boom"))); // 從生成器中拋出了錯(cuò)誤
在這個(gè)示例中,前兩個(gè)表達(dá)式正常求值,而調(diào)用throw()方法后,在繼續(xù)執(zhí)行l(wèi)et second求值前,錯(cuò)誤就會(huì)被拋出并阻止了代碼繼續(xù)執(zhí)行。這個(gè)過程與直接拋出錯(cuò)誤很相似,二者唯一的區(qū)別是拋出的時(shí)機(jī)不同
可以在生成器內(nèi)部通過try-catch代碼塊來捕獲這些錯(cuò)誤
function *createIterator() { let first = yield 1; let second; try { second = yield first + 2; // yield 4 + 2 ,然后拋出錯(cuò)誤 } catch (ex) { second = 6; // 當(dāng)出錯(cuò)時(shí),給變量另外賦值 } yield second + 3; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
在此示例中,try-catch代碼塊包裹著第二條yield語句。盡管這條語句本身沒有錯(cuò)誤,但在給變量second賦值前還是會(huì)主動(dòng)拋出錯(cuò)誤,catch代碼塊捕獲錯(cuò)誤后將second變量賦值為6,下一條yield語句繼續(xù)執(zhí)行后返回9
這里有一個(gè)有趣的現(xiàn)象調(diào)用throw()方法后也會(huì)像調(diào)用next()方法一樣返回一個(gè)結(jié)果對象。由于在生成器內(nèi)部捕獲了這個(gè)錯(cuò)誤,因而會(huì)繼續(xù)執(zhí)行下一條yield語句,最終返回?cái)?shù)值9
如此一來,next()和throw()就像是迭代器的兩條指令,調(diào)用next()方法命令迭代器繼續(xù)執(zhí)行(可能提供一個(gè)值),調(diào)用throw()方法也會(huì)命令迭代器繼續(xù)執(zhí)行,但同時(shí)也拋出一個(gè)錯(cuò)誤,在此之后的執(zhí)行過程取決于生成器內(nèi)部的代碼
在迭代器內(nèi)部,如果使用了yield語句,則可以通過next()方法和throw()方法控制執(zhí)行過程,當(dāng)然,也可以使用return語句返回一些與普通函數(shù)返回語句不太一樣的內(nèi)容
【生成器返回語句】
由于生成器也是函數(shù),因此可以通過return語句提前退出函數(shù)執(zhí)行,對于最后一次next()方法調(diào)用,可以主動(dòng)為其指定一個(gè)返回值。正如在其他函數(shù)中那樣,可以通過return語句指定一個(gè)返回值。而在生成器中,return表示所有操作已經(jīng)完成,屬性done被設(shè)置為true;如果同時(shí)提供了相應(yīng)的值,則屬性value會(huì)被設(shè)置為這個(gè)值
function *createIterator() { yield 1; return; yield 2; yield 3; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
這段代碼中的生成器包含多條yield語句和一條return語句,其中return語句緊隨第一條yield語句,其后的yield語句將不會(huì)被執(zhí)行
在return語句中也可以指定一個(gè)返回值,該值將被賦值給返回對象的value屬性
function *createIterator() { yield 1; return 42; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 42, done: true }" console.log(iterator.next()); // "{ value: undefined, done: true }"
在此示例中,第二次調(diào)用next()方法時(shí)返回對象的value屬性值為42,done屬性首次設(shè)為true;第三次調(diào)用next()方法依然返回一個(gè)對象,只是value屬性的值會(huì)變?yōu)閡ndefined。因此,通過return語句指定的返回值,只會(huì)在返回對象中出現(xiàn)一次,在后續(xù)調(diào)用返回的對象中,value屬性會(huì)被重置為undefined
[注意]展開運(yùn)算符與for-of循環(huán)語句會(huì)直接忽略通過return語句指定的任何返回值,只要done一變?yōu)閠rue就立即停止讀取其他的值。不管怎樣,迭代器的返回值依然是一個(gè)非常有用的特性
【委托生成器】
在某些情況下,我們需要將兩個(gè)迭代器合二為一,這時(shí)可以創(chuàng)建一個(gè)生成器,再給yield語句添加一個(gè)星號(hào),就可以將生成數(shù)據(jù)的過程委托給其他生成器。當(dāng)定義這些生成器時(shí),只需將星號(hào)放置在關(guān)鍵字yield和生成器的函數(shù)名之間即可
function *createNumberIterator() { yield 1; yield 2; } function *createColorIterator() { yield "red"; yield "green"; } function *createCombinedIterator() { yield *createNumberIterator(); yield *createColorIterator(); yield true; } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "red", done: false }" console.log(iterator.next()); // "{ value: "green", done: false }" console.log(iterator.next()); // "{ value: true, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
這里的生成器createCombinedIterator()先后委托了另外兩個(gè)生成器createNumberlterator()和createColorlterator()。僅根據(jù)迭代器的返回值來看,它就像是一個(gè)完整的迭代器,可以生成所有的值。每一次調(diào)用next()方法就會(huì)委托相應(yīng)的迭代器生成相應(yīng)的值,直到最后由createNumberlterator()和cpeateColorlterator()創(chuàng)建的迭代器無法返回更多的值,此時(shí)執(zhí)行最后一條yield語句并返回true
有了生成器委托這個(gè)新功能,可以進(jìn)一步利用生成器的返回值來處理復(fù)雜任務(wù)
function *createNumberIterator() { yield 1; yield 2; return 3; } function *createRepeatingIterator(count) { for (let i=0; i < count; i++) { yield "repeat"; } } function *createCombinedIterator() { let result = yield *createNumberIterator(); yield *createRepeatingIterator(result); } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
在生成器createCombinedlterator()中,執(zhí)行過程先被委托給了生成器createNumberlterator(),返回值會(huì)被賦值給變量result,執(zhí)行到return 3時(shí)會(huì)返回?cái)?shù)值3。這個(gè)值隨后被傳入createRepeatinglterator()作為它的參數(shù),因而生成字符串"repeat"的yield語句會(huì)被執(zhí)行三次
無論通過何種方式調(diào)用迭代器next()方法,數(shù)值3都不會(huì)被返回,它只存在于生成器createCombinedlterator()的內(nèi)部。但如果想輸出這個(gè)值,則可以額外添加一條yield語句
function *createNumberIterator() { yield 1; yield 2; return 3; } function *createRepeatingIterator(count) { for (let i=0; i < count; i++) { yield "repeat"; } } function *createCombinedIterator() { let result = yield *createNumberIterator(); yield result; yield *createRepeatingIterator(result); } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
此處新添加的yield語句顯式地輸出了生成器createNumberlterator()的返回值。
[注意]yield*也可直接應(yīng)用于字符串,例如yield* "hello",此時(shí)將使用字符串的默認(rèn)迭代器
異步任務(wù)執(zhí)行
生成器令人興奮的特性多與異步編程有關(guān),JS中的異步編程有利有弊:簡單任務(wù)的異步化非常容易;而復(fù)雜任務(wù)的異步化會(huì)帶來很多管理代碼的挑戰(zhàn)。由于生成器支持在函數(shù)中暫停代碼執(zhí)行,因而可以深入挖掘異步處理的更多用法
執(zhí)行異步操作的傳統(tǒng)方式一般是調(diào)用一個(gè)函數(shù)并執(zhí)行相應(yīng)回調(diào)函數(shù)
let fs = require("fs"); fs.readFile("config.json", function(err, contents) { if (err) { throw err; } doSomethingWith(contents); console.log("Done"); });
調(diào)用fs.readFile()方法時(shí)要求傳入要讀取的文件名和一個(gè)回調(diào)函數(shù),操作結(jié)束后會(huì)調(diào)用該回調(diào)函數(shù)并檢查是否存在錯(cuò)誤,如果沒有就可以處理返回的內(nèi)容。如果要執(zhí)行的任務(wù)很少,那么這樣的方式可以很好地完成任務(wù);如若需要嵌套回調(diào)或序列化一系列的異步操作,事情會(huì)變得非常復(fù)雜。此時(shí),生成器和yield語句就派上用場了
【簡單任務(wù)執(zhí)行器】
由于執(zhí)行yield語句會(huì)暫停當(dāng)前函數(shù)的執(zhí)行過程并等待下一次調(diào)用next()方法,因此可以創(chuàng)建一個(gè)函數(shù),在函數(shù)中調(diào)用生成器生成相應(yīng)的迭代器,從而在不用回調(diào)函數(shù)的基礎(chǔ)上實(shí)現(xiàn)異步調(diào)用next()方法
function run(taskDef) { // 創(chuàng)建迭代器,讓它在別處可用 let task = taskDef(); // 啟動(dòng)任務(wù) let result = task.next(); // 遞歸使用函數(shù)來保持對 next() 的調(diào)用 function step() { // 如果還有更多要做的 if (!result.done) { result = task.next(); step(); } } // 開始處理過程 step(); }
函數(shù)run()接受一個(gè)生成器函數(shù)作為參數(shù),這個(gè)函數(shù)定義了后續(xù)要執(zhí)行的任務(wù),生成一個(gè)迭代器并將它儲(chǔ)存在變量task中。首次調(diào)用迭代器的next()方法時(shí),返回的結(jié)果被儲(chǔ)存起來稍后繼續(xù)使用。step()函數(shù)會(huì)檢查result.done的值,如果為false則執(zhí)行迭代器的next()方法,并再次執(zhí)行step()操作。每次調(diào)用next()方法時(shí),返回的最新信息總會(huì)覆寫變量result。在代碼的最后,初始化執(zhí)行step()函數(shù)并開始整個(gè)的迭代過程,每次通過檢查result.done來確定是否有更多任務(wù)需要執(zhí)行
借助這個(gè)run()函數(shù),可以像這樣執(zhí)行一個(gè)包含多條yield語句的生成器
run(function*() { console.log(1); yield; console.log(2); yield; console.log(3); });
這個(gè)示例最終會(huì)向控制臺(tái)輸出多次調(diào)用next()方法的結(jié)果,分別為數(shù)值1、2和3。當(dāng)然,簡單輸出迭代次數(shù)不足以展示迭代器高級功能的實(shí)用之處,下一步將在迭代器與調(diào)用者之間互相傳值
【向任務(wù)執(zhí)行器傳遞數(shù)據(jù)】
給任務(wù)執(zhí)行器傳遞數(shù)據(jù)的最簡單辦法是,將值通過迭代器的next()方法傳入作為yield的生成值供下次調(diào)用。在這段代碼中,只需將result.value傳入next()方法即可
function run(taskDef) { // 創(chuàng)建迭代器,讓它在別處可用 let task = taskDef(); // 啟動(dòng)任務(wù) let result = task.next(); // 遞歸使用函數(shù)來保持對 next() 的調(diào)用 function step() { // 如果還有更多要做的 if (!result.done) { result = task.next(result.value); step(); } } // 開始處理過程 step(); }
現(xiàn)在result.value作為next()方法的參數(shù)被傳入,這樣就可以在yield調(diào)用之間傳遞數(shù)據(jù)了
run(function*() { let value = yield 1; console.log(value); // 1 value = yield value + 3; console.log(value); // 4 });
此示例會(huì)向控制臺(tái)輸出兩個(gè)數(shù)值1和4。其中,數(shù)值1取自yield 1語句中回傳給變量value的值;而4取自給變量value加3后回傳給value的值?,F(xiàn)在數(shù)據(jù)已經(jīng)能夠在yield調(diào)用間互相傳遞了,只需一個(gè)小小改變便能支持異步調(diào)用
【異步任務(wù)執(zhí)行器】
之前的示例只是在多個(gè)yield調(diào)用間來回傳遞靜態(tài)數(shù)據(jù),而等待一個(gè)異步過程有些不同。任務(wù)執(zhí)行器需要知曉回調(diào)函數(shù)是什么以及如何使用它。由于yield表達(dá)式會(huì)將值返回給任務(wù)執(zhí)行器,所有的函數(shù)調(diào)用都會(huì)返回一個(gè)值,因而在某種程度上這也是一個(gè)異步操作,任務(wù)執(zhí)行器會(huì)一直等待直到操作完成
下面定義一個(gè)異步操作
function fetchData() { return function(callback) { callback(null, "Hi!"); }; }
本示例的原意是讓任務(wù)執(zhí)行器調(diào)用的所有函數(shù)都返回一個(gè)可以執(zhí)行回調(diào)過程的函數(shù),此處fetchData()函數(shù)的返回值是一個(gè)可接受回調(diào)函數(shù)作為參數(shù)的函數(shù),當(dāng)調(diào)用它時(shí)會(huì)傳入一個(gè)字符串"Hi!"作為回調(diào)函數(shù)的參數(shù)并執(zhí)行。參數(shù)callback需要通過任務(wù)執(zhí)行器指定,以確?;卣{(diào)函數(shù)執(zhí)行時(shí)可以與底層迭代器正確交互。盡管fetchData()是同步函數(shù),但簡單添加一個(gè)延遲方法即可將其變?yōu)楫惒胶瘮?shù)
function fetchData() { return function(callback) { setTimeout(function() { callback(null, "Hi!"); }, 50); }; }
在這個(gè)版本的fetchData()函數(shù)中,讓回調(diào)函數(shù)延遲了50ms再被調(diào)用,所以這種模式在同步和異步狀態(tài)下都運(yùn)行良好。只需保證每個(gè)要通過yield關(guān)鍵字調(diào)用的函數(shù)都按照與之相同的模式編寫
理解了函數(shù)中異步過程的運(yùn)作方式,可以將任務(wù)執(zhí)行器稍作修改。當(dāng)result.value是一個(gè)函數(shù)時(shí),任務(wù)執(zhí)行器會(huì)先執(zhí)行這個(gè)函數(shù)再將結(jié)果傳入next()方法
function run(taskDef) { // 創(chuàng)建迭代器,讓它在別處可用 let task = taskDef(); // 啟動(dòng)任務(wù) let result = task.next(); // 遞歸使用函數(shù)來保持對 next() 的調(diào)用 function step() { // 如果還有更多要做的 if (!result.done) { if (typeof result.value === "function") { result.value(function(err, data) { if (err) { result = task.throw(err); return; } result = task.next(data); step(); }); } else { result = task.next(result.value); step(); } } } // 開始處理過程 step(); }
通過===操作符檢査后,如果result.value是一個(gè)函數(shù),會(huì)傳入一個(gè)回調(diào)函數(shù)作為參數(shù)調(diào)用它,回調(diào)函數(shù)遵循Node.js有關(guān)執(zhí)行錯(cuò)誤的約定:所有可能的錯(cuò)誤放在第一個(gè)參數(shù)(err)中,結(jié)果放在第二個(gè)參數(shù)中。如果傳入了err,意味著執(zhí)行過程中產(chǎn)生了錯(cuò)誤,這時(shí)通過task.throw()正確輸出錯(cuò)誤對象;如果沒有錯(cuò)誤產(chǎn)生,data被傳入task.next()作為結(jié)果儲(chǔ)存起來,并繼續(xù)執(zhí)行step()。如果result.value不是一個(gè)函數(shù),則直接將其傳入next()方法
現(xiàn)在,這個(gè)新版的任務(wù)執(zhí)行器已經(jīng)可以用于所有的異步任務(wù)了。在Node.js環(huán)境中,如果要從文件中讀取一些數(shù)據(jù),需要在fs.readFile()外圍創(chuàng)建一個(gè)包裝器(wrapper),并返回一個(gè)與fetchData()類似的函數(shù)
let fs = require("fs"); function readFile(filename) { return function(callback) { fs.readFile(filename, callback); }; }
readFile()接受一個(gè)文件名作為參數(shù),返回一個(gè)可以執(zhí)行回調(diào)函數(shù)的函數(shù)?;卣{(diào)函數(shù)被直接傳入fs.readFile()方法,讀取完成后會(huì)執(zhí)行它
run(function*() { let contents = yield readFile("config.json"); doSomethingWith(contents); console.log("Done"); });
在這段代碼中沒有任何回調(diào)變量,異步的readFile()操作卻正常執(zhí)行,除了yield關(guān)鍵字外,其他代碼與同步代碼完全一樣,只不過函數(shù)執(zhí)行的是異步操作。所以遵循相同的接口,可以編寫一些讀起來像是同步代碼的異步邏輯
當(dāng)然,這些示例中使用的模式也有缺點(diǎn),也就是不能百分百確認(rèn)函數(shù)中返回的其他函數(shù)一定是異步的。著眼當(dāng)下,最重要的是能理解任務(wù)執(zhí)行過程背后的理論知識(shí)
以上這篇詳談ES6中的迭代器(Iterator)和生成器(Generator)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持創(chuàng)新互聯(lián)。
名稱欄目:詳談ES6中的迭代器(Iterator)和生成器(Generator)
當(dāng)前地址:http://muchs.cn/article24/gpheje.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站內(nèi)鏈、響應(yīng)式網(wǎng)站、全網(wǎng)營銷推廣、網(wǎng)站設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)