aralejs / class

Class Utility
aralejs.org/class
102 stars 88 forks source link

提供调用extend之后的可拓展性 #8

Closed lianqin7 closed 11 years ago

lianqin7 commented 11 years ago

需求来源于 arale/base 的性能优化:

将 attribute 的 merge 操作由 实例化时 -> 类构建时,可以提升较多的性能

可惜现在的class的extend之后没有接口能保证所有的子类构造时,都能运行同一段功能,且对其他使用者透明( 除了hack Class.create 方法

或者说大家有更好的解决方案?

@lifesinger @popomore @afc163

lianqin7 commented 11 years ago

需求来源:https://github.com/aralejs/base/issues/26

popomore commented 11 years ago

其实也不然,如果继承链比较长,extend 会调用多遍,每次 extend 都需要去 merge 之前的属性。

这就要看使用场景了,是实例化的数量还是继承链的长短。

而且我觉得 hack class 并不是一个明智的选择。

lifesinger commented 11 years ago

能否找一个真实场景测试?现在的用例有点极端。

2013/7/8 Haoliang Gao notifications@github.com

其实也不然,如果继承链比较长,extend 会调用多遍,每次 extend 都需要去 merge 之前的属性。

这就要看使用场景了,是实例化的数量还是继承链的长短。

而且我觉得 hack class 并不是一个明智的选择。

— Reply to this email directly or view it on GitHubhttps://github.com/aralejs/class/issues/8#issuecomment-20588495 .

王保平 / 玉伯(射雕) 送人玫瑰手有余香

lianqin7 commented 11 years ago

@popomore

只要有个固定的规则,每次调用extend 只要merge自己和parent(parent的attrs由parent及parent的parent merge而成)的attrs 就ok了,这个调用次数基本上小于实例化的数量。

hack class 当然只是为了验证这个方法的性能提升,还是希望能有更优雅的解决方案

lianqin7 commented 11 years ago

@lifesinger

用例确实极端了点,不过稍微复杂点的功能型页面,有多个 popup,overlay 之类的实例还是很正常的。更别说 validator 这种每个 item 都是一个 widget 实例的场景了。每次都在实例化的时候都去遍历一遍继承链,重复开销很严重。。


或者说换个思路,做第一次实例化的时候对attrs做个缓存,以后每次实例化的去取该缓存,也可以节省不少开销。。

lianqin7 commented 11 years ago

有点奇怪了,理论上做过缓存之后,后面反复实例化某个类的时候 merge 的次数更少了,速度应该更快才是。 测试结果是 IE系列速度提升, chrome和ff性能反而下降了 orz

代码如下:

  var attrsCache = {};
  var cid = 1;
  function mergeInheritedAttrs(attrs, instance, specialProps) {
    var inherited = [];
    var proto = instance.constructor.prototype;
    var attrCid = instance.constructor.__cid;

    // 如果 constructor 存在缓存,取缓存
    if (attrCid && attrsCache[attrCid]){
      mergeAttrs(attrs, attrsCache[attrCid]);
      return;
    }

    while (proto) {
      // 不要拿到 prototype 上的
      if (!proto.hasOwnProperty('attrs')) {
        proto.attrs = {};
      }

      // 将 proto 上的特殊 properties 放到 proto.attrs 上,以便合并
      copySpecialProps(specialProps, proto.attrs, proto);

      // 为空时不添加
      if (!isEmptyObject(proto.attrs)) {
        inherited.unshift(proto.attrs);
      }

      // 向上回溯一级
      proto = proto.constructor.superclass;
    }

    // Merge and clone default values to instance.
    for (var i = 0, len = inherited.length; i < len; i++) {
      mergeAttrs(attrs, normalize(inherited[i]));
    }

    // 缓存在 constructor 上
    instance.constructor.__cid = cid;
    attrsCache[cid++] = mergeAttrs({}, attrs);
  }

测试结果:http://jsperf.com/arale-attribute-performance

lifesinger commented 11 years ago

cache 不一定能提升性能,之前 Sea.js 里,resolveCache 我加过,但在一般场景下,会降低性能,我当初也是纳闷死了。

后来发现 cache 本身是存在开销的(存储和查询),只有当 cache 本身的开销小于节省的开销时,对性能才有提升,否则不光耗费性能,还多占用了内存。

2013/7/8 lianqin7 notifications@github.com

有点奇怪了,理论上做过缓存之后,后面反复实例化某个类的时候 merge 的次数更少了,速度应该更快才是。 测试结果是 IE系列速度提升, chrome和ff性能反而下降了 orz

代码如下:

var attrsCache = {}; var cid = 1; function mergeInheritedAttrs(attrs, instance, specialProps) { var inherited = []; var proto = instance.constructor.prototype; var attrCid = instance.constructor.__cid;

// 如果 constructor 存在缓存,取缓存
if (attrCid && attrsCache[attrCid]){
  mergeAttrs(attrs, attrsCache[attrCid]);
  return;
}

while (proto) {
  // 不要拿到 prototype 上的
  if (!proto.hasOwnProperty('attrs')) {
    proto.attrs = {};
  }

  // 将 proto 上的特殊 properties 放到 proto.attrs 上,以便合并
  copySpecialProps(specialProps, proto.attrs, proto);

  // 为空时不添加
  if (!isEmptyObject(proto.attrs)) {
    inherited.unshift(proto.attrs);
  }

  // 向上回溯一级
  proto = proto.constructor.superclass;
}

// Merge and clone default values to instance.
for (var i = 0, len = inherited.length; i < len; i++) {
  mergeAttrs(attrs, normalize(inherited[i]));
}

// 缓存在 constructor 上
instance.constructor.__cid = cid;
attrsCache[cid++] = mergeAttrs({}, attrs);

}

测试结果:http://jsperf.com/arale-attribute-performance

— Reply to this email directly or view it on GitHubhttps://github.com/aralejs/class/issues/8#issuecomment-20592024 .

王保平 / 玉伯(射雕) 送人玫瑰手有余香

lianqin7 commented 11 years ago

@lifesinger

soga,学习了。看来在这里加缓存并没有什么实质性的提升。暂时先放弃这个优化思路了。看看从别的地方去入手

29200344 commented 8 years ago

attribute.js ,我这样改怎么样?specialProps 感觉自己用不上就直接注释了 var Class = require("./class");

Class.Mutators["attrs"] = function( attrs ) { var proto = this.prototype;

// 原型链上不存在 attrs 属性
if ( !proto.attrs ) {
    proto.attrs = normalize( attrs );
    return;
}

var inherited = [];

// 如果 attrs 在父类原型链中
if ( !proto.hasOwnProperty("attrs") ) {
    //debugger;
    inherited.push( proto.attrs );
    proto.attrs = {};
}

inherited.push( attrs );

for ( var i = 0, len = inherited.length; i < len; i++ ) {
    mergeAttrs( proto.attrs, normalize( inherited[ i ] ) );
}

};

exports.initAttrs = function(config) { // initAttrs 是在初始化时调用的,默认情况下实例上肯定没有 attrs,不存在覆盖问题 var attrs = {};

// Get all inherited attributes. //var specialProps = this.propsInAttrs || []; //mergeInheritedAttrs(attrs, this, specialProps);

// 原型链上的属性要重新合并, 不需要 normalize ,原型合并时已调用, this.attrs 会指向最近的原型链 mergeAttrs( attrs, this.attrs ); this.attrs = attrs;

// Merge user-specific attributes from config. if (config) { mergeUserValue(attrs, config); }

// 对于有 setter 的属性,要用初始值 set 一下,以保证关联属性也一同初始化 setSetterAttrs(this, attrs, config);

// Convert on/before/afterXxx config to event handler. parseEventsFromAttrs(this, attrs);

// 将 this.attrs 上的 special properties 放回 this 上 //copySpecialProps(specialProps, this, attrs, true); };