Attribute
Luke Smith
@ls_n
You know this stuff
model.get(“id”);
overlay.set(“visible”, true);


var drag = new Y.DD.Drag({
    node: “#dragme”
});
You know this stuff
Bakery.ATTRS = {
     flour: {
         value: “pastry”
     },
     ...
};
What is an attribute?

 Encapsulation of a value state and set of
 feature configurations for customizing logic
 associated with the mutation and storage of
 that state, while providing opportunities to
 monitor changes.
What is an attribute?


      Object property + magic
The magic
• What it is
• Why we need it
• Where it comes from
• How it works
• What it costs
• Is it worth it?
The magic
• What it is
• Why we need it
• Where it comes from
• How it works
• What it costs
• “Should I use a property or an attribute?”
What is the magic?
What is the magic?
• getters
• setters
• default values
• change events
ES5 has magic
Object.defineProperties(this, {
   flour: {
     value: “pastry”,
     getter: ...
     setter: ...
     ...
ES5 has magic ... except


IE6 IE7 IE8
ES5 has magic ... except


IE6 IE7 IE8
          * Dear old IE, please die in a fire.
So, here we are

obj.get(“attr”);


obj.set(“attr”, “I’m needed, thanks IE!”);
Why do we want magic?
Why do we want magic?
  Properties are dumb
Why do we want magic?
1. Properties are dumb
2. Maintenance
Where is the magic?
The Classes
         (all the things)

           Y.Base
The Classes
                 (all the things)

                   Y.Base
    Y.BaseCore              Y.BaseObservable   Y.AE
The Classes
                     (all the things)

                       Y.Base
     Y.BaseCore                 Y.BaseObservable      Y.AE

   Y.AttributeCore            Y.AttributeObservable
The Classes
                     (all the things)

                       Y.Base
     Y.BaseCore                 Y.BaseObservable      Y.AE

   Y.AttributeCore            Y.AttributeObservable

       Y.State                    Y.EventTarget
The Modules
                     (all the things)

                         base
    base-core                  base-observable        a-extras


   attribute-core              attribute-observable

  (attribute-core)           event-custom-complex
The Modules
                      (all the things)

                          base
     base-core                  base-observable        a-extras


 Y.AttributeCore
    attribute-core            Y.AttributeObservable
                                attribute-observable

   (attribute-core)           event-custom-complex
The Modules


 Y.AttributeCore    Y.AttributeObservable

   attribute-core    attribute-observable
Y.AttributeCore
obj.get(name);
obj.set(name, value);


obj.getAttrs(); // see docs
obj.setAttrs({ vals });
Y.AttributeCore
Class.ATTRS = { configs }; // sort of


obj.addAttr(name, { config }, lazy?);

obj.addAttrs({ configs }, { values }, lazy?);

obj.attrAdded(name);
Y.AttributeObservable
bakery.on(“flourChange”, callback);
bakery.after(“flourChange”, callback);


bakery.once(...);
bakery.addTarget(...);   // etc.
Creating attributes
One at a time

 bakery.addAttr(“flour”, {
       setter: “_setFlour”,
       value: “whole wheat”
 });
Creating attributes
A bunch at a time

bakery.addAttrs({
      flour: {
           setter: “_setFlour”,
           value: “whole wheat”
      }, ...
});
Creating attributes
Statically for a class

 Bakery.ATTRS = {
       flour: {
         setter: “_setFlour”,
         value: “whole wheat”
       }
 });
How does the magic work?
Configurations
• value        • valueFn
• getter       • validator
• setter       • readOnly
               • writeOnce
               • lazyAdd
 http://yuilibrary.com/yui/docs/attribute/#configuration
Configurations
• value        • valueFn                • broadcast
• getter       • validator              •   cloneDefaultValue

• setter       • readOnly
               • writeOnce
               • lazyAdd
 http://yuilibrary.com/yui/docs/attribute/#configuration
value and valueFn
• Most expensive
• lazyAdd = true to defer cost
• valueFn always called at instantiation
lazyAdd
• Minimal setup, no set()
• Full setup on first call to get() or set()
• Some might never get fully setup (good)
• Y.BaseCore defaults lazyAdd = true for all
  (override with prototype._lazyAddAttrs)
• Beware lazyAdd + setter
setter for robustness
• Use for
  a) Input normalization, or
  b) Tightly coupled state related logic
