所谓尺寸,就是指宽高,本文分析width和height方法。
由于源码太短了。如果直接贴一下,本文然后直接结束的话,那么感觉有点太那个了。
所以,本文决定稍微多扯点东西。可以看成是凑字数,当然了,也没稿费,哈哈。
#函数重载
从zepto一个其设计原则说起:函数重载。
众所周知,js是没有“形”的函数重载,只有“意”的模拟。
这种设计,通过参数的不同,来实现不同的功能。
#get one, set all
根据传不传参数,来实现不同的功能。这里又引出了zepto的另一个设计原则:get one, set all。
不传递参数表示获取,传递参数表示设置。
但我们知道$实例,是一个类数组对象,是有很多元素的。
如果设置的话,那么当然直接设置所有元素(使用each)。
但是读取的话,就有两种情况。
第一种,读取所有,然后拿到一个集合(使用map),比如zepto的offsetParent方法。
另外一种,就是只读第一个,即get one。zepto中的很多方法都是这样的,比如css以及本文分析的width。
#通用代码格式
get one, set all原则的代码结构一般都是这样的:
$.fn['myAPI'] = function(arg) { if (arg === undefined) { return getSomethingOf(this[0]); } else { return this.each(function(element) { setSomethingOf(element, arg); }); } };
因为each是返回this的,进而myAPI也可以链式调用。
#让人想不到的语义重载
回到函数重载这个原则。zepto中有个方法最有意思,它的重载功能目的比较有特点:index方法。
不传参数,是问:我是家里的老几?实现如下:
$.fn.index = function() { return this.parent().children().indexOf(this[0]); };
这个是我们看到index这个单词直接想到的意思。即相对同胞的索引。其中拿到“我”,也体现了get one原则。
而传递参数后,其意思变得有点让人想不到:你在我家排老几?实现如下:
$.fn.index = function(element) { return this.indexOf($(element)[0]); };
使用$(element)[0]而不是element的原因是支持element可以为选择器。其中“我家”是指当前实例,也就是一个集合的概念。
#可以支持函数为参数
zepto的很多方法,不仅可以传递普通参数,也可以传递函数。
其实这也算是一种函数重载,当然这也是类jquery库流行的原因之一。
zepto对参数可以是函数的情形,做了统一处理,提取了一个通用内部函数:
function funcArg(context, arg, idx, payload) { return isFunction(arg) ? arg.call(context, idx, payload) : arg; }
我们可以那数组举个例子。
比如我要在下面数组后每个元素后面都追加个’px’。可以以如下的方式实现:
[1, 2, 3, 4, 5].map(function(value) { return value + 'px'; });
但是追加的不一定’px’,有可能是’cm’。那么我就有必要建个函数:
function suffix(arr, sfx) { return arr.map(function(value) { return value + sfx; }); }
但是又想奇数加’px’,偶数加’cm’,怎么办?
也许你说直接用map就行了,但是如果人家要求你拓展suffix来做,一种方式就是:
function suffix(arr, sfx) { return arr.map(function(value, index, array) { return value + funcArg(array, sfx, index, value); }); }
完整测试案例如下:
function funcArg(context, arg, idx, payload) { return typeof arg == 'function' ? arg.call(context, idx, payload) : arg; } function suffix(arr, sfx) { return arr.map(function(value, index, array) { return value + funcArg(array, sfx, index, value); }); } console.log(suffix([1, 2, 3, 4, 5], 'cm')); console.log(suffix([1, 2, 3, 4, 5], 'px')); console.log(suffix([1, 2, 3, 4, 5], function(index, value) { return value % 2 == 1 ? 'px' : 'cm'; }));
#width
有了以上铺垫width方法就容易实现了:
$.fn.width = function(value) { if (value === undefined) { return this[0].getBoundingClientRect().width; } else { return this.each(function(idx) { var el = $(this); el.css('width', funcArg(this, value, idx, el.width())); }); } };
设置使用css方法来做,读取使用原生getBoundingClientRect方法。
但是读取的代码不健全,没有考虑到this[0]是window或document的情况。因此代码修改成:
$.fn.width = function(value) { var offset, el = this[0]; if (value === undefined) { if (isWindow(el)) { return el['innerWidth']; } else if (isDocument(el)) { return el.documenteElement['scrollWidth'] } else { offset = this.offset(); return offset['width']; } } else { return this.each(function(idx) { el = $(this); el.css('width', funcArg(this, value, idx, el.width())); }); } };
其中isWindow以及isDocument等辅助函数之前的文章分析过,这里省略。
offset方法的内部实现用的就是原生getBoundingClientRect方法。
#height
height与width类似,因此源代码中,用了统一的方式来实现:
['width', 'height'].forEach(function(dimension) { var dimensionProperty = dimension.replace(/./, function(m) { return m[0].toUpperCase(); }); $.fn[dimension] = function(value) { var offset, el = this[0]; if (value === undefined) { return isWindow(el) ? el['inner' + dimensionProperty] : isDocument(el) ? el.documenteElement['scroll' + dimensionProperty] : (offset = this.offset()) && offset[dimension]; } else return this.each(function(idx) { el = $(this); el.css(dimension, funcArg(this, value, idx, el[dimension]())); }); }; });
其中dimensionProperty是首字母大写的Width和Height,以便拼接成innerWidth和scrollWidth。
#后记
其实从系列的角度来说前面的铺垫可以写出单独的文章。
但是width和height的代码太少,觉得放在这里说也合适。
系列写完后,应该会重写一遍吧。
欢迎分享本文,转载请保留出处:前端ABC » Zepto源码-设计原则与尺寸方法