無標題文檔

关联的 script 标签

从 James Padolsey 这里 得到个好的点子。

在实际写脚本过程中可能有段 Javascript 和 HTML 非常相关(比如实例化 Slider 等这样组件),那么通常我们会将它紧放到 HTML 的后面。

「传统」的做法需要顾虑的点有很多。因为脚本是立即被执行的,所以要考虑例如调用的组件是否已经声明,以及如果有 Ajax 请求是否会堵死浏览器等等。

下面的代码就是本篇 Blog 提供的另个思路,但愿我看起来不是那么的火星:

<div id="some-div">
    <script type=":contextual">
        alert(this.id); // "some-div" is alerted
    </script>
</div>

原文 作者的想法是改变 script 标签的 this 指向到父节点的 Element,从而关联上下文 HTML 结构。

看它的实现代码:

<script type="text/javascript">
~function() {
    var scripts = document.getElementsByTagName('script'),
        script, i = 0;
    while ((script = scripts[i++])) {
        if (/:contextual$/.test(script.type)) {
            (new Function(script.innerHTML)).call(script.parentNode);
        }
    }
}();
</script>

不过如原作者所说的外,其实还有很多顺带的好处

  1. 将 this 指向关联到父节点,遍历查找 DOM 非常的方便
  2. 相关的 script 标签和 HTML 结合紧密,很清楚就能明白这段脚本需要做什么
  3. 统一调用,可以考虑懒加载
  4. 方便复制粘贴 :^)

当然,上面的代码仅仅是个想法而已,在实际编码中还需谨慎应用。滥用此方法可以预料到的 些问题,比如:

  1. 脚本执行顺序改变
  2. 弄乱作用域,如果你的代码严重依赖 this ,那么将会是个噩梦(当然,这本身也不是个好习惯)
  3. 让不了解此机制的其他开发者迷惑

正如原作者所言,在我们写代码的时候能「Thinking outside the box」,那才是最重要的 :^)

-- EOF --

Mini,又个 Javascript 选择器

Javascript 选择器(selector engine)似乎从 jQuery 流行以来就大行其道,改变了原有 Javascript 选择 DOM 节点的方式。

目前 Javascript 选择器也有众多的选择,包括能列举出来的就有 PeppySizzle 以及 Sly 等,它们都实现了所有的 CSS3 选择器 并且性能不俗。

https://friable.rocks/_/2009_11_05/241818145cd9.jpg

而此次特别要推荐 Mini 的因素有很多。其最大的亮点就是从实用主义出发,简单高效的完成任务。

实用主义

jQuery 的作者 John Resig 曾经统计 jQuery 框架常用的几个选择器 。 很惊讶的发现用户其实常用 tagName、className 以及 id 就能完成 95% 以上的工作。

而 Mini 就从实用出发,它并没有标榜自己实现了全部 CSS3 的所有选择器,只是实现了下面的选择器及其变种:

  • div
  • .example
  • body div
  • div, p
  • div, p, .example
  • div p
  • div > p
  • div.example
  • ul .example
  • title

  • h1#title
  • div #title
  • ul.foo > * span

是的,虽然不多,但是相信在日常中已经足够使用。

https://friable.rocks/_/2009_11_05/601198145f37.jpg

实用主义虽然让 Mini 提供的功能有限,但从个侧面带来的优势就是 性能方面非常的理想 。 当然这还有与它的内部实现有关。

简单至上

Mini 的代码很简单,甚至不用恐惧于如何去读懂 Sizzle 这样的每行源代码。先从它的调用函数 _find 看起,往下阅读几行,你就发现它性能的秘密

if (!simple && context.querySelectorAll) {
    return realArray(context.querySelectorAll(selector));
}

很多的现代浏览器(包括 Gecko、WebKit 等)都 提供了原生的 Javascript 选择器支持 。 Mini 充分得利用了这点,从而将大部分的工作交给了浏览器(也免除了日后的维护之忧)。这点非常值得称道, 也是我们以后写 Javascript 应该走的方向 -- 要基于浏览器提供的功能而非类型进行对应的开发。

_find 其后的逻辑非常的简单,使用几个正则将提供的选择器解析出来,分别根据 getElementsByClassName、 getElementsByClassName 以及 getElementById 获取节点。

继续根据上面的思路,由于陈旧的 IE 没有既没有提供 querySelectorAll 也没有提供 getElementsByClassName, 那么它只能走到 else 的尽头,使用 filterByAttr 函数。

取到对应 className 以及 tagName 后,还得根据选择器获取其与其相符的节点,那么就轮到 filterParents 干活了。

filterParents 这个函数也是相对 _find 比较核心的函数,而且也是主要的性能消耗点。filterParents 其实要做的和 _find 类似,唯一不同的是它根据关系符向上剔除未满足条件的节点。

通过上面两个函数所得到的节点,通常已经是满足选择符条件的节点了。 但是可能的情况就是有重复的节点,这时就该 unique 函数上场,顾名思义剔除重复的节点。

