资讯专栏INFORMATION COLUMN

jQuery 简单小结

Channe / 3350人阅读

摘要:根据以上信息,简单的总结一下选择符应该是唯一的,不需要添加额外的选择符。利用这一点,可以大大简化事件的绑定。然而,出于一致性考虑,可以简单的全部使用方法。事实上,这种处理完全不必要。我这干脆就简单起个小结吧。

前言

之前一段时间一直在进行旧项目的重构,发现了很多问题,系统用的库是jQuery, 主要是用着方便,其他同事,包括一些外包同事,对这个库也比较熟悉 。这里就对一些常见的问题,结合查到的资料,进行一些简单的总结。

为什么使用jQuery

这个问题,答案是显而易见的。还能因为啥,当然是方便呗。没错,它本身就是一个为dom而生的库。
jQuery的思想,实际上很简单,八个字可以概括: 选择元素,进行操作。

各类强大的选择器极大的简化了dom操作,我们最常使用的也是它的选择器。
下面就对经常使用的一些功能,给出一些建议。

使用最新版本

https://jsperf.com/jquery-1-4...
新版本会改进性能,还有很多新功能。

为了直观的看到版本对性能的影响,我们来看一些测试用例:

以三个常见的选择器为例:

$(".elem")
$(".elem", context)
context.find(".elem")

测试结果是相同时间内 选择器的执行次数:

可以很明显的看到,一般来说,新版本比旧版本有显著的性能提升。

用好选择器

首先列举几个常用的选择器:

$("#xxx") // ID选择器 
$(".xxx") // 类选择器
$(input)  // 标签选择器
$(":xxx") // 伪类选择器 比如 $(":hidden")
$("[attribute = value]") //属性选择器

使用上面几个选择器 来进行测试:

https://jsperf.com/dh-jquery-...
还是先看一下测试数据:

可以非常直观的看到,ID选择器的速度最快,远超其他选择器。

根据以上信息,简单的总结一下:

 1. ID选择符应该是唯一的,不需要添加额外的选择符。 

   不推荐的: $("div#id"), $("#a #id"), $("div#id span.class") 
   正确的姿势: $("#id") ,$("#id .class")
   
 2. 避免使用隐式通用选择符

   不推荐的:  $(".class :radio")
   正确的姿势: $(".class input:radio")
   
 3. 避免使用通用选择器`*` , 性能很差。
  
  不推荐的: $(".container > *") 
  正确的姿势: $(".container").children()

遵循以上规则, 选择器的使用上基本就没什么问题了,之后,我们还可以继续优化。

利用缓存

频繁操作dom浪费性能,所以我们可以把重复使用的的元素缓存起来。

    
   // 在变量前加$前缀,便于识别出jQuery对象 

   var $a =  $("#a"),
       $container =  $("#container"),
       $container_item = $container.find("li"), 
                       // 利用缓存,比 $("#container li") 好一些
       // ...
  
   //如果你想更方便的管理,也可以使用对象字面量的方式:
   
   var DOM = {
        $a : $("#a"),
        $container :  $("#container"),
        $container_item : $container.find("li"), 
        // ...
        
} 

后面使用的时候,就直接可以使用我们定义的DOM对象了。
DOM.$a.on("click",function(){
    //xxx
})

选择器也好了,下面我们看dom的操作。

链式操作

采用链式写法,jQuery自动缓存每一步的结果,因此比非链式写法要快。

$("div").find("span").eq(2).html("Hello World");
        
// 如果操作很多,会导致链很长,这时可以添加适当的换行来 增加可读性 :

$("div")
       .find("span")
       .eq(2)
       .html("Hello World");
       
同样适用于一些样式的操作:

$target.on("click",function(){
    $target.css({
        "border":"1px solid #ffffd",
        "color":"#fff",
         // ...
    })
    
})
       
非空判断

如果要进行非空判断 或者 未定义判断的时候 使用 === 而不要使用==.

如果你使用jsHint ,会有这样的提示:

     // Use "===" to compare with "null"
     // Use "===" to compare with ""
     // Use "===" to compare with "undefined"
     
    在分支判断的时候, 可以直接利用其本身的含义就可以了:
    
    if ( $something !== null) {
        // xxx
    }
    
    // 更好的方式:
     if( something) {
         // xxx
     }
     
     同样的,还有其他类似的判断,比如长度。
     
     if(list.length > 0 ){
         
         // xxx
     }
     
     就可以写成 
     
     if(list.length){
         // xxx
     }
