閱讀更多
引用
原文:JavaScript to Rust and Back Again: A wasm-bindgen Tale
鏈接:https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/
譯者:Tocy, 琪花億草, 雪落無痕xdj, 邊城

最近我們已經見識了WebAssembly如何快速編譯、加速JS庫以及生成更小的二進制格式。我們甚至為Rust和JavaScript社區以及其他Web編程語言之間的更好的互操作性制定了高級規劃。正如前面一篇文章中提到的,我想深入了解一個特定組件的細節,wasm-bindgen。

今天WebAssembly標準只定義了四種類型:兩種整數類型和兩種浮點類型。然而,大多數情況下,JS和Rust開發人員正在使用更豐富的類型! 例如,JS開發人員經常與互以添加或修改HTML節點相關的文檔交互,而Rust開發人員使用類似Result等類型進行錯誤處理,幾乎所有程序員都使用字符串。


被局限在僅使用由WebAssembly所提供的類型將會受到太多的限制,這就是wasm-bindgen出現的原因。


wasm-bindgen的目標是提供一個JS和Rust類型之間的橋接。它允許JS使用字符串調用Rust API,或Rust函數捕獲JS異常。wasm-bindgen抹平了WebAssembly和JavaScript之間的阻抗失配,確保JavaScript可以高效地調用WebAssembly函數,并且無需boilerplate,同時WebAssembly可以對JavaScript函數執行相同的操作。

wasm-bindgen項目在其README文件中有更多描述。要入門,讓我們深入到一個使用wasm-bindgen的例子中,然后探索它還有提供了什么。

1、Hello World!

學習新工具的最好也是最經典的方法之一就是探索下用它來輸出“Hello, World!”。在這里,我們將探索一個這樣的例子——在頁面里彈出“Hello World!”提醒框。

這里的目標很簡單,我們想要定義一個Rust的函數,給定一個名字,它會在頁面上創建一個對話框,上面寫著Hello,$name!在JavaScript中,我們可以將這個函數定義為:
export function greet(name) {
    alert(`Hello, ${name}!`);
}

不過在這個例子里要注意的是,我們將把它用Rust編寫。這里已經發生了很多我們必須要處理的事情:
  • JavaScript將會調用一個WebAssembly 模塊, 模塊名是 greetexport.
  • Rust函數將一個字符串作為輸入參數,也就是我們要打招呼的名字。
  • 在內部Rust會生成一個新的字符串,也就是傳入的名字。
  • 最后Rust會調用JavaScript的 alert函數,以剛創建的字符串作為參數。
啟動第一步,我們創建一個新的Rust工程:
$ cargo new wasm-greet --lib

