Git Product home page Git Product logo

Comments (5)

errorrik avatar errorrik commented on July 22, 2024

关于initChildren行为的问题,我的思考过程如下,抛砖引玉:

initChildren使用场景

首先,initChildren方法不是给控件使用者用的,而是给控件开发者用的。在控件的渲染(包括重绘)过程中,可能会使用initChildren方法初始化子控件。

其次,initChildren( {HTMLElement} wrap )初始化子控件的场景有两种:

  1. wrap内部的html结构完全是控件开发者生成的。
  2. wrap内部的html结构有可能是控件使用者(如使用setContent)传入的。这里我们先不考虑这种方式是否需要支持,先假设有这种场景

如果initChildren支持自动树状结构

不算问题的问题1

<div data-ui-type="Tab">
    <ul data-role="navigator">
        <li data-for="a-element">a</li>
    </ul>
    <div id="a-element">
        <textarea data-ui-type="TextBox"></textarea>
    </div>
</div>

上面的case,TextBox是Tab的子控件,但其实Tab根本不需要关心TextBox的存在。当然,其作为Tab的的子控件并没有逻辑上的问题。

可以解决的问题2

另外一个场景,Dialog自己在render时生成一个childName为close的子控件,但是用户使用setBodyContent方法,传入下面这样一个html,就会有childName的冲突问题。所以,如果我们要支持使用场景2,控件内部自己render时生成的子控件,childName命名就必须使用__name__。这个就需要控件开发者保证。

<button data-ui-type="Button" data-ui-child-name="close"></button>

可能是问题的问题3

想想Dialog控件的控件开发者,生成下面这段html,并且initChildren。在绑事件的时候,他就需要调用多次getChild。这让我想起很多写.parentNode.parentNode.parentNode.setAttribute()的同学...

<!-- 下面是Dialog开发者给main灌的innerHTML -->
<div data-ui-type="Panel" data-ui-child-name="head">
    <h3 data-ui-type="Label" data-ui-child-name="title"></h3>
    <button data-ui-type="Button" data-ui-child-name="close"></button>
</div>
<div data-ui-type="Panel" data-ui-child-name="body"></div>
<div data-ui-type="Panel" data-ui-child-name="foot">
    <button data-ui-type="Button" data-ui-child-name="ok"></button>
    <button data-ui-type="Button" data-ui-child-name="cancel"></button>
</div>

// 下面是绑定事件代码
this.getChild( 'head' ).getChild( 'close' ).on( 'click', closeHandler );
var footChild = this.getChild( 'foot' );
footChild.getChild( 'ok' ).on( 'click', okHandler );
footChild.getChild( 'cancel' ).on( 'click', cancelHandler );

而且,close/ok/cancel确实应该是Dialog的子控件,因为其行为是归Dialog管的。

不可解决的问题4,这是我最介意的点

性能,是不可解决的问题。这个不可解决在于遍历过程

现在的机制是,getElementsByTagName('*'),结果缓存一遍(当然是缓存成数组),然后对缓存数组进行处理。简单的说,就是遍历过程仅仅调用1次dom method。

如果是树状结构,我们缓存结果的遍历过程,就只有两种可能:

  1. element.firstChild + element.nextSibling
  2. element.children

我们曾经做过测试,一个稍微复杂的页面(一个门户站首页改版前的页面)。

  1. children遍历是最慢的,超过1s
  2. firstChild + nextSibling是400多ms
  3. getElementsByTagName('*')是100多ms

所以,我想说,initChildren支持自动树状结构,性能的2-3倍损失,我们为了什么?得到了什么?开发更方便?


曾经灰大说过,esui是他见过设计比较奇怪,但是性能最高的ui组件库。其实,很多设计,很多不做的事情,都是为性能做的考虑。

from esui.

otakustay avatar otakustay commented on July 22, 2024