事件的委托处理

javascript的事件模型,采用"冒泡"模式,也就是说,子元素的事件会逐级向上"冒泡",成为父元素的事件。利用这一点,可以大大简化事件的绑定。

比如,有一个表格(table元素),里面有100个格子(td元素),现在要求在每个格子上面绑定一个点击事件(click):

$("td").on("click", function(){
    $(this).toggleClass("active");
  });

这种做法是不推荐的,,因为td元素发生点击事件之后,这个事件会"冒泡"到父元素table上面,从而被监听到。所以,这个事件只需要在父元素绑定1次即可。

$("table").on("click", "td", function(){
    $(this).toggleClass("active");
  });

//更好的写法,则是把事件绑定在document对象上面。
$(document).on("click", "td", function(){
    $(this).toggleClass("active");
  });

如果要取消事件的绑定,就使用off()方法。

$(document).off("click", "td");
使用"on"
在新版jQuery中,更短的 on(“click”) 用来取代类似 click() 这样的函数。
在之前的版本中 on() 就是 bind()。
自从jQuery 1.7版本后,on() 附加事件处理程序的首选方法。
然而,出于一致性考虑,可以简单的全部使用 on()方法。
    
$("#id").click(function(){
    //xxx
});

替换为:

$("#id").on("click", function(){
    //xxx
})

使用短路求值

短路求值是一个从左到右求值的表达式,用 && 或 || 操作符。

    
if(!$value){
    $value = $("#id").val();
}

可以写成:

$value = $value || $("#id").val();    

//也可以用 || 来填充默认值 
var age = myAge || 18 ;

避免过度使用jQuery

每当你使用一次选择器(比如$("#id")),就会生成一个jQuery对象。jQuery对象是一个很庞大的对象,带有很多属性和方法,会占用不少资源。所以,尽量少生成jQuery对象。

以最简单的选择器为例:

document.getElementById("foo") 就要比 $("#foo") 快很多。

再来看一个例子:

$("a").on ("click" ,function(){
  alert($(this).attr("id"));
})

//点击a元素后,弹出该元素的id属性。
//为了获取这个属性,必须连续两次调用jQuery,第一次是$(this),第二次是attr("id")。
 
//事实上,这种处理完全不必要。更正确的写法是,直接采用javascript原生方法,调用this.id:

$("a").on ("click" ,function(){
 alert(this.id);
})

上面简单说了一些简单的优化写法,我们再看看如何更好的组织你的逻辑

逻辑组织

如果遇到抄起键盘就开干的小伙伴,没有组织逻辑,那写的代码可能是这样的:


var a = xxx;
var b = xxx;

$("#id").on("click", function(){
    // xxx
})

$(".class").on("click", function(){
    // xxx
})

function xxx() {
    // xxx
}


// 直到最后

如果整个页面页面的逻辑比较复杂,那到最后的代码,估计只有你自己清楚是怎么走的,别人要去看,要不断的查找你的写的各种方法,简直痛苦。

这里就说一种 或许是更好的方式。

下面就给个实际的例子吧:

/**
 * Created by jf on 2015/9/11.
 * Modified by bear on 2016/9/7.
 */
