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

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

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

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

以下是一个示例

function doAdd() {var argsLength = arguments.lengthif (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.elemvar argsLength = arguments.lengthif (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 valuesif ( 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 setif ( 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 :// Getsbulk ?fn.call( elems ) :len ? fn( elems[ 0 ], key ) : emptyGet;
};

  

该函数的注释提到:这是一个多功能的函数,用来获取和设置一个集合元素的属性和值。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 方法

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 innerHTMLif ( 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 leaksif ( 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, outerWidthjQuery.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 heightif ( elem.nodeType === 9 ) {doc = elem.documentElement;// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],// whichever is greatestreturn 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 parseFloatjQuery.css( elem, type, extra ) :// Set width or height on the elementjQuery.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 valuesif ( 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 valuesif ( 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 Datadata = dataUser.get( elem, key );if ( data !== undefined ) {return data;}// Attempt to "discover" the data in// HTML5 custom data-* attrsdata = 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 keydataUser.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 模式相关推荐

  1. JS中setter/getter理解

    JS中setter/getter理解 JS对象属性 get/set和getter/setter 数据属性 Object.defineProperty() 访问器属性 getter/setter创建及删 ...

  2. Objective-C 中自动生成 setter getter 方法

    为什么80%的码农都做不了架构师?>>>    对于 C++ 这种语言,类中的私有成员变量需要自己去实现 setter 和 getter 方法.这种重复的东西其实没必要手动去完成,可 ...

  3. setter/getter

    setter/getter 作用:在面向对象开发中,对象是属性和行为的结合体,不能再对象的外部直接访问属性.若需要访问对象的属性通过getter/setter方法来进行,就相当于在对象的外部屏蔽了对象 ...

  4. php oauth2.0 实例,详解laravel passport OAuth2.0的4种模式

    参考: 1... 熟悉的场景 某个网站,某用户未注册,注册时提示可微信账号登录(github, google都有类似 某网站是第三方(客户端), 认证服务器和资源服务器都在微信,资源是指微信的用户名, ...

  5. jQuery 3.0的domManip浅析

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

  6. 基于jQuery 2.0的源代码分析

    有段时间没有使用jQuery了,对他的认识还停留在1.2 - 1.4左右. 前几天看,哇,原来jQuery 2.0 beta都发布了-- 以后不敢说自己会jQuery了. 决定趁着年末不忙,干脆分析一 ...

  7. 属性访问器(Property Accessor)----Setter/Getter

    Setter/Getter:属性/成员变量的封装 本质上是实例方法,但是在类的外部作为属性来访问,它允许创建只读和只写属性. 使用: getter方法:必须有返回类型,且和要访问的私有属性类型一致.必 ...

  8. IntelliJ IDEA for Mac 封装字段(添加setter/getter方法)

    可以利用 IDEA 对类中的字段进行封装,所谓"字段封装",就是指通过调用方法的方式来访问字段,而不是直接通过"对象.字段名"的方式去访问. 例如,成员变量 h ...

  9. 菜鸟读jQuery 2.0.3 源码分析系列(1)

    原文链接在这里,作为一个菜鸟,我就一边读一边写 jQuery 2.0.3 源码分析系列 前面看着差不多了,看到下面一条(我是真菜鸟),推荐木有入门或者刚刚JS入门摸不着边的看看,大大们手下留情,想一起 ...

最新文章

  1. springboot怎么写上传头像接口?
  2. SICP 习题 (2.7) 解题总结 : 定义区间数据结构
  3. kd树的根节点_kd树总结
  4. 1089: [SCOI2003]严格n元树
  5. hibernate多对多映射拆成2个一对多映射(注解)
  6. mysql 对表插入多行_MySQL表中怎么一次插入两行或更多行
  7. 【Excel】使用VLOOKUP+IF实现多列条件匹配查询
  8. Redux从设计到源码
  9. 啥是Attention?
  10. snowflake改进_分布式SnowFlakeID(雪花ID)原理、改进优化
  11. 08-R包那么多,怎么才能快速找到自己需要的包呢?
  12. 使用springmvc时处理404的方法
  13. Java实现List数组的几种替代方案
  14. sqlyog备份数据和导入备份数据
  15. python3语法都相同吗_Python 3.3.0的语法和3.0以前的版本有什么不一样的吗?
  16. oracle中变量前加冒号_oracle变量的定义和使用【转】
  17. Atitit 编程语言的分类 v2 目录 1.1. 基于代数划分 第一代。。。第三代。。4gl。。5gl自然语言 1 1.2. 按照编程范式分类 . 命令式语言 .函数式语言...逻辑式语言
  18. Vue开发实例(02)之将Vue项目代码导入到IDEA并运行
  19. logback 配置 日志
  20. 逆流而上的黑胶唱片  数位趋势下的一支奇兵?

热门文章

  1. Openwrt中luci配置页面cbi小记
  2. C++primer习题--第1章
  3. Hdu 1753 大明A+B 高精度小数相加
  4. Windows消息循环理解及窗体创建步骤
  5. MFC 操作配置文件INI的方法
  6. 如何使用C#自带的GDI+双缓冲类BufferedGraphics实现双缓冲功能
  7. 统计并输出某给定字符在给定字符串中出现的次数_查找常用字符
  8. 查看修改Linux隐藏文件的四种简便方法
  9. 渗透测试小马(一句话)篇
  10. Android Scrollview嵌套RecyclerView导致滑动卡顿问题解决(屡试不爽)