首先有一点,事实上这个initChildren的问题,本质上是main.init的问题。而main.init是会给业务开发人员用到的,如果main.init没有生成树型的结构,业务开发人员主没办法通过树型的结构去取到自己要的东西,而被迫使用比较容易重复的id来做这事,这在多人协同的情况下更容易出乱子。

以DOM作为比较,我们可以用getElementById获取唯一的元素,但同时也提供通过children的层级查找,不然jQuery的选择器也没有意义了。所以我认为我们不应该放弃main.init实现树状,并且让业务开发者可以用children的可能性。(似乎childrengetChild的设计也不是私有的,业务人员应该去用)


抛开上面这个,再来谈谈后面的几个问题。

@errorrik 提的前面的几个问题均认可,就是__childName__的形式不大愿意接受,理由是无端洁癖不解释,当然为了解决洁癖必然要有方案提出来,见下文。

关键点在性能上,这个非常重要,所以我尝试设计了一个方式,可以维护性能不会明显降低的情况下实现层级结构的initChildren,以下是一个main.init方法的伪代码,我没验证是否正确,就请 @errorrik 看下能不能实现并保证正确性吧:

function init(wrap) {
    var elements = wrap.getElementsByTagName('*');
    elements = toArray(elements);

    var controls = [];
    var map = {};
    for (var i = 0; i < elements; i++) {
        var element = elements[i];
        if (isControl(element)) { // 判断`data-ui`
            var control = createControl(element):
            map[control.id] = control;
            element.setAttribute('data-ui-id', control.id);
            var parent = findParent(wrap, element);
            if (parent) {
                parent.addChild(control);
            }
            else {
                controls.push(control);
            }
        }
    }

    return controls;
}

function findParent(wrap, element, map) {
    var parent = element.parentNode;

    while (parent !== wrap 
        && parent.hasAttribute('data-ui-id')) {
        parent = parent.parentNode;
    }

    if (parent !== wrap) {
        return map[parent.getAttribute('data-ui-id')];
    }

    return null;
}

这套方案基于2个前提:

  1. getElementsByTagName返回的元素顺序是 深度优先的 ,因此 当前元素的父元素肯定已经被处理过
  2. DOM的深度层级通常不会太深,因此作纵向的遍历不会有太多消耗

因此就使用一个Map管理已经处理过的元素与控件的对应关系,并在元素上通过data-ui-id(用户可能也会写上这属性,所以这属性加上也无所谓)标记对应的键并声明这个元素是一个控件。当子元素发现自己的某个祖先元素(不超过wrap范围)是控件时,通过id自动加到这个控件里去,并且不在返回的数组中。

我认为一般DOM的深度有5层就非常可观了,且通常来说往上找1-2层肯定能遇上控件,多层普通HTML元素里套一个控件的场景不多(我特地去看了下CLB现在的状态,确实不多或者说非常之少),因此这个方案的性能开销,相当于每个元素多读1次parentNode并在parentNode上多读一次data-ui-id属性,感情上不觉得有很大开销。

同时通过 先移除wrap,处理完再放回来 也能提升不少的性能(直觉上这样不会有问题),因此我觉得性能 并非不可突破的屏障 ,至少绝对不会有 2-3倍的损失

from esui.

errorrik avatar errorrik commented on July 22, 2024

这个问题,我想听听大家意见,不想我们两个人就敲了。还有人来讨论不。。。

from esui.

firede avatar firede commented on July 22, 2024

两位已经把创建树和不创建树的优缺点讲的很透彻了,我还是倾向 不创建树,效率优先

创建树的优点是多人协同下,可以解决childName冲突的问题,不容易出乱子。
但是我理解,控件的开发会在充分了解现有代码的基础上进行,这样的冲突场景不会太多。
所以我认为引入子控件树增加了复杂度,收益可能并没有那么高,不如保持简单。

from esui.

otakustay avatar otakustay commented on July 22, 2024

于是我没什么意见,有讨论理清思路就好,就按不创建树来做吧,这边的几个控件发了一下以后也较容易兼容了这种模式,我想其他人也不会来参与了,关了吧?

from esui.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.