精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

理解 Wasm 基礎(chǔ)概念,了解 Wasm 是如何被加載運行的?

開發(fā) 前端
Wasm 是 js 的一個強(qiáng)有力的補(bǔ)充,未來可期,在一些領(lǐng)域比如圖像處理、音視頻處理大有可為。但也不得不承認(rèn) Wasm 并不能很簡單地就能給應(yīng)用提高性能的,因為安全原因,相比原生是有一定性能損失的。

大家好,我是前端西瓜哥,這次帶大家來簡單系統(tǒng)學(xué)習(xí)一下 wasm(WebAssembly)。

示例源碼在這個 github 倉庫,可自行下載運行:

https://github.com/F-star/wasm-demo。

wasm 是如何被加載運行的?

wasm 文件本身并不能像 JavaScript 一樣,下載完成后就立即執(zhí)行。

它更類似于 webgl 編譯著色器代碼,需要調(diào)用 JavaScript 提供的 API 去編譯執(zhí)行。

wasm 被加載并執(zhí)行的過程一般為:

  • 請求 wasm 文件。
  • 轉(zhuǎn)換為 ArrayBuffer 格式(也就是字節(jié)數(shù)組)。
  • 編譯并返回 Module 對象(異步的,可使用阻塞寫法)。
  • 基于 Module 創(chuàng)建一個 instance 實例(異步的,可使用阻塞寫法) 。instance 的 exports 對象下為 wasm 暴露出來的方法和屬性。創(chuàng)建 instance 有時需要提供一個額外的 importObject 對象,后文再細(xì)說。
  • 執(zhí)行 JavaScript 代碼,調(diào)用 wasm 的方法,進(jìn)行數(shù)據(jù)的交換。

代碼實例:

fetch('./add.wasm')
  .then(rep => rep.arrayBuffer()) // 轉(zhuǎn) ArrayBuffer
  .then(bytes => WebAssembly.compile(bytes)) // 編譯為 module 對象
  .then(module => WebAssembly.instantiate(module)) // 創(chuàng)建 instance 對象
  .then(instance => {
   // 拿到 wasm 文件暴露的 add 方法
    const { add } = instance.exports;
    console.log(add(12, 34));
  });

上面是為了讓大家理解所有步驟,所以寫得很繁瑣。

我們有簡單寫法,用一個 API 把步驟 1、2、3、4 組合在一起:

WebAssembly.instantiateStreaming(fetch('./add.wasm')).then(res => {
  const { module, instance } = res;
  const { add } = instance.exports;
  console.log(add(12, 34));
});

WebAssembly.instantiateStreaming 支持流式編譯,在 wasm 文件下載過程中就開始編譯了,并最后會一次性返回編譯和實例化產(chǎn)生的 module 和 instance 對象。

wasm 目前現(xiàn)在無法像 ES Module 一樣,通過 import 的方式直接被引入(<script type="module">),將來會支持,且在提案中,但不會很快。

wat:wasm 文本格式

先寫一個 wasm。

原來我打算用 C 寫的,然后用 Emscripten 編譯,但我發(fā)現(xiàn)編譯出來的 wasm 有很多和 C 有關(guān)的冗余的代碼,且需要配合生成好的代碼量巨多的膠水 JavaScript 文件,有不少雜音。

為了更簡單些,我選擇寫 wat,然后轉(zhuǎn)為 wasm。

wat 指的是 wasm 的文本格式(WebAssembly text format)。wat 是一種低級語言,使用的是基于 S-表達(dá)式 的文本寫法,可以直接映射為 WASM 的二進(jìn)制指令,你可以把它類比為匯編語言。

因為用 wat 手寫復(fù)雜邏輯并不可行,最后還是會用 C 或 Rust 這些高級語言去寫業(yè)務(wù)。

所以這里我不會講太多 wat 語法,目光更聚焦在 探究 wasm 是怎么和 js 通信的。

要實現(xiàn) wat 轉(zhuǎn) wasm,通常需要安裝 WABT(The WebAssembly Binary Toolkit)工具集,用 wat2wasm 命令行工具進(jìn)行轉(zhuǎn)換。

如果覺得安裝麻煩,可以用 WABT 提供的一個在線轉(zhuǎn)換工具,貼 wat 文本上去點 download 按鈕即可得到 wasm。

官方有提供 VSCode 插件,建議安裝,可以高亮 wat 語法。

另外可以選中文件右鍵菜單可進(jìn)行 wat 和 wasm 互轉(zhuǎn),但有點問題,一些正確的 wat 也會轉(zhuǎn)換失敗。

每次修改完都要手動生成 wasm 可能有點繁瑣,可以考慮安裝 wabt 命令工具,并配合 nodemon 監(jiān)聽 wat 文件,當(dāng)文件被修改時自動編譯 wasm。

