<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # Iterator和for...of循環 ## Iterator(遍歷器)的概念 JavaScript原有的表示“集合”的數據結構,主要是數組(Array)和對象(Object),ES6又添加了Map和Set。這樣就有了四種數據集合,用戶還可以組合使用它們,定義自己的數據結構,比如數組的成員是Map,Map的成員是對象。這樣就需要一種統一的接口機制,來處理所有不同的數據結構。 遍歷器(Iterator)就是這樣一種機制。它是一種接口,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署Iterator接口,就可以完成遍歷操作(即依次處理該數據結構的所有成員)。 Iterator的作用有三個:一是為各種數據結構,提供一個統一的、簡便的訪問接口;二是使得數據結構的成員能夠按某種次序排列;三是ES6創造了一種新的遍歷命令`for...of`循環,Iterator接口主要供`for...of`消費。 Iterator的遍歷過程是這樣的。 (1)創建一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。 (2)第一次調用指針對象的`next`方法,可以將指針指向數據結構的第一個成員。 (3)第二次調用指針對象的`next`方法,指針就指向數據結構的第二個成員。 (4)不斷調用指針對象的`next`方法,直到它指向數據結構的結束位置。 每一次調用`next`方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含`value`和`done`兩個屬性的對象。其中,`value`屬性是當前成員的值,`done`屬性是一個布爾值,表示遍歷是否結束。 下面是一個模擬`next`方法返回值的例子。 ~~~ var it = makeIterator(['a', 'b']); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true } function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true}; } }; } ~~~ 上面代碼定義了一個`makeIterator`函數,它是一個遍歷器生成函數,作用就是返回一個遍歷器對象。對數組`['a', 'b']`執行這個函數,就會返回該數組的遍歷器對象(即指針對象)`it`。 指針對象的`next`方法,用來移動指針。開始時,指針指向數組的開始位置。然后,每次調用`next`方法,指針就會指向數組的下一個成員。第一次調用,指向`a`;第二次調用,指向`b`。 `next`方法返回一個對象,表示當前數據成員的信息。這個對象具有`value`和`done`兩個屬性,`value`屬性返回當前位置的成員,`done`屬性是一個布爾值,表示遍歷是否結束,即是否還有必要再一次調用`next`方法。 總之,調用指針對象的`next`方法,就可以遍歷事先給定的數據結構。 對于遍歷器對象來說,`done: false`和`value: undefined`屬性都是可以省略的,因此上面的`makeIterator`函數可以簡寫成下面的形式。 ~~~ function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++]} : {done: true}; } }; } ~~~ 由于Iterator只是把接口規格加到數據結構之上,所以,遍歷器與它所遍歷的那個數據結構,實際上是分開的,完全可以寫出沒有對應數據結構的遍歷器對象,或者說用遍歷器對象模擬出數據結構。下面是一個無限運行的遍歷器對象的例子。 ~~~ var it = idMaker(); it.next().value // '0' it.next().value // '1' it.next().value // '2' // ... function idMaker() { var index = 0; return { next: function() { return {value: index++, done: false}; } }; } ~~~ 上面的例子中,遍歷器生成函數`idMaker`,返回一個遍歷器對象(即指針對象)。但是并沒有對應的數據結構,或者說,遍歷器對象自己描述了一個數據結構出來。 在ES6中,有些數據結構原生具備Iterator接口(比如數組),即不用任何處理,就可以被`for...of`循環遍歷,有些就不行(比如對象)。原因在于,這些數據結構原生部署了`Symbol.iterator`屬性(詳見下文),另外一些數據結構沒有。凡是部署了`Symbol.iterator`屬性的數據結構,就稱為部署了遍歷器接口。調用這個接口,就會返回一個遍歷器對象。 如果使用TypeScript的寫法,遍歷器接口(Iterable)、指針對象(Iterator)和next方法返回值的規格可以描述如下。 ~~~ interface Iterable { [Symbol.iterator]() : Iterator, } interface Iterator { next(value?: any) : IterationResult, } interface IterationResult { value: any, done: boolean, } ~~~ ## 數據結構的默認Iterator接口 Iterator接口的目的,就是為所有數據結構,提供了一種統一的訪問機制,即`for...of`循環(詳見下文)。當使用`for...of`循環遍歷某種數據結構時,該循環會自動去尋找Iterator接口。 ES6規定,默認的Iterator接口部署在數據結構的`Symbol.iterator`屬性,或者說,一個數據結構只要具有`Symbol.iterator`屬性,就可以認為是“可遍歷的”(iterable)。調用`Symbol.iterator`方法,就會得到當前數據結構默認的遍歷器生成函數。`Symbol.iterator`本身是一個表達式,返回Symbol對象的`iterator`屬性,這是一個預定義好的、類型為Symbol的特殊值,所以要放在方括號內(請參考Symbol一章)。 在ES6中,有三類數據結構原生具備Iterator接口:數組、某些類似數組的對象、Set和Map結構。 ~~~ let arr = ['a', 'b', 'c']; let iter = arr[Symbol.iterator](); iter.next() // { value: 'a', done: false } iter.next() // { value: 'b', done: false } iter.next() // { value: 'c', done: false } iter.next() // { value: undefined, done: true } ~~~ 上面代碼中,變量`arr`是一個數組,原生就具有遍歷器接口,部署在`arr`的`Symbol.iterator`屬性上面。所以,調用這個屬性,就得到遍歷器對象。 上面提到,原生就部署Iterator接口的數據結構有三類,對于這三類數據結構,不用自己寫遍歷器生成函數,`for...of`循環會自動遍歷它們。除此之外,其他數據結構(主要是對象)的Iterator接口,都需要自己在`Symbol.iterator`屬性上面部署,這樣才會被`for...of`循環遍歷。 對象(Object)之所以沒有默認部署Iterator接口,是因為對象的哪個屬性先遍歷,哪個屬性后遍歷是不確定的,需要開發者手動指定。本質上,遍歷器是一種線性處理,對于任何非線性的數據結構,部署遍歷器接口,就等于部署一種線性轉換。不過,嚴格地說,對象部署遍歷器接口并不是很必要,因為這時對象實際上被當作Map結構使用,ES5沒有Map結構,而ES6原生提供了。 一個對象如果要有可被`for...of`循環調用的Iterator接口,就必須在`Symbol.iterator`的屬性上部署遍歷器生成方法(原型鏈上的對象具有該方法也可)。 ~~~ class RangeIterator { constructor(start, stop) { this.value = start; this.stop = stop; } [Symbol.iterator]() { return this; } next() { var value = this.value; if (value < this.stop) { this.value++; return {done: false, value: value}; } else { return {done: true, value: undefined}; } } } function range(start, stop) { return new RangeIterator(start, stop); } for (var value of range(0, 3)) { console.log(value); } ~~~ 上面代碼是一個類部署Iterator接口的寫法。`Symbol.iterator`屬性對應一個函數,執行后返回當前對象的遍歷器對象。 下面是通過遍歷器實現指針結構的例子。 ~~~ function Obj(value) { this.value = value; this.next = null; } Obj.prototype[Symbol.iterator] = function() { var iterator = { next: next }; var current = this; function next() { if (current) { var value = current.value; current = current.next; return { done: false, value: value }; } else { return { done: true }; } } return iterator; } var one = new Obj(1); var two = new Obj(2); var three = new Obj(3); one.next = two; two.next = three; for (var i of one){ console.log(i); } // 1 // 2 // 3 ~~~ 上面代碼首先在構造函數的原型鏈上部署`Symbol.iterator`方法,調用該方法會返回遍歷器對象`iterator`,調用該對象的`next`方法,在返回一個值的同時,自動將內部指針移到下一個實例。 下面是另一個為對象添加Iterator接口的例子。 ~~~ let obj = { data: [ 'hello', 'world' ], [Symbol.iterator]() { const self = this; let index = 0; return { next() { if (index < self.data.length) { return { value: self.data[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } }; ~~~ 對于類似數組的對象(存在數值鍵名和length屬性),部署Iterator接口,有一個簡便方法,就是`Symbol.iterator`方法直接引用數組的Iterator接口。 ~~~ NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // 或者 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]; [...document.querySelectorAll('div')] // 可以執行了 ~~~ 下面是類似數組的對象調用數組的`Symbol.iterator`方法的例子。 ~~~ let iterable = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; for (let item of iterable) { console.log(item); // 'a', 'b', 'c' } ~~~ 注意,普通對象部署數組的`Symbol.iterator`方法,并無效果。 ~~~ let iterable = { a: 'a', b: 'b', c: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; for (let item of iterable) { console.log(item); // undefined, undefined, undefined } ~~~ 如果`Symbol.iterator`方法對應的不是遍歷器生成函數(即會返回一個遍歷器對象),解釋引擎將會報錯。 ~~~ var obj = {}; obj[Symbol.iterator] = () => 1; [...obj] // TypeError: [] is not a function ~~~ 上面代碼中,變量obj的Symbol.iterator方法對應的不是遍歷器生成函數,因此報錯。 有了遍歷器接口,數據結構就可以用`for...of`循環遍歷(詳見下文),也可以使用`while`循環遍歷。 ~~~ var $iterator = ITERABLE[Symbol.iterator](); var $result = $iterator.next(); while (!$result.done) { var x = $result.value; // ... $result = $iterator.next(); } ~~~ 上面代碼中,`ITERABLE`代表某種可遍歷的數據結構,`$iterator`是它的遍歷器對象。遍歷器對象每次移動指針(`next`方法),都檢查一下返回值的`done`屬性,如果遍歷還沒結束,就移動遍歷器對象的指針到下一步(`next`方法),不斷循環。 ## 調用Iterator接口的場合 有一些場合會默認調用Iterator接口(即`Symbol.iterator`方法),除了下文會介紹的`for...of`循環,還有幾個別的場合。 **(1)解構賦值** 對數組和Set結構進行解構賦值時,會默認調用`Symbol.iterator`方法。 ~~~ let set = new Set().add('a').add('b').add('c'); let [x,y] = set; // x='a'; y='b' let [first, ...rest] = set; // first='a'; rest=['b','c']; ~~~ **(2)擴展運算符** 擴展運算符(...)也會調用默認的iterator接口。 ~~~ // 例一 var str = 'hello'; [...str] // ['h','e','l','l','o'] // 例二 let arr = ['b', 'c']; ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd'] ~~~ 上面代碼的擴展運算符內部就調用Iterator接口。 實際上,這提供了一種簡便機制,可以將任何部署了Iterator接口的數據結構,轉為數組。也就是說,只要某個數據結構部署了Iterator接口,就可以對它使用擴展運算符,將其轉為數組。 ~~~ let arr = [...iterable]; ~~~ **(3)yield*** yield*后面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。 ~~~ let generator = function* () { yield 1; yield* [2,3,4]; yield 5; }; var iterator = generator(); iterator.next() // { value: 1, done: false } iterator.next() // { value: 2, done: false } iterator.next() // { value: 3, done: false } iterator.next() // { value: 4, done: false } iterator.next() // { value: 5, done: false } iterator.next() // { value: undefined, done: true } ~~~ **(4)其他場合** 由于數組的遍歷會調用遍歷器接口,所以任何接受數組作為參數的場合,其實都調用了遍歷器接口。下面是一些例子。 * for...of * Array.from() * Map(), Set(), WeakMap(), WeakSet()(比如`new Map([['a',1],['b',2]])`) * Promise.all() * Promise.race() ## 字符串的Iterator接口 字符串是一個類似數組的對象,也原生具有Iterator接口。 ~~~ var someString = "hi"; typeof someString[Symbol.iterator] // "function" var iterator = someString[Symbol.iterator](); iterator.next() // { value: "h", done: false } iterator.next() // { value: "i", done: false } iterator.next() // { value: undefined, done: true } ~~~ 上面代碼中,調用`Symbol.iterator`方法返回一個遍歷器對象,在這個遍歷器上可以調用next方法,實現對于字符串的遍歷。 可以覆蓋原生的`Symbol.iterator`方法,達到修改遍歷器行為的目的。 ~~~ var str = new String("hi"); [...str] // ["h", "i"] str[Symbol.iterator] = function() { return { next: function() { if (this._first) { this._first = false; return { value: "bye", done: false }; } else { return { done: true }; } }, _first: true }; }; [...str] // ["bye"] str // "hi" ~~~ 上面代碼中,字符串str的`Symbol.iterator`方法被修改了,所以擴展運算符(`...`)返回的值變成了`bye`,而字符串本身還是`hi`。 ## Iterator接口與Generator函數 `Symbol.iterator`方法的最簡單實現,還是使用下一章要介紹的Generator函數。 ~~~ var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3] // 或者采用下面的簡潔寫法 let obj = { * [Symbol.iterator]() { yield 'hello'; yield 'world'; } }; for (let x of obj) { console.log(x); } // hello // world ~~~ 上面代碼中,`Symbol.iterator`方法幾乎不用部署任何代碼,只要用yield命令給出每一步的返回值即可。 ## 遍歷器對象的return(),throw() 遍歷器對象除了具有`next`方法,還可以具有`return`方法和`throw`方法。如果你自己寫遍歷器對象生成函數,那么`next`方法是必須部署的,`return`方法和`throw`方法是否部署是可選的。 `return`方法的使用場合是,如果`for...of`循環提前退出(通常是因為出錯,或者有`break`語句或`continue`語句),就會調用`return`方法。如果一個對象在完成遍歷前,需要清理或釋放資源,就可以部署`return`方法。 ~~~ function readLinesSync(file) { return { next() { if (file.isAtEndOfFile()) { file.close(); return { done: true }; } }, return() { file.close(); return { done: true }; }, }; } ~~~ 上面代碼中,函數`readLinesSync`接受一個文件對象作為參數,返回一個遍歷器對象,其中除了`next`方法,還部署了`return`方法。下面,我們讓文件的遍歷提前返回,這樣就會觸發執行`return`方法。 ~~~ for (let line of readLinesSync(fileName)) { console.log(x); break; } ~~~ 注意,`return`方法必須返回一個對象,這是Generator規格決定的。 `throw`方法主要是配合Generator函數使用,一般的遍歷器對象用不到這個方法。請參閱《Generator函數》一章。 ## for...of循環 ES6借鑒C++、Java、C#和Python語言,引入了`for...of`循環,作為遍歷所有數據結構的統一的方法。一個數據結構只要部署了`Symbol.iterator`屬性,就被視為具有iterator接口,就可以用`for...of`循環遍歷它的成員。也就是說,`for...of`循環內部調用的是數據結構的`Symbol.iterator`方法。 for...of循環可以使用的范圍包括數組、Set和Map結構、某些類似數組的對象(比如arguments對象、DOM NodeList對象)、后文的Generator對象,以及字符串。 ### 數組 數組原生具備iterator接口,`for...of`循環本質上就是調用這個接口產生的遍歷器,可以用下面的代碼證明。 ~~~ const arr = ['red', 'green', 'blue']; let iterator = arr[Symbol.iterator](); for(let v of arr) { console.log(v); // red green blue } for(let v of iterator) { console.log(v); // red green blue } ~~~ 上面代碼的`for...of`循環的兩種寫法是等價的。 `for...of`循環可以代替數組實例的`forEach`方法。 ~~~ const arr = ['red', 'green', 'blue']; arr.forEach(function (element, index) { console.log(element); // red green blue console.log(index); // 0 1 2 }); ~~~ JavaScript原有的`for...in`循環,只能獲得對象的鍵名,不能直接獲取鍵值。ES6提供`for...of`循環,允許遍歷獲得鍵值。 ~~~ var arr = ['a', 'b', 'c', 'd']; for (let a in arr) { console.log(a); // 0 1 2 3 } for (let a of arr) { console.log(a); // a b c d } ~~~ 上面代碼表明,`for...in`循環讀取鍵名,`for...of`循環讀取鍵值。如果要通過`for...of`循環,獲取數組的索引,可以借助數組實例的`entries`方法和`keys`方法,參見《數組的擴展》章節。 `for...of`循環調用遍歷器接口,數組的遍歷器接口只返回具有數字索引的屬性。這一點跟`for...in`循環也不一樣。 ~~~ let arr = [3, 5, 7]; arr.foo = 'hello'; for (let i in arr) { console.log(i); // "0", "1", "2", "foo" } for (let i of arr) { console.log(i); // "3", "5", "7" } ~~~ 上面代碼中,`for...of`循環不會返回數組`arr`的`foo`屬性。 ### Set和Map結構 Set和Map結構也原生具有Iterator接口,可以直接使用`for...of`循環。 ~~~ var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]); for (var e of engines) { console.log(e); } // Gecko // Trident // Webkit var es6 = new Map(); es6.set("edition", 6); es6.set("committee", "TC39"); es6.set("standard", "ECMA-262"); for (var [name, value] of es6) { console.log(name + ": " + value); } // edition: 6 // committee: TC39 // standard: ECMA-262 ~~~ 上面代碼演示了如何遍歷Set結構和Map結構。值得注意的地方有兩個,首先,遍歷的順序是按照各個成員被添加進數據結構的順序。其次,Set結構遍歷時,返回的是一個值,而Map結構遍歷時,返回的是一個數組,該數組的兩個成員分別為當前Map成員的鍵名和鍵值。 ~~~ let map = new Map().set('a', 1).set('b', 2); for (let pair of map) { console.log(pair); } // ['a', 1] // ['b', 2] for (let [key, value] of map) { console.log(key + ' : ' + value); } // a : 1 // b : 2 ~~~ ### 計算生成的數據結構 有些數據結構是在現有數據結構的基礎上,計算生成的。比如,ES6的數組、Set、Map都部署了以下三個方法,調用后都返回遍歷器對象。 * `entries()`?返回一個遍歷器對象,用來遍歷`[鍵名, 鍵值]`組成的數組。對于數組,鍵名就是索引值;對于Set,鍵名與鍵值相同。Map結構的iterator接口,默認就是調用entries方法。 * `keys()`?返回一個遍歷器對象,用來遍歷所有的鍵名。 * `values()`?返回一個遍歷器對象,用來遍歷所有的鍵值。 這三個方法調用后生成的遍歷器對象,所遍歷的都是計算生成的數據結構。 ~~~ let arr = ['a', 'b', 'c']; for (let pair of arr.entries()) { console.log(pair); } // [0, 'a'] // [1, 'b'] // [2, 'c'] ~~~ ### 類似數組的對象 類似數組的對象包括好幾類。下面是`for...of`循環用于字符串、DOM NodeList對象、arguments對象的例子。 ~~~ // 字符串 let str = "hello"; for (let s of str) { console.log(s); // h e l l o } // DOM NodeList對象 let paras = document.querySelectorAll("p"); for (let p of paras) { p.classList.add("test"); } // arguments對象 function printArgs() { for (let x of arguments) { console.log(x); } } printArgs('a', 'b'); // 'a' // 'b' ~~~ 對于字符串來說,`for...of`循環還有一個特點,就是會正確識別32位UTF-16字符。 ~~~ for (let x of 'a\uD83D\uDC0A') { console.log(x); } // 'a' // '\uD83D\uDC0A' ~~~ 并不是所有類似數組的對象都具有iterator接口,一個簡便的解決方法,就是使用Array.from方法將其轉為數組。 ~~~ let arrayLike = { length: 2, 0: 'a', 1: 'b' }; // 報錯 for (let x of arrayLike) { console.log(x); } // 正確 for (let x of Array.from(arrayLike)) { console.log(x); } ~~~ ### 對象 對于普通的對象,`for...of`結構不能直接使用,會報錯,必須部署了iterator接口后才能使用。但是,這樣情況下,`for...in`循環依然可以用來遍歷鍵名。 ~~~ var es6 = { edition: 6, committee: "TC39", standard: "ECMA-262" }; for (e in es6) { console.log(e); } // edition // committee // standard for (e of es6) { console.log(e); } // TypeError: es6 is not iterable ~~~ 上面代碼表示,對于普通的對象,`for...in`循環可以遍歷鍵名,`for...of`循環會報錯。 一種解決方法是,使用`Object.keys`方法將對象的鍵名生成一個數組,然后遍歷這個數組。 ~~~ for (var key of Object.keys(someObject)) { console.log(key + ": " + someObject[key]); } ~~~ 在對象上部署iterator接口的代碼,參見本章前面部分。一個方便的方法是將數組的`Symbol.iterator`屬性,直接賦值給其他對象的`Symbol.iterator`屬性。比如,想要讓`for...of`環遍歷jQuery對象,只要加上下面這一行就可以了。 ~~~ jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; ~~~ 另一個方法是使用Generator函數將對象重新包裝一下。 ~~~ function* entries(obj) { for (let key of Object.keys(obj)) { yield [key, obj[key]]; } } for (let [key, value] of entries(obj)) { console.log(key, "->", value); } // a -> 1 // b -> 2 // c -> 3 ~~~ ### 與其他遍歷語法的比較 以數組為例,JavaScript提供多種遍歷語法。最原始的寫法就是for循環。 ~~~ for (var index = 0; index < myArray.length; index++) { console.log(myArray[index]); } ~~~ 這種寫法比較麻煩,因此數組提供內置的forEach方法。 ~~~ myArray.forEach(function (value) { console.log(value); }); ~~~ 這種寫法的問題在于,無法中途跳出`forEach`循環,break命令或return命令都不能奏效。 `for...in`循環可以遍歷數組的鍵名。 ~~~ for (var index in myArray) { console.log(myArray[index]); } ~~~ for...in循環有幾個缺點。 * 數組的鍵名是數字,但是for...in循環是以字符串作為鍵名“0”、“1”、“2”等等。 * for...in循環不僅遍歷數字鍵名,還會遍歷手動添加的其他鍵,甚至包括原型鏈上的鍵。 * 某些情況下,for...in循環會以任意順序遍歷鍵名。 總之,`for...in`循環主要是為遍歷對象而設計的,不適用于遍歷數組。 `for...of`循環相比上面幾種做法,有一些顯著的優點。 ~~~ for (let value of myArray) { console.log(value); } ~~~ * 有著同for...in一樣的簡潔語法,但是沒有for...in那些缺點。 * 不同用于forEach方法,它可以與break、continue和return配合使用。 * 提供了遍歷所有數據結構的統一操作接口。 下面是一個使用break語句,跳出`for...of`循環的例子。 ~~~ for (var n of fibonacci) { if (n > 1000) break; console.log(n); } ~~~ 上面的例子,會輸出斐波納契數列小于等于1000的項。如果當前項大于1000,就會使用break語句跳出`for...of`循環。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看