jQuery实战教程源码系列(十)hooks 的原理
沉沙 2018-07-10 来源 : 阅读 1007 评论 0

摘要:本篇jQuery教程通过代码讲解了hooks 的原理,希望阅读本篇文章以后大家有所收获,帮助大家对jQuery的理解更加深入。

hooks 在英语中的意思表示钩子或挂钩,在 jQuery 中也有 hooks 这么一个概念,它的功能在考虑到一些兼容性和其它特殊情况的条件下,优先考虑这些特殊情况,而后才去用普通的方法处理,这种说法还是比较形象的。


hooks 的使用非常用技术含量,可以支撑在原来的基础上扩展,而对于接口则无需改变,举个例子,像fn.css() 这个函数我们都是非常熟悉的了,拿来就用,而不需要考虑浏览器的兼容性,这里的兼容性包括 border-radius 兼容,使用的时候不需要在前面加上 -webkit- 浏览器标识。而 css 函数的内部则是借助$.cssHooks()来实现这种“钩子”的效果的,扩展的时候,也是在这个对象上进行扩展。

先来说说 attr 和 prop

不急着上来就谈 hooks,先来看看 hooks 涉及到的应用。一个典型的应用就是 fn.attr 和 fn.prop,这两个原型函数的作用是用来给 jQuery 对象绑定元素的,如果不了解,可以参考这两个链接,attr,prop。

虽然它们都是添加属性,却是不同的方式,其中,attr 是把属性放到 html 中(实际上是 elem.attributes 属性),而 prop 是把属性添加到 dom 对象上,可以通过 [.] 来读取。

那么什么叫做 html 中?就是我们常说的 data- 数据:

var body = $('body');
body.attr('data-name','body');// <body data-name="body"></body>
body.data('name'); //'body'

   

attr 方法是对应于 jQuery 中的方法,而内部是通过 setAttribute,getAttribute 这种低级 api 来实现的,而且在 dom 对象的 attributes 属性上是可以找到绑定值的,所以 attr 和 prop 是两种不同的方法。

这两个函数有四个功能,分别包括读取和设置,如果参数只有一个,表示读(如果参数是 Object 另外考虑),参数为两个,表示写。

当然,除此之外,还有 removeAttr 和 removeProp 方法,源码如下:

jQuery.fn.extend({
  attr: function (name, value) {
    return access(this, jQuery.attr, name, value, arguments.length > 1);
  },
  removeAttr: function (name) {
    return this.each(function () {
      jQuery.removeAttr(this, name);
    });
  },
  prop: function (name, value) {
    return access(this, jQuery.prop, name, value, arguments.length > 1);
  },
  removeProp: function (name) {
    return this.each(function () {
      delete this[jQuery.propFix[name] || name];
    });
  }
})

   

access 方法

先看 attr 和 prop,都是通过 access 函数,至少传入的参数不同,一个是 jQuery.attr,一个是 jQuery.prop。来看看 access:

var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
  var i = 0,
    len = elems.length,
    bulk = key == null;
 
  // 参数为对象,一次性设置多个值
  if ( jQuery.type( key ) === "object" ) {
    chainable = true;
    for ( i in key ) {
      access( elems, fn, i, key[ i ], true, emptyGet, raw );
    }
 
  // 设置一个值
  } else if ( value !== undefined ) {
    chainable = true;
 
    if ( !jQuery.isFunction( value ) ) {
      raw = true;
    }
    
    // key 为 null 的情况
    if ( bulk ) {
 
      if ( raw ) {
        fn.call( elems, value );
        fn = null;
 
      // value 为 function
      } else {
        bulk = fn;
        fn = function( elem, key, value ) {
          return bulk.call( jQuery( elem ), value );
        };
      }
    }
 
    // 函数执行在这里
    if ( fn ) {
      for ( ; i < len; i++ ) {
        fn(
          elems[ i ], key, raw ?
          value :
          value.call( elems[ i ], i, fn( elems[ i ], key ) )
        );
      }
    }
  }
 
  if ( chainable ) {
    // 写情况的返回值
    return elems;
  }
 
  // Gets
  if ( bulk ) {
    return fn.call( elems );
  }
  // 这个返回值是比较熟悉的,即 get
  return len ? fn( elems[ 0 ], key ) : emptyGet;
}

   

access 不是今天的重点,函数不是很难,源码读起来挺有意思。

attr 和 prop 源码

来看看 jQuery.attr 和 jQuery.prop:

