资讯专栏INFORMATION COLUMN

React.js 下的 $.data() "踩坑"实录

yy13818512006 / 3626人阅读

摘要:愚安我这时候在睡午觉,迷糊中被他叫醒。很显然,二者是不同类型的。后记这篇写在愚安我离职的第二天,在星巴克坐了一下午,无聊写的,延续了我以往写东西狂贴代码凑字数的原则。

引子

最近在做得一个项目,我是基于reactjs来写的。项目不大不小,就带了个童鞋一起写,为了不让react写起来那么吃力,我还是引入了jquery (1.11.1)。就这样整个项目开展的还算顺利,期间踩到了一些坑,但都是react的,直到...

一切都源于这样的一个写法

_edit:(e)->
    $ele = $(e.currentTarget).parents("td")
    _name = $ele.data("name")
    _filterArr = @props.items.filter (item)->
        item.activityName is _name
    if _filterArr.length
        @props.onEditCallBack(_filterArr[0]) if @props.onEditCallBack
    e.preventDefault()
    return

很简单地一段coffee,获取绑在td上的data-name,然后在items里找到name为_name的item,执行callback

可发布到线上之后,出了问题,有得item就是无法编辑,线上代码又uglify过,不好调试,这位童鞋看了半天代码也没有发现什么问题。愚安我这时候在睡午觉,迷糊中被他叫醒。

点了下页面发现,页面上一个data-name="111"的item无法删除,看了下代码之后,拽拽的对他说:“不要乱用jquery的data,这里有缓存,大小写,类型转换三大坑,看源码去!”。然后将原来的data改为getAttribute之后,果然跑通了。为什么跑通,且看下文。事后,我也不知道当时为什么突然来了这句三大坑。既然说了,那总要跟别人讲下三个坑吧,不能打脸,不能不讲道理是吧。

先说data属性

贴一段MDN上关于data属性的介绍,链接

  

HTML5是具有扩展性的设计,它初衷是数据应与特定的元素相关联,但不需要任何定义。data-* 属性允许我们在标准内于HTML元素中存储额外的信息,而不许需要使用类似于 classList,标准外属性,DOM额外属性或是 setUserData之类的伎俩。

一股浓浓的谷歌翻译味儿,英语好的童鞋还是去看原文,或者帮忙去翻译下,就在愚安我写这篇博客的时候,顺便提交了下翻译,连我这种大学英语考试总共有几级都不知道的人都敢翻译,何况你呢。

  

在外部使用JavaScript去访问这些属性的值同样非常简单。你可以使用getAttribute()配合它们完整的HTML名称去读取它们,但标准定义了一个更简单的方法:DOMStringMap你可以使用dataset读取到数据。

文档里写到无论是通过getAttribute()还是dataset都可以轻松访问节点上得data-*属性的值,但二者是有区别的。

getAttribute()与dataset的区别

这里补充一点儿关于DOM的小知识,直接访问节点属性和通过getAttribute访问节点属性返回的结果不一定是一样的,但getAttribute和attributes["索引"]访问节点属性的结果一定是不同的(即使都访问都不存在的属性,前者返回null,后者返回undefined),举个例子

var div = document.getElementById("test");
div.name 
//undefined
div.id 
//"test"
div.getAttribute("name") 
//"div"
div.attributes["name"] 
//name="div"
Object.prototype.toString.call(div.attributes["name"])
//"[object Attr]"

事实上,对于DOM节点而言,id与attributes是同样等级的属性。DOM不熟的同学,可以去看看这方面的资料,这里我就不跑题了。

继续看区别。

Object.prototype.toString.call(div.dataset)
//"[object DOMStringMap]"
Object.prototype.toString.call(div.attributes)
//"[object NamedNodeMap]"

很显然,二者是不同类型的map。

div["data-a"] = 1
//1
div.getAttribute("data-a")
//null
div.attributes["data-a"]
//undefined
div.dataset["a"]
//undefined
//--------------------
div.setAttribute("data-foo", "bar")
//undefined
div.getAttribute("data-foo")
"bar"
div.attributes["data-foo"]
//data-foo="bar"
div.dataset["foo"]
//"bar"
//--------------------
div.dataset["foo2"] = "123"
//"123"
div.getAttribute("data-foo2")
//"123"
div.attributes["data-foo2"]
//data-foo2="123"
div["data-foo2"]
//undefined

通过以上三种方式,大家应该大致知道节点字段,节点属性,节点dataset之间的小关系与区别
再来贴一段文档

  

