8月份时,断断续续把整个网站的 JavaScript 重写了一遍,原因很简单,自这个博客创建以来,一直都是依赖于 jQuery 库,随着 jQuery 库的愈发臃肿,以及自身对于 JavaScript 的日渐熟悉,摆脱 jQuery 的想法也越来越强烈了,于是终于在上个月,达成目标了。这里必须补充一下,Kayo 之所以想摆脱 jQuery ,原因有二:
- 一是因为 jQuery 的体积已经比较臃肿了,加载需时,对于移动网络来说更是费时间和流量;
- 二是 jQuery 把所有方法和属性都封装在一个对象中纵然很方便,但是实际上也可能造成浪费。这里之所以会浪费,也是要分开两个方面考虑:一是 jQuery 在实现过程中考虑了诸多情况,包括跨平台、跨浏览器甚至是浏览器自身 bugs 的考虑与兼顾,但对于这个博客以及 Kayo 其他一些项目,并不需要考虑到以上种种,二是 jQuery 把大量属性和方法都封装在一个 jQuery 对象中,实际上用不到那么多方法和属性时也很浪费。因此,于我来说,脱离 jQuery ,把自己常用的方法用原生 JavaScript 重新实现,需要用到时再去添加相应的方法到项目中会是更加灵活的做法。
当然如果项目本身足够复杂,需要用到 jQuery 中大量的属性或方法,那么我认为直接使用 jQuery 是一个很不错的选择!
下面会陆续介绍一些心得,如何用 JavaScript 标准语法,取代 jQuery 的一些主要功能,希望有助于也想摆脱 jQuery 库的童鞋能快速找到一些 jQuery 方法的解决方案。既然是 jQuery 的替代方案,因此以下的方案也都是跨浏览器的解决方案,即考虑到 IE6+ 以及现代浏览器。由于内容比较多,具体方案会分开几篇文章叙述,本篇会首先介绍了一些最常用的 jQuery 方法的替代方案。另外,由于 Kayo 水平有限,如有更适合的方案,欢迎指正!
1. 选择器
jQuery 之所以能得到快速传播,出色的选择器是一个很重要的因素,的确,如果要想用原生的 JavaScript 来重写一个这样的选择器会是一件很麻烦的事,但如果你的项目并不需要兼容 IE6/7,那么你可以使用 HTML5 的 querySelectorAll 来代替 jQuery 选择器,该选择器支持 CSS3 的选择器语法。
例如:
// jQuery 语法 $('p.test') // DOM 语法 document.querySelectorAll('p.test'); // 也可以代替 jQuery 的 find 方法 // jQuery 语法 $('#test').find('span'); // DOM 语法 document.getElementById('test').querySelectorAll('span');
这里需要注意两点:
- 虽然 IE8 支持 querySelectorAll ,但 IE8 并不支持 nth-of-type() 选择器,具体可见:Compatibility table: CSS3 Selectors 。
- querySelectorAll 方法返回的是 NodeList 对象,NodeList 具有数组的某些属性,例如整数索引和 length 属性,但本质并不是数组,也不能使用数组特有的 pop、push 等方法。因此如有必要,需要强制转换为数组:
customList = Array.prototype.slice.call(nodeList);
另外,如果你的项目仍需要支持 IE6/7 ,可以考虑使用 Sizzle ,这是 jQuery 的选择器库,从 jQuery 1.2 版本开始已经独立出来。相对于引入近 100K 的 jQuery 库,只有 18K 的 Sizzle 还是比较容易接受的。
2. 浏览器兼容
关于浏览器兼容,网上也有很多解决方案,但 Kayo 的建议是把 jQuery 的浏览器兼容模块独立抽取出来,再根据实际需要调整。从 jQuery 1.9 开始,引入了根据浏览器特征判断浏览器型号和版本的机制,这个机制代码简洁,并且相对于传统的判断 uset-agent 的方法会更加准确和方便自行调整。例如本博客使用到的 JS 浏览器判断只有一种,就是判断浏览器是否为 IE8 或以下的版本,Kayo 自行调整后的代码如下:
support = (function(support){ var div = document.createElement('div'); div.innerHTML = ' <link/><table></table><a href="/a">a</a><input type="checkbox"/>'; // IE8及以下的版本会在 innerHTML 时把开头的空格去掉,所以第一个子元素的 nodeType 不是 3(文本) support.leadingWhitespace = (div.firstChild.nodeType === 3); return support; })({}); if( support.leadingWhitespace == true ){ // 判断浏览器版本为 IE8 或以下版本,进行相应的操作 }
关于这个部分,我会另外写文章详细介绍。
3. forEach
forEach 可以很方便的遍历数组和 NodeList ,jQuery 中的 jQuery 对象本身已经部署了这类遍历方法,而在原生 JavaScript 中则可以使用 forEach 方法,但是 IE 并不支持,因此我们可以手动把 forEach 方法部署到数组和 NodeList 中:
if ( !Array.prototype.forEach ){ Array.prototype.forEach = function(fn, scope) { for( var i = 0, len = this.length; i < len; ++i) { fn.call(scope, this[i], i, this); } } } // 部署完毕后 IE 也可以使用 forEach 了 document.getElementsByTagName('p').forEach(function(e){ e.className = 'inner'; });
4. DOM 操作
原生的 JavaScript 的 DOM 操作其实也很强大,例如:
(1)在元素内追加元素
// jQuery 语法 $(parent).append($(child)); // DOM 语法 parent.appendChild(child);
(2)在元素头部插入元素
// jQuery 语法 $(parent).prepend($(child)); // DOM 语法 parent.insertBefore(child, parent.childNodes[0]);
(3)在元素后面插入节点
// jQuery 语法 $(referenceNode).after($(e)); // DOM 语法 function after(referenceNode, e){ referenceNode.parentNode.insertBefore(e, referenceNode.nextSibling); } after(referenceNode, e);
(4)在元素前面插入节点
// jQuery 语法 $(referenceNode).before($(e)); // DOM 语法 function before(referenceNode, e){ referenceNode.parentNode.insertBefore(e, referenceNode.previousSibling); } before(referenceNode, e);
(5)删除节点
// jQuery 语法 $(parent).remove(child); // DOM 语法 function remove(e){ var _parentElement = e.parentNode; if(_parentElement){ _parentElement.removeChild(e); } } remove(child);
(6)清空节点
// jQuery 语法 $(parent).empty(); // DOM 语法 function empty(e){ var t = e.childNodes.length; for( var i = 0; i < t; i++ ){ e.removeChild(e.firstChild); } } empty(parent);
5. class 操作
class 操作也是很常用的操作,下面列举一些 jQuery class 操作的原生 JavaScript 替代方案。
首先,如果你的项目无需兼容 IE9 及其以下版本,那么你可以使用 HTML 5 的 classList 对象,它包含了丰富的 class 操作:
document.body.classList.add('test'); document.body.classList.remove('test'); document.body.classList.toggle('test'); document.body.classList.contains('test'); // 例如 document.querySelectorAll('.test')[0].classList.remove('test');
如果你的项目必须支持 IE9 或更低版本,你也可以使用如下的代码实现增加和移除 class 的功能:
(1)增加一个 class
// jQuery 语法 $(e).addClass('myclass1 myclass2'); // DOM 语法 function addClass(e, c){ var t = ' ' + e.className + ' ', cs = c.split(' '); for( var i = 0; i < cs.length; i++ ){ var current = cs[i]; if( t.indexOf(' ' + current + ' ') === -1 ){ t += ' ' + current; } t = t.replace(' ', ' '); // 去掉连续两个空格 } e.className = t.replace(/(^\s*)|(\s*$)/g, ''); // 去掉首尾空白 } addClass(e, 'myclass1 myclass2');
(2)移除一个 class
// jQuery 语法 $(e).removeClass('myclass1 myclass2'); // DOM 语法 function removeClass(e, c){ var cs = c.split(' '), t = ' ' + e.className + ' '; for( var i = 0; i < cs.length; i++ ){ var current = cs[i]; if( t.indexOf(' ' + current + ' ') >= 0 ){ t = t.replace(' ' + current + ' ', ' '); } } e.className = t.replace(/(^\s*)|(\s*$)/g, ''); // 去掉首尾空白 } removeClass(e, 'myclass1 myclass2');
6. 设置和获取 CSS
使用原生的 JavaScript 设置元素的 CSS 相当简单,例如:
e.style.display = 'block';
获取 CSS 则由于 IE 和现代浏览器中的情况并不一样,因此需要重新封装一个函数:
function css(e, styleName){ if (e.currentStyle) var result = e.currentStyle[styleName]; else if (window.getComputedStyle) var result = document.defaultView.getComputedStyle(e, null).getPropertyValue(styleName); return result; } css(e, 'display'); // 获取元素的 display 值
7. 属性的设置或获取
// jQuery 语法 $(e).attr('target', '_blank'); // 设置属性 $(e).removeAttr('target'); // 移除属性 // DOM 语法 e.setAttribute('target', '_blank'); // 设置属性 e.removeAttribute('target'); // 移除属性 // 但 IE6/7 并不支持 setAttribute 和 removeAttribute 操纵某些属性 // 如 class 属性实际需要操纵 className // style 属性则没有替代值,只能使用 e.style.cssText 来读写 // 即 e.setAttribute('style', 'margin: 0;') 是无效的,需要写成 e.style.cssText = 'margin: 0;' // 这类型属性在开发时需要注意
8. 事件处理
jQuery 中的事件监听,完全可以用 addEventListener/attachEvent 模拟,分别对应于现代浏览器和 IE ,可以把两个方法封装一下,但是为了方便,这里把其他事件相关处理,如移除事件监听、阻止默认事件等方法统一写在一个对象中,方便调用,具体代码如下:
// 事件处理对象 var EventUtil = { // 添加事件监听 add: function(element, type, callback){ if(element.addEventListener){ element.addEventListener(type, callback, false); } else if(element.attachEvent){ element.attachEvent('on' + type, callback); } else { element['on' + type] = callback; } }, // 移除事件监听 remove: function(element, type, callback){ if(element.removeEventListener){ element.removeEventListener(type, callback, false); } else if(element.detachEvent){ element.detachEvent('on' + type, callback); } else { element['on' + type] = null; } }, // 跨浏览器获取 event 对象 getEvent: function(event){ return event ? event : window.event; }, // 跨浏览器获取 target 属性 getTarget: function(event){ return event.target || event.srcElement; }, // 阻止事件的默认行为 preventDefault: function(event){ if(event.preventDefault){ event.preventDefault(); } else { event.returnValue = false; } }, // 阻止事件流或使用 cancelBubble stopPropagation: function(){ if(event.stopPropagation){ event.stopPropagation(); } else { event.cancelBubble = true; } } }; // 使用例子 var at = document.getElementbyId('atemp'); EventUtil.add(at, 'click', function(){ console.log('被点击了'); event = EventUtil.getEvent(event); // 跨浏览器获取 event 对象 EventUtil.preventDefault(event); // 阻止默认事件 });
9. document.ready
jQuery 中有 document.ready 事件是判断页面中的 DOM 是否加载完毕,实际上只要把页面中的 JavaScript 都放在页面底部即可,这样 JavaScript 运行时页面的 DOM 已经加载完毕,另外也可以避免 JavaScript 的加载阻塞其他资源的加载。
10. Ajax
便捷的 Ajax 方法也是不少人依赖 jQuery 的原因,但实际上原生 JavaScript 的 Ajax api 也很强大,并且基本的使用在各个浏览器中的差异不大,因此完全可以自行封装一个 Ajax 对象,下面是 Kayo 封装好的 Ajax 对象:
// Ajax 方法 // 惰性载入创建 xhr 对象 function createXHR(){ if ( 'XMLHttpRequest' in window ){ createXHR = function(){ return new XMLHttpRequest(); }; } else if( 'ActiveXObject' in window ){ createXHR = function(){ return new ActiveXObject("Msxml2.XMLHTTP"); }; } else { createXHR = function(){ throw new Error("Ajax is not supported by this browser"); }; } return createXHR(); } // Ajax 执行 function request(ajaxData){ var xhr = createXHR(); ajaxData.before && ajaxData.before(); // 通过事件来处理异步请求 xhr.onreadystatechange = function(){ if( xhr.readyState == 4 ){ if( xhr.status == 200 ){ if( ajaxData.dataType == 'json' ){ // 获取服务器返回的 json 对象 jsonStr = xhr.responseText; json1 = eval('(' + jsonStr + ')'), json2 = (new Function('return ' + jsonStr))(); ajaxData.callback(json2); // ajaxData.callback(JSON.parse(xhr.responseText)); // 原生方法,IE6/7 不支持 } else ajaxData.callback(xhr.responseText); } else { ajaxData.error && ajaxData.error(xhr.responseText); } } }; // 设置请求参数 xhr.open(ajaxData.type, ajaxData.url); if( ajaxData.noCache == true ) xhr.setRequestHeader('If-Modified-Since', '0'); if( ajaxData.data ){ xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); xhr.send( ajaxData.data ); } else { xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.send( null ); } return xhr; } function post(ajaxData){ ajaxData.type = 'POST'; var _result = request(ajaxData); return _result; } function get(ajaxData){ ajaxData.type = 'GET'; ajaxData.data = null; var _result = request(ajaxData); return _result; }
下面给出一个使用例子:
index.html
<!DOCTYPE HTML> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>原生 JavaScript 实现 Ajax</title> <link rel="stylesheet" type="text/css" media="all" href="./common/common.css" /> <style> #content {text-align: center; font-family: 'lucida Grande', 'Microsoft Yahei'} #content .btn_ctr {display: block; width: 120px; height: 30px; margin: 0 auto 20px; background: #53a7bb; color: #fff; font-weight: bold; font-size: 14px; line-height: 30px; text-decoration: none; border-radius: 4px; } #test {width: 280px; height: 130px; margin: 0 auto; padding: 15px; background: #fff; border-radius: 4px; text-align: left; } </style> </head> <body> <div id="header"> <div id="header-content"> <div id="header-inside"> <p class="go-to-article"><a href="https://kayosite.com/css3-animation.html" title="打开原文" target="_blank" >Back To Article</a></p> <p class="go-to-blog"><a href="https://kayosite.com" title="进入我的博客 Kayo's Melody" target="_blank" >My Blog</a></p> <p class="copyright">Demo By Kayo © Copyright 2011-2013</p> </div> <h1>CSS3 Animation</h1> </div> </div> <div id="content"> <a class="btn_ctr" href="javascript:;" onclick="get({url: './ajax.html', callback: function(out){document.getElementById('test').innerHTML = out;}})">Ajax 获取内容</a> <div id="test"></div> </div> <script> // Ajax 方法,这里不在重复列出 </script> </body> </html>
ajax.html
<!DOCTYPE HTML> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>ajax</title> </head> <body> <p>成功获取到这段文本</p> </body> </html>
具体的效果可以浏览完整 Demo 。
接下来会再写一篇文章,补充如动画,ajax 事件绑定等 jQuery 特性的替代方案。
本文由 Kayo Lee 发表,本文链接:https://kayosite.com/lets-free-of-jquery-part-one.html
评论列表
学习了。。
来学习。
那个,问一下,这代码高亮是什么插件
@Youth.霖 代码高亮是SyntaxHighlighter Evolved,强烈推荐的插件!
有时候就是太过于依赖,得逼迫自己去掉 jQuery 才好。近期有个 http://youmightnotneedjquery.com/ 文档,也很不错。
@于江水 用多了 jQuery,有时候基本的 js 反而忘了,这个页面不错,我研究一下,改进上面的逻辑!
判断浏览器比较烦,还是在自己博客放弃IE8以下吧
深陷jQ之中而无法自拔呀。
@一条厌世的鱼 jQ还是很好用的,但是原生的Js也不能忘啊!