iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0
Modern Web

一起來玩圖像編輯器:Fabric.js 的實戰修煉系列 第 20

Day20- 修改畫布 prototype 的入門-拆解 fabric-history prototype (2)

  • 分享至 

  • xImage
  •  

來細拆 fabric-history 裏面有哪些東西吧

使用立即執行函數(IIFE):

將整個擴展包裹在一個立即執行函數中,可以避免污染全局命名空間。

fabric.Canvas.prototype.initialize = (function (originalFn) {
  return function (...args) {
    originalFn.call(this, ...args);
    this._historyInit();
    return this;
  };
})(fabric.Canvas.prototype.initialize);

IIFE(Immediately Invoked Function Expression)是指立即執行的函數表達式。它是一種在 JavaScript 中常用的設計模式,用於創建一個立即執行的匿名函數,並且不會污染全局命名空間。
IIFE 的用途:

  • 避免全局變量污染:
    IIFE 可以創建一個新的作用域,避免變量污染全局命名空間。
  • 模塊化代碼:
    IIFE 可以用來模塊化代碼,將相關的功能封裝在一個立即執行的函數中。
  • 初始化代碼:
  • IIFE 可以用來執行初始化代碼,只在頁面加載時執行一次。

public 還是 private ?

我們看到了很多有下底線為開頭的命名,這代表的是...?

在 JavaScript 中,前綴 _ 通常用來表示方法或屬性是私有的,但這只是約定俗成的命名規則,並沒有語法上的強制性。實際上,JavaScript 並沒有真正的私有方法或屬性,除非使用 ES6 的 class 語法和 # 前綴來定義私有字段。

命名約定: 使用 _ 前綴表示這個方法是內部使用的,不應該在外部直接調用。這是一種約定,開發者應該遵守這個約定來避免誤用。

實際情況: 雖然 _historyInit 被命名為私有方法,但它仍然可以在外部訪問和調用。這只是通過命名來提示其他開發者這個方法是內部使用的。

所以說這裡的_historyInit其實是假私有


fabric.Canvas.prototype._historyInit = function () {
  this.historyUndo = [];
  this.historyRedo = [];
  this.extraProps = ['selectable', 'editable'];
  this.historyNextState = this._historyNext();

  this.on(this._historyEvents());
};

使用 ES6 私有字段,會長這樣

如果你想要真正的私有方法,可以使用 ES6 的 class 語法和 # 前綴:

class Canvas {
  #historyProcessing = false;

  #historySaveAction() {
    if (!this.#historyProcessing) {
      // 執行保存操作
      console.log('History action saved');
    }
  }

  onHistory() {
    this.#historyProcessing = false;
    this.#historySaveAction();
  }

  offHistory() {
    this.#historyProcessing = true;
  }
}

// 使用示例
const canvas = new Canvas();
canvas.onHistory(); // 會執行保存操作
canvas.offHistory(); // 禁用歷史功能
// 而 #historySaveAction 不能在這邊被拿出來用,只能在 canvas 內部被使用

同場加應,也可以:

使用 Symbol 作為私有屬性:

(這是虛擬的例子,以展示概念)

const _history = Symbol('history'); fabric.Canvas.prototype[_history] = [];

在 JavaScript 中,Symbol 可以用來模擬私有屬性。雖然 Symbol 屬性不是完全私有的,但它們不會出現在普通的對象屬性枚舉中(例如 for...in 迴圈或 Object.keys 方法),因此可以用來實現更隱蔽的屬性。

如何使用 Symbol 作為私有屬性

  1. 創建一個 Symbol
    首先,創建一個唯一的 Symbol,這個 Symbol 將用作對象的私有屬性鍵。

    const _privateProperty = Symbol('privateProperty');
    
  2. 在對象中使用 Symbol 屬性
    使用這個 Symbol 作為對象屬性鍵,並賦予它值。

    class MyClass {
      constructor(value) {
        this[_privateProperty] = value;
      }
    
      getPrivateProperty() {
        return this[_privateProperty];
      }
    }
    
    const instance = new MyClass('secret');
    console.log(instance.getPrivateProperty()); // 'secret'
    console.log(instance._privateProperty); // undefined
    
  3. 屬性不可枚舉
    使用 Symbol 作為屬性鍵的屬性不會出現在普通的屬性枚舉中。

    for (let key in instance) {
      console.log(key); // 不會輸出 _privateProperty
    }
    
    console.log(Object.keys(instance)); // []
    console.log(Object.getOwnPropertyNames(instance)); // []
    
  4. 訪問 Symbol 屬性
    雖然 Symbol 屬性不會出現在普通的屬性枚舉中,但仍然可以通過 Object.getOwnPropertySymbols 方法來訪問。

    const symbols = Object.getOwnPropertySymbols(instance);
    console.log(symbols); // [Symbol(privateProperty)]
    console.log(instance[symbols[0]]); // 'secret'
    

使用 Symbol 作為私有屬性可以增加屬性的隱蔽性,避免與其他屬性發生衝突,並且不會出現在普通的屬性枚舉中。雖然這種方法並不能完全防止屬性被訪問,但它提供了一種更隱蔽的方式來管理對象的內部狀態。

擴展而不是覆蓋:

(這是虛擬的例子,以展示擴展)

const originalRender = fabric.Canvas.prototype.renderAll; fabric.Canvas.prototype.renderAll = function() {
	originalRender.call(this);  
	this.historySaveAction(); 
};
  • 在保留原有方法功能的基礎上,添加新的功能,而不是完全替換原有的方法。這樣可以確保原有方法的行為不會丟失,同時可以在其基礎上進行擴展。
  • 具體方式:
  1. 保存原有方法: 首先,將原有的方法保存到一個變量中,以便在新的方法中調用它。
const originalRender = fabric.Canvas.prototype.renderAll;
  1. 擴展方法: 然後,重新定義該方法。在新的方法中,先調用原有的方法,再添加新的功能。
fabric.Canvas.prototype.renderAll = function() {
  // 調用原有的 renderAll 方法
  originalRender.call(this);
  // 添加新的功能
  this.historySaveAction();
};

提供禁用選項:

提供啟用和禁用歷史功能的方法,並在啟用歷史功能時執行保存操作。

fabric.Canvas.prototype.onHistory = function() {  
this.historyProcessing = false;
 this._historySaveAction();
 };`
fabric.Canvas.prototype.offHistory = function () {
  this.historyProcessing = true;
};
fabric.Canvas.prototype._historySaveAction = function (e) {

  // 檢查 historyProcessing 屬性,只有在歷史功能啟用時才執行保存操作。
  if (this.historyProcessing) return; 
  
  //...其他執行紀錄的動作
};
  • 允許用戶選擇是否使用歷史功能。

通過這些技術,我們可以設計一個既強大又不影響原有 Fabric.js 代碼的擴展。這種方法保持了代碼的模塊化和可維護性,同時最小化了對現有系統的影響。


上一篇
Day19-修改畫布 prototype 的入門-拆解 fabric-history prototype (1)
下一篇
Day21- [相關介紹] 視覺互動向量圖界的運算強者- Paper.js
系列文
一起來玩圖像編輯器:Fabric.js 的實戰修煉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言