Keywords: JavaScript | Inheritance Mechanism | Read-Only Property
Abstract: This article delves into the root cause of the 'Cannot assign to read only property 'name' of object' error in JavaScript, analyzing the property descriptor of function objects' 'name' and ES5 inheritance mechanisms to reveal issues with 'this' context due to incorrect prototype chain setup. It explains how to properly use <code>Object.create()</code> and <code>Object.defineProperty()</code> to fix inheritance chains, providing complete code examples and best practices to help developers avoid common pitfalls.
Error Phenomenon and Initial Analysis
In JavaScript development, the error "Cannot assign to read only property 'name' of object" often occurs when attempting to modify a read-only property. The following code example illustrates a typical scenario:
var BaseClass = function (data) {
Object.assign(this, data);
};
var ExtendedClass = function () {
BaseClass.apply(this, arguments);
};
ExtendedClass.prototype = Object.create(BaseClass);
console.log(new ExtendedClass({ type: 'foo' }));
new ExtendedClass({ name: 'foo' }); // Throws error
The error only occurs when trying to set the <code>name</code> property, while other properties like <code>type</code> work normally. This suggests that the <code>name</code> property has special descriptor configurations.
Property Descriptor of Function Objects' 'name'
In JavaScript, function objects have a built-in <code>name</code> property that stores the function name. Its property descriptor can be inspected using <code>Object.getOwnPropertyDescriptor()</code>:
var BaseClass = function (data) {
Object.assign(this, data);
};
console.log(Object.getOwnPropertyDescriptor(BaseClass, 'name'));
// Output: { value: 'BaseClass', writable: false, enumerable: false, configurable: true }
The descriptor shows <code>writable: false</code>, meaning the <code>name</code> property is read-only and cannot be directly assigned. However, since <code>configurable: true</code>, the property can be redefined using <code>Object.defineProperty()</code>:
Object.defineProperty(BaseClass, 'name', {
writable: true,
value: 'Foo'
});
console.log(BaseClass.name); // Output: Foo
While this approach works, it is not a fundamental solution as it overlooks deeper issues in the inheritance mechanism.
Errors and Corrections in ES5 Inheritance Mechanism
The core issue lies in incorrect setup of the inheritance chain. In the original code:
ExtendedClass.prototype = Object.create(BaseClass);
This line sets <code>ExtendedClass.prototype</code> to an instance of the <code>BaseClass</code> constructor function, rather than <code>BaseClass.prototype</code>. This causes the <code>this</code> context in <code>BaseClass.apply(this, arguments)</code> to point to the function object <code>BaseClass</code> itself, not its instance:
function BaseClass(data) {
console.log(this instanceof BaseClass); // false
console.log(this instanceof Function); // true
console.log(this.name); // 'BaseClass'
Object.assign(this, data); // Attempts to modify the read-only 'name' property of the function object
}
Thus, when passing <code>{ name: 'foo' }</code>, <code>Object.assign()</code> tries to modify the <code>name</code> property of the <code>BaseClass</code> function, triggering the read-only error.
Correct Inheritance Implementation
The standard approach for implementing ES5-style inheritance in JavaScript involves two key steps:
ExtendedClass.prototype = Object.create(BaseClass.prototype);
ExtendedClass.prototype.constructor = ExtendedClass;
The first line sets <code>ExtendedClass.prototype</code> to a new object based on <code>BaseClass.prototype</code>, establishing the correct prototype chain. The second line fixes the <code>constructor</code> property to ensure instances correctly identify their constructor.
A complete corrected code example is as follows:
function BaseClass(data) {
console.log(this instanceof BaseClass); // true
console.log(this instanceof Function); // false
console.log(this.name); // undefined (initial state)
Object.assign(this, data);
}
function ExtendedClass() {
BaseClass.apply(this, arguments);
}
ExtendedClass.prototype = Object.create(BaseClass.prototype);
ExtendedClass.prototype.constructor = ExtendedClass;
var instance = new ExtendedClass({ name: 'foo' });
console.log(instance.name); // foo
console.log(BaseClass.name); // BaseClass (function name remains unchanged)
console.log(ExtendedClass.name); // ExtendedClass
Now, <code>this</code> in <code>BaseClass</code> points to an instance of <code>ExtendedClass</code>, not the function object itself. <code>Object.assign()</code> operates on writable properties of the instance, allowing <code>name</code> to be set normally.
Supplementary Solutions and Best Practices
Beyond fixing inheritance chains, read-only property issues may arise in other contexts. For example, in Angular+TypeScript+NgRX environments, selectors might return read-only objects:
let x = [...y]; // Shallow copy of read-only object
let x = JSON.parse(JSON.stringify(y)); // Deep copy to remove store references
However, these methods should be used cautiously as they may impact performance or data consistency. Best practices include:
- Always use <code>Object.create(BaseClass.prototype)</code> instead of <code>Object.create(BaseClass)</code> for setting prototype chains.
- Check descriptors with <code>Object.getOwnPropertyDescriptor()</code> before modifying properties.
- For read-only but configurable properties, consider redesigning code logic rather than forcing modifications.
- In frameworks like Redux/NgRX, follow immutable data patterns to avoid direct state object modifications.
By understanding JavaScript property descriptors and inheritance mechanisms, developers can more effectively debug and prevent such errors, writing more robust and maintainable code.