jQuery.attr = function (elem, name, value) {
  var ret, hooks, nType = elem.nodeType;
 
  // 对于 text, comment 和 attribute nodes 不处理
  if (nType === 3 || nType === 8 || nType === 2) {
    return;
  }
 
  // 如果连这个函数都不支持,还是用 prop 方法吧
  if (typeof elem.getAttribute === "undefined") {
    return jQuery.prop(elem, name, value);
  }
 
  // 先处理 hooks,优先考虑非正常情况
  if (nType !== 1 || !jQuery.isXMLDoc(elem)) {
    hooks = jQuery.attrHooks[name.toLowerCase()] || (jQuery.expr.match.bool.test(name) ? boolHook : undefined);
  }
  // value 为 underfined 的时候调用 remove
  if (value !== undefined) {
    if (value === null) {
      jQuery.removeAttr(elem, name);
      return;
    }
    // hooks.set
    if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {
      return ret;
    }
    // 非 hooks 情况,正常 set
    elem.setAttribute(name, value + "");
    return value;
  }
  // hooks.get
  if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) {
    return ret;
  }
  // 正常 get 方法
  ret = jQuery.find.attr(elem, name);
 
  // Non-existent attributes return null, we normalize to undefined
  return ret == null ? undefined : ret;
}

   

jQuery.prop = function (elem, name, value) {
  var ret, hooks, nType = elem.nodeType;
 
  // Don't get/set properties on text, comment and attribute nodes
  if (nType === 3 || nType === 8 || nType === 2) {
    return;
  }
 
  if (nType !== 1 || !jQuery.isXMLDoc(elem)) {
    // Fix name and attach hooks
    name = jQuery.propFix[name] || name;
    hooks = jQuery.propHooks[name];
  }
 
  if (value !== undefined) {
    if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {
      return ret;
    }
 
    return elem[name] = value;
  }
 
  if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) {
    return ret;
  }
 
  return elem[name];
}

   

可以看得出来,jQuery.attr 和 jQuery.prop 方法是真的非常像,但是如果你不懂 hooks,可能会有很不疑问,这个不急。可以总结出大致的处理流程:先判断 dom 类型,然后根据一些特殊情况,复制 hooks 参数,这里的特殊条件为 (nType !== 1 || !jQuery.isXMLDoc(elem)),接着对于 set 和 get 方法判断,通过 value 值是否为 underfined,如果 hooks 中有,用 hooks 中提供的方法,没有,就走正常流程。

初识 hooks

已经知道在哪里使用 hooks,那么 hooks 长什么样呢:

jQuery.extend({
  attrHooks: {
    // attrHooks 兼容 type 的低版本浏览器的情况
    type: {
      set: function( elem, value ) {
        if ( !support.radioValue && value === "radio" &&
          jQuery.nodeName( elem, "input" ) ) {
          var val = elem.value;
          elem.setAttribute( "type", value );
          if ( val ) {
            elem.value = val;
          }
          return value;
        }
      }
    }
  },
  propHooks: {
    tabIndex: {
      get: function( elem ) {
        ...
      }
    }
  }
})// jQuery 内部扩展// 对于不支持 selected 的情况if(!support.optSelected){
  jQuery.propHooks.selected = {
    get: function(){
      ...
    },
    set: function(){
 
    }
  }
}

   

在 attr 的 attrHooks 中,用来处理的特殊情况是 name=type 的情况,或许是这种情况,type 绑定不到 html 中。在 prop 的 propHooks 中,处理的特殊情况是 tabIndex,下面还扩展了一个 selected 方法,如果浏览器不支持 select,就建一个 hooks。

所以一个基本的 Hooks(jQuery 内部的)应该长这样:

jQuery.extend({
  nameHooks: {
    get: function(){
      ...
    },
    set: function(){
      ...
    },
    other: function(){
      ...
    }
  }
})

   

get 和 set 是非必需的,这是因为 attr 和 prop 的特殊性造成的,在看一个例子 jQuery.fn.val,val 的介绍jQuery.val,val 也有一个 valHooks 与之对应:

jQuery.extend({
  valHooks: {
    option: {
      get: function(){...}
    },
    select: {
      get: function(){...},
      set: function(){...}
    }
  }
})

   

valHooks 和之前略有不同,又多了一层,但基本思路是一致的。

外部扩展 Hooks

jQuery 内部的 hooks 功能是非常强大的,不过令人感觉欣慰的是可以在外部扩展。

比如有一个问题,我们之前解释 attr 的时候,知道它可以添加 html 的 attribute,但有一些固有的,比如class,我们就是想在它上面添加,但又不能影响原有的 class 属性,可以这样来修改:

jQuery.attrHooks.class = {
  // 这里的参数顺序后面两个是相反的
  set: function(elem, value){
    return $(elem).attr('class-sp', value);
  },
  get: function(elem){
    return $(elem).attr('class-sp');
  }
}
//测试
body.attr('class','test');// <body class-sp="test"></body>
body.attr('class'); // 'test'

   

perfect!

总结

Hooks 讲这么多,应该就 ok 了。Hooks 算是 jQuery 中一个非常可以借鉴的用法,以前听到这个概念是非常恐惧的,当看了源码,弄懂原理之后,发现超级有意思。

仍然有不足,比如 jQuery 中一个非常有重量级的 cssHooks 就没有提到,还是脚踏实地吧。

 

本文由职坐标整理发布,欢迎关注职坐标WEB前端jQuery频道,获取更多jQuery知识!

本文由 @沉沙 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程