數(shù)字類型

(module
  ;; 將兩個 i32 類型的參數(shù)相加返回
  (func (export "add") (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add
  )
)

這里定義了一個 add 方法,接收兩個 i32 類型的參數(shù),相加并返回一個 i32 類型返回值。

wat 使用的棧式機(jī)器的方式執(zhí)行的,將兩個參數(shù)依次壓入棧,然后調(diào)用相加運算,這個運算會取出棧頂?shù)膬蓚€數(shù)進(jìn)行相加,然后把結(jié)果壓入棧。

最后函數(shù)會取棧頂?shù)闹底鳛榉祷刂怠?/p>

另外,目前 wasm 支持返回多個值了,JavaScript 那邊會得到一個數(shù)組。

;; 是行注釋,另外 (;注釋內(nèi)容;) 是塊注釋。

wasm 的函數(shù)參數(shù)和返回值類型支持的數(shù)字類型有:i32、i64、f32、f64,分別代表 32 位和 64 位的整數(shù)和浮點數(shù)。(還有其他不常用的類型后面再講)

生成 add.wasm 文件,然后再寫一個 js 方法去加載調(diào)用 wasm 的方法:

WebAssembly.instantiateStreaming(fetch('./add.wasm')).then(res => {
  const { instance } = res;
  const { add } = instance.exports;
  console.log(add(100, 34));
  console.log(add(100.233, 34)); // 浮點數(shù)被 add 轉(zhuǎn)成整數(shù)了
  console.log(add(false, 34)); // true 被轉(zhuǎn)成 1,false 被轉(zhuǎn)成 0
  // ...
});

查看控制臺輸出:

js 的數(shù)字只有一種類型:64 位浮點數(shù),調(diào)用 wasm 函數(shù)會進(jìn)行類型轉(zhuǎn)換,在上面的例子中,add 方法會將其轉(zhuǎn)為 32 位整數(shù)。

此外 js 的非數(shù)值類型也會轉(zhuǎn)為數(shù)字,通常是 0 或 1,字符串的話會嘗試轉(zhuǎn)為數(shù)字(類似調(diào)用 Number())。

wasm 函數(shù)的返回值也會做類型轉(zhuǎn)換為 js 的數(shù)字類型。如果返回的是 i64,在 JavaScript 會轉(zhuǎn)換為 BigInt。

下面是另一種可讀性更好的 wat 寫法。這里給函數(shù)參數(shù)聲明了名字,并給函數(shù)設(shè)置為變量,后面再導(dǎo)出(類似 js 的 export { add })。

(module
  ;; 將兩個 i32 類型的參數(shù)相加返回
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add)
  (export "add" (func $add))
)

導(dǎo)入 JavaScript 方法

下面 wat 聲明了需要導(dǎo)入的 JavaScript 方法 a.b()。

(module
  ;; wasm 會拿到 importObject 的 a.b 方法
  (import "a" "b" (func $getNum (param i32)))
  (func (export "getNum")
    i32.const 114514
    call $getNum ;; 這里把數(shù)字傳給了 importObject 的 a.b 方法
  )
)

導(dǎo)入的 js 方法需要聲明名稱和函數(shù)簽名。

實例化 module 時提供前面提到的 importObject,去指定這個方法。

const importObject = {
  a: {
    b: (num) => {
      console.log('a.b', num) // 控制臺輸出:“a.b 114514”
    }
  }
}

WebAssembly.instantiateStreaming(fetch('./import.wasm'), importObject).then(res => {
  const { getNum } = res.instance.exports;
  getNum();
});

調(diào)用 wasm 定義的 getNum 方法時,該方法會調(diào)用 js 聲明的 a.b() 方法,并傳入一個整數(shù)。

a 是模塊名,b 是這個模塊的一個屬性,模塊屬性除了可以是函數(shù),也可以是其他的類型,比如線性內(nèi)存 memory、表格 table。

我們寫 C 編譯成 wasm,其中的 printf 能夠在控制臺打印出來,就是調(diào)用了導(dǎo)入的 js 的膠水方法,把一些二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成 js 字符串,然后調(diào)用 console.log()  輸出。

全局變量

將從 importObject.js.global 傳過來的變量作為 wasm 的全局變量。

定義了兩個方法:

  • getGlobal:返回這個全局變量;
  • incGlobal:給全局變量 + 1。
(module
  (global $g (import "js" "global") (mut i32))
  (func (export "getGlobal") (result i32)
    (global.get $g)
  )
  (func (export "incGlobal")
    (global.set $g
      (
        i32.add
        (global.get $g)
        (i32.const 1)
      )
    )
  )
)

js 中用 new WebAssembly.Global() 創(chuàng)建 global 對象然后導(dǎo)入。

