学习从零开始写jQuery框架
随着时代发展,javascript阵营里面出现了越来越多的优秀的框架,大大简化了我们的开发工作,在我们使用这些框架的时候是不是也应该饮水思源想想它们都是怎样构建起来的呢?如果你不满足于仅仅是使用一些现成的API,而是深入了解它们内部的实现机制(照某人的说法, API是贬值最快的东西),最好的办法就是阅读它们的源代码了,前提是你读得懂。
最近两天研究了一下jQuery的源码,在这里将本人一些粗浅认识分享出来,不当之处请各位指正。好了,下面我们就来看看jQuery大概是怎样工作的,我假定你已经具备了一些基本的javascript知识,如果基础不够俺推荐你阅读《JavaScript高级程序设计》和《悟透JavaScript》这两本书。本文不适合对js里面的类、对象、函数、prototype等概念没有了解的朋友。
我们从最开始的说起:
首先构造一个对象给使用者,假定我们这个框架叫 Shaka ( 俺的名字;) )
var Shaka = function(){}; 这里我们创建了一个空函数,里面什么也没有,这个函数实际上就是我们的构造函数。为了让我们生成的对象能够调用在prototype里定义出来的方法, 我们需要用原型的方式(把Shaka当作是一个类)给Shaka添加一些方法,于是定义:
Shaka.fn = Shaka.prototype = {};
这里的Shaka.fn相当于Shaka.prototype的别名,方便以后使用,它们指向同一个引用。
OK,我们添加一个sayHello的方法, 给Shaka添加一个参数,这样这个框架最基本的样子已经有了,如果它有生命的话那么它现在是1岁, 看代码:
运行代码框
<script type="text/javascript">
var Shaka = function(age){ this.age = age; };
Shaka.fn = Shaka.prototype = { sayHello: function() { alert('I am a little baby, my age is ' + this.age + ' years old.'); }};
var babyShaka = new Shaka(1);
babyShaka.sayHello();
</script>
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]
好啦,先别激动, 我们注意到这个框架跟jQuery在使用上是有一些差别的, 比如在jq 中我们可以这样写:
jQuery('#myid').someMethod();
这是怎样做到的呢, 也就是说 jQuery()这个构造函数返回了一个jQuery的对象实例,因此我们可以在上面调用它的方法,所以Shaka的构造函数应该返回一个实例,它看起来应该是这个样子:
var Shaka = function(){ return //返回Shaka的实例; };
那么我们要如何取得一个Shaka的实例呢, 我们先来回顾一下使用prototype方式来模拟类的时候 var someObj = new MyClass(); 这个时候实际上是创建一个新对象someObje,把新对象作为this指针,调用 MyClass函数,即类的构造函数, 然后 someObj 就获得了在 MyClass.prototype里面定义的方法, 这些方法内的this指针指当前对象实例。
/*2*/
在jQuery中使用了一个工厂方法来创建一个实例,这个方法位于jQuery.prototype中, 现在我们重新来定义Shaka.prototype, 给它添加一个init方法用于返回一个Shaka的实例, 并且把Shaka的构造函数稍稍改变一下:
var Shaka = function(age) { return new Shaka.fn.init(age); };
Shaka.fn = Shaka.prototype = {
init: function(age) { this.age = age; return this; },
sayHello: function() { alert('I am a little baby, my age is ' + this.age + ' years old.'); }
};
Shaka.fn.init.prototype = Shaka.fn;//这里new Shaka.fn.init(age)创建的对象具有init方法的prototype指向对象的方法 , 因此我们将init方法的prototype指向 Shaka的prototype, 这样创建出来的对象就具有了Shaka.prototype里面定义的方法。
OK,现在我们的小宝宝变成大一点的宝宝了,打个招呼先:
运行代码框
<script type="text/javascript">
var Shaka = function(age) { return new Shaka.fn.init(age); };
Shaka.fn = Shaka.prototype = {
init: function(age) { this.age = age; return this; },
sayHello: function() { alert('I am a little big baby, my age is ' + this.age + ' years old.'); }
};
Shaka.fn.init.prototype = Shaka.fn;
Shaka(2).sayHello();
</script>
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]
嗯,好象有点样子了,但是光这样还不行,来点实际的, 我们在新框架中实现jquery里val()方法的部分功能,这个方法不加参数调用时返回指定ID的input的值,加参数时为设定这个input的值,与jQery一样,我们约定使用id来查找对象时使用"#"符号。把要查找的目标ID作为构造函数的参数传进去,我们给Shaka.prototype添加一个val()方法, 给Shaka添加一个selector的属性用于存储我们要查找的目标。
Shaka.fn = Shaka.prototype = {
init: function(selector) { this.selector = selector; return this; },
val: function(newValue) { //方法实现代码 }
};
var Shaka = function(selector) { return new Shaka.fn.init(selector); };
运行代码框
<form method="post" action="" name="myform">
我几岁了? <br />
<input id="myInput" type="text" value="Hello world!" size="50" />
</form>
<script type="text/javascript">
var Shaka = function(selector) { return new Shaka.fn.init(selector); };
Shaka.fn = Shaka.prototype = {
init: function(selector) { if(selector) this.selector = selector; return this; },
val: function(newValue) {
//start val function body
if(!(this.selector && this.selector.indexOf('#') == 0 && this.selector.length != 1))
return; //简单地判断传入值非法, 最好使用正则
var id = this.selector.substring(1);
var obj = document.getElementById(id);
if(obj)//如果对象存在
{
if(newValue == undefined)
return obj.value;//获取目标对象的值.
obj.value = newValue;// 将目标对象的value属性设置为newValue.
return this; //为了使方法可以连续调用。
}
//end val function body
}
};
Shaka.fn.init.prototype = Shaka.fn;
alert('object old value is '+Shaka('#myInput').val());
alert(Shaka('#myInput').val('I am 3 years old now!').val());
</script>
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]
/*3*/
到目前为止我们已经创建一个可以工作的框架雏形,为了使程序可以更方便地被调用,比如jQuery可以使用$符号来简写,我们也弄一个,在此之前我们先来回顾两个东西:
1. 我们在脚本中可以这样定义变量:
var foo = 'someThing';
bar = 'otherthing';
这样两种写法都是合法的,但是意义完全不同, 第一个语句创建了一个新的变量,而第二个是定义了window对象的一个属性,相当于window.bar = 'otherthing';, 因此我们想使我们的Shaka具有这样的调用方式能力: $.someMethod();就需要将Shaka设置为window的一个属性, 于是我们的Shaka构造函数就得写成这样:
var Shaka = window.Shaka = window.$ = function(selector) { return new Shaka.fn.init(selector); };
2. javascript的匿名函数.
创建并执行一个匿名函数的基本形式: (function(){ alert('Hello World!'); })(); 为什么要用到匿名函数呢,因为我们不想把Shaka的内部实现暴露出来,这样容易与其它代码冲突,只提供一个单一的入口,我们可以这样测试一下:
运行代码框
<script type="text/javascript">
(function(){
function privateFunction(){ alert('You can not see me, haha'); };
})();
function publicMethod() {alert('I am public');};
alert('匿名函数内部的函数是不可访问的, privateMethod 目前是: ' + typeof privateMethod);
alert('全局函数可访, publicMethod 目前是: ' + typeof publicMethod);
</script>
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]
然后,还有一个问题需要解决,俺们的框架做出来了但是还很简陋,在这之前我们需要让它与其它的框架协同工作,因此带来一个问题, 如果我们都使用$作为简写形式就会冲突了, 象jQuery一样,我们需要提供一个noConfilit的方法“出让”$的使用权。在我们的程序最开始处加入下面的代码:
var _$ = window.$;
意思是将此前定义的$对象引用放到 _$ 中, 然后我们再给Shaka扩展一个方法出来, 如果其它开发者需要自行扩展的话也可以使用这个方式(jQuery的extend方法提供了更为强大的功能,请大家自行研究):
(function($){ //extend method definition. })(Shaka);
意思是将Shaka作为这个匿名函数的参数来调用这个方法。
前面我们讲过 Shaka.fn 就是 Shaka.prototype 的别名,因此我们要在Shaka.prototype 里面添加新的方法就可以写成这样:
(function($){
$.fn.noConflict = function(){
window.$ = _$;//把$还给在开始处取得的引用.
};
})(Shaka);
现在我们来看一个完整的:
运行代码框
<form method="post" action="" name="myform">
<h1> 我几岁了?</h1> <br />
<input id="myInput" type="text" value="Hello world!" size="50" />
<br /><br />
<input id="otherInput" type="text" size="50" />
</form>
<script type="text/javascript">
//我们在这里模拟一下在这之前如果加载了其它框架的情形, 这个时候window.$不为空.
window.$ = { whoAmI: function(){ alert('This function result is from other js lib.');} };
(function(){ // 创建最外层匿名函数.
window._$ = window.$;//将别的框架定义的$暂存.
//给Shaka加上$ 的别名.
var Shaka = window.Shaka = window.$ = function(selector) { return new Shaka.fn.init(selector); };
Shaka.fn = Shaka.prototype = {
init: function(selector) { if(selector) this.selector = selector; return this; },
val: function(newValue) {
//start val function body
if(!(this.selector && this.selector.indexOf('#') == 0 && this.selector.length != 1))
return; //简单地判断传入值非法, 最好使用正则
var id = this.selector.substring(1);
var obj = document.getElementById(id);
if(obj)//如果对象存在
{
if(newValue == undefined)
return obj.value;//获取目标对象的值.
obj.value = newValue;// 将目标对象的value属性设置为newValue.
return this; //为了使方法可以连续调用, 返回当前实例。
}
//end val function body
}
};
Shaka.fn.init.prototype = Shaka.fn;
})();
//扩展新的方法.
(function($){
//alert(obj.fn);
$.noConflict = function(){
window.$ = window._$;//把$还给在开始处取得的引用.
};
})(Shaka);
//如果没有引入其它的框架,可以这么写
//alert('object old value is '+$('#myInput').val());
//alert($('#myInput').val('I am 3 years now!').val());
//强制使用完整名称.
Shaka.noConflict();
alert('object old value is '+Shaka('#myInput').val());
alert(Shaka('#myInput').val('I am 5 years old now!').val());
//Shaka('#otherInput').val('这里的值是使用Shaka(\'#otherInput\').val()方法来写入的哦');
//或者可以这样写也行,仍然使用$, 把Shaka作为匿名函数的参数$传进去。
(function($){
//又可以用$了, 哈哈
$('#otherInput').val('这里的值是使用Shaka(\'#otherInput\').val()方法来写入的哦');
})(Shaka);
//现在仍然可以使用$调用其它框架的方法.
$.whoAmI();
</script>
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]
学习从零开始写jQuery框架的更多相关文章
- 从零开始写JavaWeb框架(第一章节)
买了本<从零开始写JavaWeb框架> 因为是第一次用IDEA,期间遇到很多问题,比如:怎么在IDEA中配置tomcat: 在IDEA界面的右上角点击: 点击+,选择Maven 到了如下界 ...
- 手写 jQuery 框架
1.测试页面; <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- 源码分析系列 | 从零开始写MVC框架
1. 前言 2. 为什么要自己手写框架 3. 简单MVC框架设计思路 4. 课程目标 5. 编码实战 5.1 配置阶段 web.xml配置 config.properties 自定义注解 5.2 初始 ...
- 从零开始写JavaWeb框架(第四章节的AOP)
使用"链式代理"实现 AOP 本文是<轻量级 Java Web 框架架构设计>的系列博文. 大家是否还记得<Proxy 那点事儿>中提到的 CGLib ...
- 从零开始写JavaWeb框架(第二章节)
这一章太多了...好累,不想写那么细了,就做一点总结吧. package org.smart4j.chapter2.controller; import java.io.IOException; im ...
- 读《架构探险——从零开始写Java Web框架》
内容提要 <架构探险--从零开始写Java Web框架>首先从一个简单的 Web 应用开始,让读者学会如何使用 IDEA.Maven.Git 等开发工具搭建 Java Web 应用:接着通 ...
- 一起学习造轮子(二):从零开始写一个Redux
本文是一起学习造轮子系列的第二篇,本篇我们将从零开始写一个小巧完整的Redux,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Red ...
- 一起学习造轮子(一):从零开始写一个符合Promises/A+规范的promise
本文是一起学习造轮子系列的第一篇,本篇我们将从零开始写一个符合Promises/A+规范的promise,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Pr ...
- 一起学习造轮子(三):从零开始写一个React-Redux
本文是一起学习造轮子系列的第三篇,本篇我们将从零开始写一个React-Redux,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Re ...
随机推荐
- Unity学习疑问记录之时间变量
1.Time.deltaTime 以秒计算,完成最后一帧的时间 放在Update()函数中的代码是以帧来执行的.如果我们需要物体的移动以秒来执行.我们需要将物体移动的值乘以Time.deltaTime ...
- GDC2016 【全境封锁】的全局照明技术
现在全力支持公司的GAD平台了,很多的内部分享也可以放出来 http://gad.qq.com/article/detail/7159232
- C#winform调用外部程序,等待外部程序执行完毕才执行下面代码
1.简单调用外部程序文件(exe文件,批处理等),只需下面一行代码即可 System.Diagnostics.Process.Start(“应用程序文件全路径”); 2.如果要等待调用外部程序执行完毕 ...
- 1920.154s 0.309s 30817
MyISAM HASH restart-buffer ; ; ; SELECT * FROM grab_sales_rank_month; ; 受影响的行: 时间: .002s [SQL] ; 受影 ...
- 基于VirtualBox的多重载入
问题描述 这个问题要追溯到中秋之前,也就是写第一周博客的时候,当时我用的还是虚拟机上的ubuntu:当时我的ubuntu不是最新版,所以有提示升级,你懂的,我升了(因为时间有点久,我先去吃了个饭):等 ...
- UITableViewCell的选中时的颜色设置
转自:http://hi.baidu.com/zhu410289616/item/0de0262910886011097508c2 1.系统默认的颜色设置 //无色 cell.selectionSty ...
- golang protobuf SetExtension
对golang protobuf 的扩展字段赋值时候一直提示proto: bad extension value type clkUrl:="z.cn" proto.SetExte ...
- sql cross join table
A 表数据如下图所示 B表数据如下图所示
- [BS-27] 创建NSURL的几个方法的区别
创建NSURL的几个方法的区别 URL的基本格式 = 协议://主机地址/路径 URL和Path的区别 * URL:统一资源定位符,格式 “协议+主机名称+路径” 例如:[NSURL UR ...
- Beginning Android 4 中 Demo Basic/Switch 的问题.
作者的版本: layout (main.xml): <?xml version="1.0" encoding="utf-8"?> <Linea ...