用JS 對象神侃軟硬連接與文件拷貝的區別

前言

在Linux或MacOS系統中,ln命令是一個重要的命令,它的功能是爲某一個文件在另一個位置創建一個同步的連接。php

對於前端來講,ln 命令被應用最多的地方就是, 就是全局安裝並建立一個 npm 命令html

npm i -g xxx(nrm)

當敲下回車,上面的安裝執行完成後,在輸出中,會看到這樣一串字符:前端

/usr/local/bin/nrm -> /usr/local/lib/node_modules/nrm/cli.js

這串字符背後的意思就是系統創建了node_modules/nrm/cli.js 的軟連接/bin/nrmnode

其實bin文件夾中的可執行命令,基本都是以軟連接的形式存在。linux

更多關於 ln 的使用,可參考菜鳥教程git

下面會圍繞路徑A 和 B 這兩個實例來說軟硬連接和文件拷貝的區別: github

路徑A: /user/wam/A/request.js npm

路徑B: /user/wam/B/request.js編輯器

request.js 內容函數

import utils from './utils';

console.log('res:', utils.res());

目錄與文件

在開始前,簡單回顧一下大學沒學過,可能在那裏看到過的文件系統,這裏圍繞簡單易理解的Linux爲例。
20200930152537
大部分的Linux文件系統(如ext二、ext3)規定,一個文件由目錄、節點(inode)和數據塊(block)組成

  • 目錄項:包括文件名和inode節點號。
  • inode:又稱文件索引節點,包含文件的基礎信息以及數據塊的位置。
  • block:包含文件的具體內容。

當咱們隨便打開咱們某個開發項目,命令行輸入ls -li, 就可看到目錄與inode的對應信息,下圖第一行就是目錄對應的inode。
20200930150545

因爲一個文件塊(block)的大小有限(一般爲4kb),全部經常一個文件須要存儲在多個文件塊中,這樣 inode 就須要存儲多個block的位置信息(如最上圖所示), 而一個inode自己只有128 Btyes 的存儲空間,因此存儲文件block位置也是間接經過block來作的, 因此block 能夠理解爲分兩種: 文件內容block 與 inode信息block,搞懂這些就能夠往下了。

參考資料

軟連接

軟連接(soft link) 又被稱爲符號連接,至關於Window 系統中的快捷方式。

eg: 創建A 爲 B 的軟連接

ln -s /user/wam/B/request.js /user/wam/A/request.js

image

創建軟連接 其實質就是某爲路徑的創建一個超連接(在這表現爲 A 爲 B的超連接),其不具備文件實體。當咱們嘗試打開A 路徑所在的文件,其最終在編輯器打開的是路徑B的文件,因此其文件內的相對路徑引用文件也是相對路徑B來計算的,即utils 文件路徑爲:

/user/wam/B/utils.js
當刪除B文件,再去訪問A, 會發現索引不存在,沒法訪問。

硬連接

硬連接(hard link), 是爲源文件創建另外一個索引。

eg: 創建A 爲 B 的硬連接

// 少一個 -s 選項
ln /user/wam/B/request.js /user/wam/A/request.js

image

創建硬連接 其實質就是爲文件實體建立另外一個可訪問的路徑索引。因此當咱們嘗試打開A 路徑所在的文件,與軟連接區別的是:其最終在編輯器打開的是路徑A本身,因此其文件內的 相對路徑引用文件 也是相對路徑A來計算的, 即utils 文件路徑爲:

/user/wam/A/utils.js

但值得一提的是,因爲 A 與 B 路徑都指向同一個源文件,因此在A路徑對文件內容所作的編輯都會反映在 B 路徑文件,即兩邊文件的變更是相互同步影響的。

當刪除路徑B時,源文件不會被垃圾回收,由於路徑A 仍保持對源文件的索引。

硬連接和軟連接還有一個區別是:由於系統的限制,硬連接要求路徑是在文件維度,而軟連接既能夠是文件,也能夠是文件夾。