const importObject = {
  js: {
    // 一個初始值為 233 的 i32 變量
    global: new WebAssembly.Global(
      {
        value: 'i32',
        mutable: true,
      },
      233
    ),
  },
};

WebAssembly.instantiateStreaming(fetch('./global.wasm'), importObject).then(
  (res) => {
    const { instance } = res;
    console.log(instance);
    const { getGlobal, incGlobal } = res.instance.exports;

    console.log('全局變量');
    console.log(getGlobal()); // 輸出:233
    incGlobal();
    incGlobal();
    console.log(getGlobal()); // 輸出:235
  }
);

也可以在 js 中直接用 importObject.js.global.value 拿到全局變量的值。

也可以在 wasm 中定義 global 變量,global 變量可以定義多個。

(global $g2 (mut i32) (i32.const 99))

復(fù)雜變量類型

wasm 的函數(shù)無法接收和返回一些復(fù)雜的高級類型,比如字符串、對象,這時候就需要用到 線性內(nèi)存(memory) 了。

線性內(nèi)存需要用到 WebAssembly.Memory 對象,這個對象是 ArrayBuffer。

js 和 wasm 共享這個 ArrayBuffer,作為傳輸媒介,然后雙方都在各自的作用域進(jìn)行序列和反序列化。

這也是 wasm 讓人詬病的通信問題:

如果計算本身的 CPU 密集度不高,那瓶頸就落到數(shù)據(jù)序列化反序列化以及通信上了,別說提升性能了,降低性能都可能。

wat:

(module
  (import "console" "log" (func $log (param i32 i32)))
  ;; 傳入的 memory 大小為 1 頁
  (import "js" "mem" (memory 1))
  ;; 在 memory 的地址 0 處設(shè)置數(shù)據(jù) "Hi"
  (data (i32.const 0) "Hi")
  
  (func (export "writeHi")
    i32.const 0  ;; 字符串起始位置
    i32.const 2  ;; 字符串長度
    call $log
  )
)

js:

// memory 對象,大小為 1 頁(page),1 頁為 64 KB
const memory = new WebAssembly.Memory({ initial: 1 });

// wasm 無法直接返回字符串,但可以修改線性內(nèi)存
// 然后再指定線性內(nèi)存的區(qū)間讓 js 去截取需要的 ArrayBuffer
// 最后 ArrayBuffer 轉(zhuǎn) 字符串
function consoleLogString(offset, length) {
  const bytes = new Uint8Array(memory.buffer, offset, length);
  const string = new TextDecoder('utf-8').decode(bytes);
  console.log(string);
}

const importObject = {
  console: { log: consoleLogString },
  js: { mem: memory },
};

WebAssembly.instantiateStreaming(fetch('./memory.wasm'), importObject).then(
  (res) => {
    res.instance.exports.writeHi();
  }
);

也可以在 js 傳字符串給 wasm,但 js 這邊要做字符串轉(zhuǎn) ArrayBuffer 的操作。

下面是拼接兩個字符串返回新字符串示例。

wat:

(module
  (import "console" "log" (func $log (param i32 i32)))
  (import "js" "mem" (memory 1))

  ;; 函數(shù)接受兩個字符串并拼接它們
  (func $concatStrings (param $offset1 i32) (param $length1 i32) (param $offset2 i32) (param $length2 i32) (result i32) (result i32)
    ;; 這里的代碼是將兩個字符串拼接到內(nèi)存中,并返回新字符串的偏移量和長度
    ;; 注意:為了簡單起見,這里假設(shè)你有足夠的內(nèi)存空間來拼接字符串
    (local $newOffset i32)
    ;; 假設(shè)新的偏移量是在第一個字符串的結(jié)束處
    local.get $offset1
    local.get $length1
    i32.add
    local.set $newOffset

    ;; 將第二個字符串拷貝到新的偏移量處
    local.get $newOffset
    local.get $offset2
    local.get $length2
    memory.copy

    ;; 返回新的偏移量和長度
    local.get $offset1
    local.get $length1
    local.get $length2
    i32.add
  )

  (func (export "concatAndLog") (param $offset1 i32) (param $length1 i32) (param $offset2 i32) (param $length2 i32)
    ;; 調(diào)用上面的拼接函數(shù)
    local.get $offset1
    local.get $length1
    local.get $offset2
    local.get $length2
    call $concatStrings

    ;; 使用結(jié)果來調(diào)用$log
    call $log
  )
)

js:

const memory = new WebAssembly.Memory({ initial: 1 });

function consoleLogString(offset, length) {
  // console.log(offset, length);
  const bytes = new Uint8Array(memory.buffer, offset, length);
  const string = new TextDecoder('utf-8').decode(bytes);
  console.log(string); // 輸出 Hello, WebAssembly!
}

