jQuery 的 setter/getter 共用一个函数,通过是否传参来表明它是何种意义。简单说传参它是 setter,不传它是 getter。

一个函数具有多种意义在编程语言中并不罕见,比如函数重载:一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载的好处是减少了函数名的数量,避免了名字空间的污染,对于程序的可读性也大有裨益。

函数重载主要体现的两个方面,一是参数的类型、相同个数的参数类型不同可称为函数重载;二是参数的个数,个数不同也称为函数重载。注意,重载与函数的返回值并无关系。

由于 JS 弱类型的特征,想模拟函数重载就只能通过第二种方式:参数的个数来实现。因此函数内的 arguments 对象就显得非常重要。

以下是一个示例

  1. function doAdd() {
  2. var argsLength = arguments.length
  3. if (argsLength === 0) {
  4. return 0
  5. } else if (argsLength === 1) {
  6. return arguments[0] + 10
  7. } else if (argsLength === 2) {
  8. return arguments[0] + arguments[1]
  9. }
  10. }
  11.  
  12. doAdd() // 0
  13. doAdd(5) // 15
  14. doAdd(5, 20) // 25

doAdd 通过判断函数的参数个数重载实现了三种意义,argsLength 为 0 时,直接返回 0; argsLength 为 1 时,该参数与 10 相加;argsLength 为 2 时两个参数相加。

利用函数重载特性可以实现 setter/getter

  1. function text() {
  2. var elem = this.elem
  3. var argsLength = arguments.length
  4.  
  5. if (argsLength === 0) {
  6. return elem.innerText
  7. } else if (argsLength === 1) {
  8. elem.innerText = arguments[0]
  9. }
  10. }

以上简单的解释了函数重载及利用它实现 setter/getter。即"取值器"与"赋值器"合一。到底是取值还是赋值,由函数的参数决定。jQuery 的很多 API 设计大量使用了这种模式。

下图汇总了 jQuery 中采用这种模式的所有 API,共 14 个函数

所有这些函数内部都依赖另一个函数 access, 毫不夸张的说 access 是所有这些函数的核心,是实现 setter/getter 的核心。下面是这个函数的源码,它是一个私有的函数,外部是调用不到它的。

access 的源码如下

  1. // Multifunctional method to get and set values of a collection
  2. // The value/s can optionally be executed if it's a function
  3. var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
  4. var i = 0,
  5. len = elems.length,
  6. bulk = key == null;
  7.  
  8. // Sets many values
  9. if ( jQuery.type( key ) === "object" ) {
  10. chainable = true;
  11. for ( i in key ) {
  12. access( elems, fn, i, key[ i ], true, emptyGet, raw );
  13. }
  14.  
  15. // Sets one value
  16. } else if ( value !== undefined ) {
  17. chainable = true;
  18.  
  19. if ( !jQuery.isFunction( value ) ) {
  20. raw = true;
  21. }
  22.  
  23. if ( bulk ) {
  24. // Bulk operations run against the entire set
  25. if ( raw ) {
  26. fn.call( elems, value );
  27. fn = null;
  28. // ...except when executing function values
  29. } else {
  30. bulk = fn;
  31. fn = function( elem, key, value ) {
  32. return bulk.call( jQuery( elem ), value );
  33. };
  34. }
  35. }
  36.  
  37. if ( fn ) {
  38. for ( ; i < len; i++ ) {
  39. fn(
  40. elems[ i ], key, raw ?
  41. value :
  42. value.call( elems[ i ], i, fn( elems[ i ], key ) )
  43. );
  44. }
  45. }
  46. }
  47.  
  48. return chainable ?
  49. elems :
  50. // Gets
  51. bulk ?
  52. fn.call( elems ) :
  53. len ? fn( elems[ 0 ], key ) : emptyGet;
  54. };

  

该函数的注释提到:这是一个多功能的函数,用来获取和设置一个集合元素的属性和值。value 可以是一个可执行的函数。这个函数一共不到 60 行代码。从上往下读,第一个 if 是设置多个 value 值,是一个递归调用。刨去这个递归调用,设置单个值的代码也就不到 50 行了。写的非常简练、耐读。

为了理解 access 函数,我画了两个图

access 内部两个主要分支

access 内部的执行流程

