资讯专栏INFORMATION COLUMN

我是怎么写JavaScript框架的(一)

李世赞 / 273人阅读

摘要:我就打算慢慢解析我是怎么写的,并穿插讲解我对版写法的理解。其实源代码里我都写有注释,我也是靠这些注释回忆我当时是怎么想的。把写成,那就和的调用用法一样。链式结构就是在每个方法结束时返回一个对象进行下一个方法的操作。

前言

以前大三的时候写的一个小框架,仿jquery链式结构,纯属练手,大牛勿喷。当时还把JQ的源码全部打印下来(因为在电脑上看不方便做笔记),7600+行代码,300+页A4纸,对照着看别人是怎么写的。还好一个长我一届的学长当时比较闲,我说我想读JQ代码,他也没读过,就找来上两届的学长以前练手写的小框架进行学习。可惜只找到了压缩混淆后的代码,对于一个渣渣来说,阅读起来很困难。在他的指点下,我大致了解了原理,并开始着手自己写。现在陆续有学弟找我要这个代码。虽然现在觉得写得一塌糊涂,但是,可能对于和我当时一样摸不着边的人来说,可能会有帮助。我就打算慢慢解析我是怎么写的,并穿插讲解我对JQ(2.0版)写法的理解。其实源代码里我都写有注释,我也是靠这些注释回忆我当时是怎么想的。可能会有很多不好的地方,轻喷~ 罒ω罒

我给库起名Oct.js,是因为这个是在企鹅实习生应聘失败后为10月校招而写。纪念那个稚嫩的我。

全部代码,看这里看这里~( ̄▽ ̄)~* 
配合使用文档看代码更容易理解

闭包结构

为了防止和别的库的冲突,用闭包把整个框架安全地保护好。我们待会的代码都写在里面。这里创建一个全局变量"window.O",就是在window对象里加个O,它等价于 "Oct",相当于jquery、Jquery这样的别称,意味着以后用Oct()或O()来进行操作。把window.O写成window.$,那就和JQ的$调用用法一样。window.O返回一个Octobj对象,这很重要。

(function() {
    // 创建一个全局变量"window.O"
    window.O = Oct = function(selector, root_id, tag) { 
        return new Octobj(selector, root_id, tag); 
    }; 

    // 帅气地定义一个版本
    Oct.version = "1.0"; 

})();
链式结构原理

JQ的流行,我想是因为它很方便吧,一个选择器就可以接着写一连串的方法。链式结构就是在每个方法结束时返回一个对象进行下一个方法的操作。后面会通过代码说明

选择器

选择器非常重要。JQ特别把选择器部分剥离出一个库sizzle.js。现在的JQ内部也是调用JQ里面的sizzle。“选择器”三个字写得简单,代码写起来就很有学问,sizzle.js这种高大上的我就不说了。我这里简单实现一些功能:选择class/id/标签。

首先,把上面的Octobj对象骨架搭建起来。包含selector, root_id, tag三个参数。selector就是你想要找的东西,root_id是你想要找的东西的上级的id,tag是你要指定返回某种标签。

var Octobj = function(selector, root_id, tag) {

};

定义一些参数

