Let’s free of jQuery!

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="http://kayosite.com/css3-animation.html" title="打开原文" target="_blank" >Back To Article</a></p>
    				<p class="go-to-blog"><a href="http://kayosite.com" title="进入我的博客 Kayo's Melody" target="_blank" >My Blog</a></p>
    				<p class="copyright">Demo By Kayo &copy; 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 发表,本文链接:http://kayosite.com/lets-free-of-jquery-part-one.html

评论列表

  • 评论者头像
    回复

    学习了。。

  • 评论者头像
    回复

    来学习。

    那个,问一下,这代码高亮是什么插件

    • 评论者头像
      回复

      @Youth.霖 代码高亮是SyntaxHighlighter Evolved,强烈推荐的插件!

  • 评论者头像
    回复

    有时候就是太过于依赖,得逼迫自己去掉 jQuery 才好。近期有个 http://youmightnotneedjquery.com/ 文档,也很不错。

    • 评论者头像
      回复

      @于江水 用多了 jQuery,有时候基本的 js 反而忘了,这个页面不错,我研究一下,改进上面的逻辑!

  • 评论者头像
    回复

    判断浏览器比较烦,还是在自己博客放弃IE8以下吧

  • 评论者头像
    回复

    深陷jQ之中而无法自拔呀。

  • 评论者头像
    回复

    博主 你好~ 你的原生js兼容是直接使用的if-else if-else直接条件判断的(会造成每次调用都要重复判断) 用延迟加载会好一些吧 = =

  • 评论者头像
    回复

    很有学习价值的东西,希望越多越好! :mrgreen:

回复

你正在以游客身份访问网站,请输入你的昵称和 E-mail
:wink: :roll: :oops: :mrgreen: :idea: :cry: :?: :-| :-o :-P :-? :) :( 8-O