窥一斑而见全豹--论jQuery正确的学习姿势

本文通过实现一个简易版的jQuery,引导读者理解jQuery的核心理念与设计思路。从创建一个获取元素兄弟节点的小工具开始,逐步扩展到封装命名空间、实现元素级API调用,最终形成一个具备基本功能的jQuery仿制品。

作为前端工程师,一定对jQuery不陌生。

jQuery是目前使用最广泛的JavaScript函数库。据统计,全世界57.5%的网站使用jQuery,在使用JavaScript函数库的网站中,93.0%使用jQuery。以至于熟练地使用jQuery都已经成了有经验的前端开发者默认自带的技能。

那么对于一个新人而言,怎样才能高效地学习jQuery呢?

在优快云、知乎或是其他一些社交平台,大部分答案都是建议新手直接上手jQuery官方文档,这些答案大都强调了官方文档的权威性与完整性。诚然,熟读官方文档是一个不容易被带偏的好方法,毕竟官方给出的例子是很少出现问题的。但是,对于新手而言,熟读官方文档,大部分时候都是像在记忆jQuery的各种API,且不说jQuery的API数量之多,机械地记忆文档演示的API而不知道其实际适用的场景,多多少少有点事倍功半的味道

正确学习jQuery的姿势应该是,自己实现一个简单的“jQuery”函数库。库的功能不需要多复杂多全面,就是实现几个开发中你经常会用到的的API而原生js却没有提供的,粗略地感知一下jQuery的设计与实现,然后再带着这样的认知去阅读官方文档。

那你可能会疑惑了,对于新手而言,实现一个“jQuery库”似乎是一个很没头绪的事,完全不知道怎么写。其实,你只要把函数库类比成你的工具箱,而现在我们仅仅是想造一个有一把小锤子的工具箱,所以还是很简单的。

提出一个小需求,然后实现它,这就是你的小锤子

比如在dom操作中,你可以利用Node.previousSibling获取某个元素的前一个兄弟节点,利用Node.nextSibling获取某个元素的后一个兄弟节点,但是实际开发中,你却往往会遇到想要获取某个元素的除自己外所有兄弟节点的情况,而原生的js是没有提供这样的API。那么,这就是你的“jQuery”库所需要实现的第一个功能,也就是你的第一个小锤子

如何获取页面上某个元素的所有兄弟节点

现在假设有这样一个页面index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MyDocument</title>
</head>
<body>
    <ul>
        <li id="item1"></li>
        <li id="item2"></li>
        <li id="item3"></li>
        <li id="item4"></li>
        <li id="item5"></li>
    </ul>
</body>
复制代码

现在你需要实现一个函数getSiblings,该函数接受一个节点作为参数,返回该节点的除自己外的所有兄弟节点,你可以这样组织代码

function getSiblings(node) {
    //获取该元素父节点的所有子节点
    var allChid = node.parentNode.children;
    var array = {length:0};
    //遍历所有子节点
    for(let i = 0;i < allChild.length;i++) {
     //返回该元素节点除外的所有子节点
        if(allChidl[i] !== node) {
            array[array.length] = allChild[i];
            array.length += 1;
        }
    }
    return array;
}
复制代码

有了这个函数以后,你如果要获取页面id为item3的li元素的除自己外所有兄弟节点,那么只需要以下操作:

var items = document.getElementById('item3');
getSiblings(item3);
// {0:itme1,1:item2,2:item4,3:item5,length:4}
复制代码

现在你给自己的“jQuery库”增加了第一个API getSiblings,用来获取某个元素的所有兄弟节点。这看上去似乎很不错,但是似乎又有哪里不太对。对,只要稍微细想一下,你就会发现在js里,如果你现在的getSiblings不予以封装一下的话,很容易造成全局变量污染,毕竟你也不知道是否有人还在其他地方也定义了getSiblings这个函数。全局变量污染,这应该是程序员最不想看到的事情。这个时候,你很可能需要一个命名空间来帮助你。

增加一个命名空间

在编程语言中,命名空间是对作用域的一种特殊的抽象,它包含了处于该作用域内的标识符,且本身也用一个标识符来表示,这样便将一系列在逻辑上相关的标识符用一个标识符组织了起来。通俗的理解,就是将你实现的东西,装在一个写了你标志的容器里,而js中最常用的容器,就是对象。

所以,你可以这样改写一下:

var mydom = {};
mydom.getSiblings = function(node) {
    //获取该元素父节点的所有子节点
    var allChid = node.parentNode.children;
    var array = {length:0};
    //遍历所有子节点
    for(let i = 0;i < allChild.length;i++) {
    //返回该元素节点除外的所有子节点
        if(allChidl[i] !== node) {
            array[array.length] = allChild[i];
            array.length += 1;
        }
    }
    return array;
}
复制代码