let currentOffset = 0; // 添加這個變量來跟蹤當(dāng)前可用的內(nèi)存偏移量

function stringToMemory(str, mem) {
  const encoder = new TextEncoder();
  const bytes = encoder.encode(str);
  new Uint8Array(mem.buffer, currentOffset, bytes.length).set(bytes);
  const returnOffset = currentOffset;
  currentOffset += bytes.length; // 更新偏移量
  return { offset: returnOffset, length: bytes.length };
}

const importObject = {
  console: { log: consoleLogString },
  js: { mem: memory },
};

WebAssembly.instantiateStreaming(fetch('./concat.wasm'), importObject).then(
  (res) => {
    const str1 = 'Hello, ';
    const str2 = 'WebAssembly!';
    const mem1 = stringToMemory(str1, memory);
    const mem2 = stringToMemory(str2, memory);
    res.instance.exports.concatAndLog(
      mem1.offset,
      mem1.length,
      mem2.offset,
      mem2.length
    );
  }
);

其他類型也一樣的思路,只要支持轉(zhuǎn)換成 ArrayBuffer,然后轉(zhuǎn)換回來就好了。

一個 wasm 模塊只能定義一個線性內(nèi)存 memory,這個是出于簡單的考量。

表格 table

table 是一個大小可變的引用數(shù)組,指向 wasm 的代碼地址。

前面的 wat 執(zhí)行代碼時,會使用 run 指令接一個 靜態(tài) 的函數(shù)索引。但有時候函數(shù)索引需要是動態(tài),一會指向函數(shù) a,過一段時間又指向 b。

這時候我們就可以使用 table 去維護(hù)。

(table 2 funcref)

anyfunc 類型,代表可以是任何簽名的函數(shù)引用。

因為安全問題函數(shù)引用不能保存在線性內(nèi)存(memory)中。因為線性內(nèi)存保存地址沒意義,而存真正的函數(shù)數(shù)據(jù)源有可能被惡意修改,有安全問題。

所以整出了這么一個抽象的 table 數(shù)組,這個 table 無法被讀取真正的內(nèi)容,只能更新一下數(shù)組的引用。

下面是一個示例,在 wat 創(chuàng)建了一個 table,然后讓 js 根據(jù)索引調(diào)用 table 中的動態(tài)引用的函數(shù)。

wat

(module
  ;; table 大小為 2,且為函數(shù)引用類型。
  (table $t 2 funcref)
  ;; table 從 0 偏移值填充聲明的兩個函數(shù)
  ;; 0 指向 $f1,1 指向 $f2
  (elem (i32.const 0) $f1 $f2)

  ;; 函數(shù)聲明可以在任何位置
  (func $f1 (result i32)
    i32.const 22
  )
  (func $f2 (result i32)
    i32.const 33
  )

  ;; 定義函數(shù)類型,一個返回 i32 的函數(shù)(類比 ts 的函數(shù)類型)
  (type $return_i32 (func (result i32)))

  ;; 暴露一個 callByIndex 方法給 js
  ;; callByIndex(0) 表示調(diào)用 table 上索引為 0 的函數(shù)。
  (func (export "callByIndex") (param $i i32) (result i32)
    ;; (間接)調(diào)用 $i 索引值在 table 中指向的方法
    call_indirect (type $return_i32)
  )
)

js:

WebAssembly.instantiateStreaming(fetch('./table.wasm')).then((res) => {
  const { callByIndex } = res.instance.exports;
  console.log(callByIndex(0)); // 22
  console.log(callByIndex(1)); // 33
});

也可以在 js 中更新 table,讓一些索引指向新的函數(shù)。

但需要注意,這個函數(shù)需要時 wasm 導(dǎo)出,而不是 js 函數(shù)。

下面是對應(yīng)的示例。

wat:

(module
   ;; 導(dǎo)入 table
  (import "js" "table" (table 1 funcref))
  (elem (i32.const 0) $f1)
  (func $f1 (result i32)
    i32.const 22
  )

  (type $return_i32 (func (result i32)))
  (func (export "call") (result i32)
    i32.const 0
    call_indirect (type $return_i32)
  )

  (func (export "get666") (result i32)
    i32.const 666
  )
)

js:

const table = new WebAssembly.Table({ initial: 1, element: 'anyfunc' });

const importObject = {
  js: { table },
};

WebAssembly.instantiateStreaming(
  fetch('./outer-table.wasm'),
  importObject
).then((res) => {
  const { call, get666 } = res.instance.exports;
  console.log(call()); // 22

  console.log(table.get(0)); // 獲取 wasm 函數(shù)
  table.set(0, get666); // 更換 table[0] 的函數(shù)。
  console.log(call()); // 666
});

在 wat 中,anyfunc 是舊寫法,現(xiàn)在換成了 funcref,來表示函數(shù)引用。

