كائنات التعريف القابلة للتوسيع (xmo.js) هي مكتبة فئة خفيفة الوزن لإنشاء فئات JS وmixins.
xmo.js
هي مكتبة جافا سكريبت خفيفة الوزن لإنشاء فئات JS وmixins. على عكس العديد من المكتبات الأخرى التي تحاول حل نفس المشكلة، تم تصميم xmo.js
ليكون قابلاً للتوسيع ويوفر فقط آلية لإنشاء فئة جديدة أو mixin مع إمكانية تسجيل الامتدادات المعرفة من قبل المستخدم ومعالجات init.
هناك العديد من مجموعات أدوات واجهة المستخدم وأطر عمل JS التي توفر نوعًا من النظام الفرعي للفئة الذي يضيف الخصائص والأحداث والميزات الأخرى إلى فئة جافا سكريبت التقليدية. هذا النهج يجعل النظام الفرعي للفئة يعتمد على إطار العمل ويجعله غير قابل للاستخدام خارجه.
يختلف النهج الذي يستخدمه xmo.js
فهو يسمح بتسجيل معالجات init وملحقاتها للفئة نفسها، لذا فإن أي فئة ترثها ستوفر نفس ميزات الفئة الأساسية. وهذا يعني أنه يمكن تنفيذ العديد من أطر عمل الفئات الموجودة نظريًا فوق xmo.js
نفسه.
فكيف يتم إنشاء فئة جافا سكريبت؟ ما عليك سوى استيراد مكتبة xmo
(أو تضمينها إذا كنت تعمل من جانب العميل) واستخدام xmo
كوظيفة لإنشاء الفصل الدراسي الخاص بك. تقبل الدالة وسيطة واحدة فقط def
، والتي تُستخدم لتعريف الأعضاء، والخصائص الثابتة، ومعالجات init، والامتدادات؛ سيُرجع كائن Class
جديدًا يمكن إنشاء مثيل له بكلمة رئيسية new
. ويرد مثال بسيط أدناه:
// Create a new class `Class` that doesn't inherit from any object. It
// inherits internally from a pure JavaScript `Object`, which most JS objects
// do. A `constructor` property defines the constructor and other properties
// define class members (that will be added to the prototype object).
const Class = xmo ( {
constructor ( ) {
this . text = "Hi!"
} ,
toString ( ) {
return this . text ;
}
} ) ;
تحدد خاصية constructor
مُنشئ الفئة بينما تحدد الخصائص الأخرى أعضاء الفئة. يمكن إنشاء مثيل Class
ببساطة باستخدام عامل تشغيل new
كما هو موضح في المثال التالي:
// Create an instance of `Class` defined in previous example.
const instance = new Class ( ) ;
// `instance` is now an instance of `Class`, we can check if the inheritance
// is working as expected by calling `toString` method, which should return
// "Hi!". Another expected behavior is using `instanceof` keyword which should
// return `true` if tested against `Class`.
console . log ( instance . toString ( ) ) ; // Outputs `Hi!`.
console . log ( instance instanceof Class ) ; // Outputs `true`.
console . log ( instance instanceof Object ) ; // Outputs `true`.
يمكن تحديد الفئة الأساسية بواسطة خاصية $extend
:
// `Point` is a base class.
const Point = xmo ( {
constructor ( x , y ) {
this . x = x ;
this . y = y ;
} ,
translate ( x , y ) {
this . x += x ;
this . y += y ;
} ,
toString ( ) {
return `Point { x: ${ this . x } , y: ${ this . y } }` ;
}
} ) ;
// `Circle` extends `Point`.
const Circle = xmo ( {
$extend : Point ,
constructor ( x , y , radius ) {
// Has to call superclass constructor.
Point . call ( this , x , y ) ;
this . radius = radius ;
} ,
// Overrides `toString` of `Point`.
toString ( ) {
return `Circle { x: ${ this . x } , y: ${ this . y } , radius: ${ this . radius } }`
}
} ) ;
// Create instances of `Point` and `Circle` classes.
const point = new Point ( 1 , 1 ) ;
const circle = new Circle ( 10 , 10 , 5 ) ;
console . log ( point . toString ( ) ) ; // Outputs `Point { x: 1, y: 1 }`.
console . log ( circle . toString ( ) ) ; // Outputs `Circle { x: 10, y: 10, radius: 5 }`.
// `point` is an instance of `Point`, but not `Circle`.
console . log ( point instanceof Point ) ; // Outputs `true`.
console . log ( point instanceof Circle ) ; // Outputs `false`.
// `circle` is an instance of both `Point` and `Circle`.
console . log ( circle instanceof Point ) ; // Outputs `true`.
console . log ( circle instanceof Circle ) ; // Outputs `true`.
يمكن تعريف الأعضاء الثابتين (الفئة) بواسطة خاصية $statics
.
const Class = xmo ( {
constructor ( ) {
this . status = Class . Ready ;
} ,
$statics : {
Ready : 0 ,
Running : 1
}
} ) ;
console . log ( Class . Ready ) ; // Outputs `0`.
console . log ( Class . Running ) ; // Outputs `1`.
const instance = new Class ( ) ;
console . log ( instance . status ) ; // Outputs `0`.
console . log ( instance . Ready ) ; // Outputs `undefined` (not a member of `instance`).
توفر العديد من أطر عمل JS، خاصة تلك المصممة لواجهة المستخدم، مجموعة ثابتة من الامتدادات لنموذج الكائن الذي تستخدمه. على سبيل المثال، يدعم Qooxdoo عمليات المزج والواجهات ويسمح بتحديد الخصائص والأحداث. الغرض من xmo.js
ليس توفير جميع الميزات التي يمكن تخيلها، ولكن توفير أساس لإضافة امتدادات إلى فئة معينة سيتم تضمينها بعد ذلك بواسطة جميع الفئات التي ترثها. وهذا يسمح بتوسيع نظام الفصل في وقت التشغيل ودعم أي شيء يحتاجه المستخدم تقريبًا.
يوجد حاليًا مفهومان يدعمهما xmo.js
:
$preInit
و $postInit
.$extensions
.يتم دائمًا إنشاء فئة جديدة من خلال الخطوات التالية:
$preInit
.$extensions
المحددة أيضًا)$postInit
. يستخدم المثال التالي خاصية $extensions
لتعريف ملحق $property
:
const Point = xmo ( {
constructor ( x , y ) {
this . x = x ;
this . y = y ;
} ,
$extensions : {
// Define extension `$properties`.
//
// This function will be called every time when `$properties` is used in
// class definition that directly or indirectly inherits `Point`. It is
// also called if `Point` itself uses `$properties` extension.
//
// `this` - Class object (`Point` in our case).
// `k` - Property key ("$properties" string).
// `v` - Property value (`$properties` content).
$properties ( k , v ) {
// Iterate over all keys in `$properties`.
Object . keys ( v ) . forEach ( function ( name ) {
const upper = name . charAt ( 0 ) . toUpperCase ( ) + name . substr ( 1 ) ;
// Create getter and setter for a given `name`.
this . prototype [ `get ${ upper } ` ] = function ( ) { return this [ name ] ; } ;
this . prototype [ `set ${ upper } ` ] = function ( value ) { this [ name ] = value ; } ;
} , this /* binds `this` to the callback. */ ) ;
}
} ,
// In our case this will use the defined `$properties` extension.
$properties : {
x : true ,
y : true
}
} ) ;
// Create an instance of `Point` and call the generated functions.
const point = new Point ( 1 , 2 ) ;
console . log ( point . getX ( ) ) ; // Outputs `1`.
console . log ( point . getY ( ) ) ; // Outputs `2`.
point . setX ( 10 ) ;
point . setY ( 20 ) ;
console . log ( point . getX ( ) ) ; // Outputs `10`.
console . log ( point . getY ( ) ) ; // Outputs `20`.
تحدد الامتدادات وظيفة يتم استدعاؤها في حالة وجود key
معين في تعريف الفئة. نظرًا لأن هذا المفهوم جيد ومفيد بشكل عام، فمن المفيد في بعض الأحيان أن تكون قادرًا على استدعاء معالج قبل و/أو بعد إعداد الفصل بغض النظر عن الخصائص الموجودة في def
تعريفات الفئة. يتم دعم معالجات $preInit
و $postInit
ويمكن استخدامها لإضافة معالج واحد أو عدة معالجات في وقت واحد.
المثال التالي يفعل نفس الشيء مثل ملحق $properties
، ولكنه يستخدم استخدام معالج $postInit
بدلاً من ذلك:
const Point = xmo ( {
constructor ( x , y ) {
this . x = x ;
this . y = y ;
} ,
// Add a preInit handler.
$preInit ( def ) {
// Does nothing here, just to document the syntax.
} ,
// Add a postInit handler, called once on Point and all classes that inherit it.
//
// `this` - Class object (`Point` in our case).
// `def` - The whole `def` object passed to `xmo(...)`.
$postInit ( def ) {
if ( ! def . $properties )
return ;
// Iterate over all keys in `$properties`.
Object . keys ( def . $properties ) . forEach ( function ( name ) {
const upper = name . charAt ( 0 ) . toUpperCase ( ) + name . substr ( 1 ) ;
// Create getter and setter for a given `key`.
this . prototype [ `get ${ upper } ` ] = function ( ) { return this [ name ] ; } ;
this . prototype [ `set ${ upper } ` ] = function ( value ) { this [ name ] = value ; } ;
} , this /* binds `this` to the callback. */ ) ;
} ,
// This is not necessary. Null extensions are only used to make
// a certain property ignored (won't be copied to the prototype).
$extensions : {
$properties : null
} ,
// Will be used by the hook defined above.
$properties : {
x : true ,
y : true
}
} ) ;
// Create an instance of `Point` and use functions created by the
// `property` extension.
const point = new Point ( 1 , 2 ) ;
console . log ( point . getX ( ) ) ; // Outputs `1`.
console . log ( point . getY ( ) ) ; // Outputs `2`.
point . setX ( 10 ) ;
point . setY ( 20 ) ;
console . log ( point . getX ( ) ) ; // Outputs `10`.
console . log ( point . getY ( ) ) ; // Outputs `20`.
معالجات Init تشبه إلى حد كبير الامتدادات، ومع ذلك، فهي لا تحتاج إلى تعريف أي خصائص ويتم استدعاؤها دائمًا مرة واحدة لكل فئة. تعد معالجات Init بشكل عام أكثر قوة، لأنها يمكنها استخدام أي خاصية أو خصائص متعددة لإضافة أشياء إلى الفصل نفسه.
المزيج عبارة عن مجموعة من الوظائف التي يمكن تضمينها في فئة أو مزيج آخر. يتم تعريف Mixins باستخدام xmo.mixin(def)
، حيث def
هو تعريف مشابه متوافق مع xmo.js
نفسه، ولكن بدون دعم constructor
(لا يمكن إنشاء مثيل mixins). تفهم Mixins أيضًا $extensions
ومعالجات $preinit/$postInit
، لذلك من الممكن تعريفها في mixin الذي يتم تضمينه بعد ذلك في فئات أخرى.
// Create a mixin that provides `translate(x, y)` function.
const MTranslate = xmo . mixin ( {
translate ( x , y ) {
this . x += x ;
this . y += y ;
return this ;
}
} ) ;
// Create a Point class that includes MTranslate mixin.
const Point = xmo ( {
$mixins : [ MTranslate ] ,
constructor ( x , y ) {
this . x = x ;
this . y = y ;
} ,
toString ( ) {
return `[ ${ this . x } , ${ this . y } ]` ;
}
} ) ;
// Create a Rect class that includes MTranslate mixin.
const Rect = xmo ( {
$mixins : [ MTranslate ] ,
constructor ( x , y , w , h ) {
this . x = x ;
this . y = y ;
this . w = w ;
this . h = h ;
} ,
toString ( ) {
return `[ ${ this . x } , ${ this . y } , ${ this . w } , ${ this . h } ]` ;
}
} ) ;
// The translate() functions are provided to both classes.
const p = new Point ( 0 , 0 ) ;
const r = new Rect ( 0 , 0 , 33 , 67 ) ;
p . translate ( 1 , 2 ) ;
r . translate ( 1 , 2 ) ;
console . log ( p . toString ( ) ) ; // Outputs `[1, 2]`.
console . log ( r . toString ( ) ) ; // Outputs `[1, 2, 33, 67]`.
الجمع بين المزيد من الخلطات في مزيج واحد:
// Create two mixins MTranslate and MScale.
const MTranslate = xmo . mixin ( {
translate ( x , y ) {
this . x += x ;
this . y += y ;
return this ;
}
} ) ;
const MScale = xmo . mixin ( {
scale ( x , y ) {
if ( y == null )
y = x ;
this . x *= x ;
this . y *= y ;
return this ;
}
} ) ;
// If a combined mixin is needed, it can be created simply by
// including MTranslate and MScale into another mixin.
const MCombined = xmo . mixin ( {
$mixins : [ MTranslate , MScale ]
} ) ;
تحتوي كل فئة تم إنشاؤها بواسطة xmo.js
على خاصية غير قابلة للتعداد تسمى MetaInfo. فهو يحتوي على معلومات أساسية حول الفصل نفسه (والوراثة) ويمكن استخدامه لتخزين المعلومات الإضافية التي تتطلبها الامتدادات.
دعونا نوضح أساسيات MetaInfo:
const Class = xmo ( {
$preInit ( ) {
console . log ( "PreInit()" ) ;
} ,
$postInit ( ) {
console . log ( "PostInit()" ) ;
} ,
$extensions : {
$ignoredField : null ,
$customField ( k , v ) {
console . log ( `CustomField(): ' ${ k } ' with data ${ JSON . stringify ( v ) } ` ) ;
}
} ,
$customField : {
test : [ ]
} ,
$statics : {
SomeConst : 0
}
} ) ;
// Firstly, try to instantiate the class:
// PreInit()
// CustomField(): '$customField' with data { test: [] }
// PostInit()
const instance = new Class ( ) ;
// Access MetaInfo of the class.
// (Alternatively `instance.constructor.$metaInfo`)
const MetaInfo = Class . $metaInfo ;
// Boolean value indicating a mixin:
// false
console . log ( MetaInfo . isMixin ) ;
// Super class:
// null (would link to super class if the class was inherited)
console . log ( MetaInfo . super ) ;
// Map of ignored properties:
// { $ignoredField: true, $customField: true }
console . log ( MetaInfo . ignored ) ;
// Map of all static properties:
// { SomeConst: true }
console . log ( MetaInfo . statics ) ;
// PreInit handlers:
// [function(...)]
console . log ( MetaInfo . preInit ) ;
// PostInit handlers:
// [function(...)]
console . log ( MetaInfo . postInit ) ;
// Extensions:
// { $customField: function(...) }
console . log ( MetaInfo . extensions ) ;
//
من المهم الإشارة إلى أن جميع أعضاء MetaClass متجمدون (غير قابلين للتغيير) ولا يمكن تعديلهم بعد إنشاء الفصل (بعد استدعاء جميع معالجات postInit). لا يمكن تغيير MetaInfo إلا أثناء إنشاء الفصل الدراسي بواسطة معالجات init وامتدادات الفصل. استخدم وظيفة العضو getMutable()
لجعل خاصية MetaInfo قابلة للتغيير مؤقتًا.
يوضح المثال التالي كيفية إضافة معلومات انعكاس مخصصة إلى MetaInfo:
// Creates some base class that defines a property system.
const Base = xmo ( {
$preInit ( ) {
const meta = this . $metaInfo ;
// Add `properties` property to MetaInfo object.
if ( ! meta . properties )
meta . properties = Object . create ( null ) ;
} ,
$extensions : {
$properties ( k , v ) {
const defProperties = v ;
const metaProperties = this . $metaInfo