React使用对象池()来管理合成事件对象的创建和销毁,在启动时React分配一个对象池,从对象池中getPooled对象可以实现重复利用,减少了垃圾回收操作和新对象内存的分配,提高了性能。
补充:对象池就是将对象存储在一个池子中,当需要一个新对象时,如果对象池中有空闲对象则直接输出对象如果没空闲对象则需要创建新对象,而不是每次都实例化一个新的对象。当对象不再使用时则放回池中以便后来对象使用。池的最重要的特性,也就是对象池设计模式的本质是允许我们获取一个“新的”对象而不管它真的是一个新的对象还是循环使用的对象。对象池的使用场景是:(1)需要使用大量对象(2)对象的实例化过程开销大但使用时间短。
那么React中如何实现对象池的呢?
一个对象池通常会提供一个获取对象的接口和关闭池的机制来释放对象。比如在React的setState机制中的ReconcileTransaction 和ReactUpdatesFlushTransaction 以getPooled的方式创建transaction,CallbackQueue也是以getPooled方式创建对象。在对象使用结束后以released方式释放。下面以CallbackQueue分析对象池的原理:
在CallbackQueue.js中
var CallbackQueue = function () {
function CallbackQueue(arg) {
_classCallCheck(this, CallbackQueue);
this._callbacks = null;
this._contexts = null;
this._arg = arg;
}
CallbackQueue.prototype.enqueue = function enqueue(callback, context) {
this._callbacks = this._callbacks || [];
this._callbacks.push(callback);
this._contexts = this._contexts || [];
this._contexts.push(context);
};
CallbackQueue.prototype.notifyAll = function notifyAll() {
var callbacks = this._callbacks;
var contexts = this._contexts;
var arg = this._arg;
if (callbacks && contexts) {
!(callbacks.length === contexts.length) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Mismatched list of contexts in callback queue') : _prodInvariant('24') : void 0;
this._callbacks = null;
this._contexts = null;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i].call(contexts[i], arg);
}
callbacks.length = 0;
contexts.length = 0;
}
};
CallbackQueue.prototype.checkpoint = function checkpoint() {
return this._callbacks ? this._callbacks.length : 0;
};
CallbackQueue.prototype.rollback = function rollback(len) {
if (this._callbacks && this._contexts) {
this._callbacks.length = len;
this._contexts.length = len;
}
};
CallbackQueue.prototype.reset = function reset() {
this._callbacks = null;
this._contexts = null;
};
CallbackQueue.prototype.destructor = function destructor() {
this.reset();
};
return CallbackQueue;
}();
module.exports = PooledClass.addPoolingTo(CallbackQueue);
CallbackQueue我们先看CallbackQueue的编程技巧,是一个自执行函数的,该函数返回在函数内部创建的函数对象CallbackQueue,并在自执行函数内部对内部的CallbackQueue的原型进行了封装。包括enqueue(回调函数入队列)、notifyAll(触发回调函数)、reset(重置)、checkpoint、destructor(销毁队列)。在销毁this._callbacks和this._contexts时对其赋值null,而对于变量callbacks和contexts的销毁方式是直接将其len赋值为0。
该模块最后的输出是通过将CallbackQueue 添加到PooledClass的方式实现的,即
PooledClass.addPoolingTo(CallbackQueue)。
PooledClass才是真正的对象池的创建方法,在PooledClass.js找到它的:
/**
* Static poolers. Several custom versions for each potential number of
* arguments. A completely generic pooler is easy to implement, but would
* require accessing the `arguments` object. In each of these, `this` refers to
* the Class itself, not an instance. If any others are needed, simply add them
* here, or in their own files.
*/
var oneArgumentPooler = function (copyFieldsFrom) {
var Klass = this;
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
return new Klass(copyFieldsFrom);
}
};
var twoArgumentPooler = function (a1, a2) {
var Klass = this;
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, a1, a2);
return instance;
} else {
return new Klass(a1, a2);
}
};
var threeArgumentPooler = function (a1, a2, a3) {
var Klass = this;
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, a1, a2, a3);
return instance;
} else {
return new Klass(a1, a2, a3);
}
};
var fourArgumentPooler = function (a1, a2, a3, a4) {
var Klass = this;
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, a1, a2, a3, a4);
return instance;
} else {
return new Klass(a1, a2, a3, a4);
}
};
var standardReleaser = function (instance) {
var Klass = this;
!(instance instanceof Klass) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Trying to release an instance into a pool of a different type.') : _prodInvariant('25') : void 0;
instance.destructor();
if (Klass.instancePool.length < Klass.poolSize) {
Klass.instancePool.push(instance);
}
};
var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler;
/**
* Augments `CopyConstructor` to be a poolable class, augmenting only the class
* itself (statically) not adding any prototypical fields. Any CopyConstructor
* you give this may have a `poolSize` property, and will look for a
* prototypical `destructor` on instances.
*
* @param {Function} CopyConstructor Constructor that can be used to reset.
* @param {Function} pooler Customizable pooler.
*/
var addPoolingTo = function (CopyConstructor, pooler) {
// Casting as any so that flow ignores the actual implementation and trusts
// it to match the type we declared
var NewKlass = CopyConstructor;
NewKlass.instancePool = [];
NewKlass.getPooled = pooler || DEFAULT_POOLER;
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE;
}
NewKlass.release = standardReleaser;
return NewKlass;
};
var PooledClass = {
addPoolingTo: addPoolingTo,
oneArgumentPooler: oneArgumentPooler,
twoArgumentPooler: twoArgumentPooler,
threeArgumentPooler: threeArgumentPooler,
fourArgumentPooler: fourArgumentPooler
};
module.exports = PooledClass;
前面5个方法的第一步都是将this赋值给一个变量Klass, this指向将被Pooler对象本身,因此 Klass也是指向Pooler的。为什么getPooled()可以从pool中抽取出一个实例对象呢?在addPoolingTo方法中可以找到getPooled在本场景中getPooled指向oneArgumentPooler,从oneArgumentPooler的代码中可以知道,如果当前对象池中的instancePool的个数不为0,则从对象池中pop实例,否则new一个实例对象。
var oneArgumentPooler = function (copyFieldsFrom) {
var Klass = this;
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
return new Klass(copyFieldsFrom);
}
};
而instancePool内部的实例是CallbackQueue.js的最后一行PooledClass.addPoolingTo(CallbackQueue)将NewKlass.instancePool 设置为[],并且将NewKlass.poolSize初始化为10。但是,何时将创建实例的呢??? 个人理解如下(不知道是否正确,若有争议请指出,谢谢):
再回顾一下getPooled 的核代码:
var Klass = this;
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
return new Klass(copyFieldsFrom);
}
第一次getPooled 实例对象时,Klass.instancePool.length为0,因次会new出一个实例,当该实例使用结束后会realeased释放对象,该核心代码有一段时:
if (Klass.instancePool.length < Klass.poolSize) {
Klass.instancePool.push(instance);
}
因为此时的Klass.instancePool.length 为1 小于poolSize。因次第一次释放的实例对象会被push到instancePool中,以此类推。当instancePool保存实例个数大于10个了将不会收集新对象。
总结:React对象池,将对象封装为pooler,并设置池子中可以存储实例的容量Size。对于初次使用或者池子中没有空闲的instance,需要new新实例。在释放实例对象时,如果池子中还有存储空间则push到pooler中,否则直接当做垃圾回收。