<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊聊Deno的那些事

          共 15019字,需瀏覽 31分鐘

           ·

          2021-05-31 11:46

          本文首發(fā)于政采云前端團(tuán)隊(duì)博客:聊聊Deno的那些事

          https://www.zoo.team/article/talk-about-deno

          Deno 是什么

          Deno 是一個(gè)簡(jiǎn)單、現(xiàn)代、安全的 JavaScript、TypeScript、Webassembly (https://webassembly.org/) 運(yùn)行時(shí)環(huán)境。

           

          Deno 是 Node 的變位詞,其發(fā)音是恐龍(dinosaur)的縮寫讀音"蒂諾"。

          它是建立在:

          • Rust (https://www.rust-lang.org/zh-CN/):Deno 的底層是用 Rust 開發(fā),而 Node 是用 C++。
          • Tokio (https://tokio-zh.github.io/):Deno 的事件機(jī)制是基于 Tokio,而 Node 是基于 libuv。
          • TypeScript (https://www.typescriptlang.org/)
          • V8 (https://v8.dev/)

          Deno 的背景

          Deno 起源于 Node 的創(chuàng)建者 Ryan Dahl,這也是大家對(duì) Deno 項(xiàng)目充滿期待的原因之一。在 JSConfEu 上,Dahl 在他的的演講 (https://www.youtube.com/watch?v=M3BM9TB-8yA&vl=en) 中說出了自己對(duì) Node 中存在的一些缺陷,并解釋了如何圍繞 Node 的架構(gòu)做出更好的決定,在演講的最后,宣布了 Deno 的第一個(gè)原型,并承諾構(gòu)建一個(gè)更好、更安全的運(yùn)行時(shí)環(huán)境。

          Node 的缺陷

          原生 API 缺少 Promise

          Node 最大的亮點(diǎn)在于事件驅(qū)動(dòng), 非阻塞 I/O 模型,這使得 Node 具有很強(qiáng)的并發(fā)處理能力,非常適合編寫網(wǎng)絡(luò)應(yīng)用。在 Node 中大部分的 I/O 操作幾乎都是異步的,于是乎 Callback Hell 產(chǎn)生了:

          // fs.js
          const fs = require('fs');
          const myFile = '/tmp/test';

          fs.readFile(myFile, 'utf8', (err, txt) => {
            if (!err) {
              fs.writeFile(myFile);
            }
          });

          若要實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,你需要使用 Promise 重新包裝下原生 API,如下所示:

          const fs = require("fs");
          const myFile = '/tmp/test';

          function readFile_promise(path{
            return new Promise((resolve, reject) => {
              fs.readfile(path, "utf-8", (err, data) => {
                if (err) {
                  reject(err);
                } else {
                  resolve(data);
                }
              })
            });
          }

          readFile_promise(myFile)
            .then((res) => {
              fs.writeFile(myFile, res);
            })

          缺少安全性

          在 Node 中,可以調(diào)用 fs.chmod 來修改文件或目錄的讀寫權(quán)限。說明 Node 運(yùn)行時(shí)的權(quán)限是很高的。如果你在 Node 中導(dǎo)入一份不受信任的軟件包,那么很可能它將刪除你計(jì)算機(jī)上的所有文件,所以說 Node 缺少安全模塊化運(yùn)行。除非手動(dòng)提供一個(gè)沙箱環(huán)境,諸如 Docker 這類的容器環(huán)境來解決安全性問題。

          const fs = require('fs');
          //刪除hello.txt
          fs.unlinkSync('./hello.txt');
          // 刪除css文件夾
          fs.rmdirSync('./css');

          構(gòu)建系統(tǒng)與 Chrome 存在差異

          首先我們需要了解構(gòu)建系統(tǒng)是啥?

          寫慣前端的童鞋可能不是很明白這個(gè)東西是干啥用的?但是其實(shí)平時(shí)你都會(huì)接觸到,只是概念不同而已。前端我們一般稱其為打包構(gòu)建,類似工具諸如 Webpack、Rollup、Parcel 做的事情。它們最后的目標(biāo)其實(shí)都是想得到一些目標(biāo)性的文件,這里我們的目標(biāo)是編譯 V8 (https://v8.dev/docs/build-gn) 代碼。

          Node 的 V8 構(gòu)建系統(tǒng)是 GYP (https://gyp.gsrc.io/)(Generate Your Projects),而 Chrome 的 V8 已升級(jí)為 GN (https://chromium.googlesource.com/chromium/src/tools/gn/+/48062805e19b4697c5fbd926dc649c78b6aaa138/README.md)(Generate Ninja)。我們知道 V8 是由 Google 開發(fā)的,這也證明 Node 和 Google 的親兒子 Chrome 漸行漸遠(yuǎn),而且 GN 的構(gòu)建速度比 GYP 快20倍,因?yàn)?GN 是用 C++ 編寫,比起用 Python 寫的 GYP 快了很多。但是 Node 底層架構(gòu)已無法挽回。

          復(fù)雜的包管理模式

          Node 自帶的 NPM 生態(tài)系統(tǒng)中,由于嚴(yán)重依賴語義版本控制和復(fù)雜的依賴關(guān)系圖,少不了要與 package.json、node_modules 打交道。node_modules 的設(shè)計(jì)雖然能滿足大部分的場(chǎng)景,但是其仍然存在著種種缺陷,尤其在前端工程化領(lǐng)域,造成了不少的問題。特別是不同包依賴版本不一致時(shí),各種問題接踵而來,于是乎 yarn lock、npm lock 閃亮登場(chǎng)。

          然而還是有很多場(chǎng)景是 lock 無法覆蓋的,比如當(dāng)我們第一次安裝某個(gè)依賴的時(shí)候,此時(shí)即使第三方庫里含有 lock 文件,但是 npm install|、yarn install 也不會(huì)去讀取第三方依賴的 lock,這導(dǎo)致第一次創(chuàng)建項(xiàng)目的時(shí)候,還是可能會(huì)觸發(fā) bug。而且由于交叉依賴,node_modules 里充滿了各種重復(fù)版本的包,造成了極大的空間浪費(fèi),也導(dǎo)致 install 依賴包很慢,以及 require 讀取文件的算法越來越復(fù)雜化。

          讀取文件復(fù)雜化

          Node 使用 require (https://nodejs.org/api/modules.html#modules_all_together) 引用其他腳本文件,其內(nèi)部邏輯如下:

          當(dāng) Node 遇到 require(X) 時(shí),按下面的順序處理。
          1)如果 X 是內(nèi)置模塊(比如 require('http'))
            a. 返回該模塊。
            b. 不再繼續(xù)執(zhí)行。

          2)如果 X 以 "./" 或者 "/" 或者 "../" 開頭
            a. 根據(jù) X 所在的父模塊,確定 X 的絕對(duì)路徑。
            b. 將 X 當(dāng)成文件,依次查找下面文件,只要其中有一個(gè)存在,就返回該文件,不再繼續(xù)執(zhí)行。
                X
                X.js
                X.json
                X.node
            c. 將 X 當(dāng)成目錄,依次查找下面文件,只要其中有一個(gè)存在,就返回該文件,不再繼續(xù)執(zhí)行。
                X/package.json(main字段)
                X/index.js
                X/index.json
                X/index.node
                
          3)如果 X 不帶路徑
            a. 根據(jù) X 所在的父模塊,確定 X 可能的安裝目錄。
            b. 依次在每個(gè)目錄中,將 X 當(dāng)成文件名或目錄名加載。

          4) 拋出 "not found"

          可以看得出來,require 的讀取邏輯是很復(fù)雜的,雖然用起來很可愛,但是沒必要。

          Deno 的架構(gòu)

          1. Deno 以 Rust 作為啟動(dòng)入口,通過 Rust FFI 去執(zhí)行 C++ 代碼,然后在 C++ 中引入 V8 實(shí)例。

          2. 初始化 V8 對(duì)象以及注入外部 C++ 方法,例如 send、recv 等方法。

          3. 向 V8 全局作用域下注入 Deno 對(duì)象,暴露 Deno 的一些基本 API 給 JavaScript。

          4. 通過綁定在 V8 上的 C++ 方法,調(diào)用對(duì)應(yīng)的 Rust 方法,去執(zhí)行底層邏輯。

          不難發(fā)現(xiàn) Deno 其實(shí)和 RN、Flutter 這些框架很類似,因?yàn)樗举|(zhì)上也是跑了個(gè) JS 引擎,只是這個(gè) JS 引擎是 V8,不負(fù)責(zé) UI 的 binding 而已。所以說架構(gòu)的本質(zhì)就是思路復(fù)刻、模塊重組。

          Deno 的特點(diǎn)

          安全

          與 Node 相反,Deno 默認(rèn)在沙箱中執(zhí)行代碼,這意味著運(yùn)行時(shí)無法訪問以下權(quán)限:

          • 文件系統(tǒng)
          • 網(wǎng)絡(luò)
          • 環(huán)境變量

          你可以通過命令行參數(shù)形式來開啟默認(rèn)關(guān)閉的權(quán)限,類似下面這樣:

          // 授予從磁盤讀取和偵聽網(wǎng)絡(luò)的權(quán)限
          deno run --allow-read --allow-net https://deno.land/std/http/file_server.ts

          // 授予從磁盤filepath讀取白名單文件的權(quán)限
          deno run --allow-read=/etc https://deno.land/std/http/file_server.ts

          // 授予所有權(quán)限
          deno run --allow-all https://deno.land/std/http/file_server.ts

          或者通過編程形式控制權(quán)限,類似下面這樣:

          // 檢測(cè)是否有讀取權(quán)限
          const status = await Deno.permissions.query({ name"write" });
          if (status.state !== "granted") {
            throw new Error("need write permission");
          }

          // 讀取log文件
          const log = await Deno.open("request.log""a+");

          // 關(guān)閉讀寫權(quán)限
          await Deno.permissions.revoke({ name"read" });
          await Deno.permissions.revoke({ name"write" });

          // 打印log內(nèi)容
          const encoder = new TextEncoder();
          await log.write(encoder.encode("hello\n"));

          內(nèi)置工具

          Deno 目前提供了以下內(nèi)置工具,在使用 JavaScript 和 TypeScript 時(shí)非常有用,只需要執(zhí)行以下命令即可:

          • deno bundler (https://deno.land/[email protected]/tools/bundler):自帶打包和 tree shaking功能,可以將我們的代碼打包成單文件
          • deno compile (https://deno.land/[email protected]/tools/compiler):將 Deno 項(xiàng)目構(gòu)建為完全獨(dú)立的可執(zhí)行文件
          • deno install (https://deno.land/[email protected]/tools/script_installer):可以將我們的代碼生成可執(zhí)行文件進(jìn)行直接使用
          • deno info (https://deno.land/[email protected]/tools/dependency_inspector):查看所有模塊的依賴關(guān)系樹
          • deno doc (https://deno.land/[email protected]/tools/documentation_generator):將源代碼中的注釋生成文檔
          • deno fmt (https://deno.land/[email protected]/tools/formatter):遞歸地格式化每個(gè)子目錄中的每個(gè)文件
          • deno repl (https://deno.land/[email protected]/tools/repl):?jiǎn)?dòng)一個(gè) read-eval-print-loop,它允許您在全局上下文中交互式地構(gòu)建程序狀態(tài)
          • deno test (https://deno.land/[email protected]/testing):對(duì)名為 .test 的文件進(jìn)行單元測(cè)試
          • deno lint (https://deno.land/[email protected]/tools/linter):代碼檢測(cè)器

          支持 TyprScript

          使用 Deno 運(yùn)行 TypeScript 代碼不需要編譯步驟以及繁瑣的配置文件—— Deno 會(huì)自動(dòng)為你執(zhí)行這一步驟。

          源碼 (https://github.com/denoland/deno/tree/main/cli/tsc) 中我們發(fā)現(xiàn),Deno 其實(shí)是集成了一個(gè) TypeScript 編譯器和一個(gè)用于運(yùn)行時(shí)快照的小型編譯器主機(jī)。轉(zhuǎn)換的核心代碼 (https://github.com/denoland/deno/blob/main/cli/tsc.rs) 如下:

          // globalThis.exec 這個(gè)函數(shù)在/cli/tsc/99_main_compiler.js中
          // 其主要作用就是把TypeScript轉(zhuǎn)換成JavaScript
          let exec_source = format!("globalThis.exec({})", request_str);

            runtime
              .execute("[native code]", startup_source)
              .context("Could not properly start the compiler runtime.")?;
            runtime.execute("[native_code]", &exec_source)?;

          前段時(shí)間 Deno 內(nèi)部把 TS 改回 JS 的討論很是熱鬧,但并不意味著 Deno 放棄了 TypeScript,它依然是一個(gè)安全的 TS/JS Runtime。

          例如:

          // index.ts
          const str: string = 'hello word';
          console.log(str);

          你可以直接在命令行運(yùn)行并打印出 hello word:

          deno run index.ts

          支持 ES 模塊標(biāo)準(zhǔn)

          Deno 采用的是 ES Module 的瀏覽器實(shí)現(xiàn)。ES Module (https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) 大家應(yīng)該都是比較熟悉的,它是 JavaScript 官方的標(biāo)準(zhǔn)化模塊系統(tǒng),其瀏覽器實(shí)現(xiàn)如下所示:

          // 從 URL 導(dǎo)入
          import React from "https://cdn.bootcdn.net/ajax/libs/react/17.0.1/cjs/react-jsx-dev-runtime.development.js";
          // 從相對(duì)路徑導(dǎo)入
          import * as Api from "./service.js";
          // 從絕對(duì)路徑導(dǎo)入
          import "/index.js";

          需要注意的是,Deno 不支持以下寫法:

          import foo from "foo.js";
          import bar from "bar/index.js";
          import zoo from "./index"// 沒有后綴

          兼容瀏覽器 API

          Deno 通過與瀏覽器 API 保持一致,來減少大家的認(rèn)知。

          • 模塊系統(tǒng):從上面的介紹看出 Deno 是完全遵循瀏覽器實(shí)現(xiàn)的。
          • 默認(rèn)安全
          • 對(duì)于異步操作返回 Promise
          • 使用 ArrayBuffer 處理二進(jìn)制
          • 存在 window 全局變量
          • 支持 fetch、webCrypto、worker 等 Web 標(biāo)準(zhǔn),也支持 onload、onunload、addEventListener 等事件操作函數(shù)
          console.log(window === thiswindow === self, window === globalThis); // true true true

          支持 Promise

          Deno 所有的異步操作,一律返回 Promise,并且全局支持 await。

          // 讀取異步接口數(shù)據(jù)
          const response = await fetch("http://my.json.host/data.json");
          console.log(response.status)
          console.log(response.statusText);
          const jsonData = await response.json();

          // 讀取文件
          const decoder = new TextDecoder("utf-8");
          const data = await Deno.readFile("hello.txt");
          console.log(decoder.decode(data));

          去中心化包

          Deno 沒有 package.json、node_modules,那么它是怎么進(jìn)行包管理的呢?我們先看下面的例子:

          // index.js
          import { white, bgRed } from "https://deno.land/std/fmt/colors.ts";
          console.log(bgRed(white("hello world!")));

          // 命令行執(zhí)行
          > deno run index.js
          Download https://deno.land/std/fmt/colors.ts
          Compile https://deno.land/std/fmt/colors.ts
          hello world!

          我們看到執(zhí)行時(shí)會(huì)有 Download 和 Compile 兩個(gè)步驟,于是乎我們會(huì)產(chǎn)生幾個(gè)疑問:

          1、每次執(zhí)行都要下載嗎?

          答:不需要每次下載,有緩存機(jī)制。

          > deno run index.js
          hello world!

          2、Download 和 Compile 的文件在哪里呢?

          答:我們可以通過上面介紹的自帶工具 deno info 來查看依賴關(guān)系。

          > deno info index.js
          local/Users/xxx/Desktop/index.ts
          type: TypeScript
          emit/Users/xxx/Library/Caches/deno/gen/file/Users/xxx/Desktop/index.ts.js
          dependencies0 unique (total 41B)

          file:///Users/xxx/Desktop/index.ts (41B)

          3、依賴代碼更新了怎么辦?

          答:當(dāng)依賴模塊更新時(shí),我們可以通過 --reload 進(jìn)行更新緩存,例如:

          > deno run --reload index.js
          // 通過白名單的方式更新部分依賴
          > deno run --reload=https://deno.land index.js

          4、多版本怎么處理?

          答:暫時(shí)沒有好的解決方案,只能通過 git tag 的方式區(qū)分版本。

          Deno 是通過 URL 導(dǎo)入代碼,可以在互聯(lián)網(wǎng)上的任何地方托管模塊。并且相比 Node 的 require 讀取文件,它顯得更加輕巧玲瓏,并且無需集中注冊(cè)表即可分發(fā) Deno 軟件包。不需要 package.json 文件和依賴項(xiàng)列表,因?yàn)樗心K都是在應(yīng)用程序運(yùn)行時(shí)下載,編譯和緩存的。

          上手 Deno

          安裝

          使用 Shell (macOS 和 Linux):

          curl -fsSL https://deno.land/x/install/install.sh | sh

          使用 PowerShell (Windows):

          iwr https://deno.land/x/install/install.ps1 -useb | iex

          運(yùn)行 deno --version,如果它打印出 Deno 版本,說明安裝成功。

          > deno --version
          deno 1.8.1 (release, aarch64-apple-darwin)
          v8 9.0.257.3
          typescript 4.2.2

          實(shí)戰(zhàn)體驗(yàn)

          Hello Word

          本地創(chuàng)建一個(gè) index.ts 文件,內(nèi)容如下所示:

          // index.ts
          console.log("Welcome to Deno ??");

          打開終端,輸入以下命令行:

          > deno run index.ts

          以上輸出 "Welcome to Deno ??"。

          HTTP 請(qǐng)求

          本地創(chuàng)建一個(gè) http.ts 文件,內(nèi)容如下所示:

          const url = Deno.args[0]; // 取得第一個(gè)命令行參數(shù),存儲(chǔ)到變量 url。
          const res = await fetch(url); // 向指定的地址發(fā)出請(qǐng)求,等待響應(yīng),然后存儲(chǔ)到變量 res。
          const body = new Uint8Array(await res.arrayBuffer()); // 把響應(yīng)體解析為一個(gè) ArrayBuffer,等待接收完畢,將其轉(zhuǎn)換為 Uint8Array,最后存儲(chǔ)到變量 body。
          await Deno.stdout.write(body); // 把 body 的內(nèi)容寫入標(biāo)準(zhǔn)輸出流 stdout。

          打開終端,輸入以下命令行:

          deno run --allow-net=api.github.com http.ts https://api.github.com/users/answer518

          以上輸出 json 對(duì)象。

          遠(yuǎn)程導(dǎo)入

          從遠(yuǎn)程模塊導(dǎo)入 add 和 multiply 方法:

          import {
            add,
            multiply,
          from "https://x.nest.land/[email protected]/source/index.js";

          function totalCost(outbound: number, inbound: number, tax: number): number {
            return multiply(add(outbound, inbound), tax);
          }

          console.log(totalCost(19311.2)); // 60
          console.log(totalCost(45271.15)); // 82.8

          支持 WASM

          // wasm.ts
          const wasmCode = new Uint8Array([
            09711510910001133128128128019601127,
            313012812812801041321281281280111200,
            513112812812801016129128128128007145,
            12812812802610910110911111412120410997,
            10511000101381281281280113212812812800,
            654211
          ]);
          const wasmModule = new WebAssembly.Module(wasmCode);
          const wasmInstance = new WebAssembly.Instance(wasmModule);
          const main = wasmInstance.exports.main as CallableFunction;
          console.log(main().toString());

          打開終端,輸入以下命令行:

          > deno run wasm.ts

          以上輸出數(shù)字 42。

          RESTful 服務(wù)

          // restful.ts
          import { Application, Router } from "https://deno.land/x/oak/mod.ts";

          const books = new Map<string, any>();
          books.set("1", {
            id"1",
            title"平凡的世界",
            author"路遙",
          });

          const router = new Router();
          router
            .get("/", (context) => {
              context.response.body = "Hello world!";
            })
            .get("/book", (context) => {
              context.response.body = Array.from(books.values());
            })
            .get("/book/:id", (context) => {
              if (context.params && context.params.id && books.has(context.params.id)) {
                context.response.body = books.get(context.params.id);
              }
            });

          const app = new Application();
          app.use(router.routes());
          app.use(router.allowedMethods());

          await app.listen({ hostname'127.0.0.1'port8000 });

          終端輸入以下命令:

          > deno run  --allow-net restful.ts

          本地訪問 http://localhost:8000/book/1 將會(huì)返回 id 為 1 的 book 數(shù)據(jù)。

          靜態(tài)資源服務(wù)

          // static.ts
          import { Application } from "https://deno.land/x/oak/mod.ts";

          const app = new Application();

          app.use(async (context) => {
            await context.send({
              root: Deno.cwd(), // 靜態(tài)資源的根路徑
            });
          });

          await app.listen({ hostname"127.0.0.1"port8000 });

          終端輸入以下命令:

          > deno run  --allow-net --allow-read static.ts

          本地訪問 http://localhost:8000/static.ts 將會(huì)返回 static.ts 的源碼。

          結(jié)束語

          Deno 是一個(gè)非常偉大的項(xiàng)目,但卻不是 “下一代 Nods.js ”。Ryan Dahl 自己也說: “Node.js isn't going anywhere” 。并且 Deno 還處在開發(fā)中,功能還不穩(wěn)定,不建議用于生產(chǎn)環(huán)境。但是,它已經(jīng)是一個(gè)可用的工具,有很多新特性都是 Node 所沒有的,大家可以多多試玩。


          內(nèi)推社群


          我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對(duì)加入騰訊感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩中文字幕熟妇人妻 | 猫咪AV成人永久网站 | 91精品国产综合久久久不打电影 | 91热爆TS人妖系列 | 在线免费观看一区 |