access 定义的形参有 7 个

  1. elems 元素集合,实际调用时传的都是 this,这里的 this 是 jQuery 对象,我们知道 jQuery 对象本身是一个集合,具有 length 属性和索引。必传。
  2. fn 实现 setter/getter 的函数,就是说这个函数里需要有条件能判断哪部分是 setter,哪部分是 getter。必传。
  3. key 比如 attr 和 prop 方法要传,设置或获取哪个 key 的值。有的则不用传,但为了占位用以 null 替代,比如 text、html 方法。可选。
  4. value 仅当 setter 时要传,即 value 为 undefined 时是 getter,否则是 setter。可选。
  5. chainable 当为 true 时,进入 setter 模式,会返回 jQuery 对象。false 则进入 getter模式。调用时通过 arguments.length 或 arguments.length>1 传入。
  6. emptyGet 当 jQuery 对象为空时,返回的结果,默认不传为 undefined,data 方法调用时传的是 null。
  7. raw 当 value 为函数类型时 raw 为 false,否则为 true。

上面提到了 access 是 jQuery 所有 setter/getter 函数的核心,换句话说所有 14 个函数 setter/getter 函数内部都会调用 access。这也是为什么 access 有 7 个参数,里面分支众多。因为它要处理的各种条件就很多呢。但所有这些 setter/getter 有很多类同的代码,最后还是提取一个公共函数。

为了便于理解,我把 access 的调用分类以下,便于我们理解。

1. 调用 access 时,第三个参数 key 传值为 null,分别是 text/html 方法

  1. text: function( value ) {
  2. return access( this, function( value ) {
  3. return value === undefined ?
  4. jQuery.text( this ) :
  5. this.empty().each( function() {
  6. if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
  7. this.textContent = value;
  8. }
  9. } );
  10. }, null, value, arguments.length );
  11. },
  12.  
  13. html: function( value ) {
  14. return access( this, function( value ) {
  15. var elem = this[ 0 ] || {},
  16. i = 0,
  17. l = this.length;
  18.  
  19. if ( value === undefined && elem.nodeType === 1 ) {
  20. return elem.innerHTML;
  21. }
  22.  
  23. // See if we can take a shortcut and just use innerHTML
  24. if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
  25. !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
  26.  
  27. value = jQuery.htmlPrefilter( value );
  28.  
  29. try {
  30. for ( ; i < l; i++ ) {
  31. elem = this[ i ] || {};
  32.  
  33. // Remove element nodes and prevent memory leaks
  34. if ( elem.nodeType === 1 ) {
  35. jQuery.cleanData( getAll( elem, false ) );
  36. elem.innerHTML = value;
  37. }
  38. }
  39.  
  40. elem = 0;
  41.  
  42. // If using innerHTML throws an exception, use the fallback method
  43. } catch ( e ) {}
  44. }
  45.  
  46. if ( elem ) {
  47. this.empty().append( value );
  48. }
  49. }, null, value, arguments.length );
  50. },

图示这两个方法在 access 内部执行处

为什么 key 传 null,因为 DOM API 已经提供了。text 方法使用 el.innerText 设置或获取;html 方法使用 innerHTML 设置或获取(这里简单说,实际还有一些异常处理)。

