关于jquery对象的remove参数中出现伪位置类选择器,出现非预期结果的研究

2023-05-16

记得前几天有人在论坛发帖问了一个关于jquery删除节点的问题 原帖是这样的(原帖的地址是:原帖)

<ul>
<li>1</li>
<li title="a">2</li>
<li>3</li>
<li>4</li>
</ul>
$("ul li:eq(1)").remove( ); // 删除了第2个,正常
$("ul li").remove("ul li:eq(1)"); // 结果只剩下第4个li 为毛啊这步
$("ul li").remove("ul li[title='a']"); // 删除了第2个,正常
这是为何?

当时我也回复了,但是当时时间太紧,没有看懂源码,这两天在看jquery的sizzle选择器引擎。然后才想起这个问题可能是jquery的bug,也有可能是出于jquery不鼓励这样的写法。

其实jquery对象调用remove方法的基本流程是这样的:对jquery中的每个dom元素调用remove方法。代码如下

在这个remove方法中重点是对jquery.filter(selector,[this]).这句话是对由当前元素组成的数组由表达式进行过滤,返回数组中都是符合selector的元素。如果过滤后数组依然不为空


jQuery.each({  

// keepData is for internal use only--do not document  

remove: function( selector, keepData ) {  

if ( !selector || jQuery.filter( selector, [ this ] ).length ) {  

if ( !keepData && this.nodeType === 1 ) {  

jQuery.cleanData( this.getElementsByTagName("*") );  

jQuery.cleanData( [ this ] );  

}  

if ( this.parentNode ) {  

this.parentNode.removeChild( this );  

}  

}  

},   

,说明当前元素就是要删除的元素,则 调用this.parentNode.removeChild( this );删除此元素。逻辑上是很简单的。重点就是这个filter是怎么工作的。它的工作方式直接关系到该删除哪个元素。

抛开这些问题先不说,我们使用jquery经常使用$(“xxx”)这样的写法,这个方法就会返回我们想要的包装好地jquery元素,内部是怎么解析地呢?在jQuery1.4后关于这方面的问题已专门由一个开源项目Sizzle,Jquery在遇到上述问题是调用的Sizzle选择器引擎(Sizzle官网:sizzle官网了解更多)。Sizzle其实说白了就是一个函数函数原型为


var Sizzle = function(selector, context, results, seed)  

selector就是选择字符串,context是选择执行时的上下文,如果contex为null则默认为document,Result是结果集,如果不为空则将结果附加在其后,seed就是种子集,可以理解为所有的匹配结果都是从seed中取的(即最后得到的结果集肯定是seed的子集)。

Sizzle对有伪位置选择器()和无位置伪选择器是不同的处理顺序,如果selector匹配 /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/这个正则表达式说明选择器字符中若有伪位置选择器,则selector从左至右解析,如果不是则从右往左解析。

如果是从右往左解析,我们很容易想到:在开始解析的时候就会在seed中寻找最左边的字符。挺抽象的举个例子:

“ul li[title=’a’]”为选择字符串,第三个li DOM元素为seed ,从右往左解析就会从seed中过滤不符合条件li[title=’a’]的元素,然后再过滤父元素不是ul的元素。最后得到结果,当然结果大家都知道是为空。

然而如果从左往右解析式,不可能像上面那样做,它只有一步一步从左往右解析,这样得到的最后结果是没有考虑seed的情况还是上面的seed,选择字符串替换为”ul li:eq(1)”,则jquery最后返回的结果就是第二个li元素,完全没有考虑seed给出的范围。

现在我们看上面的那个帖子,remove对每个jquery对象中的每个元素,遍历调用remove方法,如果过滤参数中含有伪位置选择调用符则jQuery.filter(selector,[this]),而此函数此时不会考虑[this]给的范围而直接返回selector选择的结果。

遍历第一个元素时调用filter(“ul li:eq(1)”,[第一个li元素]),此时ul中是有第二个li元素的,故返回innerhtml为2的li DOM元素,此时返回结果集长度不为0,故删除第一个元素。

遍历第二个元素时调用filter(“ul li:eq(1)”,[第一个li元素])(为什么是第一个li元素呢,因为原先第一个li因为符合条件被删了),,此时ul中也是有第二个li元素,故返回innerhtml为3的li DOM元素 ,此时返回结果集长度不为0,故删除‘第一个’元素。

遍历第二个元素时调用filter(“ul li:eq(1)”,[第一个li元素])(原因同上),此时ul中也是有第二个li元素,故返回innerhtml为4的li DOM元素 ,此时返回结果集长度不为0,故删除‘第一个’元素。

遍历第四个元素时调用filter(“ul li:eq(1)”,[第一个li元素])(原因同上),此时ul下只有自己一个li元素故返回的结果集长度为零,不符合条件故不删除。

分析完了,应该明白了为什么会出现上述的奇怪现象。

那怎么让filter按照我们想的那样工作呢。其实就是修改Sizzle的源码。

其实你可能已经猜到,出现上述问题的主要原因是没有使用seed,我们可以在选择字符串从左至右解析完,对解析的结果进行遍历,看是不是seed中的元素如果不是就踢掉,这样就能返回我们以为正确的结果。


if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {  

            set = posProcess( parts[0] + parts[1], context );  

        } else {  

            set = Expr.relative[ parts[0] ] ?  

                [ context ] :  

                Sizzle( parts.shift(), context );  

   

            while ( parts.length ) {  

                selector = parts.shift();  

   

                if ( Expr.relative[ selector ] ) {  

                    selector += parts.shift();  

                }  

                  

                      

                      

                  

                set = posProcess( selector, set );  

            }  

              

        }  

这段代码就是用来处理有伪位置选择符的选择字符串的。set就是每步的结果。

我增加的代码加在这之后


修改代码如下  

   

if(parts.length==0&&seed){  

                    var p_result=new Array();  

                    var seeds=makeArray(seed);  

                    var i;  

                    for(i=0;i<seeds.length;i++)  

                    {   

                        var j;  

                        for(j=0;j<set.length;j++){  

                          if(seeds[i]==set[j])  

                           p_result.push(seeds[i]);  

                      }  

                    }  

                    set=p_result;  

                }  

   

这样得到的set就是我们想要的结果。

我在运行上面的例子是,结果只剩下1 这个li元素,你可能已经猜到了。分析如下

遍历第一个元素时调用filter(“ul li:eq(1)”,[第一个li元素]),此时虽然有第二个li元素,但是seed并不包含此元素。故只会但会空集合长度为0.不删除。

遍历第二个元素调用filter(“ul li:eq(1)”,[第二个li元素]),此时ul中是有第二个li元素的,并且seed中也包含此元素,故返回的结果集长度为一,符合条件删除此元素。

遍历第三个元素时调用filter(“ul li:eq(1)”,[第二个li元素])(因为原来的第二个元素符合条件被删了),此时ul中是有第二个li元素的,并且seed中也包含此元素,故返回的结果集长度为一,符合条件删除此元素。

遍历第四个元素时调用filter(“ul li:eq(1)”,[第二个li元素])(原因同上),此时ul中是有第二个li元素的,并且seed中也包含此元素,故返回的结果集长度为一,符合条件删除此元素。

故最后只剩下了1这个li元素。

综上所诉:

虽然我们修改了sizzle中的具体实现,但是得到的结果还是不符合我们预期的结果(我们预期的结果是删除第二个元素),所以思考一下这种情况也许不是jQuery的bug,或许是不提倡在remove中使用伪位置选择符。因为你在用的时候没有该清楚的它是怎么运行的。或许你说还要修改remove算法,那就没有必要了。在加了上面的代码后还是没有符合我们的预期,但是我们分析发现,这种处理是完全符合逻辑的。使我们对删除时的选择器写法产生了误解。

总之,不要在remove方法的选择符参数中含有伪位置选择符,它不是想看起来那么工作的!


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关于jquery对象的remove参数中出现伪位置类选择器,出现非预期结果的研究 的相关文章

随机推荐

  • Java 对象转化为Map

    方式一 利用FastJson 把对象转化为Map 64 MethodName getUserInfoDataByUserId 64 Description 根据useId查询用户信息 xff0c 封装成map key xff1a 属性名 x
  • Linux命令之mv命令

    mv命令是move的缩写 xff0c 可以用来移动文件或者将文件改名 xff08 move rename files xff09 命令格式 mv 选项 源文件或目录 目标文件或目录 1 当mv命令中的第二个参数类型 xff08 目标 xff
  • Linux查看当前时间

    一 查看和修改Linux的时区 1 查看当前时区 命令 xff1a date R 2 修改设置Linux服务器时区 方法 A 命令 xff1a tzselect 方法 B 仅限于RedHat Linux 和 CentOS 命令 xff1a
  • 如何查看redis版本号

    Windows下查看redis版本号 1 打开redis所在目录启动 redis server 服务器端 2 启动 redis cli 客户端 3 客户端输入 xff1a info 结果如下 xff1a linux下查看redis的版本号
  • 三个练手的软件测试实战项目(附全套视频跟源码)偷偷卷死他们

    项目一 xff1a 12306抢票项目 项目测试目的 学会Selenium定位web元素的方法 熟练浏览器调试工具使用 项目主体步骤 1 xff09 人工走一遍流程 xff0c 对自动化的流程心中有数 2 xff09 按步骤拆分 xff0c
  • 基于LSTM的多变量温度预测(python从入门到入坑)

    基于LSTM的多变量温度预测 xff08 python xff09 我保证他能够运行 xff0c 而且结果能够让你满意 xff08 如果你和我一样都是一个新手的话 xff09 这里写目录标题 基于LSTM的多变量温度预测 xff08 pyt
  • 安装RabbitMQ出现Plugin configuration unchanged.问题

    安装RabbitMQ出现Plugin configuration unchanged 问题 rabbitmq server和opt版本 出现问题 xff1a 在安装RabbitMQ并配置好环境之后运行命令rabbitmq plugins e
  • selenium之 如何控制网页内嵌div中滚动条的滚动

    http blog csdn net huilan same article details 52131277 很多人疑惑怎么用selenium控制网页div中滚动条的滚动 xff0c 其实这个问题很简单 xff0c 用JS很简单就可以实现
  • Mysql如何设置id自增

    xff08 1 xff09 设置自增 方案一 xff1a CREATE TABLE IF NOT EXISTS user id INT UNSIGNED AUTO INCREMENT name VARCHAR 100 NOT NULL se
  • Centos安装RabbitMQ超详细(必须收藏)

    最近搞了台liunx xff08 centos xff09 服务器 xff0c 想玩一下rabbitMQ 但是发现csdn没有一个详细或者便捷的安装流程 xff0c 特此写了这么一篇文章 xff01 记得三连 万分感谢 xff01 首先Wi
  • Mysql占用CPU过高排查过程及可能优化方案

    1 查看生产DB服务器top列表 xff0c 执行 top 命令 2 使用root用户登录mysql 执行 show full processlist 查看慢查询 xff0c 反复执行 xff0c 如果发现一直有select 查询语句存在
  • SQLServer2019安装(Windows)

    目录 一 SQLServer2019下载 1 官网下载地址 2 下载安装包 2 1 选择Developer版本 立即下载 2 2 选择下载位置 3 安装 3 1 打开 SQLServer2019 x64 CHS Dev iso
  • 香橙派4在armbian上应用自启动设置

    编辑了一个qt界面 需要设置开机启动 xff0c 费了九牛二虎之力 xff0c 终于搞定了 设置开机显示命令行 开机不显示图形界面 xff0c 直接显示命令行的操作有两种方法 xff1a 多用户方法 sudo systemctl set s
  • vscode 中git解决冲突,合并代码的两种方法

    一 报错信息 有文件冲突的话 xff0c 会报错 xff1a git merge pre span class token operator span master span class token comment 合并 pre maste
  • 用Python爬取淘宝4403条大裤衩数据进行分析,终于找到可以入手的那一条

    炎炎夏日 xff0c 长裤已难以满足广大男生的需求 xff0c 为了在搬砖和摆摊的过程中增添一丝舒适感 xff0c 他们开始寻找一种神奇的存在 大裤衩 J哥在种菜的这些日子里也日益感受到大裤衩的重要性 xff0c 于是 xff0c 默默打开
  • 用户标签有哪些类型,如何进行科学分类?

    标签的分类 xff0c 是理解用户标签 理解用户画像的基础 优秀的标签分类 xff0c 将使得标签画像系统易于用户的使用 本篇文章和大家简单介绍一下标签数据的分类 按照不同的分类方法 xff0c 标签的分类也自然不同 本文主要介绍几种最常规
  • Python字符串查找函数find

    Python字符串查找函数find 比较简单 xff0c 直接上代码 总结下规则 xff0c 结果数字说明 按字符串索引下标查询 xff0c 索引从0开始 xff0c 空格会占位并且有下标结果 1 xff0c 代表没有查找到英文区分大小写
  • 闲着看看jquery.ajax源码

    框架的作用就是简化我们做的事情 xff0c 却又不失灵活性 jquery是js框架中的中流砥柱 xff0c 灵活并且强大 jquery中对ajax的封装很完美 xff0c 且不说底层的ajax函数的强大 xff0c 但是其上层的get xf
  • java 转json 报错处理机制 案例 .

    JSON lib这个Java类包用于把bean map和XML转换成JSON并能够把JSON转回成bean和DynaBean http kingpingping iteye com blog 1157771 报错处理机制 commons b
  • 关于jquery对象的remove参数中出现伪位置类选择器,出现非预期结果的研究

    记得前几天有人在论坛发帖问了一个关于jquery删除节点的问题 原帖是这样的 xff08 原帖的地址是 xff1a 原帖 xff09 lt ul gt lt li gt 1 lt li gt lt li title 61 34 a 34 g