$.each 串联起来的知识点

849 阅读2分钟
原文链接: www.w3cfuns.com
好久没写文章了,今天写一篇。

本文研究一下$.each相关的总总。
本文的结构
  1. 1.简介
  2. 2.break的等效写法
  3. 3.continue的等效写法
  4. 4.源码分析
  5. 5.$.fn.each
  6. 6.likeArray的源码
  7. 7.判断类型方法的实现
  8. 8.总结

1.简介
对于each方法,应该没有同学不会的。
如果要遍历一个数组,我们可以如下:


使用each代替循环没有问题。
但对于break和continue的支持是又怎么样的呢?

2.break的等效写法
比如我要找到数组里第一个偶数,使用for循环如下:

使用each方法可以使用如下:



3.continue的等效写法:
比如要找到所有的偶数的可以使用如下for语句:

其对于的continue的等效写法是什么呢?


只需要return即可。不一定要返回true。

return表示结束当前函数,那么自然相当于在for循环里结束本次循环。
return什么都是表示continue的意思,但false除外,
return false表示break的意思,是结束整个循环的意思。

4.源码分析
为什么呢?
我们要看看each的源码(来自zepto):
$.each = function (elements, callback) {
	var i, key
	if (likeArray(elements)) {
		for (i = 0; i < elements.length; i++)
			if (callback.call(elements[i], i, elements[i]) === false) return elements
	} else {
		for (key in elements)
			if (callback.call(elements[key], key, elements[key]) === false) return elements
	}
	return elements
}

$.each的实现思路很简单,
首先判断elements是否是类数组;
如果是类数组,那么就按照下标来遍历;
如果是对象,就按照属性遍历。

其中核心代码是下面这句话:
callback.call(elements[key], key, elements[key]) === false
这里面用了“三等号”来做判断,如果callback的返回结果是false那么就结束循环。
如果结束循环,自然就是break的意思。

为啥是用“三等号”呢?
因为一个函数不写return的话,也相当于return undefined的,
而undefined == false是为真的。
而用了三等号,从而实现了break和continue的区分。不得不说这句代码很巧妙。

each方法的补充。
1.第一个参数可以是类数组可以是对象

My name is Laoyao

I love JavaScript



2.this的指向问题

callback.call(elements[i], i, elements[i])
可以看出callback中的this指向的是键值对中的“值”:

My name is Laoyao

I love JavaScript



3.对其api的吐槽
callback传递的参数的顺序是按“键”和“值”的顺序来的。
个人觉得是把比较重要的“值”放在了第二个参数位置上,不是太好的。

此时,不由得让人想起[].forEach方法。其callback参数顺序是value,index, array。
如果我要弹出数组中的元素,可以用:

而使用each的话,只能添一层匿名函数了。


当然这不是大问题,只是个人对这个顺序不太习惯罢了。

5.$.fn.each
我们对jq的了解知道,还有个$().each方法:

My name is Laoyao

I love JavaScript


其源码(zepto)大致如下:
var $ = function() {};
$.fn = $.prototype = {};
$.fn.each = function(callback) {
	[].every.call(this, function(el, index) {
		return callback.call(el, index, el) !== false;
	});
	return this;
};

里面使用的[].every表示:
看看每次callback是不是都不是false,如果是false的话也结束遍历。
如改成for语句,那么便跟之前$.each的类数组很类似。

6.likeArray的源码
至此$.each的方法也算是研究明白了。
送佛送到西,下来我们来看看zepto中怎么判断类数组的:
function likeArray(obj) {
	var length = !!obj && 'length' in obj && obj.length;
	var	type = $.type(obj);
	return 'function' != type 
			&& !isWindow(obj) 
			&& ('array' == type 
				|| length === 0 
				|| (typeof length == 'number' 
					&& length > 0 
					&& (length - 1) in obj));
}

zepto没有把此方法暴露出来。
用了一堆与或非,仔细看下来应该能看懂。

什么是类数组?(请参考《JavaScript权威指南》161页)
有length属性,并能按下标访问的对象。
数组本身是,arguments是,NodeList是,字符串也是。
但是window对象有length属性(表示当前窗口中frames的数量),
函数也有length属性(表示函数形参的个数),一般不认为他们是类数组。

7.判断类型方法的实现
因为likeArray中用了判断类型的方法,最后顺便也说一说其相关知识点
var class2type = {};
var toString = class2type.toString;
'Boolean Number String Function Array Date RegExp Object Error'.split(' ').forEach(function(name) {
	class2type["[object " + name + "]"] = name.toLowerCase();
})
function type(obj) {
	return obj == null ? String(obj) : class2type[toString.call(obj)] || "object"
}
console.log(class2type)
//$.type = type;

type函数核心还是使用的{}.toString方法。
源码中用字符串来存储数据的方法也值得学习。

有了type函数之后,其他的指定类型判断函数就简单了:
function isFunction(value) {
	return type(value) == "function"
}

function isWindow(obj) {
	return obj != null && obj == obj.window
}

function isDocument(obj) {
	return obj != null && obj.nodeType == obj.DOCUMENT_NODE
}

function isObject(obj) {
	return type(obj) == "object"
}

function isPlainObject(obj) {
	return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}

具体的就不说了。

8.总结
至此由$.each串起来的知识点都已经总结完,
其实内容来说都是很简单的,本文算是zepto的部分源码分析了。
其中break和continue的等价用法还是需要掌握的。
至于源码都不是很难的东西,尤其是后面的判断类型的方法,
判断是不是window和document的实现都是经典的实现。

本文完。