Zepto源码-设计原则与尺寸方法

所谓尺寸,就是指宽高,本文分析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源码-设计原则与尺寸方法

分享到:更多 ()

发表评论 0