• Not for side effects (use events)
• setter + lazyAdd gotcha
setter vs validator
• validator is called before setter
• If you have a setter, validate inline
_setFlour: function (val) {
   return (typeof val === “string”) ?
     val.toLowerCase() :
     Y.Attribute.INVALID_VALUE;
getter, setter, etc
• Pass string values to late bind handlers
flour: {
   setter : “_setFlour” // this._setFlour(val)
   getter : “_getFlour”,
   validator: “_validateFlour”,
   valueFn : “_initFlour”,
cloneDefaultValue
• {undefined, true, “deep”}, “shallow”, false
• Y.Base defaults Y.clone() behavior
• Always set or use a valueFn instead
flour: {
  value: { grain: ‘fine’, gluten: false, ... },
  cloneDefaultValue: true,
Change events
•   For decoupling systems
•   Why custom events have on() and after()
•   Assigning initial value does NOT fire event
•   Change event published on first set()
Ad hoc attributes
•   From Y.BaseCore
•   Create dynamic objects with Attribute API
•   prototype._allowAdHocAttrs = true
•   static _NON_ATTRS_CFG to blacklist
Ad hoc attributes
Bakery.prototype._allowAdHocAttrs = true;


var llamaBakery = new Bakery({
    flour: “pastry”,
    llamas: “sheared” // unknown, but added
});
What does the magic cost?
Magic taxes
1. Attribute setup cost
2. get()/set() cost



     *Many Bothans died to bring us this information
Attribute setup
Attribute setup
1. Creates State object
Attribute setup
1. Creates State object
2. Depth 2 copy of configs
Attribute setup
1. Creates State object
2. Depth 2 copy of configs
3. Gets value from construction values,
   valueFn, or value
Attribute setup
1. Creates State object
2. Depth 2 copy of configs
3. Gets value from construction values,
   valueFn, or value
4. lazyAdd attributes are stowed in State
Attribute setup
1. Creates State object
2. Depth 2 copy of configs
3. Gets value from construction values,
   valueFn, or value
4. lazyAdd attributes are stowed in State
5. non-lazy populate State and set(value)
Attribute setup
                 LOC   fn calls
 no attributes   34       9
  +lazy attr     +46    +14
 +empty attr     +56    +16
+attr property   +7      +0
    +value       +80    +20
   +valueFn      +3      +1
Base setup
Base setup
1. Creates State object
Base setup
1. Creates State object
2. Aggregates ATTRS up superclass chain
Base setup
1. Creates State object
2. Aggregates ATTRS up superclass chain
3. for each class call...
Base setup
1. Creates State object
2. Aggregates ATTRS up superclass chain
3. for each class call...
  1. class extension constructors
Base setup
1. Creates State object
2. Aggregates ATTRS up superclass chain
3. for each class call...
  1. class extension constructors
  2. addAttrs with filtered list from ATTRS aggregate
Base setup
1. Creates State object
2. Aggregates ATTRS up superclass chain
3. for each class call...
  1. class extension constructors
  2. addAttrs with filtered list from ATTRS aggregate
  3. initializer
Base setup
1. Creates State object
2. Aggregates ATTRS up superclass chain
3. for each class call...
  1. class extension constructors
  2. addAttrs with filtered list from ATTRS aggregate
  3. initializer
  4. class extension initializers
Base setup
1. Creates State object
2. Aggregates ATTRS up superclass chain
3. for each class call...
  1. class extension constructors
  2. addAttrs with filtered list from ATTRS aggregate
  3. initializer            2.5. addAttrs(ad-hocs)

  4. class extension initializers
get()
1. Checks if attribute is lazy, inits if necessary
2. Gets the value and getter from State
3. Returns value or result of calling getter
get()
                 LOC    fn calls
   get(attr)      10       6
 get(sub.attr)   +3       +5
   +getter       +2       +1
  +lazy init     +100    +20
set()




  http://yuilibrary.com/yui/docs/attribute/setflow.html
set() - just attribute
                 LOC    fn calls
   set(attr)      80      20
 set(sub.attr)   +27      +4
  +lazy init     +100    +20
  +validator     +5       +1
   +setter       +6       +1
set() - events
                  LOC    fn calls
   set(attr)       80      20
 set(sub.attr)    +27      +4
+change event     +151    +24
  +first event     +100    +12
+per subscriber   +27      +5
 +per bubble      +205    +21
Avoid Subattributes
obj.set(“nested.sub.attribute”, false);


• Triggered by . in attribute name
• A lot of extra work, most is wasted
• Confusing relationship with getter/setter

                                 * sorry about this
Is the magic worth it?
Should you use a property or an attribute?
Property
           PROs                CONs
•   Fast                 •   Dumb

•   Terse                •   Can’t migrate to
                             attribute w/o API
•   Nested structs are       breakage
    the same

•   Magic allowed if
    environment
    mandated
Attribute
          PROs                   CONs
•   Stable abstraction    •   Slow

•   Code organization     •   Nested structs are
                              awkward
•   Consistent API            (subattributes--)
•   Easy loose coupling
                          •   More typing
    with events

•   Abstraction
    performance can
    improve invisibly
Property or Attribute?
• Will it be accessed a lot? Like, really A LOT.
• Will there be lots of instances? (see #1)
• Will it be public, or interesting outside the
  object?
• Might the class be extended? Plugged?
• Would it amount to a micro-optimization?
Property or Attribute?

   Justify the use of properties
             rather than
  justifying the use of attributes

    (It’s less likely to bite you in the ass)
The Future
Attribute
          PROs                   CONs
•   Stable abstraction    •   Slow

•   Code organization     •   Nested structs are
                              awkward
•   Consistent API            (subattributes--)
•   Easy loose coupling
                          •   More typing
    with events

• Abstraction
    performance can
    improve invisibly
Faster; no new features
Faster; no new features
• Replace State with ES5 objects or shim
Faster; no new features
• Replace State with ES5 objects or shim
• Compile configs once for all instances
Faster; no new features
• Replace State with ES5 objects or shim
• Compile configs once for all instances
• Event optimizations
kittens + pancakes = future
The End.
             Thanks!



Luke Smith
@ls_n

Attribute

  • 2.
  • 3.
    You know thisstuff model.get(“id”); overlay.set(“visible”, true); var drag = new Y.DD.Drag({ node: “#dragme” });
  • 4.
    You know thisstuff Bakery.ATTRS = { flour: { value: “pastry” }, ... };
  • 5.
    What is anattribute? Encapsulation of a value state and set of feature configurations for customizing logic associated with the mutation and storage of that state, while providing opportunities to monitor changes.
  • 6.
    What is anattribute? Object property + magic
  • 7.
    The magic • Whatit is • Why we need it • Where it comes from • How it works • What it costs • Is it worth it?
  • 8.
    The magic • Whatit is • Why we need it • Where it comes from • How it works • What it costs • “Should I use a property or an attribute?”
  • 9.
  • 10.
    What is themagic? • getters • setters • default values • change events
  • 11.
    ES5 has magic Object.defineProperties(this,{ flour: { value: “pastry”, getter: ... setter: ... ...
  • 12.
    ES5 has magic... except IE6 IE7 IE8
  • 13.
    ES5 has magic... except IE6 IE7 IE8 * Dear old IE, please die in a fire.
  • 14.
    So, here weare obj.get(“attr”); obj.set(“attr”, “I’m needed, thanks IE!”);
  • 15.
    Why do wewant magic?
  • 16.
    Why do wewant magic? Properties are dumb
  • 17.
    Why do wewant magic? 1. Properties are dumb 2. Maintenance
  • 18.
  • 19.
    The Classes (all the things) Y.Base
  • 20.
    The Classes (all the things) Y.Base Y.BaseCore Y.BaseObservable Y.AE
  • 21.
    The Classes (all the things) Y.Base Y.BaseCore Y.BaseObservable Y.AE Y.AttributeCore Y.AttributeObservable
  • 22.
    The Classes (all the things) Y.Base Y.BaseCore Y.BaseObservable Y.AE Y.AttributeCore Y.AttributeObservable Y.State Y.EventTarget
  • 23.
    The Modules (all the things) base base-core base-observable a-extras attribute-core attribute-observable (attribute-core) event-custom-complex
  • 24.
    The Modules (all the things) base base-core base-observable a-extras Y.AttributeCore attribute-core Y.AttributeObservable attribute-observable (attribute-core) event-custom-complex
  • 25.
    The Modules Y.AttributeCore Y.AttributeObservable attribute-core attribute-observable
  • 26.
  • 27.
    Y.AttributeCore Class.ATTRS = {configs }; // sort of obj.addAttr(name, { config }, lazy?); obj.addAttrs({ configs }, { values }, lazy?); obj.attrAdded(name);
  • 28.
  • 29.
    Creating attributes One ata time bakery.addAttr(“flour”, { setter: “_setFlour”, value: “whole wheat” });
  • 30.
    Creating attributes A bunchat a time bakery.addAttrs({ flour: { setter: “_setFlour”, value: “whole wheat” }, ... });
  • 31.
    Creating attributes Statically fora class Bakery.ATTRS = { flour: { setter: “_setFlour”, value: “whole wheat” } });
  • 32.
    How does themagic work?
  • 33.
    Configurations • value • valueFn • getter • validator • setter • readOnly • writeOnce • lazyAdd http://yuilibrary.com/yui/docs/attribute/#configuration
  • 34.
    Configurations • value • valueFn • broadcast • getter • validator • cloneDefaultValue • setter • readOnly • writeOnce • lazyAdd http://yuilibrary.com/yui/docs/attribute/#configuration
  • 35.
    value and valueFn •Most expensive • lazyAdd = true to defer cost • valueFn always called at instantiation
  • 36.
    lazyAdd • Minimal setup,no set() • Full setup on first call to get() or set() • Some might never get fully setup (good) • Y.BaseCore defaults lazyAdd = true for all (override with prototype._lazyAddAttrs) • Beware lazyAdd + setter
  • 37.
    setter for robustness •Use for a) Input normalization, or b) Tightly coupled state related logic • Not for side effects (use events) • setter + lazyAdd gotcha
  • 38.
    setter vs validator •validator is called before setter • If you have a setter, validate inline _setFlour: function (val) { return (typeof val === “string”) ? val.toLowerCase() : Y.Attribute.INVALID_VALUE;
  • 39.
    getter, setter, etc •Pass string values to late bind handlers flour: { setter : “_setFlour” // this._setFlour(val) getter : “_getFlour”, validator: “_validateFlour”, valueFn : “_initFlour”,
  • 40.
    cloneDefaultValue • {undefined, true,“deep”}, “shallow”, false • Y.Base defaults Y.clone() behavior • Always set or use a valueFn instead flour: { value: { grain: ‘fine’, gluten: false, ... }, cloneDefaultValue: true,
  • 41.
    Change events • For decoupling systems • Why custom events have on() and after() • Assigning initial value does NOT fire event • Change event published on first set()
  • 42.
    Ad hoc attributes • From Y.BaseCore • Create dynamic objects with Attribute API • prototype._allowAdHocAttrs = true • static _NON_ATTRS_CFG to blacklist
  • 43.
    Ad hoc attributes Bakery.prototype._allowAdHocAttrs= true; var llamaBakery = new Bakery({ flour: “pastry”, llamas: “sheared” // unknown, but added });
  • 44.
    What does themagic cost?
  • 45.
    Magic taxes 1. Attributesetup cost 2. get()/set() cost *Many Bothans died to bring us this information
  • 46.
  • 47.
  • 48.
    Attribute setup 1. CreatesState object 2. Depth 2 copy of configs
  • 49.
    Attribute setup 1. CreatesState object 2. Depth 2 copy of configs 3. Gets value from construction values, valueFn, or value
  • 50.
    Attribute setup 1. CreatesState object 2. Depth 2 copy of configs 3. Gets value from construction values, valueFn, or value 4. lazyAdd attributes are stowed in State
  • 51.
    Attribute setup 1. CreatesState object 2. Depth 2 copy of configs 3. Gets value from construction values, valueFn, or value 4. lazyAdd attributes are stowed in State 5. non-lazy populate State and set(value)
  • 52.
    Attribute setup LOC fn calls no attributes 34 9 +lazy attr +46 +14 +empty attr +56 +16 +attr property +7 +0 +value +80 +20 +valueFn +3 +1
  • 53.
  • 54.
  • 55.
    Base setup 1. CreatesState object 2. Aggregates ATTRS up superclass chain
  • 56.
    Base setup 1. CreatesState object 2. Aggregates ATTRS up superclass chain 3. for each class call...
  • 57.
    Base setup 1. CreatesState object 2. Aggregates ATTRS up superclass chain 3. for each class call... 1. class extension constructors
  • 58.
    Base setup 1. CreatesState object 2. Aggregates ATTRS up superclass chain 3. for each class call... 1. class extension constructors 2. addAttrs with filtered list from ATTRS aggregate
  • 59.
    Base setup 1. CreatesState object 2. Aggregates ATTRS up superclass chain 3. for each class call... 1. class extension constructors 2. addAttrs with filtered list from ATTRS aggregate 3. initializer
  • 60.
    Base setup 1. CreatesState object 2. Aggregates ATTRS up superclass chain 3. for each class call... 1. class extension constructors 2. addAttrs with filtered list from ATTRS aggregate 3. initializer 4. class extension initializers
  • 61.
    Base setup 1. CreatesState object 2. Aggregates ATTRS up superclass chain 3. for each class call... 1. class extension constructors 2. addAttrs with filtered list from ATTRS aggregate 3. initializer 2.5. addAttrs(ad-hocs) 4. class extension initializers
  • 62.
    get() 1. Checks ifattribute is lazy, inits if necessary 2. Gets the value and getter from State 3. Returns value or result of calling getter
  • 63.
    get() LOC fn calls get(attr) 10 6 get(sub.attr) +3 +5 +getter +2 +1 +lazy init +100 +20
  • 64.
  • 66.
    set() - justattribute LOC fn calls set(attr) 80 20 set(sub.attr) +27 +4 +lazy init +100 +20 +validator +5 +1 +setter +6 +1
  • 67.
    set() - events LOC fn calls set(attr) 80 20 set(sub.attr) +27 +4 +change event +151 +24 +first event +100 +12 +per subscriber +27 +5 +per bubble +205 +21
  • 68.
    Avoid Subattributes obj.set(“nested.sub.attribute”, false); •Triggered by . in attribute name • A lot of extra work, most is wasted • Confusing relationship with getter/setter * sorry about this
  • 69.
    Is the magicworth it? Should you use a property or an attribute?
  • 70.
    Property PROs CONs • Fast • Dumb • Terse • Can’t migrate to attribute w/o API • Nested structs are breakage the same • Magic allowed if environment mandated
  • 71.
    Attribute PROs CONs • Stable abstraction • Slow • Code organization • Nested structs are awkward • Consistent API (subattributes--) • Easy loose coupling • More typing with events • Abstraction performance can improve invisibly
  • 72.
    Property or Attribute? •Will it be accessed a lot? Like, really A LOT. • Will there be lots of instances? (see #1) • Will it be public, or interesting outside the object? • Might the class be extended? Plugged? • Would it amount to a micro-optimization?
  • 73.
    Property or Attribute? Justify the use of properties rather than justifying the use of attributes (It’s less likely to bite you in the ass)
  • 74.
  • 75.
    Attribute PROs CONs • Stable abstraction • Slow • Code organization • Nested structs are awkward • Consistent API (subattributes--) • Easy loose coupling • More typing with events • Abstraction performance can improve invisibly
  • 76.
  • 77.
    Faster; no newfeatures • Replace State with ES5 objects or shim
  • 78.
    Faster; no newfeatures • Replace State with ES5 objects or shim • Compile configs once for all instances
  • 79.
    Faster; no newfeatures • Replace State with ES5 objects or shim • Compile configs once for all instances • Event optimizations
  • 80.
  • 81.
    The End. Thanks! Luke Smith @ls_n

Editor's Notes

  • #2 \n
  • #3 \n
  • #4 \n
  • #5 \n
  • #6 \n
  • #7 \n
  • #8 \n
  • #9 \n
  • #10 \n
  • #11 \n
  • #12 \n
  • #13 \n
  • #14 \n
  • #15 modeling DOM\n
  • #16 modeling DOM\n
  • #17 modeling DOM\n
  • #18 \n
  • #19 \n
  • #20 \n
  • #21 \n
  • #22 \n
  • #23 \n
  • #24 \n
  • #25 \n
  • #26 \n
  • #27 \n
  • #28 \n
  • #29 \n
  • #30 \n
  • #31 \n
  • #32 \n
  • #33 \n
  • #34 \n
  • #35 \n
  • #36 \n
  • #37 AttributeCore ATTRS less restrictive than Base\n
  • #38 \n
  • #39 \n
  • #40 \n
  • #41 \n
  • #42 You can also use obj.set(‘unknown’, val) and setAttrs({ ... }); but they will be magicless\n
  • #43 \n
  • #44 \n
  • #45 \n
  • #46 \n
  • #47 \n
  • #48 “Be conservative in what you send, liberal in what you accept”\nsetter code creep = serving multiple masters\n
  • #49 \n
  • #50 You can find this in the API docs, but I want to strongly recommend\n
  • #51 \n
  • #52 \n
  • #53 \n
  • #54 \n
  • #55 \n
  • #56 \n
  • #57 \n
  • #58 \n
  • #59 \n
  • #60 \n
  • #61 \n
  • #62 \n
  • #63 cloneDefaultValue for obj values during aggregation\naddAttrs() called with the aggregated configs for that class\naddAttrs() applies user values - Attribute ignores user values for unknown attrs\naddAttrs() calls Y.merge() on user value hash, so once for each level\n
  • #64 cloneDefaultValue for obj values during aggregation\naddAttrs() called with the aggregated configs for that class\naddAttrs() applies user values - Attribute ignores user values for unknown attrs\naddAttrs() calls Y.merge() on user value hash, so once for each level\n
  • #65 cloneDefaultValue for obj values during aggregation\naddAttrs() called with the aggregated configs for that class\naddAttrs() applies user values - Attribute ignores user values for unknown attrs\naddAttrs() calls Y.merge() on user value hash, so once for each level\n
  • #66 cloneDefaultValue for obj values during aggregation\naddAttrs() called with the aggregated configs for that class\naddAttrs() applies user values - Attribute ignores user values for unknown attrs\naddAttrs() calls Y.merge() on user value hash, so once for each level\n
  • #67 cloneDefaultValue for obj values during aggregation\naddAttrs() called with the aggregated configs for that class\naddAttrs() applies user values - Attribute ignores user values for unknown attrs\naddAttrs() calls Y.merge() on user value hash, so once for each level\n
  • #68 cloneDefaultValue for obj values during aggregation\naddAttrs() called with the aggregated configs for that class\naddAttrs() applies user values - Attribute ignores user values for unknown attrs\naddAttrs() calls Y.merge() on user value hash, so once for each level\n
  • #69 cloneDefaultValue for obj values during aggregation\naddAttrs() called with the aggregated configs for that class\naddAttrs() applies user values - Attribute ignores user values for unknown attrs\naddAttrs() calls Y.merge() on user value hash, so once for each level\n
  • #70 cloneDefaultValue for obj values during aggregation\naddAttrs() called with the aggregated configs for that class\naddAttrs() applies user values - Attribute ignores user values for unknown attrs\naddAttrs() calls Y.merge() on user value hash, so once for each level\n
  • #71 \n
  • #72 \n
  • #73 \n
  • #74 \n
  • #75 \n
  • #76 \n
  • #77 \n
  • #78 \n
  • #79 Similarly, if you capture the object returned from get(‘nested’), you can set its properties whenever.\n
  • #80 \n
  • #81 \n
  • #82 \n
  • #83 \n
  • #84 \n
  • #85 \n
  • #86 \n
  • #87 \n
  • #88 \n
  • #89 \n
  • #90 \n
  • #91 \n
  • #92 \n
  • #93 \n
  • #94 \n
  • #95 \n
  • #96 \n
  • #97 It’s not just a numbers game - maintenance, future proofing\n
  • #98 \n
  • #99 and remember, you’re talking about giving up all those yummy features\n
  • #100 \n
  • #101 \n
  • #102 result in the cost being replaced by kittens making you pancakes\n
  • #103 result in the cost being replaced by kittens making you pancakes\n
  • #104 result in the cost being replaced by kittens making you pancakes\n
  • #105 result in the cost being replaced by kittens making you pancakes\n
  • #106 \n
  • #107 \n