jQuery 3.0 的 setter/getter 模式
jQuery 的 setter/getter 共用一个函数,通过是否传参来表明它是何种意义。简单说传参它是 setter,不传它是 getter。
一个函数具有多种意义在编程语言中并不罕见,比如函数重载:一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载的好处是减少了函数名的数量,避免了名字空间的污染,对于程序的可读性也大有裨益。
函数重载主要体现的两个方面,一是参数的类型、相同个数的参数类型不同可称为函数重载;二是参数的个数,个数不同也称为函数重载。注意,重载与函数的返回值并无关系。
由于 JS 弱类型的特征,想模拟函数重载就只能通过第二种方式:参数的个数来实现。因此函数内的 arguments 对象就显得非常重要。
以下是一个示例
- function doAdd() {
- var argsLength = arguments.length
- if (argsLength === 0) {
- return 0
- } else if (argsLength === 1) {
- return arguments[0] + 10
- } else if (argsLength === 2) {
- return arguments[0] + arguments[1]
- }
- }
- doAdd() // 0
- doAdd(5) // 15
- doAdd(5, 20) // 25
doAdd 通过判断函数的参数个数重载实现了三种意义,argsLength 为 0 时,直接返回 0; argsLength 为 1 时,该参数与 10 相加;argsLength 为 2 时两个参数相加。
利用函数重载特性可以实现 setter/getter
- function text() {
- var elem = this.elem
- var argsLength = arguments.length
- if (argsLength === 0) {
- return elem.innerText
- } else if (argsLength === 1) {
- elem.innerText = arguments[0]
- }
- }
以上简单的解释了函数重载及利用它实现 setter/getter。即"取值器"与"赋值器"合一。到底是取值还是赋值,由函数的参数决定。jQuery 的很多 API 设计大量使用了这种模式。
下图汇总了 jQuery 中采用这种模式的所有 API,共 14 个函数
所有这些函数内部都依赖另一个函数 access, 毫不夸张的说 access 是所有这些函数的核心,是实现 setter/getter 的核心。下面是这个函数的源码,它是一个私有的函数,外部是调用不到它的。
access 的源码如下
- // Multifunctional method to get and set values of a collection
- // The value/s can optionally be executed if it's a function
- var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
- var i = 0,
- len = elems.length,
- bulk = key == null;
- // Sets many values
- if ( jQuery.type( key ) === "object" ) {
- chainable = true;
- for ( i in key ) {
- access( elems, fn, i, key[ i ], true, emptyGet, raw );
- }
- // Sets one value
- } else if ( value !== undefined ) {
- chainable = true;
- if ( !jQuery.isFunction( value ) ) {
- raw = true;
- }
- if ( bulk ) {
- // Bulk operations run against the entire set
- if ( raw ) {
- fn.call( elems, value );
- fn = null;
- // ...except when executing function values
- } 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 ) )
- );
- }
- }
- }
- return chainable ?
- elems :
- // Gets
- bulk ?
- fn.call( elems ) :
- len ? fn( elems[ 0 ], key ) : emptyGet;
- };
该函数的注释提到:这是一个多功能的函数,用来获取和设置一个集合元素的属性和值。value 可以是一个可执行的函数。这个函数一共不到 60 行代码。从上往下读,第一个 if 是设置多个 value 值,是一个递归调用。刨去这个递归调用,设置单个值的代码也就不到 50 行了。写的非常简练、耐读。
为了理解 access 函数,我画了两个图
access 内部两个主要分支
access 内部的执行流程
access 定义的形参有 7 个
- elems 元素集合,实际调用时传的都是 this,这里的 this 是 jQuery 对象,我们知道 jQuery 对象本身是一个集合,具有 length 属性和索引。必传。
- fn 实现 setter/getter 的函数,就是说这个函数里需要有条件能判断哪部分是 setter,哪部分是 getter。必传。
- key 比如 attr 和 prop 方法要传,设置或获取哪个 key 的值。有的则不用传,但为了占位用以 null 替代,比如 text、html 方法。可选。
- value 仅当 setter 时要传,即 value 为 undefined 时是 getter,否则是 setter。可选。
- chainable 当为 true 时,进入 setter 模式,会返回 jQuery 对象。false 则进入 getter模式。调用时通过 arguments.length 或 arguments.length>1 传入。
- emptyGet 当 jQuery 对象为空时,返回的结果,默认不传为 undefined,data 方法调用时传的是 null。
- raw 当 value 为函数类型时 raw 为 false,否则为 true。
上面提到了 access 是 jQuery 所有 setter/getter 函数的核心,换句话说所有 14 个函数 setter/getter 函数内部都会调用 access。这也是为什么 access 有 7 个参数,里面分支众多。因为它要处理的各种条件就很多呢。但所有这些 setter/getter 有很多类同的代码,最后还是提取一个公共函数。
为了便于理解,我把 access 的调用分类以下,便于我们理解。
1. 调用 access 时,第三个参数 key 传值为 null,分别是 text/html 方法
- text: function( value ) {
- return access( this, function( value ) {
- return value === undefined ?
- jQuery.text( this ) :
- this.empty().each( function() {
- if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
- this.textContent = value;
- }
- } );
- }, null, value, arguments.length );
- },
- html: function( value ) {
- return access( this, function( value ) {
- var elem = this[ 0 ] || {},
- i = 0,
- l = this.length;
- if ( value === undefined && elem.nodeType === 1 ) {
- return elem.innerHTML;
- }
- // See if we can take a shortcut and just use innerHTML
- if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
- !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
- value = jQuery.htmlPrefilter( value );
- try {
- for ( ; i < l; i++ ) {
- elem = this[ i ] || {};
- // Remove element nodes and prevent memory leaks
- if ( elem.nodeType === 1 ) {
- jQuery.cleanData( getAll( elem, false ) );
- elem.innerHTML = value;
- }
- }
- elem = 0;
- // If using innerHTML throws an exception, use the fallback method
- } catch ( e ) {}
- }
- if ( elem ) {
- this.empty().append( value );
- }
- }, null, value, arguments.length );
- },
图示这两个方法在 access 内部执行处
为什么 key 传 null,因为 DOM API 已经提供了。text 方法使用 el.innerText 设置或获取;html 方法使用 innerHTML 设置或获取(这里简单说,实际还有一些异常处理)。
2. 与第一种情况相反,调用 access 时 key 值传了且不为 null。除了 text/html 外的其它 setter 都是如此
- attr: function( name, value ) {
- return access( this, jQuery.attr, name, value, arguments.length > 1 );
- },
- prop: function( name, value ) {
- return access( this, jQuery.prop, name, value, arguments.length > 1 );
- },
- // Create scrollLeft and scrollTop methods
- jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
- var top = "pageYOffset" === prop;
- jQuery.fn[ method ] = function( val ) {
- return access( this, function( elem, method, val ) {
- var win = getWindow( elem );
- if ( val === undefined ) {
- return win ? win[ prop ] : elem[ method ];
- }
- if ( win ) {
- win.scrollTo(
- !top ? val : win.pageXOffset,
- top ? val : win.pageYOffset
- );
- } else {
- elem[ method ] = val;
- }
- }, method, val, arguments.length );
- };
- } );
- css: function( name, value ) {
- return access( this, function( elem, name, value ) {
- var styles, len,
- map = {},
- i = 0;
- if ( jQuery.isArray( name ) ) {
- styles = getStyles( elem );
- len = name.length;
- for ( ; i < len; i++ ) {
- map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
- }
- return map;
- }
- return value !== undefined ?
- jQuery.style( elem, name, value ) :
- jQuery.css( elem, name );
- }, name, value, arguments.length > 1 );
- }
- // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
- jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
- jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
- function( defaultExtra, funcName ) {
- // Margin is only for outerHeight, outerWidth
- jQuery.fn[ funcName ] = function( margin, value ) {
- var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
- extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
- return access( this, function( elem, type, value ) {
- var doc;
- if ( jQuery.isWindow( elem ) ) {
- // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
- return funcName.indexOf( "outer" ) === 0 ?
- elem[ "inner" + name ] :
- elem.document.documentElement[ "client" + name ];
- }
- // Get document width or height
- if ( elem.nodeType === 9 ) {
- doc = elem.documentElement;
- // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
- // whichever is greatest
- return Math.max(
- elem.body[ "scroll" + name ], doc[ "scroll" + name ],
- elem.body[ "offset" + name ], doc[ "offset" + name ],
- doc[ "client" + name ]
- );
- }
- return value === undefined ?
- // Get width or height on the element, requesting but not forcing parseFloat
- jQuery.css( elem, type, extra ) :
- // Set width or height on the element
- jQuery.style( elem, type, value, extra );
- }, type, chainable ? margin : undefined, chainable );
- };
- } );
- } );
- data: function( key, value ) {
- var i, name, data,
- elem = this[ 0 ],
- attrs = elem && elem.attributes;
- // Gets all values
- if ( key === undefined ) {
- if ( this.length ) {
- data = dataUser.get( elem );
- if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
- i = attrs.length;
- while ( i-- ) {
- // Support: IE 11 only
- // The attrs elements can be null (#14894)
- if ( attrs[ i ] ) {
- name = attrs[ i ].name;
- if ( name.indexOf( "data-" ) === 0 ) {
- name = jQuery.camelCase( name.slice( 5 ) );
- dataAttr( elem, name, data[ name ] );
- }
- }
- }
- dataPriv.set( elem, "hasDataAttrs", true );
- }
- }
- return data;
- }
- // Sets multiple values
- if ( typeof key === "object" ) {
- return this.each( function() {
- dataUser.set( this, key );
- } );
- }
- return access( this, function( value ) {
- var data;
- // The calling jQuery object (element matches) is not empty
- // (and therefore has an element appears at this[ 0 ]) and the
- // `value` parameter was not undefined. An empty jQuery object
- // will result in `undefined` for elem = this[ 0 ] which will
- // throw an exception if an attempt to read a data cache is made.
- if ( elem && value === undefined ) {
- // Attempt to get data from the cache
- // The key will always be camelCased in Data
- data = dataUser.get( elem, key );
- if ( data !== undefined ) {
- return data;
- }
- // Attempt to "discover" the data in
- // HTML5 custom data-* attrs
- data = dataAttr( elem, key );
- if ( data !== undefined ) {
- return data;
- }
- // We tried really hard, but the data doesn't exist.
- return;
- }
- // Set the data...
- this.each( function() {
- // We always store the camelCased key
- dataUser.set( this, key, value );
- } );
- }, null, value, arguments.length > 1, null, true );
- },
图示这些方法在 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 模式的更多相关文章
- jQuery 3.0 的新特性
1. jQuery 3.0 运行在严格模式下 当下几乎支持jQuery 3.0的浏览器都支持严格模式,该版本正是基于此进行编译发布的. 你的代码已经运行在非严格模式?不用担心,你无需重写.jQuery ...
- jQuery 3.0 的 Data 浅析
jQuery 3.0 在6月9日正式发布了,3.0 也被称为下一代的 jQuery .这个版本从14年10月开始,其中发布过一次beta 版(2016/1/14,)和候选版(2016/05/20).一 ...
- 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 ...
- jQuery 2.0.3 源码分析core - 选择器
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 打开jQuery源码,一眼看去到处都充斥着正则表达式,jQuery框架的基础就是查询了,查询文档元素对象 ...
- iPhone开发教程之retain/copy/assign/setter/getter
assign: 简单赋值,不更改索引计数 copy: 建立一个索引计数为1的对象,然后释放旧对象retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1 1. 接触过C,那么 ...
- jQuery 3.0的domManip浅析
domManip 这个函数的历史由来已久,从 jQuery 1.0 版本开始便存在了,一直到最新的 jQuery 版本.可谓是元老级工具函数. domManip 的主要功能是为了实现 DOM 的插入和 ...
- jQuery 3.0正式发布
jQuery 基金会刚刚发布了该 JavaScript 框架的 3.0 版本,并且首次抛弃了对老旧的 IE 浏览器的支持.jQuery 3.0 的工作始于 2014 年 10 月,其最初目标是在 2. ...
- jQuery 3.0 的变化
时隔 3 个月,jQuery 团队终于发布了 3.0 Alpha 版本.有两个版本 jQuery compat 3.0 和 jQuery 3.0. jQuery compat 3.0 对应之前的 1. ...
- jQuery 2.0发布,不再支持IE6/7/8
有时发现jQuery库引用的都对,javascript代码写的也没问题,可是jquery就是出现问题,额--我发现换个jquery库就没问题了,长时间不关注jquery的问题而已: 很多人都没有使用最 ...
随机推荐
- authentication与网站安全验证
1.Forms 身份验证提供程序 通过 Forms 身份验证,可以使用所创建的登录窗体验证用户的用户名和密码.未经过身份验证的请求被重定向到登录页,用户在该页上提供凭据和提交窗体.如果应用程序对请求进 ...
- c#自定义日志记录
废话不多说,直接上代码: 很简单:将类复制到项目中,最后在配置文件上配置一下:logUrl即可. 默认保存在:项目/temp/log /// <summary> /// 日志类 /// & ...
- LaTeX
毕业论文用LaTeX编辑,方便使用,专注于内容.无须分心于格式. 字符 - Char 希腊符号 加粗 \usepackage{amsmath} \boldsymbol{\sigma} \usepack ...
- jquery css事件编程 位置 操作
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- android studio 1.0 开发 ndk 调用 c++ so库
一个没用过java和安卓的人使用android studio开发带c++ so库的安卓程序用例(以ndk的hello-jni为例),对于不熟悉java和安卓的人来说这个很花时间,希望通过这篇文章帮助跟 ...
- 个人项目框架搭建 -- Autofac简单使用记录
1.添加autofac相关程序集/使用Nuget 2.引入命名空间 using Autofac; using Autofac.Configuration; 3.使用 3.1:直接使用 var buil ...
- [moka同学笔记]使用composer 安装yii2以及遇到的问题
[一.Yii2安装过程] 使用composer安装,composer安装请参考其他博客 1.下载 Yii2 高级模板 跟普通模板一样 , 可以通过 Composer 和 github 下载 ,不过官方 ...
- Tomcat问题,不能正确访问http://localhost:8080/
最近在学Struts2框架部分的内容,但是eclipse中配置tomcat遇到了很大的问题,当辛辛苦苦的配置完了之后,竟让连小猫的首页都不能访问,http://localhost:8080/输入了之后 ...
- java.lang.IllegalArgumentException: Illegal character in query at index 261
在BaseFragment中使用了LoadingPage,而LoadingPage的联网加载使用的是AsyncHttpClient.一直报java.lang.IllegalArgumentExcept ...
- 国内IT软件开发人员现状
首先在这里讨论的是国内的大陆地区.在今天这个中国IT环境下,开发人员出路何在?一个优秀开发人,应该有致力于编写优雅代码,让别人读得懂,具有可读性,可测试性的代码,不仅仅是可以运行的代 ...