不過 js 中創(chuàng)建 table,element 參數(shù)還得傳 "anyfunc"。

table 的這個特性可以實現(xiàn)類似 dll 的動態(tài)鏈接能力,可以在程序運行時才動態(tài)鏈接需要的代碼和數(shù)據(jù)。

引用類型

wasm 的函數(shù)現(xiàn)在支持傳 引用類型(externref)。

(func (export "callJSFunction") (param externref)
  ...
)

你可以傳任何 js 變量作為 externref 類型傳入 wasm 函數(shù),但該變量在 wasm 不能被讀寫和執(zhí)行,但可以把作為返回值,或是它作為參數(shù)傳給 import 進(jìn)來的 js 函數(shù)。

wasm 只能對 externref 做中轉(zhuǎn),傳入以及返回回去,無法做任何其他操作。

示例:

(module
  (type $jsFunc (func (param externref)))
  (func $invoke (import "js" "invokeFunction") (type $jsFunc))
  
  (func (export "callJSFunction") (param externref)
    local.get 0
    call $invoke
  )
)
const importObject = {
  js: {
    invokeFunction: (fn) => {
      fn();
    },
  },
};

WebAssembly.instantiateStreaming(fetch('./type.wasm'), importObject).then(
  (res) => {
    const { instance } = res;
    const { callJSFunction } = instance.exports;

    callJSFunction(() => {
      console.log('被執(zhí)行的是來自 js 函數(shù)');
    });
  }
);

矢量類型

v128,一個 128 比特的矢量類型。

用于 SIMD(Single Instruction, Multiple Data),它是一種計算機(jī)并行處理技術(shù),允許一個單一的操作指令同時處理多個數(shù)據(jù)元素,使用用在大量數(shù)據(jù)執(zhí)行相同操作的場景,比如矩陣運算。

v128 是其他數(shù)據(jù)的打包,打包一起好做并行運行,提高計算速度。

這些數(shù)據(jù)可能是:

  • 4 個 i32(或 f32)
  • 2 個 i64(或 f64)
  • 16 個 i8
  • 8 個 i16

然后它們會使用類似 i32x4 的指令進(jìn)行批量操作:

i32x4.add (local.get $a) (local.get $b)

雖然沒有 i8 和 i16 這種類型,但它們本質(zhì)是 ArrayBuffer(字節(jié)數(shù)組)的一種高層級,js 那邊可以用 ArrayBuffer 構(gòu)造出 Int8Array 對象。

所以 wat 提供了對應(yīng)的指令,比如 i8x16.add。

示例:

(module
  (memory 1)
  (export "memory" (memory 0))

  (func (export "add_vectors")
    (param $aOffset i32) (param $bOffset i32)
    (local $a v128) (local $b v128)
    (local.set $a (v128.load (local.get $aOffset)))
    (local.set $b (v128.load (local.get $bOffset)))
    (v128.store (i32.const 0) (i32x4.add (local.get $a) (local.get $b)))
  )
)
WebAssembly.instantiateStreaming(fetch('./v128.wasm')).then((res) => {
  const { add_vectors, memory } = res.instance.exports;

  // 首先在內(nèi)存中分配兩個向量a和b
  const a = new Int32Array(memory.buffer, 0, 4);
  const b = new Int32Array(memory.buffer, 16, 4);

  // 初始化向量a和b的值
  a.set([1, 2, 3, 4]);
  b.set([5, 6, 7, 8]);

  console.log('Vector A:', a);
  console.log('Vector B:', b);

  // 調(diào)用add_vectors函數(shù),傳入向量a和b在內(nèi)存中的偏移量
  add_vectors(0, 16);

  // 讀取和打印結(jié)果
  const result = new Int32Array(memory.buffer, 0, 4);
  console.log('Result:', result); // [6, 8, 10, 12]
});

多線程

wasm 支持多線程。

我們可以使用多個 Web Worker 各自創(chuàng)建 wasm 實例,讓它們共享同一段內(nèi)存 SharedArrayBuffer。

因為多線程特有的競態(tài)條件問題,我們需要用到 Atomics 對象,它提供了一些原子操作,防止沖突。

最后是 wait/notify 進(jìn)行線程的掛起和喚醒。

這個用的不多,就簡單介紹一下就好了。

結(jié)尾

wasm 是 js 的一個強(qiáng)有力的補(bǔ)充,未來可期,在一些領(lǐng)域比如圖像處理、音視頻處理大有可為。

但也不得不承認(rèn) wasm 并不能很簡單地就能給應(yīng)用提高性能的,因為安全原因,相比原生是有一定性能損失的。