这里要提到的就是 unique 函数做了个简单的 memoization 实现 , 作者的用心可谓从细节方面体现得淋漓尽致。还有个小技巧就是

var uid = +new Date();

竟然可以用这样直接返回时间戳,俏皮的代码让人再次感到犹如嚼了口薄荷糖,非常的清新(好吧,我火星了)。

读码到最后,有一点思考就是为何每次都要使用 realArray 函数强制将 NodeList 转换成 Array?虽然使用 querySelectorAll 返回的是 NodeList ,但是也是可以使用 Array 操作迭代。作者这样的做法我推测,可能是出于返回数据类型一致的缘故。

观点

说说到我的个人观点,这可能会让这篇文章前后矛盾。Javascript 选择器(selector engine)我个人不是非常推荐使用。

原因主要有两点,其一就是性能,其二就是会让写出来的代码过于的依赖于 DOM 结构。但能读到如 Mini 这样的代码,窥其内部运作机制,未尝不是件非常让人愉悦的事情。

http://james.padolsey.com/wp-content/uploads/me.jpg

最后,说说 Mini 的作者 James Padolsey 。我本人也很难相信,他竟然只有 19 岁!更难 得的是在 他 Blog 上的 About me 页面 中写道

What services do I currently offer?
- Everything JavaScript!

同时, 他也是 jQuery Cookbook 的作者 。 「小小年纪」能够有这样的心态以及成就,让我们这帮「前端老古董」感到唏嘘不已 :^)

Bang 兄 已对 Mini 的代码做了更为详尽的解析,有兴趣的可以参看 他的相关 Blog

innerHTML 的些摘记

异步 innerHTML

innerHTML 插入节点的性能的问题,通常是我们最关注的。 在回答这问题时James Padolsey 给出了他的解决方案 ,看到上述代码不仅赞叹了下:

function asyncInnerHTML(HTML, callback) {
    var temp = document.createElement('div'),
        frag = document.createDocumentFragment();
    temp.innerHTML = HTML;
    (function(){
        if(temp.firstChild) {
            frag.appendChild(temp.firstChild);
            setTimeout(arguments.callee, 0);
        } else {
            callback(frag);
        }
    })();
}
  1. 充分利用闭包解决 IE6 的内存溢出问题
  2. 使用 延时 0 将操作从队列中拉出 ,防止浏览器假死
  3. Document Fragment 给予我们个相当好的沙盘,只是我们经常忘记了它
  4. 回调的节点可以使用 DOM 标准的手法(appendChild)插入

了解了参数就很容易调用,例如

var htmlStr = '<div><p>...</p><p>...</p><div><div>...</div>';
asyncInnerHTML(htmlStr, function(fragment){
    document.body.appendChild(fragment);
});

再次不禁赞叹下!

组织 innerHTML 字符串

说到 innerHTML ,通常在这操作之前会有大部分的字符串操作用于连接节点。考虑下面的三种做法,有何不同

方式一

var arr = ['item 1', 'item 2', 'item 3', ...];
for (var i = 0, l = arr.length, list = ''; i < l; i++) {
    list += '<li>' + arr[i] + '</li>';
}
list = '<ul>' + list + '</ul>';

方式二

var arr = ['item 1', 'item 2', 'item 3', ...];
for (var i = 0, l = arr.length, list = []; i < l; i++) {
    list[list.length] = '<li>' + arr[i] + '</li>';
}
list = '<ul>' + list.join('') + '</ul>';

方式三

var arr = ['item 1', 'item 2', 'item 3', ...];
var list = '<ul><li>' + arr.join('</li><li>') + '</li></ul>';

详细的对比 测试在这里 (没错,还是 James Padolsey 那小子的 Blog)。同时, PPK 也整理了份有关 innerHTML 的速度测试报告

IE 的陷阱

对于 IE,innerHTML 有个不大不小的陷阱( via ),就是在 tbody 中插入 innerHTML 时,会报莫名的「未知的运行错误」。

测试地址在这里 (经过测试,在 IE8 中仍然如此)。有兴趣的同学可以 参看更详细的信息

我的照片

嗨!我叫「明城」,八零后、码农、宁波佬,现居杭州。除了这里,同时也欢迎您关注我的 GitHubTwitterInstagram 等。

这个 Blog 原先的名字叫 Gracecode.com 、现在叫 「無標題文檔」 。 要知道作为码农取名是件很难的事情,所以不想在取名这事情上太费心思。

作为八零后,自认为还仅存点点可能不怎么被理解的幽默感,以及对平淡生活的追求和向往。 为了避免不必要的麻烦,声明本站所输出的内容以及观点仅代表个人,不代表自己所服务公司或组织的任何立场。

如果您想联系我,可以发我邮件 `echo bWluZ2NoZW5nQG91dGxvb2suY29tCg== | base64 -d`

分类

搜索

文章