博客申请下来已经过去一个月了,一直不知道写点什么,毕竟我的文笔不是很好orz。
不过既然申请下来了,不写点什么总是觉得很可惜。正好最近在自己写框架,就把自己的进程和一些心得体会分享出来吧。
写在前面:
这一系列<从头开始写框架>文章,算是我的经验总结。因为以前读各位大牛的源代码时,总是会冒出这样的想法“如果他能把每一步都标注的很明确,那该多好,一定会省下我很多时间。”。但是大牛们愿意分享出源代码已经是很感谢了,怎么还能要求别人这么多呢~ 所以在这系列文章里,会事无巨细的把所有细节都标注清楚。方便后来者参阅的时候能够清楚明晰的了解每一行代码的意图,加深自己的理解。
下面是正文:
为什么要先从模块化的发展谈起呢?这是为了让新手更容易的理解后面的代码意图:为什么有些看起来很简单的事情要花2倍甚至3倍的时间去完成,以及,多花费的时间对我们有何帮助? 让我们带着这些问题来展开本篇文章。
说起JS模块化的发展,我们先从什么是模块化开始说起
很多前端新人刚入行时,写的代码是这样的:
window.onload=function(){ var a = 1;
var b = 2; var oBtn=document.getElementById("btn"); oBtn.onclick=function(){ alert(a+b) // 输出3 }
}
完全面向过程的写法与大量的全局变量。
这样写的好处有什么?也不能说完全没有吧,唯一的好处就是快。想到哪些到哪 ,不用花时间去想代码结构之类的复杂的事情,用来赶工还挺不错的对吧?:-D
但是如果说到坏处,就太多了。随处可见的全局变量导致写着写着某一行就突然报错了,回去检查发现是因为某个变量被修改导致报错。想改已经来不及了,因为太多地方依赖于这个变量,牵一发而动全身。
同时也会给一起合作的伙伴带来麻烦,因为不敢随意修改,所以只能在下面添加新的全局变量来编写业务,但是因为全局变量泛滥,变量与函数同名的事情时有发生。
更不用说因为面向过程的编写方式,导致可重用性几乎为零,维护的时候想要改某个小地方时却发现因为太多的代码依赖于这个地方而无法轻易改动等等各种问题。
那么为了解决这些问题怎么办呢?
我们进化到了函数封装的阶段。
这个阶段,我们的代码是这样的:
function addition(num1,num2){
return num1+num2;
}
function Subtraction(num1,num2){
return num1-num2;
}
window.onload=function(){
alert(addition(1,2));//输出3
alert(Subtraction(2,2))//输出0
}
恩,好一点了。至少可以做到复用了。而且减少了很多全局变量。
然而实际工作中,我们的页面往往是这样的:
<!DOCTYPE html>
<html>
<head>
<title>demo</title>
<script src="....js"></script>
<script src="....js"></script>
<script src="....js"></script>
<script src="....js"></script>
<script src="....js"></script>
<script src="....js"></script>
<script src="....js"></script>
<script src="....js"></script>
</head>
<body> <!--......--> </body>
</html>
因为方便分工,导致了大量的JS文件引入。而封装的函数本质上也是全局对象window的方法,所以还是会发生同名函数的冲突等一系列问题。
而对于动辄就是几千上万行代码的大型项目,显然这样做还是不够安全。
为了解决这个问题,我们开始接触到原型,利用prototype来编写构造器函数的方式。这也是我们初次大范围运用OOP的编程思维,而我们也第一次接触到了设计模式:“构造器模式”
这个时期我们的代码是这样的:
function Calculation(){
this.a;
this.b;
}
Calculation.prototype.init=function(num1,num2){
this.a=num1;
this.b=num2;
}
Calculation.prototype.add=function(){
return this.a+this.b;
}
Calculation.prototype.remove=function(){
return this.a-this.b;
} var cal = new Calculation();
cal.init(2,2) alert(cal.add())//输出4
alert(cal.remove())//输出0
恩,看起来已经很好了,解决了大量全局变量的问题,同时复用性也大大提高。
当然,我们一定不会满足于此。可以看到,我们的构造器函数仍然是一个全局函数。仍然有命名冲突的危险。
为了解决这个问题,我们开始利用闭包+对象的方式。
闭包是一个好东西,它的主要作用分两点:1.防止出现大量的全局变量与函数。因为我们可以用一个立即执行的匿名函数来模拟全局作用域,同时又不会污染全局作用域。
而对象的值对也可以有很多种形式:字符串、数字、布尔值、函数! 没错,对象的值对可以用来储存函数,这样的话,我们只需要把代码写成这样:
var Calculator=(function(){ var a = 2;
var b = 2; return {
add:function(){
return a+b;
},
subtract:function(){
return a-b;
},
}
})() alert(Calculator.add()) //输出4
alert(Calculator.subtract()) //输出0
首先这里的a和b两个变量因为写在了匿名函数里,所以变成了两个局部变量。这样做的好处是,外部无法修改它们。它们是两个只读属性,这样做就避免了因为修改它们而导致的出错。
恩,看起来已经很安全了。但是这样真的就安全了么?
让我们来看看Calculator这个变量,它本质上是一个返回的对象,而对象的键值对都是可以被修改的。这也就导致有可能会有其他人也同时使用这个名称定义了一个对象,而误修改了它的属性。而这是我们一定不允许发生的!
而且我们的工作方式实际上好像并没有什么变化:仍然是每人定义一个全局对象,然后各写各的,期盼着不要遇到上述问题。这样带来的问题仍然是:不利于分工合作。
相信到这里,你已经非常明白,为什么看起来如此简单的a+b,我们需要“大费周章”的绕这么多的弯去解决它,已经这可以为我们带来怎样的好处。
那么,让我们来想一下,如果我们可以把所有的代码分成一个个模块,在我们需要时,去引用它,不需要时,它也不会对其它部分产生任何影响。
听起来很美好不是么?恩,下一章节我们就来着手实现它。