diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c5a1ebce65..64976d9179e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- perf(): Rework constructors to avoid the extra perf cost of current setup [#9891](https://github.com/fabricjs/fabric.js/pull/9891) - perf(): Remove redundant matrix multiplication in multiplayTransformMatrixArray [#9893](https://github.com/fabricjs/fabric.js/pull/9893) - test(): Convert Animation tests to jest [#9892](https://github.com/fabricjs/fabric.js/pull/9892) - perf(ObjectGeometry): replace cache key string with array [#9887](https://github.com/fabricjs/fabric.js/pull/9887) diff --git a/fabric.ts b/fabric.ts index 623255692e8..4dc1cd7877b 100644 --- a/fabric.ts +++ b/fabric.ts @@ -56,6 +56,10 @@ export { */ FabricObject as Object, } from './src/shapes/Object/FabricObject'; +/** + * Exported so we can tweak default values + */ +export { FabricObject as BaseFabricObject } from './src/shapes/Object/Object'; export type { TFabricObjectProps, diff --git a/src/Shadow.ts b/src/Shadow.ts index f4f97ac9842..2681c5bff36 100644 --- a/src/Shadow.ts +++ b/src/Shadow.ts @@ -127,7 +127,7 @@ export class Shadow { constructor(arg0: string | Partial>) { const options: Partial> = typeof arg0 === 'string' ? Shadow.parseShadow(arg0) : arg0; - Object.assign(this, (this.constructor as typeof Shadow).ownDefaults); + Object.assign(this, Shadow.ownDefaults); for (const prop in options) { // @ts-expect-error for loops are so messy in TS this[prop] = options[prop]; diff --git a/src/shapes/ActiveSelection.ts b/src/shapes/ActiveSelection.ts index 19ff25a0f65..d30668e4493 100644 --- a/src/shapes/ActiveSelection.ts +++ b/src/shapes/ActiveSelection.ts @@ -59,13 +59,13 @@ export class ActiveSelection extends Group { constructor( objects: FabricObject[] = [], - options: Partial = {} + { layoutManager, ...options }: Partial = {} ) { super(objects, { - ...options, - layoutManager: - options.layoutManager ?? new ActiveSelectionLayoutManager(), + layoutManager: layoutManager ?? new ActiveSelectionLayoutManager(), }); + Object.assign(this, ActiveSelection.ownDefaults); + this.setOptions(options); } /** diff --git a/src/shapes/Circle.ts b/src/shapes/Circle.ts index 050e3bbf025..0ebfb73fc1e 100644 --- a/src/shapes/Circle.ts +++ b/src/shapes/Circle.ts @@ -87,6 +87,16 @@ export class Circle< }; } + /** + * Constructor + * @param {Object} [options] Options object + */ + constructor(options?: Props) { + super(); + Object.assign(this, Circle.ownDefaults); + this.setOptions(options); + } + /** * @private * @param {String} key diff --git a/src/shapes/Ellipse.ts b/src/shapes/Ellipse.ts index d56e8c610b5..141cb3cf467 100644 --- a/src/shapes/Ellipse.ts +++ b/src/shapes/Ellipse.ts @@ -61,6 +61,16 @@ export class Ellipse< }; } + /** + * Constructor + * @param {Object} [options] Options object + */ + constructor(options?: Props) { + super(); + Object.assign(this, Ellipse.ownDefaults); + this.setOptions(options); + } + /** * @private * @param {String} key diff --git a/src/shapes/Group.ts b/src/shapes/Group.ts index fe45a2e6bb6..c9e85c282b6 100644 --- a/src/shapes/Group.ts +++ b/src/shapes/Group.ts @@ -137,8 +137,9 @@ export class Group * @param {Object} [options] Options object */ constructor(objects: FabricObject[] = [], options: Partial = {}) { - // @ts-expect-error options error - super(options); + super(); + Object.assign(this, Group.ownDefaults); + this.setOptions(options); this._objects = [...objects]; // Avoid unwanted mutations of Collection to affect the caller this.__objectSelectionTracker = this.__objectSelectionMonitor.bind( diff --git a/src/shapes/IText/IText.ts b/src/shapes/IText/IText.ts index 7ed312ec130..bf1c9a48d34 100644 --- a/src/shapes/IText/IText.ts +++ b/src/shapes/IText/IText.ts @@ -217,13 +217,12 @@ export class IText< } /** - * Constructor * @param {String} text Text string * @param {Object} [options] Options object */ constructor(text: string, options?: Props) { - super(text, options); + super(text, { ...IText.ownDefaults, ...options } as Props); this.initBehavior(); } diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts index 37038e759b9..e90eae1ebe3 100644 --- a/src/shapes/Image.ts +++ b/src/shapes/Image.ts @@ -196,8 +196,11 @@ export class FabricImage< */ constructor(elementId: string, options?: Props); constructor(element: ImageSource, options?: Props); - constructor(arg0: ImageSource | string, options: Props = {} as Props) { - super({ filters: [], ...options }); + constructor(arg0: ImageSource | string, options?: Props) { + super(); + this.filters = []; + Object.assign(this, FabricImage.ownDefaults); + this.setOptions(options); this.cacheKey = `texture${uid()}`; this.setElement( typeof arg0 === 'string' diff --git a/src/shapes/Line.ts b/src/shapes/Line.ts index 953a3af8340..7da88f52575 100644 --- a/src/shapes/Line.ts +++ b/src/shapes/Line.ts @@ -71,8 +71,14 @@ export class Line< * @param {Object} [options] Options object * @return {Line} thisArg */ - constructor([x1, y1, x2, y2] = [0, 0, 0, 0], options: Props = {} as Props) { - super({ ...options, x1, y1, x2, y2 }); + constructor([x1, y1, x2, y2] = [0, 0, 0, 0], options: Partial = {}) { + super(); + Object.assign(this, Line.ownDefaults); + this.setOptions(options); + this.x1 = x1; + this.x2 = x2; + this.y1 = y1; + this.y2 = y2; this._setWidthHeight(); const { left, top } = options; typeof left === 'number' && this.set(LEFT, left); diff --git a/src/shapes/Object/InteractiveObject.ts b/src/shapes/Object/InteractiveObject.ts index 1a8c1b9b9e8..0bd13b9014e 100644 --- a/src/shapes/Object/InteractiveObject.ts +++ b/src/shapes/Object/InteractiveObject.ts @@ -139,11 +139,34 @@ export class InteractiveFabricObject< static getDefaults(): Record { return { ...super.getDefaults(), - controls: createObjectDefaultControls(), ...InteractiveFabricObject.ownDefaults, }; } + /** + * Constructor + * @param {Object} [options] Options object + */ + constructor(options?: Props) { + super(); + Object.assign( + this, + (this.constructor as typeof InteractiveFabricObject).createControls(), + InteractiveFabricObject.ownDefaults + ); + this.setOptions(options); + } + + /** + * Creates the default control object. + * If you prefer to have on instance of controls shared among all objects + * make this function return an empty object and add controls to the ownDefaults + * @param {Object} [options] Options object + */ + static createControls(): { controls: Record } { + return { controls: createObjectDefaultControls() }; + } + /** * Update width and height of the canvas for cache * returns true or false if canvas needed resize. diff --git a/src/shapes/Object/Object.ts b/src/shapes/Object/Object.ts index e7c9ef58e80..37363f2f2b6 100644 --- a/src/shapes/Object/Object.ts +++ b/src/shapes/Object/Object.ts @@ -296,7 +296,7 @@ export class FabricObject< static ownDefaults = fabricObjectDefaultValues; static getDefaults(): Record { - return { ...FabricObject.ownDefaults }; + return FabricObject.ownDefaults; } /** @@ -333,12 +333,9 @@ export class FabricObject< * Constructor * @param {Object} [options] Options object */ - constructor(options: Props = {} as Props) { + constructor(options?: Props) { super(); - Object.assign( - this, - (this.constructor as typeof FabricObject).getDefaults() - ); + Object.assign(this, FabricObject.ownDefaults); this.setOptions(options); } diff --git a/src/shapes/Path.ts b/src/shapes/Path.ts index 6b9ae2c7a3d..1a7485d8d45 100644 --- a/src/shapes/Path.ts +++ b/src/shapes/Path.ts @@ -76,9 +76,12 @@ export class Path< */ constructor( path: TComplexPathData | string, + // todo: evaluate this spread here { path: _, left, top, ...options }: Partial = {} ) { - super(options as Props); + super(); + Object.assign(this, Path.ownDefaults); + this.setOptions(options); this._setPath(path || [], true); typeof left === 'number' && this.set(LEFT, left); typeof top === 'number' && this.set(TOP, top); diff --git a/src/shapes/Polygon.ts b/src/shapes/Polygon.ts index 4e1540f5eb4..8a0e52bf00d 100644 --- a/src/shapes/Polygon.ts +++ b/src/shapes/Polygon.ts @@ -6,13 +6,6 @@ export class Polygon extends Polyline { static type = 'Polygon'; - static getDefaults(): Record { - return { - ...super.getDefaults(), - ...Polyline.ownDefaults, - }; - } - protected isOpen() { return false; } diff --git a/src/shapes/Polyline.ts b/src/shapes/Polyline.ts index bcede176566..1679a6e7536 100644 --- a/src/shapes/Polyline.ts +++ b/src/shapes/Polyline.ts @@ -65,6 +65,7 @@ export class Polyline< ...Polyline.ownDefaults, }; } + /** * A list of properties that if changed trigger a recalculation of dimensions * @todo check if you really need to recalculate for all cases @@ -108,7 +109,10 @@ export class Polyline< * }); */ constructor(points: XY[] = [], options: Props = {} as Props) { - super({ ...options, points }); + super(); + Object.assign(this, Polyline.ownDefaults); + this.setOptions(options); + this.points = points; const { left, top } = options; this.initialized = true; this.setBoundingBox(true); diff --git a/src/shapes/Rect.ts b/src/shapes/Rect.ts index 99bf14db3ed..c4e70a35345 100644 --- a/src/shapes/Rect.ts +++ b/src/shapes/Rect.ts @@ -64,13 +64,13 @@ export class Rect< /** * Constructor * @param {Object} [options] Options object - * @return {Object} thisArg */ - constructor(options: Props) { - super(options); + constructor(options?: Props) { + super(); + Object.assign(this, Rect.ownDefaults); + this.setOptions(options); this._initRxRy(); } - /** * Initializes rx/ry attributes * @private diff --git a/src/shapes/Text/Text.ts b/src/shapes/Text/Text.ts index 4dcd8312d83..0a8f3cd5446 100644 --- a/src/shapes/Text/Text.ts +++ b/src/shapes/Text/Text.ts @@ -420,8 +420,14 @@ export class FabricText< return { ...super.getDefaults(), ...FabricText.ownDefaults }; } - constructor(text: string, options: Props = {} as Props) { - super({ ...options, text, styles: options?.styles || {} }); + constructor(text: string, options?: Props) { + super(); + Object.assign(this, FabricText.ownDefaults); + this.setOptions(options); + if (!this.styles) { + this.styles = {}; + } + this.text = text; this.initialized = true; if (this.path) { this.setPathInfo(); diff --git a/src/shapes/Textbox.ts b/src/shapes/Textbox.ts index 2f91768d92b..a8cffe32580 100644 --- a/src/shapes/Textbox.ts +++ b/src/shapes/Textbox.ts @@ -7,6 +7,7 @@ import type { TextStyleDeclaration } from './Text/StyledText'; import type { SerializedITextProps, ITextProps } from './IText/IText'; import type { ITextEvents } from './IText/ITextBehavior'; import type { TextLinesInfo } from './Text/Text'; +import type { Control } from '../controls/Control'; // @TODO: Many things here are configuration related and shouldn't be on the class nor prototype // regexes, list of properties that are not suppose to change by instances, magic consts. @@ -97,11 +98,28 @@ export class Textbox< static getDefaults(): Record { return { ...super.getDefaults(), - controls: createTextboxDefaultControls(), ...Textbox.ownDefaults, }; } + /** + * Constructor + * @param {String} text Text string + * @param {Object} [options] Options object + */ + constructor(text: string, options?: Props) { + super(text, { ...Textbox.ownDefaults, ...options } as Props); + } + + /** + * Creates the default control object. + * If you prefer to have on instance of controls shared among all objects + * make this function return an empty object and add controls to the ownDefaults object + */ + static createControls(): { controls: Record } { + return { controls: createTextboxDefaultControls() }; + } + /** * Unlike superclass's version of this function, Textbox does not update * its width. diff --git a/src/shapes/Triangle.ts b/src/shapes/Triangle.ts index abd78f7c8c2..a7716ebef29 100644 --- a/src/shapes/Triangle.ts +++ b/src/shapes/Triangle.ts @@ -25,6 +25,16 @@ export class Triangle< return { ...super.getDefaults(), ...Triangle.ownDefaults }; } + /** + * Constructor + * @param {Object} [options] Options object + */ + constructor(options?: Props) { + super(); + Object.assign(this, Triangle.ownDefaults); + this.setOptions(options); + } + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on diff --git a/test/unit/canvas.js b/test/unit/canvas.js index 73b8d9ba760..bfffe7f4ee3 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -343,8 +343,11 @@ function initActiveSelection(canvas, activeObject, target, multiSelectionStacking) { fabric.classRegistry.setClass(class TextActiveSelection extends fabric.ActiveSelection { - static getDefaults() { - return {...super.getDefaults(),multiSelectionStacking} + static ownDefaults = { + multiSelectionStacking, + } + constructor(objects, options) { + super(objects, { ...TextActiveSelection.ownDefaults, ...options }) } }); canvas.setActiveObject(activeObject); diff --git a/test/unit/object_interactivity.js b/test/unit/object_interactivity.js index a8ccb9634de..1befd91a6b2 100644 --- a/test/unit/object_interactivity.js +++ b/test/unit/object_interactivity.js @@ -147,7 +147,7 @@ // set size for bottom left corner and have different results for bl than normal setCornerCoords test QUnit.test('corner coords: custom control size', function(assert) { //set custom corner size - const sharedControls = fabric.Object.getDefaults().controls; + const sharedControls = fabric.FabricObject.createControls().controls; sharedControls.bl.sizeX = 30; sharedControls.bl.sizeY = 10;