如果沒做正確的設(shè)計甚至因為通信成本導(dǎo)致負(fù)優(yōu)化,你需要考量性能的瓶頸在哪里,到底是代碼寫得爛呢還是 CPU 計算就是高。v8 的 JIT 過于優(yōu)秀,導(dǎo)致 wasm 的光芒不夠耀眼。

另外,wasm 有不小的學(xué)習(xí)成本的。

但不可否認(rèn),wasm 是前端的一個大方向,還是有一定學(xué)習(xí)投入的必要。

責(zé)任編輯:姜華 來源: 前端西瓜哥
相關(guān)推薦

2022-11-28 11:41:24

Wasm

2021-11-29 18:27:12

Web Wasmjs

2023-11-10 15:03:08

2023-09-04 07:30:03

Wasm匯編語言

2021-11-29 10:24:56

WasmEnvoy 負(fù)載均衡

2023-09-06 09:20:45

FigmaWasm

2024-08-01 12:08:52

2022-02-15 14:08:32

虛擬機(jī)Wasm瀏覽器

2024-09-30 16:25:40

2023-02-24 13:07:07

2017-08-29 15:34:10

CanvasWASM算法

2016-11-29 10:49:29

Android

2021-09-09 06:55:43

Web剪輯視頻

2024-11-05 09:20:47

2023-11-06 07:16:22

WasmK8s模塊

2025-03-13 00:25:00

SpringJava瀏覽器

2024-04-01 08:23:20

代碼Javajavascript

2011-06-08 14:22:51

延遲加載

2023-09-12 15:39:07

WASIXprocessZig

2009-07-07 17:10:57

JSP和Servlet
點贊
收藏

51CTO技術(shù)棧公眾號

