作为前端工程师,一定对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组合使用得到的。
如果这篇文章对你有帮助,可以点个喜欢,谢谢!