Without further explanation, today we’ll look at how to write superclasses (call them template classes or custom types) in the object-oriented paradigm in the Reac@ 15.0.0 source code.
It is written like this:
function Constructor(){}
Object. Assign (Constructor. Prototype, literal-object1, literal-object2)module.exports = Constructor;
Copy the code
In the [email protected] source code, the use of this method can be seen in the react component template class definition code.
In SRC/renderers/dom/Shared/ReactDOMTextComponent in js:
var ReactDOMTextComponent = function(text) {
// TODO: This is really a ReactText (ReactNode), not a ReactElement
this._currentElement = text;
this._stringText = ' ' + text;
// other properties
/ /...
};
Object.assign(ReactDOMTextComponent.prototype, {
mountComponent: function(){ / /... },
receiveComponent: function(){ / /... }
// other methods
/ /...
});
module.exports = ReactDOMTextComponent;
Copy the code
In SRC/renderers/dom/Shared/ReactDOMComponent in js:
function ReactDOMComponent(element) {
this._currentElement = element;
this._tag = tag.toLowerCase();
// other properties
/ /...
}
ReactDOMComponent.Mixin = {
mountComponent: function(){ / /... },
_createOpenTagMarkupAndPutListeners:function(){ / /... },
// other methods
/ /...
}
Object.assign(
ReactDOMComponent.prototype,
ReactDOMComponent.Mixin,
ReactMultiChild.Mixin
);
module.exports = ReactDOMComponent;
Copy the code
In the SRC/renderers/Shared/reconciler instantiateReactComponent. In js:
var ReactCompositeComponentWrapper = function(element) {
this.construct(element);
};
Object.assign(
ReactCompositeComponentWrapper.prototype,
ReactCompositeComponent.Mixin,
{
_instantiateReactComponent: instantiateReactComponent,
}
);
// other code.......
instance = new ReactCompositeComponentWrapper(element);
Copy the code
Here, we may wish to compare this implementation in JS template class writing method and mainstream writing method of the similarities and differences. First of all, we first recall, in THE JS rhino book, it recommended for us, is the current industry mainstream writing method is how? Yes, it’s like this:
function SomeConstructor(){
this.property1='xxxx';
this.property2="xxxx";
/ /... other properties
}
SomeConstructor.prototype.method1=function(){}
SomeConstructor.prototype.method1=function(){}
/ /... other methods
Copy the code
Yeah, that’s a pattern that happens all the time. The book also tells us that the best practice for writing template classes is not to overwrite prototype objects. Why is that? That’s because once done, the constructor property of the prototype object will be overridden, causing others to make an error when using someinstance. constructor === SomeConstructor to determine whether an object is an instance of a constructor. Don’t believe it? Let’s take a look. When we use the dominant parent notation, everything is as we expect:
function Foo(name){
this.name = name || 'you got no name';
}
Foo.prototype.sayHello = function(){
console.log(`hello from ${this.name}`)
}
const sam = new Foo('sam');
console.log(sam instanceof Foo) // true
console.log(sam.constructor === Foo) // true
Copy the code
However, when we write overwrite the prototype object, things are not so rosy:
function Foo(name){
this.name = name || 'you got no name'; } Foo. Prototype = {sayHello:function(){
console.log(`hello from ${this.name}`)
}
}
const sam = new Foo('sam');
console.log(sam instanceof Foo) // true
console.log(sam.constructor === Foo) // falseThis is obviously not what we wantCopy the code
As mentioned above, the constructor property on the foo. prototype object was overridden, so the error was made.
Now that there’s nothing wrong with the react template class, why not use Object. Assign instead? Before we answer this question, let’s explore the features of the Object.assign API.
The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
Properties in the target object will be overwritten by properties in the sources if they have the same key. Later sources’ properties will similarly overwrite earlier ones.
So says the almighty MDN. Target objects refer to the objects we end up with, and source objects refer to the objects we merge into the target object. There are two things we can take away from this.
- With object. assign, the Object will be assigned
source objects
Property (also known as instance properties), and are enumerable properties copied totarget object
In the. This process is not consideredsource objects
Of the stereotype attribute. - Use object. assign to merge objects. The rule is “If there are objects, then the priority is assigned; if there are no objects, then add them.” What does that mean? That means when
target object
If you don’t have this property, add it to it; When multiplesource objects
All have the same one property, then the more latersource objects
The higher the priority is.
Review the react source code, which incorporates a literal object (named mixin) into a constructor’s prototype object. Although literal objects have access to the “constructor” property, this property is a stereotype property. As a result, the constructor property on the constructor’s prototype object is not overridden when objects are merged. Don’t believe it? Let’s verify this:
const sam = { nickname: 'littlepoolshark'};
console.log(sam.constructor) ƒ Object() {[native code]}; Literal objects have access to the "constructor" property
console.log(abc.constructor === Object) // true
for(let key in sam){
console.log(key); // just print "nickname" without "constructor"
}
console.log(sam.hasOwnProperty('constructor'))
Copy the code
As you can see from the code above, the “constructor” property accessible on a literal object is neither an instance nor an enumerable property. But it can be accessed, so it has to be a property on the stereotype chain, which is a stereotype property. Thus, writing React in this way conforms to the best practice of not overwriting the constructor properties of a prototype object.
You may ask, how can mainstream writing also achieve this effect? Why isn’t the React source code written this way? With this question in mind, we continue to explore. React template classes have a lot of methods, just like the ones listed above. The mainstream approach of adding to the prototype object method by method is repetitive and unwieldy. Like the following:
function ReactDOMComponent(element) {
this._currentElement = element;
this._tag = tag.toLowerCase();
// other properties
/ /...
}
ReactDOMComponent.prototype.method1= function(){}
ReactDOMComponent.prototype.method2= function(){}
ReactDOMComponent.prototype.method3= function(){}
ReactDOMComponent.prototype.method4= function(){}
ReactDOMComponent.prototype.method5= function(){}
ReactDOMComponent.prototype.method6= function(){}...Copy the code
Another problem with this approach is that, given the dynamic nature of javascript and the length of the prototype chain, property lookup is relatively time consuming. In the case of a large number of methods, the performance cost of this repeated attribute lookup must be significant. I think this is why the React source code is not written this way. In addition, extending the constructor’s prototype Object with object. assign provides two benefits by putting methods inside literal objects:
- The effect of batch addition.
- The results are similar to the decoupling and reuse of code brought by aspect programming.
In summary, we can infer the motivation for adopting the react source code as follows:
- Continue to follow the best practice of not overwriting the constructor attribute of the prototype object.
- The ability to batch add methods to prototype objects.
- Access the prototype object only once, ensuring a low performance cost during the property lookup process.
- Using literal objects to accommodate methods yields benefits similar to code decoupling and reuse that come with aspect programming.
Okay, so we’re done here.
Additional questions
Finally, let’s do a little divergent thinking. If, during the merge process, we also want to merge the source object’s prototype attributes, how should we do this? Here are my answers:
// Prototype Object properties + Object properties = all properties // Note: targetProto should be the second argument to object.assign ()function clone(targetObj){
const targetProto = Object.getPrototypeOf(targetObj);
returnObject.assign(targetObj, targetProto); } // Enhance the native object.assign () methodfunctionassign(target,... sources){ constcloneSources = sources.map(source= >clone(source));
returnObject.assign(target,... cloneSources); }function SomeConstructor(){ } assign( SomeConstructor.prototype, mixinObj1, mixinObj2, ...... ) // instantiate const inst = new SomeConstructor()Copy the code
Complete, thank you for reading.