這將初始化一個新的wasm-greet文件夾,我們的工作都在這里面完成。接下來我們要使用如下信息修改我們的Cargo.toml(在Rust里相當于package.json):
[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

我們先忽略[lib]節的內容,接下來的部分聲明了對wasm-bindgen的依賴。這里的依賴包含了我們使用wasm-bindgen需要的所有的支持包。

接下來,是時候編寫一些代碼了!我們使用下列內容替換了自動創建的src/lib.rs:
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

如果你不熟悉Rust,這可能看起來有點啰嗦,但不要害怕!隨著時間的推移,wasm-bindgen項目不斷改進,而且可以肯定的是,所有這些并不總是必要的。

要注意的最重要的一點是#[wasm_bindgen]屬性,這是一個在Rust代碼中的注釋,這里的意思是“請在必要時用wrapper處理這個”。我們對alert函數的導入和greet函數的導出都被標注為這個屬性。稍后,我們將看到在引擎蓋下發生了什么。

首先,我們從在瀏覽器中打開作為例子來切入正題!我們先編譯wasm代碼:
$ rustup target add wasm32-unknown-unknown --toolchain nightly # only needed once
$ cargo +nightly build --target wasm32-unknown-unknown

這段代碼會生成一個wasm文件,路徑為target/wasm32-unknown-unknown/debug/wasm_greet.wasm。如果我們使用工具如wasm2wat來看這個wasm文件里面的內容,可能會有點嚇人。

結果發現這個wasm文件實際上還不能直接被JS調用!為了能讓我們使用,我們需要執行一個或更多步驟:
$ cargo install wasm-bindgen-cli # only needed once
$ wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_greet.wasm --out-dir .

很多不可思議的事情發生都發生在這個步驟中:wasm-bindgen CLI工具對輸入的wasm文件做后期處理,使它變的“suitable”可用。

我們待會再來看“suitable”的意思,現在我們可以肯定的說,如果我們引入剛創建的wasm_greet.js文件(wasm-bindgen工具創建的),我們已經獲取到了在Rust中定義的greet函數。

最終我們接下來要做的是使用bundler對其打包,然后創建一個HTML頁面運行我們的代碼。

在寫這篇文章的時候,只有Webpack’s 4.0 release對WebAssembly的使用有足夠的支持(盡管暫時已經有了 Chrome caveat)。

總有一天,更多的bundler也會接著支持WebAssmbly。在這我不再描述細節,但是你可以看一下在Github倉庫里的example配置。不過如果我們看內容,這個頁面中我們的JS在看起來是這樣的:
const rust = import("./wasm_greet");
rust.then(m => m.greet("World!"));

…就是這些了!現在打開我們的網頁就會顯示一個不錯的“Hello, World!”對話框,這就是Rust驅動的。

2、wasm-bindgen是如何工作的
唷,那是一個巨大的“Hello, World!”。讓我們深入了解一下更多的細節,以了解后臺發生了什么以及該工具是如何工作的。

wasm-bindgen最重要的方面之一就是它的集成基本上是建立在一個概念之上的,即一個wasm模塊僅是另一種ES模塊。例如,在上述中我們想要一個帶有如下簽名的ES模塊(在Typescript中):
export function greet(s: string);

WebAssembly無法在本地執行此操作(請記住,它目前只支持數字),所以我們依靠wasm-bindgen來填補空白。

在上述的最后一步中,當我們運行wasm-bindgen工具時,你會注意到wasm_greet.js文件與wasm_greet_bg.wasm文件一起出現。前者是我們想要的實際JS接口,執行任何必要的處理以調用Rust。* _bg.wasm文件包含實際的實現和我們所有的編譯后的代碼。

我們可以通過引入 ./wasm_greet 模塊得到 Rust 代碼愿意暴露出來的東西。我們已經看到了是如何集成的,可以繼續看看執行的結果如何。首先是我們的示例:
const rust = import("./wasm_greet");
rust.then(m => m.greet("World!"));

我們在這里以異步的方式導入接口,等待導入完成(下載和編譯 wasm)。然后調用模塊的 greet 函數。

注: 這里用到的異步加載目前需要 Webpack 來實現,但總會不需要的。而且,其它打包工具可能沒有此功能。

如果我們看看由 wasm-bindgen 工具為 wasm_greet.js 文件生成的內容,會看到像這樣的代碼:
import * as wasm from './wasm_greet_bg';

// ...

export function greet(arg0) {
    const [ptr0, len0] = passStringToWasm(arg0);
    try {
        const ret = wasm.greet(ptr0, len0);
        return ret;
    } finally {
        wasm.__wbindgen_free(ptr0, len0);
    }
}

export function __wbg_f_alert_alert_n(ptr0, len0) {
    // ...
}

注: 記住這是生成的,未經優化的代碼,它可能既不優雅也不簡潔!!在 Rust 中通過 LTO(Link Time Optimization,連接時優化)創建新的發行版,再通過 JS 打包工具流程(壓縮)之后,可能會精簡一些。

現在可以了解如何使用wasm-bindgen來生成greet函數。在底層它仍然調用wasm的greet函數,但是它是用一個指針和長度來調用的而不是用字符串。

了解passStringToWasm的更多細節可以訪問Lin Clark’s previous post。它包含了所有的模板,對我們來說這是除了wasm-bindgen工具以外還需要去寫的東西!然后我們接下來看__wbg_f_alert_alert_n函數。

進入更深一層,下一個我們感興趣的就是WebAssmbly中的greet函數。為了了解這個,我們先來看Rust編譯器能訪問到的代碼。注意像上面生成的這種JS wrapper,在這里你不用寫greet的導出符號,#[wasm_bindgen]屬性會生成一個shim,由它來為你翻譯,命名如下:
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

#[export_name = "greet"]
pub extern fn __wasm_bindgen_generated_greet(arg0_ptr: *mut u8, arg0_len: usize) {
    let arg0 = unsafe { ::std::slice::from_raw_parts(arg0_ptr as *const u8, arg0_len) }
    let arg0 = unsafe { ::std::str::from_utf8_unchecked(arg0) };
    greet(arg0);
}

現在可以看到原始代碼,greet,也就是由#[wasm_bindgen]屬性插入的看起來有意思的函數__wasm_bindgen_generated_greet。這是一個導出函數(用#[export_name]和extern關鍵詞來指定的),參數為JS傳進來的指針/長度對。在函數中它會將這個指針/長度轉換為一個&str (Rust中的一個字符串),然后將它傳遞給我們定義的greet函數。

從另一個方面看,#[wasm_bindgen]屬性生成了兩個wrappers:一個是在JavaScript中將JS類型的轉換為wasm,另外一個是在Rust中接收wasm類型并將其轉為Rust類型。

現在我們來看wrappers的最后一塊,即alert函數。Rust中的greet函數使用標準format!宏來創建一個新的字符串然后傳給alert。回想當我們聲明alert方法的時候,我們是使用 #[wasm_bindgen]聲明的,現在我們看看在這個函數中暴露給rustc的內容:
fn alert(s: &str) {
    #[wasm_import_module = "__wbindgen_placeholder__"]
    extern {
        fn __wbg_f_alert_alert_n(s_ptr: *const u8, s_len: usize);
    }
    unsafe {
        let s_ptr = s.as_ptr();
        let s_len = s.len();
        __wbg_f_alert_alert_n(s_ptr, s_len);
    }
}

這并不是我們寫的,但是我們可以看看它是怎么變成這樣的。alert函數事實上是一個簡化的wrapper,它帶有Rust的 &str然后將它轉換為wasm類型(數字)。它調用了我們在上面看到過的比較有意思的函數__wbg_f_alert_alert_n,然而它奇怪的一點就是#[wasm_import_module]屬性。

在WebAssembly中所有導入的函數都有一個其存在的模塊,而且由于wasm-bindgen構建在ES模塊之上,所以這也將被轉譯為ES模塊導入!

目前__wbindgen_placeholder__模塊實際上并不存在,但它表示該導入將被wasm-bindgen工具重寫,以從我們生成的JS文件中導入。

最后,對于最后一部分的疑惑,我們得到了我們所生成的JS文件,其中包含:
export function __wbg_f_alert_alert_n(ptr0, len0) {
    let arg0 = getStringFromWasm(ptr0, len0);
    alert(arg0)
}

哇! 事實證明,這里隱藏著相當多的東西,我們從JS中的瀏覽器中的警告都有一個相對較長的知識鏈。不過,不要害怕,wasm-bindgen的核心是所有這些基礎設施都被隱藏了! 你只需要在隨便使用幾個#[wasm_bindgen]編寫Rust代碼即可。然后你的JS可以像使用另一個JS包或模塊一樣使用Rust了。

wasm-bindgen還能做什么

wasm-bindgen項目在這個領域內志向遠大,我們在此不再詳細贅述。探索wasm-bindgen中的功能一個有效的方法就是探索示例目錄,這些示例涵蓋了從我們之前看到的Hello World! 到在Rust中對DOM節點的完全操作。

wasm-bindgen高級特性如下:

  • 引入JS結構,函數,對象等來在wasm中調用。你可以在一個結構中調用JS方法,也可以訪問屬性,這給人一種Rust是“原生”的感覺,讓人覺得你曾經寫過的Rust #[wasm_bindgen] annotations都可以連接了起來。
  • 將Rust結構和函數導出到JS。與只用JS使用數字類型來工作相比,你可以導出一個Rust結構并在JS中轉換成一個類。然后可以將結構傳遞,而不是只使用整形數值來傳遞。 smorgasboard 這個例子可以讓你體會支持的互操作特性。
  • 其他各種各樣的特性例如從全局范圍內導入(就像alert函數),在Rust中使用一個Result來獲取JS異常,以及在Rust程序中通用方法模擬存儲JS值。
如果你想了解更多的功能,繼續閱讀 issue tracker

3、wasm-bindgen接下來做什么?

在我們結束之前,我想花一點時間來下描述wasm-bindgen的未來愿景,因為我認為這是當今項目最激動人心的一方面。

不僅僅支持Rust

從第1天起,wasm-bindgen CLI工具就設計成了多語言支持的。盡管Rust目前是唯一被支持的語言,但該工具也可以嵌入C或C++。 #[wasm_bindgen]屬性創建了可被wasm-bindgen工具解析并隨后刪除的輸出(* .wasm)文件的自定義部分。

本節介紹要生成哪些JS綁定以及它們的接口是什么。這個描述中沒有關于Rust的特定部分,因此C ++編譯器插件可以很容易地創建該部分,并通過wasm-bindgen工具進行處理。

我覺得這個方面特別令人振奮,因為我相信它使像wasm-bindgen這樣的工具成為WebAssembly和JS集成的標準做法。希望所有編譯為WebAssembly的語言都能受益,并且可以被bundler自動識別,以避免上述幾乎所有的配置和構建工具。

自動綁定JS生態

使用#[wasm_bindgen] 宏導入功能唯一不好的一面就是你必須將所有東西都寫出來,還要保證沒有任何錯誤。這種讓人覺得很單調(而且易錯)的操作的自動化技術已經成熟了。

所有的web APIs都由WebIDL指定,而且在generate #[wasm_bindgen] annotations from WebIDL是可行的。這個就意味著你不需要像前面一樣定義alert函數,而是你只需要寫下面這些:
#[wasm_bindgen]
pub fn greet(s: &str) {
    webapi::alert(&format!("Hello, {}!", s));
}

在這個例子中,WebIDL對web APIs的描述可以完全自動生成webapi集合,保證沒有錯誤。

我們甚至可以將自動化更進一步,TypeScript組織已經做了這方面的復雜工作,參照generate #[wasm_bindgen] from TypeScript as well。可以免費用npm上的TypeScript自動綁定任何包!

比 JS DOM 操作更快的性能

最后要說的事情對 wasm-bindgen 來說也很重要:超快的 DOM 操作 —— 這是很多 JS 框架的終極目標。如今需要使用一些中間工具來調用 DOM 函數,這些工具正在由 JavaScript 實現轉向 C++ 引擎實現。然而,在 WebAssembly 來臨之后,這些工具并非必須。WebAssembly 是有類型的。

從第一天起,wasm-bindgen 代碼生成的設計就考慮到了將來的宿主綁定方案。當這一特征出現在 WebAssembly 之后,我們可以直接調用導入的函數,而不需要 wasm-bindgen 的中間工具。

此外,它使得 JS 引擎積極優化 WebAssembly 對 DOM 的操作,使其對類型的支持更好,而且在調用 JS 的時候不再需要進行參數驗證。在這一點上,wasm-bindgen 不僅在操作像 string 這樣的富類型變得容易,還提供了一流的 DOM 操作性能。

收工

我自己發現使用WebAssembly是異常令人振奮的,不僅僅是因為其社區,還因為其如此快速地在進度上突飛猛進。wasm-bindgen工具擁有光明的未來。它使JS和諸如Rust這樣的編程語言之間的互操作性變成了一流的體驗,并且隨著WebAssembly的不斷發展它也將提供了長期的好處。

試著給wasm-bindgen一次機會,因功能需求而創建一個問題,亦或繼續保持參與Rust和WebAssembly!

關于Alex Crichton(作者)
Alex是Rust核心團隊的成員之一,自2012年底以來一直從事于Rust。目前他正在幫助WebAssembly Rust Working Group使得Rust + Wasm成為最佳體驗。Alex還幫助維護Cargo(Rust的包管理器),Rust標準庫以及Rust的發布和CI的基礎架構。
來自: oschina
0
0
評論 共 1 條 請登錄后發表評論
1 樓 xxbb77 2018-11-12 16:32
非常感謝 謝謝分享

發表評論

您還沒有登錄,請您登錄后再發表評論

相關推薦

  • 歡迎來到wasm-packdocs!

    該工具旨在成為構建和使用生銹的WebAssembly的一站式商店,您希望在瀏覽器中或使用Node.js與JavaScript互操作。wasm-pack幫助您構建生銹的WebAssembly包,您可以將其發布到npm注冊表,或者與您已經使用的工作流中的任何javascript包一起使用,例如webpack 或greenkeeper。 該項目是rust-wasm團隊的一部分。您可以通過訪問該回購找到更...

  • [WebAssembly技術]Rust編譯成wasm文件

    WebAssembly (以下簡稱WASM)最近聽到最多的,相對比較火的一個技術,現在主流的瀏覽器已經完成了對WebAssembly 的初步實現,并且圍繞wasm的工具鏈也日趨完善。 由于 WASM 是靜態類型,因此很難直接使用我們熟悉的 JavaScript來直接編寫,目前的 WASM 都是通過其他靜態語言編譯而來。目前支持 WASM 的語言有 C++、Rust、Go等。其中 Rust 對 WA...

  • WebAssembly 與 Rust 綜述

    首先要說一句,WebAssembly是一項極速發展的技術,互聯網上流傳的很多文章(17,18年所寫)已經過時了。所以,請盡量查閱最新時間的相關描述文檔。WebAssem...

  • Programming WebAssembly with Rust 譯 WebAssembly基礎知識 ( Part I 建立一個基礎 第一章 )

    讓我們開始探索WebAssembly基礎知識并學習如何編寫原始WebAssembly代碼 使用WebAssembly,編譯的WebAssembly二進制文件(稱為模塊)與負責解釋它的主機之間存在共生關系。這種關系是您使用這項新技術可以做的一切的核心,了解模塊和主機之間的界限是能夠構建有效的WebAssembly應用程序的關鍵。 WebAssembly可以在兩個不同的級別上查看 - 原始的,基...

  • 區塊鏈3.0:擁抱EOS

    EOS是當下最火的區塊鏈技術,被社會廣泛看好為下一代區塊鏈3.0。不同于以太坊的學習,EOS的主語言是C++,本文作為EOS研究的首篇文章,重點介紹EOS的創新點,它的周邊生態,各種概念原理的解釋,以及它被看好的原因。而針對EOS的源碼學習,原理實現以及并行的C++語言的快速學習與掌握,我會在接下來制定一系列學習計劃一一付諸實現。 關鍵字:EOS,DAPP,石墨烯技術,構建本地節點,公鏈映射...

  • [譯] 或許你并不需要 Rust 和 WASM 來提升 JS 的執行效率 — 第二部分

    原文地址:Maybe you don't need Rust and WASM to speed up your JS — Part 2 原文作者:Vyacheslav Egorov 譯文出自:掘金翻譯計劃 本文永久鏈接:github.com/xitu/gold-m… 譯者:geniusq1981 校對者:D-kylin 、leviding 以下內容為本系列文章的第二部分,如果你還沒看第一...

  • Rust日報】 2019-04-16 : nude-rs - 高性能黃圖檢測

    ripgrep 11 發布ripgrep是Linux命令行文件內容檢索工具grep的rust實現版本。版本11修復了很多bug,改進了性能,對二進制文...

  • WASM學習

    一、認識WASM ??最近越來越多的項目開始轉向VNT使用的WASM,像EOS、Ontology,包括最初引入虛擬機EVM運行智能合約環境的以太坊,最近也開始轉向使用WASM。 ??除以太坊外,一些其他項目EOS(c++),Polkadot(rust), Cardano(Haskell,rust)已經或者計劃開發支持wasm的虛擬機。目前 WebAssembly 在以太坊下一代虛擬機(EWASM)...

  • Rust日報】 2019-02-14

    本文轉載自:https://rust.cc/article?id=8a865774-9551-475e-a855-55a7fef40c92 Rust ALL-Hands大會WASM工作組會議紀要 #wasm 進一步探討了wasm-pack 1.0 RoadMap 深入探討了wasm的模塊化工具包的相關細節 討論如何在Cargo中添加某些通用的構建hook,可以將wasm-pac...

  • 使用 Rust + WebAssembly 編寫 crc32

    背景 WebAssembly 在最近幾年里可以說是如火如荼了。從基于 LLVM 的 Emscripten ,到嘗試打造全流程工具鏈的 binaryen ,再到 Rust 社區出現的wasm-bindgen……現在 webpack 4 已經內置了wasm的引入,甚至連 Go 社區也不甘落后地推出了相關的計劃。 作為一個普通的前端開發者,我雖然一直在關注它的發展,但始終沒有直接操刀使用的機會。直到最近...

  • Rust日報】 2019-12-20 Serverless - Rust 使用 WASM 加 Cloudflare

    Rust 1.40.0 了新增了一些新特性不詳細的#[non_exhaustive] structs, enums, and variants這表示當前的屬性有缺少,要增加屬性欄位,沒增...

  • WebAssembly(Wasm)中的字符串

    作者:Timothy McCallum Second State 核心開發 這篇文章詳細解釋了 WASM 中如何實現字符串,文章有點長,建議收藏后慢慢讀~ 字符串的重要性 計算機程序只用數字就可以成功執行。 然而,為了方便人機交互,人類可讀的字符和文字是必需的。 當我們思考人類如何與 Web 上的應用程序進行交互時,情況尤其如此。 絕佳的例子是,人們在訪問Web 時選擇使用域名,而非數字 IP...

  • TiDB-Wasm 原理與實現 | Hackathon 優秀項目介紹

    作者:Ti-Cool 上周我們推送了《讓數據庫運行在瀏覽器里?TiDB + WebAssembly 告訴你答案》,向大家展示了 TiDB-Wasm 的魅力:TiDB-Wasm 項目是 TiDB Hackathon 2019 中誕生的二等獎項目,實現了將 TiDB 編譯成 Wasm 運行在瀏覽器里,讓用戶無需安裝就可以使用 TiDB。 本文由 Ti-Cool 隊成員主筆,為大家詳細介紹 TiDB-...

  • 重新定義代理的擴展性:WebAssembly在Envoy與Istio中的應用

    原文:https://istio.io/blog/2020/wasm-announce/作者:CRAIG BOX, MANDAR JOG, JOHN PLEVYAK, LOUIS RYA...

  • 終極講師介紹:集齊 27 位大神召喚亞洲首屆 Rust 開發者大會!

    RustCon Asia 進入倒計時!就在這個周六,將有 300+ 位開發者齊聚北京,參加亞洲最大的 Rust 語言開發者大會 RustCon Asia。此次大會幾乎將聚集全部 Rust 中國社區的資深開發者和已在生產環境應用的中國本土的 Rust 項目,以及來自亞洲之外的歐洲、澳洲、北美的頂尖開發者們。大家都約好面基了嗎? 時間:4 月 20 -23 日 大會地點: 北京朝陽區廣順南大街 8 號...

  • 學Python后到底能干什么?網友:我太難了

    感覺全世界營銷文都在推Python,但是找不到工作的話,又有哪個機構會站出來給我推薦工作? 筆者冷靜分析多方數據,想跟大家說:關于超越老牌霸主Java,過去幾年間Python一直都被寄予厚望。但是事實是雖然上升趨勢,但是國內環境下,一時間是無法馬上就超越Java的,也可以換句話說:超越Java只是時間問題罷。 太囂張了會Python的人!找工作拿高薪這么簡單? https://edu....

  • 在中國程序員是青春飯嗎?

    今年,我也32了 ,為了不給大家誤導,咨詢了獵頭、圈內好友,以及年過35歲的幾位老程序員……舍了老臉去揭人家傷疤……希望能給大家以幫助,記得幫我點贊哦。 目錄: 你以為的人生 一次又一次的傷害 獵頭界的真相 如何應對互聯網行業的「中年危機」 一、你以為的人生 剛入行時,拿著傲人的工資,想著好好干,以為我們的人生是這樣的: 等真到了那一天,你會發現,你的人生很可能是這樣的: ...

  • Auto.JS實現抖音,刷寶等刷視頻app,自動點贊,自動滑屏,自動切換視頻

    Auto.JS實現抖音,刷寶等刷視頻app,自動點贊,自動滑屏,自動切換視頻 代碼如下 auto(); var appName=rawInput("","刷寶短視頻"); launchApp(appName); sleep("5000"); setScreenMetrics(1080,1920); toast("1023732997"); sleep("3000"); var num = 200...

  • 畢業5年,我問遍了身邊的大佬,總結了他們的學習方法

    我問了身邊10個大佬,總結了他們的學習方法,原來成功都是有跡可循的。

  • 推薦10個堪稱神器的學習網站

    每天都會收到很多讀者的私信,問我:“二哥,有什么推薦的學習網站嗎?最近很浮躁,手頭的一些網站都看煩了,想看看二哥這里有什么新鮮貨。” 今天一早做了個惡夢,夢到被老板辭退了。雖然說在我們公司,只有我辭退老板的份,沒有老板辭退我這一說,但是還是被嚇得 4 點多都起來了。(主要是因為我掌握著公司所有的核心源碼,哈哈哈) 既然 4 點多起來,就得好好利用起來。于是我就挑選了 10 個堪稱神器的學習網站,推...

Global site tag (gtag.js) - Google Analytics 开心农场种蔬菜赚钱 1码出特 那个股票app软件 长春麻将小鸡飞蛋免费下载ios 快乐8怎么玩才赢钱 钱龙捕鱼好打的网站 股票短线是什么意思 网上赚钱的兼职项目 意甲直播 温州麻将算分 浙江快乐彩开奖结果 西甲积分榜排名规则 大地棋牌唯一 百分百平特一肖公式 股票交易规则过程 韩国卑诗快乐8预测 游戏厅捕鱼游戏下载