2. 与第一种情况相反,调用 access 时 key 值传了且不为 null。除了 text/html 外的其它 setter 都是如此

  1. attr: function( name, value ) {
  2. return access( this, jQuery.attr, name, value, arguments.length > 1 );
  3. },
  4.  
  5. prop: function( name, value ) {
  6. return access( this, jQuery.prop, name, value, arguments.length > 1 );
  7. },
  8.  
  9. // Create scrollLeft and scrollTop methods
  10. jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
  11. var top = "pageYOffset" === prop;
  12.  
  13. jQuery.fn[ method ] = function( val ) {
  14. return access( this, function( elem, method, val ) {
  15. var win = getWindow( elem );
  16.  
  17. if ( val === undefined ) {
  18. return win ? win[ prop ] : elem[ method ];
  19. }
  20.  
  21. if ( win ) {
  22. win.scrollTo(
  23. !top ? val : win.pageXOffset,
  24. top ? val : win.pageYOffset
  25. );
  26.  
  27. } else {
  28. elem[ method ] = val;
  29. }
  30. }, method, val, arguments.length );
  31. };
  32. } );
  33.  
  34. css: function( name, value ) {
  35. return access( this, function( elem, name, value ) {
  36. var styles, len,
  37. map = {},
  38. i = 0;
  39.  
  40. if ( jQuery.isArray( name ) ) {
  41. styles = getStyles( elem );
  42. len = name.length;
  43.  
  44. for ( ; i < len; i++ ) {
  45. map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
  46. }
  47.  
  48. return map;
  49. }
  50.  
  51. return value !== undefined ?
  52. jQuery.style( elem, name, value ) :
  53. jQuery.css( elem, name );
  54. }, name, value, arguments.length > 1 );
  55. }
  56.  
  57. // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
  58. jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
  59. jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
  60. function( defaultExtra, funcName ) {
  61.  
  62. // Margin is only for outerHeight, outerWidth
  63. jQuery.fn[ funcName ] = function( margin, value ) {
  64. var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
  65. extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
  66.  
  67. return access( this, function( elem, type, value ) {
  68. var doc;
  69.  
  70. if ( jQuery.isWindow( elem ) ) {
  71.  
  72. // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
  73. return funcName.indexOf( "outer" ) === 0 ?
  74. elem[ "inner" + name ] :
  75. elem.document.documentElement[ "client" + name ];
  76. }
  77.  
  78. // Get document width or height
  79. if ( elem.nodeType === 9 ) {
  80. doc = elem.documentElement;
  81.  
  82. // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
  83. // whichever is greatest
  84. return Math.max(
  85. elem.body[ "scroll" + name ], doc[ "scroll" + name ],
  86. elem.body[ "offset" + name ], doc[ "offset" + name ],
  87. doc[ "client" + name ]
  88. );
  89. }
  90.  
  91. return value === undefined ?
  92.  
  93. // Get width or height on the element, requesting but not forcing parseFloat
  94. jQuery.css( elem, type, extra ) :
  95.  
  96. // Set width or height on the element
  97. jQuery.style( elem, type, value, extra );
  98. }, type, chainable ? margin : undefined, chainable );
  99. };
  100. } );
  101. } );
  102.  
  103. data: function( key, value ) {
  104. var i, name, data,
  105. elem = this[ 0 ],
  106. attrs = elem && elem.attributes;
  107.  
  108. // Gets all values
  109. if ( key === undefined ) {
  110. if ( this.length ) {
  111. data = dataUser.get( elem );
  112.  
  113. if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
  114. i = attrs.length;
  115. while ( i-- ) {
  116.  
  117. // Support: IE 11 only
  118. // The attrs elements can be null (#14894)
  119. if ( attrs[ i ] ) {
  120. name = attrs[ i ].name;
  121. if ( name.indexOf( "data-" ) === 0 ) {
  122. name = jQuery.camelCase( name.slice( 5 ) );
  123. dataAttr( elem, name, data[ name ] );
  124. }
  125. }
  126. }
  127. dataPriv.set( elem, "hasDataAttrs", true );
  128. }
  129. }
  130.  
  131. return data;
  132. }
  133.  
  134. // Sets multiple values
  135. if ( typeof key === "object" ) {
  136. return this.each( function() {
  137. dataUser.set( this, key );
  138. } );
  139. }
  140.  
  141. return access( this, function( value ) {
  142. var data;
  143.  
  144. // The calling jQuery object (element matches) is not empty
  145. // (and therefore has an element appears at this[ 0 ]) and the
  146. // `value` parameter was not undefined. An empty jQuery object
  147. // will result in `undefined` for elem = this[ 0 ] which will
  148. // throw an exception if an attempt to read a data cache is made.
  149. if ( elem && value === undefined ) {
  150.  
  151. // Attempt to get data from the cache
  152. // The key will always be camelCased in Data
  153. data = dataUser.get( elem, key );
  154. if ( data !== undefined ) {
  155. return data;
  156. }
  157.  
  158. // Attempt to "discover" the data in
  159. // HTML5 custom data-* attrs
  160. data = dataAttr( elem, key );
  161. if ( data !== undefined ) {
  162. return data;
  163. }
  164.  
  165. // We tried really hard, but the data doesn't exist.
  166. return;
  167. }
  168.  
  169. // Set the data...
  170. this.each( function() {
  171.  
  172. // We always store the camelCased key
  173. dataUser.set( this, key, value );
  174. } );
  175. }, null, value, arguments.length > 1, null, true );
  176. },

图示这些方法在 access 内部执行处

各个版本的实现差异

1.1 ~ 1.3 各个 setter/getter 独自实现,没有抽取一个公共函数。
1.4 ~ 1.9 抽取了独立的 jQuery.access 这个核心函数为所有的 setter/getter 服务。
1.10 ~ 2.24 同上一个版本区间,但在内部使用了一个私有的 access 函数,不使用公开的 jQuery.access,即弱化了 jQuery.access。
3.0 ~ 未来 去掉了 jQuery.access ,内部直接使用私有的 access 。

