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 个

  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 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 模式的更多相关文章

  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. Mybatis XML 映射配置文件 -- 熟悉配置

    来源:http://www.mybatis.org/mybatis-3/zh/configuration.html properties mybatis读取属性顺序. 如果属性在不只一个地方进行了配置 ...

  2. 一个基于mysql构建的队列表

    通常大家都会使用redis作为应用的任务队列表,redis的List结构,在一段进行任务的插入,在另一端进行任务的提取. 任务的插入 $redis->lPush("key:task:l ...

  3. 4.DB Initialization(数据库初始化)[EF Code-First系列]

    前面的例子中,我们已经看到了Code-First自动为我们创建数据库的例子. 这里我们将要学习的是,当初始化的时候,Code-First是怎么决定数据库的名字和服务的呢??? 下面的图,解释了这一切! ...

  4. WinForm 窗体属性 窗体美化

    WinForm是·Net开发平台中对Windows Form的一种称谓. Windows窗体的一些重要特点如下: 功能强大:Windows窗体可用于设计窗体和可视控件,以创建丰富的基于Windows的 ...

  5. 【C#进阶系列】25 线程基础

    线程的概念 线程的职责是对CPU进行虚拟化. CPU为每个进程都提供了该进程专用的线程(功能相当于cpu),应用程序如果进入死循环,那么所处的进程会"冻结",但其他进程不会冻结,它 ...

  6. python基础之面对对象

    Python3 面向对象 Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的.本章节我们将详细介绍Python的面向对象编程. 如果你以前没有接触 ...

  7. MATLAB连接ACCESS数据库

    1.创建Windows系统ODBC数据源<打开控制面板----管理工具----ODBC数据源(32位或64位)----添加----安装数据源的驱动程序Microsoft Access Drive ...

  8. 关于Lind.DDD.Api客户端的使用与知识分享

    回到目录 关于Lind.DDD.Api的使用与客户端的调用 作者:张占岭 花名:仓储大叔 框架:Lind.DDD,Lind.DDD.Api 目录 Api里注册全局校验特性 1 Api中设置全局的Cor ...

  9. mariadb 10.2.3支持延时复制

    在mysql 5.6开始就支持延时复制,这在一些需要维护大量非标准化系统或者运维技术水平较低的公司和开发人员众多的项目组这是一个非常有价值的特性,可以说误操作的概率跟一个城市车祸概率的水平差不多了,我 ...

  10. ASP对XML的增、删、改、查

    首先看一下xml文件 text.xml 'encoding使用gb2312中文,如果要用英文则用utf-8 <?xml version="1.0" encoding=&quo ...