二話不說,今天咱們就來賞析react@ 15.0.0源碼中面向對象範式中父類(稱之爲模板類或者自定義類型都行)的寫法。javascript
它的具體寫法是這樣的:java
function Constructor(){}
Object.assign(
Constructor.prototype,
literal-object1,
literal-object2
)
module.exports = Constructor;
複製代碼
在react@ 15.0.0源碼中,這種寫法的應用見諸於react幾種component的模板類的定義代碼。react
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;
複製代碼
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;
複製代碼
var ReactCompositeComponentWrapper = function(element) {
this.construct(element);
};
Object.assign(
ReactCompositeComponentWrapper.prototype,
ReactCompositeComponent.Mixin,
{
_instantiateReactComponent: instantiateReactComponent,
}
);
// other code.......
instance = new ReactCompositeComponentWrapper(element);
複製代碼
在這裏,咱們不妨比較一下這種在js中實現模板類的寫法與主流寫法的異同。 首先,咱們先回憶一下,在js犀牛書中,它爲咱們推薦的,也是當前業界主流的寫法是怎樣呢?對,是這樣的:編程
function SomeConstructor(){
this.property1='xxxx';
this.property2="xxxx";
// ...... other properties
}
SomeConstructor.prototype.method1=function(){}
SomeConstructor.prototype.method1=function(){}
// ......other methods
複製代碼
對,這是家常便飯的模式了。書中還告訴咱們,模板類編寫的最佳實踐是不要採用【覆蓋原型對象】的寫法。爲何呢?那是由於一旦這麼作了,原型對象的constructor屬性就會被覆蓋掉,這會致使其餘人在使用【someInstance.constructor === SomeConstructor】來判斷某個對象是不是某個構造函數的實例的時候出錯。不信?我們來看一看。當咱們採用主流的父類寫法的時候,一切都正如咱們所願:bash
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
複製代碼
然而,當咱們採用【覆蓋原型對象】的寫法,一切就不那麼美好了:app
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) // false,這顯然不是咱們想要的結果
複製代碼
正如上面所說的Foo.prototype對象上的constructor屬性被覆蓋了,因此致使了判斷錯誤。dom
那麼既然主流的模板類的寫法沒有什麼毛病,那react的源碼中,爲何不採用這種寫法而是採用Object.assign這種寫法呢?在回答這個問題以前,咱們不妨探索一下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.ui
萬能的MDN如是說。上面所說的target object
指的是咱們最終獲得的對象,source objects
是指咱們將某些對象合併到這個target object
去的【那些對象】。 從上面,咱們能夠獲得兩點信息。
source objects
的自有屬性(也稱爲實例屬性),而且是可枚舉屬性拷貝到target object
中。這個過程是不考慮source objects
的原型屬性的。target object
沒有這個屬性的時候,就往它身上添加;而當多個source objects
都具備相同的一個屬性時,那麼越是後面的source objects
的優先級越高。咱們回顧一下react源碼中的寫法,它都是將一個字面量對象(命名爲mixin)合併到構造函數的原型對象上的。雖然字面量對象可以訪問「constructor」屬性,可是這個屬性是原型屬性。因此,在合併對象的時候,構造函數的原型對象上的constructor屬性並不會被覆蓋掉。不信?我們來驗證一下:
const sam = { nickname: 'littlepoolshark'};
console.log(sam.constructor) // ƒ Object() { [native code] };字面量對象能訪問「constructor」屬性
console.log(abc.constructor === Object) // true
for(let key in sam){
console.log(key); // 只打印了一個「nickname」,並無「constructor」
}
console.log(sam.hasOwnProperty('constructor'))
複製代碼
從上面的代碼能夠看出,字面量對象上能訪問的「constructor」屬性既不是實例屬性,也不是可枚舉屬性。可是它能夠訪問,那它只能是原型鏈上的屬性了,也就是原型屬性。因此,react的這種寫法符合【不要覆蓋原型對象的constructor屬性】的這個最佳實踐的要求。
也許你會問,那主流的寫法也能夠達到這種效果啊?爲何react源碼不這麼寫呢?帶着這個疑問,咱們繼續往下探索。不知道,你有沒有去看看源碼,正如上面所羅列的幾個模板類那樣,在react源碼中模板類的方法通常都是不少的。若是採用主流的寫法,一個方法一個方法地往原型對象上添加,那麼就顯得重複和笨拙了。就像下面那樣:
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(){}
.............
複製代碼
這麼寫法還有一個問題,那就是鑑於javascript這門語言的動態性再加上原型鏈的冗長,屬性查找是相對耗時的。在方法數量很大的狀況下,那麼這種重複的屬性查找所帶來的性能消耗必然是很大的。我想這就是react源碼不採用這種寫法的緣由。還有一點是,採用把方法都放在字面量對象裏面,而後結合Object.assign來擴展構造函數的原型對象,能帶來兩點好處:
綜上所述,咱們能夠把react源碼中採用這種寫法的動機推測以下:
好,到這裏咱們已經賞析完畢了。
最後咱們來一下發散思惟。若是,咱們在合併的過程當中,也想把source object
的原型屬性也一併合併過來呢,應該怎麼實現呢?下面是個人答案:
// 原型對象屬性 + 對象自身屬性 = 全部屬性
// 注意點:targetProto要成爲Object.assign()的第二個參數
function clone(targetObj){
const targetProto = Object.getPrototypeOf(targetObj);
return Object.assign(targetObj, targetProto);
}
// 加強原生的Object.assign()方法
function assign(target,...sources){
const cloneSources = sources.map(source => clone(source));
return Object.assign(target,...cloneSources);
}
function SomeConstructor(){
}
assign(
SomeConstructor.prototype,
mixinObj1,
mixinObj2,
......
)
// 實例化
const inst = new SomeConstructor()
複製代碼
全文完,謝謝閱讀。