Looks like we can't reproduce the issue or there's a problem in the test case provided.
  1. #1
    Sencha User
    Join Date
    Jul 2007
    Posts
    5
    Vote Rating
    0
    Nick99 is on a distinguished road

      0  

    Default [4.0.2a] Class inheritance keeps references to original fields

    [4.0.2a] Class inheritance keeps references to original fields


    Hi,

    I have 2 classes, one of which inherits another. I first create an instance of the Person class and populate its fields (test, items1, wrappedItems.wrap); the contents of the fields is alerted. After that I create a Developer and alert fields values; the inherited fields MUST be empty - but they are NOT (except for the "test" fields).

    The example demonstrates the behaviour. My understanding is that the second alert should output all NULLs/empty values.

    Code:
    Ext.define('Person', {
        test: null,
        items1: [],
        wrappedItems: {
            wrap: null
        },
    
        constructor: function(name) {
            this.test = name;
            this.items1.push(name);
            this.wrappedItems.wrap = name;
    
            alert("Person: " + this.items1.toString() + ", " + this.test + ", " + this.wrappedItems.wrap);
            return this;
        }
    });
    Ext.define('Developer', {
        extend: 'Person',
    
        constructor: function() {
            alert("Developer: " + this.items1.toString() + ", " + this.test + ", " + this.wrappedItems.wrap);
            return this;
        }
    });
    
    var person = new Person("test123");
    var dev = new Developer();

  2. #2
    Sencha User ykey's Avatar
    Join Date
    Mar 2010
    Location
    USA
    Posts
    245
    Vote Rating
    27
    ykey has a spectacular aura about ykey has a spectacular aura about

      0  

    Default


    This is not a problem with inheritance. It appears to be a problem with object properties. But not sure it is a problem or just a misunderstanding.

    For example:

    Code:
    var fn = function() {};
    var fn2 = function() {};
    	
    Ext.define('Person', {
    	name: 'Default',
    	data: {},
    	fn: undefined,
    	
    	constructor: function(name, fn) {
    		if (name) {
    			this.name = name;
    			this.data.name = name;
    		}				
    		
    		if (fn) {
    			this.fn = fn;
    		}
    	}
    });
    
    Ext.onReady(function() {					
    	var person = Ext.create('Person', "Name", fn);						
    	var person2 = Ext.create('Person', "Name2", fn2);
    				
    	console.log(person.name);		// Expected
    	console.log(person.data.name);	// Failure, expecting: Name  Actual: Name2
    	console.log(person.fn === fn);  // Expected
    
    	console.log(person2.name);		// Expected
    	console.log(person2.data.name); // Expected
    	console.log(person2.fn === fn2);  // Expected
    	
    	console.log(person.fn !== person2.fn); // Expected
    });

  3. #3
    Sencha - Ext JS Dev Team evant's Avatar
    Join Date
    Apr 2007
    Location
    Sydney, Australia
    Posts
    16,915
    Vote Rating
    630
    evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute evant has a reputation beyond repute

      0  

    Default


    @ykey is correct

    When you specify a property in a class definition it gets shared across all instances. Doesn't matter for strings and numbers, but you need to be careful with arrays/object literals.
    Evan Trimboli
    Sencha Developer
    Twitter - @evantrimboli
    Don't be afraid of the source code!

  4. #4
    Sencha User ykey's Avatar
    Join Date
    Mar 2010
    Location
    USA
    Posts
    245
    Vote Rating
    27
    ykey has a spectacular aura about ykey has a spectacular aura about

      0  

    Default


    So the correct way to do this would be to create the array/object literals in the constructor or elsewhere in the instance.

  5. #5
    Sencha User
    Join Date
    Jul 2007
    Posts
    5
    Vote Rating
    0
    Nick99 is on a distinguished road

      0  

    Default


    @ykey
    Thank you, you've shown the exactly same behaviour in your code.

    @evant
    I actualy thought those "class properties" were private to each class instance. Shouldn't be they (in normal OOP)? Instead they are more like "protected static".

    So,

    1. Is this the standard behaviour which won't change in next versions? It woud be nice to have the clarification somewhere @ http://docs.sencha.com/ext-js/4-0/#/guide/class_system

    2. While the suggestion to put all the init code into constructor is ok as a workaround, this is somewhat clumsy. I have a class extended from Ext.window.Window, which defines "items" property. It is pretty big and I'd need to put it into constructor now, otherwise I can't create multiple instances of the class

    Thank you in advance.

  6. #6
    Sencha User ykey's Avatar
    Join Date
    Mar 2010
    Location
    USA
    Posts
    245
    Vote Rating
    27
    ykey has a spectacular aura about ykey has a spectacular aura about

      0  

    Default


    I am pretty sure I have narrowed this down and it really just comes down to standard JavaScript and prototypal inheritance.

    Code:
    function Person(name) {
    	if (name) {
    		this.name = name;
    		this.data.name = name;
    	}
    }
    
    Person.prototype.name = 'Default';
    Person.prototype.data = {};
    
    var person = new Person();
    var person2 = new Person('Name2');
    
    console.log(person.name);       // Default
    console.log(person.data.name);  // Name2
    console.log(person2.name);      // Name2
    console.log(person2.data.name); // Name2

    Ext.Class constructor is calling Ext.Base.implement with provided arguments which just adds them to the prototype.

    Code:
    Ext.define('Person', {
    	constructor: function(name) {
                    if (name) {
    		    this.name = name;
    		    this.data.name = name;
                    }
    	}
    });
    
    Person.implement({
    	name: 'Default',
    	data: {
    		name: 'Default'
    	}
    });
    
    var person = new Person();
    var person2 = new Person('Name2');
    
    console.log(person.name);          // Default
    console.log(person.data.name);   // Name2
    console.log(person2.name);        // Name2
    console.log(person2.data.name); // Name2
    Last edited by ykey; 26 Jul 2011 at 8:47 PM. Reason: Fixed examples

  7. #7
    Sencha User
    Join Date
    Jul 2007
    Posts
    5
    Vote Rating
    0
    Nick99 is on a distinguished road

      0  

    Default


    Right, it is pure js.

    The Ext.clone() call fixes this "pecularity". It seems it can be done automatically for Object and Array types somewhere in the Class framework.

    Code:
    function Person(name) {
            this.obj = Ext.clone(this.obj);
            //this.date = Ext.clone(this.date);
    
            this.name = name;
            this.obj.name = name;
            this.fn = function() {return name;};
            if (name == "second") {
                this.date.setTime(0); // 1970
            }
    }
    
    Person.prototype.name = 'Default';
    Person.prototype.obj = {};
    Person.prototype.fn = function(){};
    Person.prototype.date = new Date(); // Current
    
    var person1 = new Person("first");
    var person2 = new Person("second");
    console.log(person1.name);      // first
    console.log(person1.obj.name);  // Expected: first, real: second
    console.log(person1.fn());  // first
    console.log(person1.date);  // Expected: current, real: 1970
    Update: the Date is vulnerable too.
    Last edited by Nick99; 26 Jul 2011 at 9:46 PM. Reason: fixes example

  8. #8
    Sencha User
    Join Date
    Jul 2007
    Posts
    5
    Vote Rating
    0
    Nick99 is on a distinguished road

      0  

    Default


    I've crafted a patch to change the default prototype inheritance behaviour. Also, I wonder why I didn't encounter this problem in ExtJS 1/2/3...

    Next steps:
    Wait for answers/clarification from Sencha team and then close the thread.

    Code:
    (function() {
        var overridenClass = Ext.Class;
    
        Ext.Class = Class = function(newClass, classData, onClassCreated) {
            // ext-all-debug-w-comments.js @ 5392
            if (typeof newClass !== 'function') {
                onClassCreated = classData;
                classData = newClass;
                newClass = function() {
                    for (key in this) {
                        if (key == "self" || key == "superclass" || key == "$class" ||
                            key == "mixins" || key == "requires" || key == "statics") continue;
    
                        if (this[key] != null && typeof(this[key]) == "object") {
                            this[key] = Ext.clone(this[key]);
                        }
                    }
    
                    return this.constructor.apply(this, arguments);
                };
            }
    
            overridenClass.call(this, newClass, classData, onClassCreated);
        }
    }());

  9. #9
    Sencha User mberrie's Avatar
    Join Date
    Feb 2011
    Location
    Bangkok, Thailand
    Posts
    506
    Vote Rating
    14
    mberrie will become famous soon enough mberrie will become famous soon enough

      0  

    Default


    As I understand it this is related to prototype inheritance and cannot easily be changed.

    Cloning the property upon instantiation only works for simple objects. Once you have objects that hold references to other objects, it gets pretty ugly (Do you do a deep clone or not? And how deep?) and it will seriously mess things up.

    When comparing this behavior to classic OOP and compiled languages, you have to consider that for class fields the actual code will be executed upon each instantiation of the class. This is not possible in Javascript since the code 'line' that defines the class field will be evaluated upon class definition (well, actually upon creation of the config object literal) and the result of the evaluation will be passed to Ext's class manager.

    So, as a developer, the tools to deal with this behavior are
    • understanding and awareness
    • Ext.apply - this will always add properties to the actual object , overriding properties inherited from the prototype chain
    • hasOwnProperty - allows to check whether a property is inherited or a direct member of the object
    • best practices

    Not pretending to say absolute truths here, this is just my understanding

    mberrie

  10. #10
    Sencha User Jacky Nguyen's Avatar
    Join Date
    Jul 2009
    Location
    Palo Alto, California
    Posts
    469
    Vote Rating
    14
    Jacky Nguyen has a spectacular aura about Jacky Nguyen has a spectacular aura about

      0  

    Default


    Summary:

    - It's simply how JavaScript prototypal inheritance works.
    - Unless the object / array members of the class are immutable, define them in the class constructor.
    - Consider using the config feature.
    Sencha Touch Lead Architect