jQuery 3.0 的 setter/getter 模式的更多相关文章

  1. jQuery 3.0 的新特性

    1. jQuery 3.0 运行在严格模式下 当下几乎支持jQuery 3.0的浏览器都支持严格模式,该版本正是基于此进行编译发布的. 你的代码已经运行在非严格模式?不用担心,你无需重写.jQuery ...

  2. jQuery 3.0 的 Data 浅析

    jQuery 3.0 在6月9日正式发布了,3.0 也被称为下一代的 jQuery .这个版本从14年10月开始,其中发布过一次beta 版(2016/1/14,)和候选版(2016/05/20).一 ...

  3. jQuery 3.0 的 Data

    jQuery 3.0 的 Data Snandy If you cannot hear the sound of the genuine in you, you will all of your li ...

  4. jQuery 2.0.3 源码分析core - 选择器

         声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢!      打开jQuery源码,一眼看去到处都充斥着正则表达式,jQuery框架的基础就是查询了,查询文档元素对象 ...

  5. iPhone开发教程之retain/copy/assign/setter/getter

    assign: 简单赋值,不更改索引计数 copy: 建立一个索引计数为1的对象,然后释放旧对象retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1 1. 接触过C,那么 ...

  6. jQuery 3.0的domManip浅析

    domManip 这个函数的历史由来已久,从 jQuery 1.0 版本开始便存在了,一直到最新的 jQuery 版本.可谓是元老级工具函数. domManip 的主要功能是为了实现 DOM 的插入和 ...

  7. jQuery 3.0正式发布

    jQuery 基金会刚刚发布了该 JavaScript 框架的 3.0 版本,并且首次抛弃了对老旧的 IE 浏览器的支持.jQuery 3.0 的工作始于 2014 年 10 月,其最初目标是在 2. ...

  8. jQuery 3.0 的变化

    时隔 3 个月,jQuery 团队终于发布了 3.0 Alpha 版本.有两个版本 jQuery compat 3.0 和 jQuery 3.0. jQuery compat 3.0 对应之前的 1. ...

  9. jQuery 2.0发布,不再支持IE6/7/8

    有时发现jQuery库引用的都对,javascript代码写的也没问题,可是jquery就是出现问题,额--我发现换个jquery库就没问题了,长时间不关注jquery的问题而已: 很多人都没有使用最 ...

随机推荐

  1. authentication与网站安全验证

    1.Forms 身份验证提供程序 通过 Forms 身份验证,可以使用所创建的登录窗体验证用户的用户名和密码.未经过身份验证的请求被重定向到登录页,用户在该页上提供凭据和提交窗体.如果应用程序对请求进 ...

  2. c#自定义日志记录

    废话不多说,直接上代码: 很简单:将类复制到项目中,最后在配置文件上配置一下:logUrl即可. 默认保存在:项目/temp/log /// <summary> /// 日志类 /// & ...

  3. LaTeX

    毕业论文用LaTeX编辑,方便使用,专注于内容.无须分心于格式. 字符 - Char 希腊符号 加粗 \usepackage{amsmath} \boldsymbol{\sigma} \usepack ...

  4. jquery css事件编程 位置 操作

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. android studio 1.0 开发 ndk 调用 c++ so库

    一个没用过java和安卓的人使用android studio开发带c++ so库的安卓程序用例(以ndk的hello-jni为例),对于不熟悉java和安卓的人来说这个很花时间,希望通过这篇文章帮助跟 ...

  6. 个人项目框架搭建 -- Autofac简单使用记录

    1.添加autofac相关程序集/使用Nuget 2.引入命名空间 using Autofac; using Autofac.Configuration; 3.使用 3.1:直接使用 var buil ...

  7. [moka同学笔记]使用composer 安装yii2以及遇到的问题

    [一.Yii2安装过程] 使用composer安装,composer安装请参考其他博客 1.下载 Yii2 高级模板 跟普通模板一样 , 可以通过 Composer 和 github 下载 ,不过官方 ...

  8. Tomcat问题,不能正确访问http://localhost:8080/

    最近在学Struts2框架部分的内容,但是eclipse中配置tomcat遇到了很大的问题,当辛辛苦苦的配置完了之后,竟让连小猫的首页都不能访问,http://localhost:8080/输入了之后 ...

  9. java.lang.IllegalArgumentException: Illegal character in query at index 261

    在BaseFragment中使用了LoadingPage,而LoadingPage的联网加载使用的是AsyncHttpClient.一直报java.lang.IllegalArgumentExcept ...

  10. 国内IT软件开发人员现状

             首先在这里讨论的是国内的大陆地区.在今天这个中国IT环境下,开发人员出路何在?一个优秀开发人,应该有致力于编写优雅代码,让别人读得懂,具有可读性,可测试性的代码,不仅仅是可以运行的代 ...