午夜精品区一区二区三| 欧美午夜精品久久久| 国产69精品久久久久9999apgf| 黄色一级视频免费| 亚洲精品国产动漫| 欧美视频一区二区三区四区| 青青草视频国产| 青青国产在线| 紧缚捆绑精品一区二区| 欧美激情精品久久久久久久变态| 色呦呦一区二区| 成人a在线观看高清电影| 一区二区三区在线视频免费观看| 久久人人爽爽人人爽人人片av| 中文字幕av网站| 亚洲欧洲一区二区天堂久久| 中文字幕精品视频| 欧美成人三级伦在线观看| 亚洲毛片在线免费| 欧美日韩国产在线看| 黄色一级视频播放| 久久精品蜜桃| 国产福利电影一区二区三区| 国产成人精品电影| 日韩精品一区三区| 婷婷久久综合| 中文字幕免费国产精品| 波多野结衣加勒比| 一区二区三区四区精品视频 | 亚洲欧洲视频在线| 91丨porny丨九色| 欧美国产日韩电影| 欧美日韩在线第一页| 日本香蕉视频在线观看| 欧美69xxx| 欧美激情一区二区三区在线| 久久久精品动漫| 亚洲成a人片77777精品| 精品一区二区三区日韩| 日本欧美国产在线| 伊人手机在线视频| 亚洲作爱视频| 国内揄拍国内精品| 日韩 国产 在线| 欧美午夜电影在线观看| 久久影院在线观看| 日本爱爱小视频| 色爱综合网欧美| 亚洲日本中文字幕| 久久精品—区二区三区舞蹈| 美女网站色精品尤物极品姐弟| 日韩欧美国产系列| 香蕉视频xxxx| 久久国产精品美女| 日韩亚洲电影在线| 男人女人拔萝卜视频| 视频在线观看免费影院欧美meiju 视频一区中文字幕精品 | 欧亚av在线| 亚洲高清三级视频| 日本中文字幕网址| av资源网在线播放| 精品欧美一区二区三区| 男人揉女人奶房视频60分 | 午夜影院免费观看视频| 性欧美video另类hd尤物| 欧美日本国产一区| 福利视频999| 一区二区三区四区视频免费观看| 日韩精品一区二区三区在线| 少妇伦子伦精品无吗| 超碰97久久| 日韩电影视频免费| 久久久久无码精品国产sm果冻| 国际精品欧美精品| 日韩在线视频导航| 波多野结衣亚洲色图| 亚洲天堂黄色| 欧美中文字幕在线| 黄色网址中文字幕| 久久se精品一区精品二区| 成人美女免费网站视频| www.精品久久| 99久久777色| 亚洲福利av| 午夜在线激情影院| 黑人巨大精品欧美一区免费视频| 黄色av免费在线播放| 国产一区二区高清在线| 亚洲精品在线免费观看视频| 国产精品成人一区二区三区电影毛片| 成人精品亚洲| 国内揄拍国内精品少妇国语| 无码人妻av一区二区三区波多野| 狠狠网亚洲精品| 国产精品一区二区欧美黑人喷潮水| 蜜桃视频在线免费| 亚洲免费伊人电影| 欧洲黄色一级视频| 国产不卡精品在线| 亚洲人成网站免费播放| 侵犯稚嫩小箩莉h文系列小说| 亚洲毛片一区| 国产一区视频在线播放| 天天摸天天干天天操| 国产精品美女久久久久久久久 | 538精品视频| 天天揉久久久久亚洲精品| 久久久久久欧美| 国产精品高清无码| 成人国产精品视频| 一区不卡字幕| 欧美aa在线| 欧美日韩国产综合久久| 日本黄色免费观看| 综合五月婷婷| 国产精品人人做人人爽| 天堂中文网在线| 亚洲欧美激情视频在线观看一区二区三区 | 亚洲午夜极品| 国产日韩欧美日韩| 日本免费不卡| 夜夜嗨av一区二区三区网页| 中文字幕国产传媒| 大陆精大陆国产国语精品 | 天堂av最新在线| 精品视频一区二区三区免费| www.四虎在线| 午夜精品影院| 91精品在线国产| 国产1区2区3区在线| 欧美色图在线视频| 老司机免费视频| 亚洲乱码精品| 国产日本欧美在线观看| 可以在线观看的av| 黑人欧美xxxx| 欧美无人区码suv| 欧美午夜影院| 97人人干人人| 国产在线69| 7777精品伊人久久久大香线蕉完整版 | 国产亚洲字幕| www.久久撸.com| 国产精品成人久久久| 久久久精品tv| 妺妺窝人体色www在线小说| 成人福利免费在线观看| 欧美高清第一页| 精品国产区一区二| 樱桃视频在线观看一区| 爱情岛论坛成人| 日韩av成人| 性做久久久久久| 在线播放av网址| 中文字幕一区二区精品区| 成人黄色在线观看| 欧美被日视频| 在线播放91灌醉迷j高跟美女 | 精品精品国产毛片在线看| 欧美多人爱爱视频网站| 亚洲国产成人精品一区二区三区| 一区二区三区中文在线| 欧美性生交xxxxx| 亚洲国产免费| 久久一区二区精品| 日韩av超清在线观看| 一个色综合导航| 成人免费一级片| 国产精品久久久久影院| 亚洲高清在线不卡| 欧美网站在线| 久久人人爽爽人人爽人人片av| 午夜精品久久久久久久久久蜜桃| 一区二区三区在线播放欧美| 国产又黄又粗又长| 亚洲国产日韩av| 亚洲av无码成人精品国产| 日本视频免费一区| 国产手机视频在线观看| 国产一区在线电影| 国产精品video| 麻豆影视在线观看_| 欧美一区二区三区四区五区 | 欧美影院一区二区| 黄色裸体一级片| 国产福利电影一区二区三区| 国产亚洲精品网站| 欧美成人直播| 国产精品一区二区三区四区五区 | 亚洲最大的黄色网| 免费观看成人av| 日韩免费在线观看av| 欧美激情在线精品一区二区三区| 91精品视频网站| 竹内纱里奈兽皇系列在线观看| 日韩在线免费视频| 亚洲人午夜射精精品日韩| 欧美视频一区在线| 国产精品第9页| 亚洲欧洲另类国产综合| 免费成人蒂法网站| 国内精品在线播放| 欧美污视频网站| 午夜日本精品| 色女孩综合网| 美国成人xxx| 5g影院天天爽成人免费下载| 成人美女大片| 欧美激情视频给我| 在线观看国产原创自拍视频| 亚洲福利视频在线| 国产老女人乱淫免费| 色综合一个色综合亚洲| 麻豆疯狂做受xxxx高潮视频| 日本一区二区三区久久久久久久久不| 丰满人妻一区二区三区大胸| 另类综合日韩欧美亚洲| 日韩av片免费在线观看| 久99久在线| av在线播放免费| 精品国产人成亚洲区| 在线观看国产黄| 欧美日韩精品在线观看| 欧美精品久久久久性色| 国产精品入口麻豆九色| 国产成人无码精品久久二区三| 国产成人精品综合在线观看| 天天干天天av| 日韩精品国产欧美| heyzo国产| 韩国亚洲精品| 在线观看成人免费| 99久久精品费精品国产风间由美| 日本一区二区久久精品| 日韩有码av| 国产精品xxxx| 亚洲精品在线国产| 成人午夜黄色影院| 欧美日韩免费电影| 国产欧美一区二区三区久久| 国产精品蜜月aⅴ在线| 日韩av男人的天堂| 性欧美1819sex性高清| 97成人精品视频在线观看| av漫画网站在线观看| 久久噜噜噜精品国产亚洲综合| 色呦呦在线看| 久久久久中文字幕2018| av伦理在线| 91精品国产91久久| 在线视频cao| 2023亚洲男人天堂| 永久免费毛片在线播放| 欧美专区福利在线| 欧美人与性动交xxⅹxx| 国产精品www色诱视频| 国产在线|日韩| 国产精品无av码在线观看| 久久精品嫩草影院| 91探花福利精品国产自产在线| 亚洲青青一区| 亚洲最大福利视频| 北条麻妃一区二区三区在线| 成人在线观看91| 欧美天堂社区| 日本午夜精品一区二区| 91麻豆精品国产91久久久平台 | 亚洲va韩国va欧美va精品| 免费无遮挡无码永久在线观看视频| 一区二区三区在线免费观看| 国产精品第一页在线观看| 精品久久久久久久久久久| 99久久精品国产亚洲| 欧美性猛交xxxx久久久| 丰满熟女人妻一区二区三| 91精品久久久久久久99蜜桃| 亚洲第一天堂影院| 日韩电影在线观看中文字幕| 九色蝌蚪在线| 精品国产欧美一区二区五十路| 中文字幕中文字幕在线中高清免费版| 欧美精品成人在线| 天天综合网站| 亚洲精品免费av| 校园春色另类视频| 亚洲欧洲精品一区二区| 欧美精品偷拍| 国内外成人激情视频| 久久精品国产亚洲a| 国产艳妇疯狂做爰视频| 国产无遮挡一区二区三区毛片日本| 色哟哟一一国产精品| 亚洲大片在线观看| 中文在线免费观看| 欧美va亚洲va香蕉在线| 国产在线超碰| 欧美乱妇高清无乱码| 国产日韩另类视频一区| 亚洲一区中文字幕在线观看| 色88888久久久久久影院| 亚洲一区二区三区四区中文| 亚洲精品欧洲| 中文国产在线观看| 91偷拍与自偷拍精品| 在线观看亚洲网站| 91官网在线观看| 老牛影视av牛牛影视av| www.国产一区| 日产精品一区| 精品视频在线观看| 亚洲人metart人体| 国产性生交xxxxx免费| 成人午夜视频在线| 日韩在线一卡二卡| 日本高清成人免费播放| 成人久久精品人妻一区二区三区| 中文字幕在线观看亚洲| 在线观看的黄色| 国产精品二区在线| 亚洲色图88| 在线观看国产中文字幕| 久久―日本道色综合久久| 男女免费视频网站| 在线不卡欧美精品一区二区三区| 色视频精品视频在线观看| 欧美韩日一区二区| 国产精品视频首页| 亚洲精品中文字幕乱码三区不卡| 亚洲一区二区动漫| 国产原创剧情av| 亚洲精品视频自拍| 91丨九色丨蝌蚪丨对白| 亚洲新声在线观看| 最近高清中文在线字幕在线观看1| 国产91亚洲精品一区二区三区| 午夜精品一区二区三区国产| 欧美婷婷精品激情| 国产午夜亚洲精品羞羞网站| www成人在线| 亚洲福利视频久久| 99在线视频影院| 国产精品传媒毛片三区| 激情综合亚洲| 黑人玩弄人妻一区二区三区| 一区二区在线看| www.久久伊人| 欧美多人爱爱视频网站| 日韩在线亚洲| 青草网在线观看| 超碰在线97免费| 欧美极品美女视频| 欧美激情一区二区三区免费观看 | 日本动漫同人动漫在线观看| 亚洲影视九九影院在线观看| 婷婷伊人综合| 日韩不卡的av| 亚洲国产日韩av| 四虎影视精品成人| 国产97在线观看| 日产精品一区二区| 国产三级国产精品国产专区50| 国产精品家庭影院| 99久久精品国产色欲| 久久久亚洲福利精品午夜| 老司机凹凸av亚洲导航| 男人日女人bb视频| 国产日韩v精品一区二区| 亚洲天堂手机在线| 九九九热精品免费视频观看网站| 香蕉大人久久国产成人av| www.99热这里只有精品| 91麻豆国产香蕉久久精品| 亚洲欧美偷拍视频| 中文字幕精品在线视频| 欧美激情精品| 欧美三级一级片| 国产精品丝袜一区| www.av日韩| 国产成人激情视频| 天天影视欧美综合在线观看| 无码成人精品区在线观看| 色妹子一区二区| 美女国产在线| 久久精品日产第一区二区三区乱码 | 欧美精品久久久久久久久25p| 亚洲日本中文字幕区| 天天综合网在线观看| 国产精品第一视频| 欧美在线视屏| 在线免费观看黄色小视频| 欧美精品丝袜久久久中文字幕| 麻豆福利在线观看| 亚洲国内在线| 成人动漫一区二区在线| 中文天堂在线视频| 97在线视频一区| 一本精品一区二区三区| 五月婷婷综合在线观看| 91精品国产综合久久久蜜臀粉嫩| av综合电影网站|