Looks like we can't reproduce the issue or there's a problem in the test case provided.
-
Sencha User
[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();
-
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
});
-
@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.
Twitter - @evantrimboli
Former Sencha framework engineer, available for consulting.
As of 2017-09-22 I am not employed by Sencha, all subsequent posts are my own and do not represent Sencha in any way.
-
So the correct way to do this would be to create the array/object literals in the constructor or elsewhere in the instance.
-
Sencha User
@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.
-
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
-
Sencha User
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
-
Sencha User
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);
}
}());
-
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
-
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