为了使用dataset对象去获取到数据属性,需要获取属性名中data-之后的部分(要注意的是破折号连接的名称需要转换为驼峰样式的名称)。

测试

div.setAttribute("data-foo-bar",123)
//undefined
div.dataset["fooBar"]
//"123",仍为字符型
div.dataset["bar-foo"] = 123
//Uncaught DOMException: Failed to set the "bar-foo" property on "DOMStringMap": "bar-foo" is not a valid property name.
div.dataset["barFoo"] = 123
//123
div.getAttribute("data-bar-foo")
//"123"
div.dataset["barFoo"]
//"123",仍为字符型

可见这里确实存在喜闻乐见的camelCase转换。

再说jquery.data的"坑"

开始翻jquery-1.11.1的源码中得data函数。
注:jquery2放弃了对一些对低版本浏览器的支持,“坑”不全,我们还是看1.X的。

jQuery.extend({
    cache: {},
    //当设置下面这三种元素的expando属性时会抛出异常
    //具体方法参见jquery的src/data/accepts下的jQuery.acceptData方法
    noData: {
        "applet ": true,
        "embed ": true,
        // ...但是 Flash对象 (拥有classid)可以处理expando
        "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
    },

    hasData: function( elem ) {
        elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
        return !!elem && !isEmptyDataObject( elem );
    },

    data: function( elem, name, data ) {
        return internalData( elem, name, data );
    },

    removeData: function( elem, name ) {
        return internalRemoveData( elem, name );
    },

    // For internal use only.
    _data: function( elem, name, data ) {
        return internalData( elem, name, data, true );
    },

    _removeData: function( elem, name ) {
        return internalRemoveData( elem, name, true );
    }
});

这个就是jquery.data的大致结构,比较清晰。接下来,我们来聊聊前面说的三大坑。

类型转换坑

首先回到最开始的事故代码里,熟悉coffee的童鞋都知道,is关键字,在编译到javascript时,会变成===号(强等于),而存储在item里的name时字符型,通过$("selector").data()函数获取文档节点的data-*属性上的值时,调用得是jquery.fn.data方法,这里就不贴完整代码了,贴下造成这个类型转换的部分dataAttr()

if ( data === undefined && elem.nodeType === 1 ) {

    var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();

    data = elem.getAttribute( name );

    if ( typeof data === "string" ) {
        try {
                //布尔型转换
            data = data === "true" ? true :
                data === "false" ? false :
                //null型转换
                data === "null" ? null :
                // 仅当将其转换成数字时,其字符值相对原字符值不变时,进行number型转换
                +data + "" === data ? +data :
                //json字符串到object的转换,rbrace = /^(?:{[wW]*}|[[wW]*])$/
                rbrace.test( data ) ? jQuery.parseJSON( data ) :
                data;
            } catch( e ) {}

            // Make sure we set the data so it isn"t changed later
            jQuery.data( elem, key, data );

        } else {
            data = undefined;
        }
    }

通过我注释的部分可以很容易看出,jquery在调用jquery.data()前,会对传入的data值进行类型转换,其中转换为number的部分就是造成引子中提到到bug的原因。当然,jQuery这里完全是为了方便大家使用,我这里说采坑,纯属强行甩锅给jquery。

当然,我们上面测试过原生的javascript通过dataset或者getAttribute都不会做这种类型转换。
举个栗子

$(div).data("foo-bar")
//123,number型
$(div).data("fooBar")
//123
div.dataset["fooBar"]
//"123",字符型
大小写转换坑

在上面代码中,我们注意到这么一段

//rmultiDash = /([A-Z])/g;
var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
    data = elem.getAttribute( name );

这里现将key中所有的大写字母前加“-”,然后统一转换为小写。
再举个栗子

var div = document.createElement("div"),
    key = "ID",
    id = 123;
div.setAttribute("data-"+key, id);
$(div).data(key);
//undefined

当然前面也已经讲过,即使使用dataset这种结果。把这个“坑”,算在jquery的头上实在是不讲道理。不过这里,也是给像我这样比较粗心的前端童鞋,提个醒,直接写在html里的data-*中记得要用小写,避免不必要的bug。

缓存坑

在jquery.data中核心的internalData函数里,进行了主要的cache读写操作。我们调用$(selector).data(key,value)的时候,进行的流程大致如下

key,value格式化处理

检查elem是否有elem[internalKey],若有则作为cache中对应得id,若无则做如下处理