$(function () {
    var pageManager = {
        $container: $("#container"),
        _pageStack: [],
        _configs: [],
        _pageAppend: function(){},
        _defaultPage: null,
        _pageIndex: 1,
        setDefault: function (defaultPage) {
            this._defaultPage = this._find("name", defaultPage);
            return this;
        },
        setPageAppend: function (pageAppend) {
            this._pageAppend = pageAppend;
            return this;
        },
        init: function () {
            var self = this;

            $(window).on("hashchange", function () {
                var state = history.state || {};
                var url = location.hash.indexOf("#") === 0 ? location.hash : "#";
                var page = self._find("url", url) || self._defaultPage;
                if (state._pageIndex <= self._pageIndex || self._findInStack(url)) {
                    self._back(page);
                } else {
                    self._go(page);
                }
            });

            if (history.state && history.state._pageIndex) {
                this._pageIndex = history.state._pageIndex;
            }

            this._pageIndex--;

            var url = location.hash.indexOf("#") === 0 ? location.hash : "#";
            var page = self._find("url", url) || self._defaultPage;
            this._go(page);
            return this;
        },
        push: function (config) {
            this._configs.push(config);
            return this;
        },
        go: function (to) {
            var config = this._find("name", to);
            if (!config) {
                return;
            }
            location.hash = config.url;
        },
        _go: function (config) {
            this._pageIndex ++;

            history.replaceState && history.replaceState({_pageIndex: this._pageIndex}, "", location.href);

            var html = $(config.template).html();
            var $html = $(html).addClass("slideIn").addClass(config.name);
            $html.on("animationend webkitAnimationEnd", function(){
                $html.removeClass("slideIn").addClass("js_show");
            });
            this.$container.append($html);
            this._pageAppend.call(this, $html);
            this._pageStack.push({
                config: config,
                dom: $html
            });

            if (!config.isBind) {
                this._bind(config);
            }

            return this;
        },
        back: function () {
            history.back();
        },
        _back: function (config) {
            this._pageIndex --;

            var stack = this._pageStack.pop();
            if (!stack) {
                return;
            }

            var url = location.hash.indexOf("#") === 0 ? location.hash : "#";
            var found = this._findInStack(url);
            if (!found) {
                var html = $(config.template).html();
                var $html = $(html).addClass("js_show").addClass(config.name);
                $html.insertBefore(stack.dom);

                if (!config.isBind) {
                    this._bind(config);
                }

                this._pageStack.push({
                    config: config,
                    dom: $html
                });
            }

            stack.dom.addClass("slideOut").on("animationend webkitAnimationEnd", function () {
                stack.dom.remove();
            });

            return this;
        },
        _findInStack: function (url) {
            var found = null;
            for(var i = 0, len = this._pageStack.length; i < len; i++){
                var stack = this._pageStack[i];
                if (stack.config.url === url) {
                    found = stack;
                    break;
                }
            }
            return found;
        },
        _find: function (key, value) {
            var page = null;
            for (var i = 0, len = this._configs.length; i < len; i++) {
                if (this._configs[i][key] === value) {
                    page = this._configs[i];
                    break;
                }
            }
            return page;
        },
        _bind: function (page) {
            var events = page.events || {};
            for (var t in events) {
                for (var type in events[t]) {
                    this.$container.on(type, t, events[t][type]);
                }
            }
            page.isBind = true;
        }
    };

    function fastClick(){
        var supportTouch = function(){
            try {
                document.createEvent("TouchEvent");
                return true;
            } catch (e) {
                return false;
            }
        }();
        var _old$On = $.fn.on;

        $.fn.on = function(){
            if(/click/.test(arguments[0]) && typeof arguments[1] == "function" && supportTouch){ // 只扩展支持touch的当前元素的click事件
                var touchStartY, callback = arguments[1];
                _old$On.apply(this, ["touchstart", function(e){
                    touchStartY = e.changedTouches[0].clientY;
                }]);
                _old$On.apply(this, ["touchend", function(e){
                    if (Math.abs(e.changedTouches[0].clientY - touchStartY) > 10) return;

                    e.preventDefault();
                    callback.apply(this, [e]);
                }]);
            }else{
                _old$On.apply(this, arguments);
            }
            return this;
        };
    }

    function preload(){
        $(window).on("load", function(){
            var imgList = [
                "./images/layers/content.png",
                "./images/layers/navigation.png",
                "./images/layers/popout.png",
                "./images/layers/transparent.gif"
            ];
            for (var i = 0, len = imgList.length; i < len; ++i) {
                new Image().src = imgList[i];
            }
        });
    }

    function androidInputBugFix(){
        // .container 设置了 overflow 属性, 导致 Android 手机下输入框获取焦点时, 输入法挡住输入框的 bug
        // 相关 issue: https://github.com/weui/weui/issues/15
        // 解决方法:
        // 0. .container 去掉 overflow 属性, 但此 demo 下会引发别的问题
        // 1. 参考 http://stackoverflow.com/questions/23757345/android-does-not-correctly-scroll-on-input-focus-if-not-body-element
        //    Android 手机下, input 或 textarea 元素聚焦时, 主动滚一把
        if (/Android/gi.test(navigator.userAgent)) {
            window.addEventListener("resize", function () {
                if (document.activeElement.tagName == "INPUT" || document.activeElement.tagName == "TEXTAREA") {
                    window.setTimeout(function () {
                        document.activeElement.scrollIntoViewIfNeeded();
                    }, 0);
                }
            })
        }
    }



    function init(){
        preload();
        fastClick();
        androidInputBugFix();
    
        window.pageManager = pageManager;
        window.home = function(){
            location.hash = "";
        };
    }
    init();
});