文件拷貝

這個應該用過電腦的人都懂。

eg: 拷貝文件B 到路徑 A

// 少一個 -s 選項
cp -f /user/wam/B/request.js /user/wam/A/request.js

image.png

文件拷貝,是平常咱們最多見的操做,只是更常見的形式是用ctrl + c/v,而非cp 命令(實際上cp 也能實現ln連接的操做), 其實質就是拷貝一份文件實體並建立一個可訪問的路徑索引。因此當咱們嘗試打開A 路徑所在的文件,其指向的實體是不一樣於B的(克隆體),因此其文件內的相對路徑引用文件也是相對路徑A來計算的即utils 文件路徑爲,與創建硬連接一致:

/user/wam/A/utils.js
因爲 A 路徑 與 B 路徑 都分別指向本身的實體,因此A/B各自是獨立的,當刪除B時,B對應的源文件會被回收,A不受任何影響。

神侃JS對象與硬軟連接

做爲前端咱們都知道,JS對象(object)是引用類型。

引用類型的值是保存在內存中的對象。JS 不容許直接訪問內存中的位置,即不能直接操做對象的內存空間。在操做對象時,其實是在操做對象的引用而不是實際的對象。爲此,引用類型的值是按引用訪問的。(摘抄自紅寶書 P87)

你品,你細品。是否是感受 引用類型 和咱們上面講到文件連接與源文件很像。

以:

const B = { a: 1 };

20200911111937

因此當執行下面這種操做:

const C = B;

C.a = 2;

console.log('B.a:', B.a); // 2

B.a = 3; 
console.log('C.a:', C.a); // 3

從上面的執行輸出,咱們能夠很容易看出,原來JS中的引用類型變量賦值和硬連接 是一回事。

接着咱們引入一個lodash 的深拷貝(cloneDeep)函數:

import { cloneDeep } from 'lodash';

const C = cloneDeep(B);

C.a = 2;

console.log('B.a:', B.a); // B.a: 1

B.a = 3; 
console.log('C.a:', C.a); // C.a: 2

從上面的執行輸出,咱們能夠很容易看出,原來JS中的深拷貝和文件拷貝 是一回事。

軟連接怎麼用 JS 來描述呢?Proxy?

Proxy 中文譯做代理,在表現上實際上是與硬連接一致的,而硬連接與軟連接從表現上最大的區別就是:B(母體) 被刪除後,A(超連接)就不可訪問了,因此這並非正確的答案。

而正確答案是:WeakRef,弱引用. 當下處於proposal階段,不過在Chrome 與 Firefox 最新的版本都對其作了實現;

看個demo:

let B = { a: 1 };

const C = new WeakRef(B);

const registry = new FinalizationRegistry(heldValue => {
  console.log('GC worked:', heldValue); // GC worked: B
  // 當B所指向的值被垃圾回收後,這個回調將被執行
  console.log('C.a:', C.deref()?.a); //C.a: undefined
});

// 註冊B所指向的值被垃圾回收的監聽
registry.register(B, "B");

console.log('C.a:', C.deref().a); // C.a: 1

C.deref().a = 2; // 經過索引改變值

console.log('B.a:', B.a); // B.a: 2

B.a = 3;

console.log('C.a:', C.deref().a); // C.a: 3

// 切斷對值得索引, 觀察上面的GC 回調
B = null;

// console.log('after C.a:', C.deref()?.a);

貌似上面的JS代碼能勉強闡述軟連接的原理,但離理想確實還有距離,這個神侃更多的是想讓你們對ES新提案中的WeakRefFinalizationRegistry有一個感性的認識。

更多關於 WeakRef 請閱讀

結語

經過本文,你是否是發現,這世間萬事萬物是否是特別奇妙。雖然是神侃,讀到這裏,但願你能有一絲絲收穫。

原文見: https://github.com/closertb/c...

公衆號:前端黑洞

相關文章
相關標籤/搜索