id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
//deletedIds默认为[]记录被铲除的id的数组
//guid是默认为1的计数器
//这样可以保证被删除的元素的id能够被放到deletedIds再利用,而不是无线递增guid造成枯竭

拿到id之后,检查jQuery.cache[id]是否存在,若不存在则jQuery.cache[id] = {}

将key为传入key的camelCase形式,value为做相应处理的value的键值对放入jQuery.cache[id]中

返回jQuery.cache[id]

  

注:internalKey = jQuery.expando = "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /D/g, "" )

同理,调用$(selector).data(key)时,也是现做key处理,id处理,去jQuery.cache[id]这个Object中拿到对应key的value,或返回undefined。

由于jquery这种cache机制,导致如果一个DOM节点上存在internalKey,且其刚好对应一个可以命中的cacheID,则无法通过jQuery.data()方法拿到data-*对应的值,而是cache对应的值。

这种情形最容易在类似reactjs这种virtual-DOM在对一组元素做部分删除操作时出现。因为virtual-DOM是做增量更新,删除的virtual-DOM并不一定是将我们主观视觉上看到的那个DOM节点,而是将相邻DOM节点进行增量更新,此时虽然data-*属性仍是原来的值,但整个DOM却是那个本来已经被删除的元素,所以如果那个被删除的DOM元素曾经调用过data方法,保留了iternalKey的话,那么恭喜你,你碰到我说的缓存坑了。

当然上面这种情况,也很容易通过getAttribute("data-*")处理解决掉,不是上面大问题,无须担心。

后记

这篇blog写在愚安我离职的第二天,在星巴克坐了一下午,无聊写的,延续了我以往写东西狂贴代码凑字数的原则。可以作为jQuery.data()的一个小解读,也可以算是对我前段时间项目中遇到的一些小问题的记录。感谢大家阅读,如有错误,欢迎指出。

  

作者博客原文地址

参考资料:

https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/Using_data_att...

https://github.com/jquery/jquery/blob/1.11-stable/src/data.js

http://blog.rx836.tw/blog/jquery-data-cache/

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

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

相关文章

  • Skypack布局前端基建实现过程详解

      用vite作为项目打包工具,这是为什么?其中最主要的原因是 ——vite在开发环境基于ESM规范实现的Nobundle模式,节省了代码打包的时间。  当前打包的需求任然有,且ESM规范兼容性越来越好,进入生产环境大面积可用的状态也不是不可能。  当生产环境打包将不再是刚需时。  另一方面,从HTTP协议的角度看,在HTTP/1.1时代,多个模块被打包成一个文件能减少浏览器并发请求数,达到优化目...

    3403771864 评论0 收藏0
  • 活动实录|拒绝"删库到跑路",探究饿了么数据安全保障体系

    摘要:数人云告别人肉运维上海的实录第二弹来啦本次分享的嘉宾是饿了么团队负责人虢国飞。虢国飞饿了么团队负责人从事数据库领域年,主要关注于数据库管理自动化建设和等领域的研究。本次主题关于数据安全的保障。在这一层,饿了么做了一些数据方面相关的保护。 数人云告别人肉运维上海Meetup的实录第二弹来啦!本次分享的嘉宾是饿了么DBA团队负责人虢国飞。实录将从用户访问、数据库架构体系、数据备份、数据流转...

    xiaowugui666 评论0 收藏0
  • 活动实录|拒绝"删库到跑路",探究饿了么数据安全保障体系

    摘要:数人云告别人肉运维上海的实录第二弹来啦本次分享的嘉宾是饿了么团队负责人虢国飞。虢国飞饿了么团队负责人从事数据库领域年,主要关注于数据库管理自动化建设和等领域的研究。本次主题关于数据安全的保障。在这一层,饿了么做了一些数据方面相关的保护。 数人云告别人肉运维上海Meetup的实录第二弹来啦!本次分享的嘉宾是饿了么DBA团队负责人虢国飞。实录将从用户访问、数据库架构体系、数据备份、数据流转...

    qc1iu 评论0 收藏0
  • vue导入处理Excel表格功能步骤实例

      1. 前言  本篇文章就是为大家讲讲前端导入并处理excel表格的情况,顺便讲讲vue导入并处理excel数据;也总结下使用工具。  2.vue导入Excel表格  vue导入Excel表格主要有两种常用的方法,一个是借助ElementUI文件上传进行表格导入,另一个是自带的input做文件上传;以下对两个方法做详细介绍;  2.1 使用ElementUI中的upload组件  安装Eleme...

    3403771864 评论0 收藏0

发表评论

0条评论

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