如果想使用另一种更直观的方式,可以接着往下看。

另一种组织方式
var Module = function() {
  this.init();
};

// 初始化
Module.prototype.init = function() {
  this.fetchData(function() {
    // do something
  });
};

// 绑定事件
Module.prototype.bindEvent = function() {
  // ...
};

// 获取数据
Module.prototype.fetchData = function(cb) {
  var self = this;
  ajax({}).then(function(data) {
    self.renderData(data);
  }).catch(function() {
    self._fetchDataFailed();
  }).fin(function() {
    cb && cb();
  });
};

// 渲染数据
Module.prototype.renderData = function(data) {
  data = this._resolveData(data);
  // ...
  this.bindEvent();
};

// 处理数据
Module.prototype._resolveData = function() {
  // ...
};

// 加载失败
Module.prototype._fetchDataFailed = function() {
  // ...
};

当然,你也可以将这两种形式混合起来用,具体怎么用,就看个人习惯了。

这里仅仅介绍一些方法,抛砖引玉。如果你觉得这种方式不好,也可以提出来,以供大家参考学习。

结语

啰哩啰嗦 终于到头了..
类似的这种博文,简单搜一下就有很多,各种 指南 ,最佳实践。我这干脆就简单起个小结吧。

说到底,这篇文字就当是对前几天做的事情的一个简单总结,结合其他资料, 写出来的一点东西。

回头看看 其实也没什么。

查资料,写出来 的过程其实也是一个自我学习的过程,这大概就是写博客的意义所在吧。
不多说了,还有很多需求要做,都排到四月去了 (容我做个悲伤的表情)。就到这里吧。

如果想了解更多类似的内容,可以查看这两篇:

https://segmentfault.com/p/12...

http://www.ruanyifeng.com/blo...

以上 :-)

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

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

相关文章

  • jQuery获取和设置checkbox的checked属性小结

    摘要:最近在项目中使用到设置,使用场景是页面上有三类单选框,一个是全选所有页数据一个是选择当前页,一个是选择一条,也算常用的场景。的属性在页面首次加载时就确定。最后,总结下获取和设置属性的方法。 最近在项目中使用到jQuery设置checkbox,使用场景是页面上有三类单选框,一个是全选所有页数据(id=cb1),一个是选择当前页(id=cb2),一个是选择一条(name=cb3),也算常用...

    scola666 评论0 收藏0
  • 面试小结--前端面试的几个雷点

    摘要:前言得益于金三银四,在最近一段时间,面试了一些人,但是符合的寥寥无几。看到我的面试题自己写的面试题,自己想的答案。听人说过一个面试套路面试官问的问题,可能面试官自己都不懂,目的只是为了压工资,挫士气。不过我是为了测试面试者是不是真的精通。 技术在不断的创新,随着框架,库,构建工具,打包工具,版本控制工具等操作越来越方便,使用越来越简单。面对这样的情况,除了兴奋,也要警惕。这些工具使得开...

    idealcn 评论0 收藏0
  • jQuery 使用小结

    摘要:一选择器部分选择不同的选择被选中的选择父类元素仅限于直接父类元素只要是父类元素即可,能向父级多级查找选择子类元素选择兄弟元素二插入和删除元素插入在被选元素的结尾插入内容请选择在被选元素的开头插入内容在被选元素之前插入内容在被选元素之后插入内 一、选择器部分 $(input[type=radio]) 选择不同 type 的 input $(input[type=radio]:ch...

    未东兴 评论0 收藏0
  • 自开发的EasyCanvas绘图库实践、Pixeler项目开发小结

    摘要:所以在此次开发中,尝试了小步快跑快速迭代的方法。开发,不仅是在开发运用上的提升,还是一个开源项目的完整实践。由于时间原因,在开发完基础版本后就去做别的项目。所以,好的文档是项目的开门钥匙。两个项目相辅相成。 showImg(https://segmentfault.com/img/bVba47g?w=900&h=150); 欢迎交换友链: laker.me--进击的程序媛Github:...

    lovXin 评论0 收藏0

发表评论

0条评论

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