那么现在引用getSiblings时,则是这样:

var item3 = document.getElementById('item3');
mydom.getSiblings(item3);
复制代码

这样一来,就避免了全局变量污染这个问题

能不能把Node放前面?

在增加命名空间了以防防全局变量污染后,你突然又想到了jQuery的dom元素调用函数的形式一般是Element.function(param),这种形式的API语义化很不错,直接能看出来API是作用在哪个元素上。恭喜你,一个新的改进方向又出现了,针对新的需求,你又再次改写了你的getSiblings:

function newNode(node) {
    return {
        element:node,
        getSiblings:function() {
            //获取该元素父节点的所有子节点
            var allChid = this.parentNode.children;
            var array = {length:0};
            //遍历所有子节点
            for(let i = 0;i < allChild.length;i++) {
             //返回该元素节点除外的所有子节点
                if(allChidl[i] !== this) {
                array[array.length] = allChild[i];
                array.length += 1;
                }
            }
            return array;
        }
    }
}
复制代码

此时你的调用形式是这样的:

var item3 = document.geElementById('item3');
var node = newNode(item3);
node.getSiblings();
复制代码

如果你足够懒,觉得每次调用newNode都要写上7个字母,不妨给它一个alias(别名)吧,你可以这样做:

window.$ = newNode;
var item3 = document.geElementById('item3');
var node = $(item3);
item3.getSiblings();
复制代码

看到的$符,是不是有一点似曾相识的感觉呢。没错,jQuery的$ 操作符也经常用来获取一个jQuery对象。现在你的极简“jQuery库”,终于有了点jQuery的感觉,虽然它只有一个功能getSiblings

模仿jQuery更像一点点

思维再发散一点,你一定想到了jQeury的$操作符,不仅能接受Node元素作为参数,也可以接收字符串参数以及css选择器参数。所以,聪明的你又多了几个坑要填。

考虑到参数的多种可能类型,以及API返回结果的一致性要求,以及模仿jQuery的text()方法,你又做了如下改动:

function newNode(node) {
    let nodes = {length:0};
    if(typeof node ==='string' ) {
        let tmp = document.querySelectorAll(node);
        for(let i =0;i < tmp.length;i++) {
            nodes[nodes.length] = tmp[i]
            nodes[nodes.length].getSiblings = function() {
                //获取该元素父节点的所有子节点
                var allChid = this.parentNode.children;
                var array = {length:0};
            //遍历所有子节点
                for(let i = 0;i < allChild.length;i++) {
             //返回该元素节点除外的所有子节点
                   if(allChidl[i] !== this) {
                      array[array.length] = allChild[i];
                      array.length += 1;
                   }
                }
                return array;
            }
            nodes.length += 1;
        }
    } else if(node instanceof Node) {
        nodes = {
            0:node,
            length:1,
            getSiblings:function() {
              //获取该元素父节点的所有子节点
              var allChid = this.parentNode.children;
              var array = {length:0};
              //遍历所有子节点
              for(let i = 0;i < allChild.length;i++) {
                  //返回该元素节点除外的所有子节点
                  if(allChidl[i] !== this) {
                  array[array.length] = allChild[i];
                  array.length += 1;
                  }
              }
              return array;
            }
        }
    }
    nodes.text = function(text) {
        let texts = [];
        if(text === undefined) {
            for(let i = 0;i < nodes.length;i++) {
                texts.push(nodes[i].textContent);
            }
        } else {
            for(let i = 0;i < nodes.length;i++) {
                nodes[i].textContent = text;
            }
        }
    }
    return nodes;
}
复制代码

这样你的$操作符,也可以做到像jQuery一样,接受多种类型的参数了,然后返回一个经过处理的“jQuery对象”--每个node元素上都新增了一个API--getSiblings。此外返回的“jQuery”对象还增加了一个text()方法,你不传参数,那么该方法将获取到nodes数组里所有元素的文本;如果传了参数,那么该方法将把nodes数组里所有元素的文本改为你传的参数。

通过以上步骤,你已经实现了一个迷你版的“jQuery”,虽然它的功能少的可怜,但是你应该明白了,jQuery在某种程度上可以类比为一个函数,它会根据你调用的API以及传入的参数,生成合适的jQuery对象,jQuery对象上则有满足你各种需求的新式API,而新式API一般是由原生JS的API组合使用得到的。

如果这篇文章对你有帮助,可以点个喜欢,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值