原文:Build Your First JavaScript Library
?
你是否曾对魔幻般的Mootools感到惊奇,是否曾想知道Dojo的内部机制,亦是否曾好奇于jQuery的巧妙?在本课程,我们将去了解它背后的原理,并尝试动手去创建一个非常简单的库。
?
我们几乎每天都在使用JavaScript库。当你刚开始的时候,像用jQuery是非常爽的,主要是因为DOM。首先对新手来讲DOM是比较难操作的,因为它是非常简劣的API,其次它没有兼容所有浏览器。
?
在本课程,我们将从零开始创建一个库。感觉非常有趣吧,但你先不要激动,让我申明几点:
- 这不是功能完整的库。我们是有一套方法要写,但它不是jQuery,不过我们做的足够使你体验到你将来在创建库时会遇到的各种问题。
- 这个项目不会兼容所有浏览器。我们写的代码会在以下浏览器中正常运行:IE8+、Firefox 5+、Opera 10+、Chrome和Safari。
- 不会涵盖所有使用我们库的情况。例如,我们的append和prepend方法只能接受我们库的实例,如果传递它原生DOM节点或节点列表它不会执行。
- 我们也不会为这库写测试用例,因为在我第一次开发它的时候已经做了这个工作。你可以通过Github获得库和测试用例。
?
Step1 创建库样板
?
开始,我们写些封装代码,这代码将包含整个库。
?
window.dome = (function () { function Dome (els) { } var dome = { get: function (selector) { } }; return dome; }());
?
我们把库命名为Dome,因为他只主要是一个DOM库,是的,它并不完整。
这里我们做了两件事。首先我们命名了一个函数,它最终是我们库实例的构造函数;这些对象会封装我们选择或者创建的元素。
然后,我们定义了dome对象,它是我们真正的库对象;可以看到,它在最后被返回。这对象有一个空的get函数,它将用于从页面选择元素。现在,我们来填充它吧。
?
Step2 获取元素
?
dome.get()接收一个参数,它可以是各种类型的值。如果参数是字符串,我们假定它是css选择器。也可以接收单个DOM节点或者一个节点列表。
?
get: function (selector) { var els; if (typeof selector === "string") { els = document.querySelectorAll(selector); } else if (selector.length) { els = selector; } else { els = [selector]; } return new Dome(els); }
?
我们用document.querySelectorAll来简化查找元素:当然这限制了一些浏览 器的支持,不过对这例子来说,没关系。如果selector 不是字符串,我们检查它是否存在length属性,如果存在我们接收到的是NodeList,否则,我们接收到是单一的元素,我们会将其放入数组。这是因 为当我们调用底部 Dom时需要传递给它一个数组。可以看到,我们返回了一个新的Dome对象。现在我们返回到Dome函数,并填充它。
?
Step3 创建Dome实例
?
这是函数Dome:
?
function Dome (els) { for(var i = 0; i < els.length; i++ ) { this[i] = els[i]; } this.length = els.length; }
?
?
非常简单,我们遍历了选择的元素,并将它放入带有数字索引的新对象,然后给这个对象添加了length属性。
注意点,为什么不直接返回元素?我们把这些元素封装在对象里是因为想要能为对象创建方法,这些方法使我们可以和这些元素发生交互操作。
现在,返回了一个Dome对象,我们给它的原型添加些方法,我将这些方法写在Dome函数的正下方。
?
Step4 添加一些工具函数
?
首先,我们来添加一些简单的工具函数 ,由于我们的Dome对象可以包含多个Dom元素,我们几乎在每个方法中遍历每个元素,所以有了这些方法将非常方便。
?
Dome.prototype.map = function (callback) { var results = [], i = 0; for ( ; i < this.length; i++) { results.push(callback.call(this, this[i], i)); } return results; };
?函数map接收一个回调函数。我们将遍历数组中的每一项,将callback返回的任何值存入results数组,注意我们是如何调用callback的:
?
callback.call(this, this[i], i));
?
通过这种方式,回调函数将在Dome实例上下文中调用,它接收两个参数:元素和索引数。
我们还需要一个函数forEach:
?
Dome.prototype.forEach = function(callback) { this.map(callback); return this; };
?
函数map和函数forEach的唯一区别是,map需要返回值。你可以只传递给this.map一个回调函数,忽略它返回的数组; 而这里我们返回了 this,这使得我们的库支持链式操作。我们会频繁的调用forEach。注意当我们从一个函数返回this.forEach调用,我们实际返回的是 this。 比如,下面两个例子返回值相同:
?
Dome.prototype.someMethod1 = function (callback) { this.forEach(callback); return this; }; Dome.prototype.someMethod2 = function (callback) { return this.forEach(callback); };
?
再一个:mapOne。很容易看出这个function是做什么的,但问题是,我们为什么需要它?这需要一些你可以称为"库哲学"的东西来解释。
?
简短的"哲学"绕道
?
如果创建一个库只是写代码,那并不是什么难事。但是我在做这项工程时,我发现艰难的是考虑这些方法的工作方式。
?
我 们马上要创建text方法了,这方法将返回被选择的元素的文本。如果Dome对象包含一些DOM节点(如:dome.get('li')),这 里应该返回什么?如果你在