您可以创建一个Javascript框架,并带有实践指导。
我们几乎每天都使用各种Javascript框架。当你刚开始,方便的DOM(文档对象模型)的操作让你觉得jQuery是一个了不起的事情。这是因为,首先,DOM是新手了解太难了;当然,这是不懂API好东西。其次,浏览器之间的兼容性问题是非常令人不安的。
我们将元素包装到对象中,因为我们希望能够将方法添加到对象中。
在本教程中,我们将尝试从零开始实现其中的一个框架。是的,它会很有趣,但在您过于兴奋之前,我想先澄清几点:
这将不是一个功能性很强的框架。事实上,我们必须写很多东西,但它不是jQuery。但是我们要做的是让您体验真正编写框架的感觉。
我们不打算确保完全兼容,我们要编写的框架可以在Internet Explorer 8 +、Firefox 5 +、Opera 10 +、Chrome和Safari中工作。
我们的框架并没有涵盖所有可能的功能。例如,我们不追加和方法工作只有你送我们的框架的一个实例;我们不会使用原生的DOM节点和节点列表。
此外,虽然我们不为我们的教程的框架编写测试用例,我完成了我第一次开发它。你可以从GitHub的框架和测试用例的代码。
第一步:创建框架模板
我们将开始与一些封装代码,这会使我们的整个框架。这是一个典型的即时功能(生活)。
window.dome =(功能){
功能圆顶(柱){
}
var = {
获取函数(选择器){
}
};
返回的穹顶;
}();
正如您所看到的,我们的框架称为圆顶,因为它是一个基本的DOM框架。
我们已经有了一些东西:首先,我们有一个函数;它将成为构造对象实例的构造函数;这些对象将包含我们选择和创建的元素。
然后我们有一个圆顶物体,它是我们的框架物体。您可以看到它作为函数的返回值返回到函数调用方。这是一个空get函数,我们将使用它从页面中选择元素。
第二步:获取元素
这个函数只有一个参数的圆顶,但它可以是很多东西。如果它是一个字符串(String),我们会认为它是一个CSS(层叠样式表)的选择,但我们也可以得到一个DOM节点或节点。
获取函数(选择器){
VAR模型;
如果(typeof选择器=字符串){
ELS = document.queryselectorall(选择器);
否则如果(选择器,长度){ }
选择器;
{人}
选择符};
}
返回新圆顶(塔);
}
我们用document.queryselectorall简单地选择元素:当然,这将限制我们的浏览器兼容性,但这是可以接受的。如果选择的不是一个字符串类型,我们将检查它的长度属性。如果它存在,我们知道我们得到了一个节点列表;否则,它是一个独立的元素,我们把它在一个数组中。这是因为我们将数组传递到下面的圆顶。正如你所看到的,我们回到了一个新的圆顶的对象。让我们回到圆顶功能和填补它与代码。
第三步:创建一个圆顶实例
这是圆函数:
功能圆顶(柱){
For (VaR I = 0; I < els.length; i++) {
这个{我};
}
this.length = els.length;
}
我强烈建议您深入了解一些您喜欢的框架。
这很简单:我们只是遍历了所有的元素,并将它们存储在一个新的对象中,这个对象在数字中进行索引,然后添加一个长度属性。
但这意味着什么呢为什么不直接返回元素呢因为我们将元素包装到对象中,因为我们希望能够将方法添加到对象中;这些方法允许我们遍历这些元素,实际上,这是jQuery解决方案的集中版本。
我们的圆顶对象已经返回,现在我们给它的原型(原型)添加一些方法,我将直接在圆顶函数下编写这些方法。
第四步:添加多个实用程序
要添加的第一批功能是几个简单的工具函数,因为圆顶对象可能包含至少一个DOM元素,因此我们几乎需要通过所有元素来进行所有的操作,因此,这些工具将是令人敬畏的。
我们从map函数开始:
dome.prototype.map =函数(回调){
结果:i = 0;
为(;;i < this.length;i++){
results.push(callback.call(这、这{我},我));
}
返回结果;
};
当然,这个map函数有一个参数,一个回调函数,我们遍历了圆顶对象的所有元素,并将回调函数的返回值收集到结果集,注意我们如何调用回调函数:
callback.call(这、这{我},我));
这样,函数将在圆顶实例的上下文中调用,函数接收两个参数:当前元素和元素序列号。
我们也想要一个foreach功能。其实,这很简单:
dome.prototype.foreach(回调){
This.map(回调);
返回此;
};
因为地图的功能和各功能之间的区别仅仅是地图需要回报的东西,我们可以通过回调来this.map忽略返回的数组。而不是回归,我们会回到这个让我们的图书馆链。foreach将频繁调用,所以注意回调函数返回,而事实上,返回的是圆顶的实例。例如,下面的方法,事实上,返回穹顶实例:
dome.prototype.somemethod1 =函数(回调){
this.foreach(回调);
返回此;
};
dome.prototype.somemethod2 =函数(回调){
返回this.foreach(回调);
};
还有一点:图一。知道这个功能也很简单,但真正的问题是,你为什么需要它这就需要我们称之为图书馆哲学。
简论哲学
首先,对于初学者来说,DOM很纠结,它的API并不完美。
如果库的构建仅仅是编写代码,这不是一件困难的事情,但是当我开发这个库时,我发现不完美的部分决定了一定数量的方法的实现方式。
很快,我们要建立一个文本的方法返回选中元素的文本。什么是圆顶对象返回如果它包含多个DOM节点(如dome.get(礼))如果你像jQuery($),你会得到一个字符串,它是所有元素的直接拼接,有用吗我不认为这有用,但我认为没有更好的办法。
对于这个项目,我将返回阵中多元素的文本,除非有数组中只有一个元素,那么我只会返回一个数组包含一个元素,而不是文字。我想你会得到一个单一元素的文本,所以我们的优化,但如果你想的话。得到一系列元素的文本,我们可以很好的使用它。
回到代码
然后,在图一的方法简单地运行的地图功能,然后返回一个数组,或一个元素的数组。如果你还不知道这是多么有用,坚持下去,你会看到它!
dome.prototype.mapone =函数(回调){
var m = this.map(回调);
返回m.length > 1 m m { 0 };
};
第五步:处理文本和HTML
然后,我们添加一个文本方法,如jQuery,我们可以传递一个字符串值,设置节点元素的文本值,或者通过没有引用方法获得返回的文本值。
dome.prototype.text =功能(文本){
如果(类型的文本!=未定义的){
Return this.forEach (function (EL) {
el.innertext =文本;
});
{人}
返回this.mapone(功能(EL){
返回el.innertext;
});
}
};
你可以想到,当我们设置(设置)或取得(获得)的价值,你需要检查文本的价值。重要的是要注意,如果justif(文本)的方法不起作用,这是因为文本是一个空字符串是一个错误的价值。
如果我们设置(设置),我们使用foreach遍历元素设置innerText属性。如果我们得到(获得),返回元素的innerText属性。在图一的使用方法,注意:如果我们面对的是多个元素,我们将返回一个数组;其他一个字符串。
如果HTML方法使用innerHTML属性而不是innerText,它将在处理涉及到文字的东西更优雅。
dome.prototype.html =功能(HTML){
如果(typeof HTML!=未定义的){
this.foreach(功能(EL){)
el.innerhtml = HTML;
});
返回此;
{人}
返回this.mapone(功能(EL){
返回el.innerhtml;
});
}
};
就像我说的,几乎一样。
第六步:修改类
接下来,我们要操作的类,所以添加addClass()和removeClass()的参数,请()是类的名称或名称的数组。为了实现动态参数,我们需要判断参数的类型。如果参数是一个数组,然后遍历数组,以元素添加这些类的名称,如果参数是一个字符串,直接添加类名称。功能需要确保原始类的名字是不是搞砸了。
dome.prototype.addclass =函数(类){
VaR classname = ;
如果(typeof类!=字符串){
对于(var i = 0;i < classes.length;i++){
名称=+类{我};
}
{人}
类名= +类;
}
返回this.foreach(功能(EL){
el.classname =类名;
});
};
非常直观,对吗嘿
现在,只要简单地写下removeClass(),但是只有一个类的名字是允许删除每一次。
dome.prototype.removeclass =函数(类){
返回this.foreach(功能(EL){
VaR CS = el.classname.split(),我;
而(((我= cs.indexof(类))> 1){
CS = cs.slice(0,i),Concat(cs.slice(+ +我));
}
el.classname = cs.join();
});
};
对于每一个元素,我们将el.classname为字符串数组,然后使用一个while循环连接在cs.indexof返回值(类)大于-1.we将结果加入el.classname。
第七步:修复IE造成的错误
我们面对的是最糟糕的浏览器IE8。在这个小图书馆,只有一个IE的bug需要修复的修复方法和指标造成的;值得庆幸的是,它是非常simple.ie8不支持数组;我们需要使用它在removeClass,让我们这样做:
如果(typeof Array.prototype.indexOf!=函数){
array.prototype.indexof =功能(项目){
对于(var i = 0;i < this.length;i++){
如果(这个{ = } =项){
还我;
}
}
返回- 1;
};
}
它看起来非常简单,它不是一个完整的实现(不支持使用第二个参数),但它可以实现我们的目标。
第八步:调整属性
现在,我们想要一个attr函数。这很容易,因为它几乎是为文本或HTML的方法相同的方法。这些方法一样,我们都可以设置和获取属性:我们将设置名称和属性值,只有参数的名称获得价值。
dome.prototype.attr =函数(属性,值){
如果(typeof Val!=未定义的){
返回this.foreach(功能(EL){
el.setattribute(ATTR,瓦迩);
});
{人}
返回this.mapone(功能(EL){
返回el.getattribute(ATTR);
});
}
};
如果参数的值,我们将遍历元素和元素的setAttribute方法设置属性值。此外,我们将使用图一通过getAttribute方法返回的参数。
第九步:创建元素
与任何优秀的框架一样,我们也应该能够创建元素。当然,演示实例中没有好的方法,所以我们将该方法添加到演示项目中。
var = {
方法在这里获取
创建:功能(tagname,attrs){
}
};
正如你所看到的:我们需要两个参数:元素的名称和参数对象。大部分的属性都是由我们的方法使用,但标签名和属性有特殊待遇。我们使用ClassName属性使用文本的方法对文本属性的节点的方法。当然,我们首先要创建一个元素,和演示对象。下面是所有的影响:
创建:功能(tagname,attrs){
var el =新的圆顶({ document.createelement(TagName))));
如果(属性){
如果(属性。className){
El.addClass (attrs.className);
attrs.classname删除;
}
如果(属性。文本){
El.text(属性。文本);
删除attrs.text;
}
对于(在属性变量关键){
如果(attrs.hasownproperty(关键)){
El.attr(关键属性{重点});
}
}
}
返回El;
}
如上所述,我们创建的元送他到一个新的对象的话,我们处理Dmoe。所有属性。注意:当类名和文本属性的使用,我们必须删除它们。这将确保我们过去的时候其他的钥匙,他们可以使用。当然,我们最终回归新的演示对象。
我们已经创建了新元素,并希望将这些元素插入DOM中,对吗
第十步:尾部添加(添加)和头部(把)元素添加
接下来,我们将实现尾部添加和头部添加方法。考虑到各种场景,这些方法的实现可能有点棘手:
dome1.append(dome2);
dome1.prepend(dome2);
IE8对我们来说是一个奇妙的东西。
尾部添加或头部添加,包括以下场景:
单个新元素添加到一个或多个现有元素中
向一个或多个现有元素添加多个新元素
单个现有元素添加到一个或多个现有元素中。
将多个现有元素添加到一个或多个现有元素中
注意:这里的新元素表示它没有被添加到DOM中的节点元素,而现有元素是指DOM中存在的节点元素。
现在让我们一步一步来。
dome.prototype.append =功能(ELS){
this.foreach(功能(一些我){
els.foreach(功能(childel){)
});
});
};
假定参数的ELS是一个DOM对象。一个全功能的DOM库应该能够处理节点(节点)或节点序列(列表),但现在我们不需要它。首先,它将需要添加的元素(父元素),然后遍历元素将增加(訾元素)在这个环。
如果将子元素添加到多个父元素中,则需要克隆子元素(避免最后一个操作来删除最后一个加法)。但是,在初始添加时不需要克隆它,只需要在其他周期中克隆它:
如果(i = 0){
childel = childel.clonenode(真的);
}
变我来自外循环:它代表了父元素的序列号。第一个元素添加到子元素本身,和其他的父元素都是克隆的目标元素。因为传入的子元素作为参数不是克隆,所以当每个子元素添加到一个单一的父元素,所有节点的响应。
最后,元素运算的实加法:
parel.appendchild(childel);
因此,我们一起得到以下内容:
dome.prototype.append =功能(ELS){
返回this.foreach(功能(一些我){
els.foreach(功能(childel){)
如果(i = 0){
childel = childel.clonenode(真的);
}
parel.appendchild(childel);
});
});
};
前置法
我们实现了前置法在相同的逻辑,很简单。
dome.prototype.prepend =功能(ELS){
返回this.foreach(功能(一些我){
对于(var j = els.length - 1;J > 1;J—){
childel =(我> 0){ } ELS J。clonenode(真):ELS {,};
parel.insertbefore(childel,服装。第一个孩子);
}
});
};
不同的是,当多个元素的加入,加入的次序将逆转。所以你不能使用foreach循环,但在相反的顺序循环代替的。同样,目标子元素应该克隆时,添加到非一母元。
第十一步:删除节点
对于我们最后一个节点的操作,仅从DOM删除这些节点非常简单:
(dome.prototype.remove =功能){
返回this.foreach(功能(EL){
返回el.parentnode.removechild(EL);
});
};
只有通过节点的迭代和删除父节点上的子节点的方法,更好的是DOM对象仍在工作(由于文档对象模型),我们可以使用我们想在它上使用的方法,包括插入,预插入到DOM,非常漂亮,不是吗
第十二步:事件处理
最后,它是最重要的一个,我们将编写一些事件处理函数。
你知道,IE8仍然使用旧的IE的事件,所以我们需要测试它。同时,我们也可以使用DOM 0级事件。
请看下面的方法,我们稍后再讨论:
dome.prototype.on =(功能){
如果(文件。addEventListener){
返回功能(EVT,FN){
返回this.foreach(功能(EL){
El.addEventListener(EVT,FN,假);
});
};
如果(文件。attachevent){ }
返回功能(EVT,FN){
返回this.foreach(功能(EL){
el.attachevent(+ EVT,FN);
});
};
{人}
返回功能(EVT,FN){
返回this.foreach(功能(EL){
埃尔{ + EVT } = FN;
});
};
}
}();
在这里我们用立即执行功能(生活),我们在功能测试做的。如果document.addeventlistener法存在,我们使用它;此外,我们还检查document.attachevent,如果不是,DOM 0级的方法是不使用。请注意我们是如何最终收益函数的功能:它立即执行将在年底分配给Dome.prototype.on,这是更方便的配置合适的方法在每个运行功能比较当特征进行检测。
事件绑定方法类似于on方法:。
dome.prototype.off =(功能){
如果(文件。removeEventListener){
返回功能(EVT,FN){
返回this.foreach(功能(EL){
El.removeEventListener(EVT,FN,假);
});
};
如果(文件。detachevent){ }
返回功能(EVT,FN){
返回this.foreach(功能(EL){
el.detachevent(+ EVT,FN);
});
};
{人}
返回功能(EVT,FN){
返回this.foreach(功能(EL){
埃尔{ + EVT } = null;
});
};
}
}();