日期:2014-05-16  浏览次数:20402 次

[译] 创建你的第一个javascript库

原文: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')),这 里应该返回什么?如果你在