// args: 存储root_id的子标签 
// type: 类型标记,id("#"), class(".") 或者 tag("&。 tag也加标记是为了代码方便
// eles: 临时的,存储`selector`变量里"# . &"标记后面的字符串
// selector_exp: 用来匹配标签的正则规则
var agrs, type, eles; 
var selector_exp = /^(?:#(w-_)+|.(w-_)+|(w)+)$/; 

// this.elements: 存储函数结束后返回对象
this.elements = [];

// 防止以下情况: $(""), $(null), $(undefined), $(false)
if (!selector) {
    return this;
}

接下来处理有没有指定root_id、tag的情况,

if (root_id) {
    root_id = typeof root_id == "string" ? document.getElementById(root_id) : root_id;
} else {
    root_id = document.body;
}
tag = tag || "*";
if (tag !== "*") {
    tag = tag.slice(1);
}

进入核心部分。分两种情况,能支持querySelector()方法的“高级”浏览器和不能支持的“低端”浏览器。先是能支持querySelector的,就很简单了:

if (document.querySelectorAll) {
    // 因为我用"&"符号来标记标签 ,所以要用"replace()"去掉"&" 
    var node_list = document.querySelectorAll(selector.replace("&", ""))
    for (var i in node_list) {
        if (node_list[i].tagName !== undefined) {
            // 把符合要求的元素存入`this.elements`
            this.elements.push(node_list[i]);
        }
    }
}

不能支持querySelector的,先来设置一些参数:

// 处理类似JQ $(".a .b")的情况
selector = selector.replace(/^s+/, "").split(/s+/);

// 如果不指明 "root_id" 和 "tag", "args" 就存储所有的标签
args = root_id.getElementsByTagName(tag);

// 通过符号标记判断是class/id/tag
type = selector[0].charAt(0);

// 所要选取的目标的字符串
eles = selector[0].slice(1);  

处理选择class的情况。有文章指出for in的效率不高,我本来是用for处理的,但是出现问题,因为会把方法一起选进来。JQ用了for in,感觉有了标杆,我就全部改用for in了。

if (type === ".") {
    // 查找每个标签中有className,提取处理进行匹配
    for (var i in args) {
        if(args[i].className) {

            // className 可能不止一个, 通过空格进行分割
            var r = args[i].className.split(/s+/);
            for (var j in r) {
                if (r[j] === eles) {
                    this.elements.push(args[i]);
                }
            }
        }
    }
}

处理选择id的情况:

else if (type === "#") {
    for (var i in args) {
        if(args[i].id) {
            var r = args[i].id.split(/s+/);
            for (var j in r) {
                if (r[j] === eles) {
                    this.elements.push(args[i]);
                }
            }
        }
    }
}

处理选择标签的情况:

else if (type === "&") {
    for (var i in args) {
        // 你可以"console.log(args[i]);" 可以看到最后一个是"length" ,没有"tagName",所以要排除这个情况,否则会报错
        if (i !== "length" && typeof args[i] !== "function") {

            // "args[i].tagName" 在浏览器的属性里是用大写的,为了符合我的习惯,改用小写进行判断
            if (args[i].tagName.toLowerCase() === eles.toLowerCase()) {
                this.elements.push(args[i]);
            }
        }
    }

}

基础选择器部分完成了,别忘了最后一句,返回找到的元素,给下一个方法进行操作:

return this;

全部代码

用法举例

1.通过 id 选择.

O("#buddy")

2.通过 class 选择.

Oct(".buddy")

3.选择所有 div.

O("&div") // also O("&Div"), O("&DIV")

4.选择 .buddy 里的

.

O(".buddy", null, "&p")

5.选择#dad下的.buddy

O(".buddy", "#dad")   

Laker"s blog

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91446.html

相关文章

  • 保姆级教程HTML两万字笔记大总结【建议收藏】(上篇)

    摘要:标签不区分大小写,但推荐小写。标签可以嵌套,但不能交叉嵌套。标签也称为元素。比如行内标签亦可成行内元素。 ❤️HTML必备知识详解❤️ 第一部分:HTML框架简介...

    paulli3 评论0 收藏0
  • 在没有DOM操作日子里,我是怎么熬过来(上)

    摘要:至于我为何要这么做,请听闰土娓娓道来。那么接下来,正文从这开始熟悉闰土的朋友都知道,我是从时代过来的前端,在刚接触和这类框架的时候,完全可以用一脸懵逼来形容我,最为贴切。作者闰土少年出处本博客的文章如无特殊说明,均为原创,转载请注明出处。 showImg(https://segmentfault.com/img/bVZjKW?w=670&h=442); 前言 在我动笔写这篇文章的时候,...

    shuibo 评论0 收藏0
  • 我是如何从零开始学习前端

    摘要:相关频道最后的坦白最后我得承认这一次我又标题党了无非是想吸引更多前端初学者进来,希望大家都能少走一些弯路,也希望那些从零开始自学前端的同学更有勇气去面对自己的选择。 我是怎么走上前端开发这条路? 首先,我是个文科生,大学里只学过vb,觉得计算机编程这东西太玄乎,不是我玩得转的。 后来机缘巧合去做了一家互联网创业公司的HR,阅了上千份程序员的简历,面了上百个不同水平不同领域的程序员。跟程...

    mykurisu 评论0 收藏0
  • 如何学JavaScript

    摘要:书籍如下面向对象编程指南,风格轻松易懂,比较适合初学者,原型那块儿讲得透彻,种继承方式呢。还有另一件事情是,比如发现自己某个知识点不太清楚,可以单独去百度。 作者:小不了链接:https://zhuanlan.zhihu.com/p/...来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 鉴于时不时,有同学私信问我(老姚,下同)怎么学前端的问题。这里统一回...

    light 评论0 收藏0
  • 第三方库

    摘要:微信支付,支付宝支付,银联支付三大支付总结支付宝植入总结支付宝的植基于和百度地图的组件库基于百度地图封装的组件库,使用这个库最好需要先了解和百度地图。 Commento - 多说 & Disqus 开源替代品 Commento - 多说 & Disqus 开源替代品 anime.js 简单入门教程 强大轻量的动画库 anime.js 入门教程 来自B站的开源的MagicaSakura源...

    seanHai 评论0 收藏0

发表评论

0条评论

李世赞

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<