js最初作为一个在浏览器中运行的脚本语言,设计的目标是用来给html增加交互行为,早期的网站都是在服务器端生成并返回给浏览器,js也只对单独的一个html进行操作,所以模块化并没有在早期的JS中得到很好的考虑,随着浏览器js引擎越发的快速,现在已经有很多前端框架,并不依赖与服务器生成html,而是自己直接通过js来生成,最典型的例子就是我们常听到的webapp。现在所有的js库都包装的非常好了,我们今天看看一些js模块化的基础知识吧。
在js中如何实现命名空间,我们来看一个例子。
<html><meta http-equiv&#61;"content-type" content&#61;"text/html;charset&#61;utf-8"><body><button id&#61;"mybtn" onclick&#61;"display();">点我加载列表button><h1>我想要不死族的英雄h1><ul id&#61;"mylist">ul> <script type&#61;"text/Javascript" src&#61;"./namespacejs1.js">script> <script type&#61;"text/Javascript" src&#61;"./namespacejs2.js">script> body>
html>
我们先定义一个html文件。点击里面的按钮&#xff0c;显示魔兽争霸的军队。响应按钮的代码分别定义在namespacejs1.js和namespacejs2.js两个文件中。
这是namespacejs1.js
var list &#61; [&#39;死亡骑士&#39;,&#39;巫妖&#39;,&#39;恐惧魔王&#39;];
function display(){var mylist &#61; document.getElementById("mylist"), fragment &#61; document.createDocumentFragment(), element; for(var i &#61; 0, x &#61; list.length; i
}
这是namespacejs2.js
var list &#61; [&#39;圣骑士&#39;,&#39;大法师&#39;,&#39;山丘之王&#39;];
当我们点击按钮的时候&#xff0c;会显示什么东东呢。我们期望显示不死族的英雄&#xff08;namespacejs1.js中的list&#xff09;&#xff0c;但是这里会显示人类的英雄&#xff08;namespacejs2.js中的list&#xff09;。显然&#xff0c;我们想要的数据被覆盖了。怎么样才能解决这个问题呢&#xff1f;我们来加上名称空间吧。看看改进后的namespacejs1.js。
var namespace1 &#61; {list : [&#39;死亡骑士&#39;,&#39;巫妖&#39;,&#39;恐惧魔王&#39;],display : function display(){var mylist &#61; document.getElementById("mylist"), fragment &#61; document.createDocumentFragment(), element; for(var i &#61; 0, x &#61; list.length; i
};
这时候点击按钮会没有用&#xff0c;需要稍微改动一下
<button id&#61;"mybtn" onclick&#61;"namespace1.display();">点我加载列表button>
可以看到display在namespace1的名称空间下面了。问题已经圆满解决。但是如果我们想扩展namespace1的功能怎么办呢&#xff0c;难道只能修改namespace1.js的源文件吗&#xff0c;还有list很不安全啊&#xff0c;谁都可以改动它&#xff0c;如何把它变成私有变量呢&#xff1f;
在js中并没有私有公有的直接支持&#xff0c;但是不代表js语言不能完成这个。我们看一下如何隐藏list。
var namespace1 &#61; (function(){var importGlobalVariable &#61; gv;var list &#61; [&#39;死亡骑士&#39;,&#39;巫妖&#39;,&#39;恐惧魔王&#39;];function display(){var mylist &#61; document.getElementById("mylist"), fragment &#61; document.createDocumentFragment(), element; for(var i &#61; 0, x &#61; list.length; i
})();
这段代码返回了一个对象&#xff0c;在这个对象中我们只能访问display方法,是不是很牛逼呢&#xff0c;这样就解决隐藏问题。
我们知道js的继承是用原型链来实现的&#xff0c;但是这里要讨论的是模块的扩展&#xff0c;所以这边不会说道继承的问题。如何扩展namespace1的功能呢。我么看一下下面的代码。
var namespace1 &#61; (function(n1){var listArmy &#61; [&#39;4个蜘蛛&#39;,&#39;2个食尸鬼&#39;,&#39;2个冰龙&#39;]n1.displayArmy &#61; function(){n1.display();var mylist &#61; document.getElementById("mylist"), fragment &#61; document.createDocumentFragment(), element; for(var i &#61; 0, x &#61; listArmy.length; i
})(namespace1);
这段代码我们用来增加一个displayArmy的方法&#xff0c;用来显示不死族军队&#xff0c;也就是listArmy的数据吧。
我们看到上面的代码把namespace1作为参数传入到一个立刻调用函数中&#xff0c;这样在里面给它增加一个函数。有没有感觉js很强大啊。
如果已经习惯了C&#xff0c;C&#43;&#43;&#xff0c;C#或者Java&#xff0c;那么上面的实现简直匪夷所思&#xff0c;感觉变量的作用域和生命周期都很奇怪。那么我们说说js中的一个重要概念闭包吧。
Closures are functions that refer to independent (free) variables.
这里就不翻译了&#xff0c;因为翻译过来实在是很奇怪&#xff0c;比如&#xff0c;闭包是那些引用了独立变量的函数。那么按照定义是否可以认为函数就是闭包呢&#xff0c;为了搞清楚闭包的概念&#xff0c;我们需要了解函数对象&#xff0c;变量生命周期&#xff0c;和嵌套的作用域三个概念。
什么是函数对象呢&#xff0c;在C语言中和C&#43;&#43;语言中&#xff0c;可以想函数那样用()去调用&#xff0c;看起来和函数一样的对象就叫函数对象。比如在C语言中&#xff1a;
typedef void (*func_t)(int); //定义函数指针
//定义一个函数
void f(int n){printf("node(?)&#61;%d\n",n);
}
int main(){func_t pf &#61; f;f(1);
}
可以看到f很像一个函数吧&#xff0c;但是呢&#xff0c;其实它只是一个函数指针而已。在C&#43;&#43;语言中&#xff0c;我们也看一个例子。
class Foreach
{
private:struct node * myList;public:Foreach(){}~Foreach(){}void operator()(){//做点有意义的事情吧}
};
int main(){Foreach *foreach &#61; new Foreach();(*foreach)();
}
可以看到foreach对象也很像函数。
我们在看一下js中的函数对象吧。
var f &#61; function(){}&#xff1b;
f();
看看&#xff0c;是不是很简单。
我们已经看到js的函数对象的定义已经很方便了&#xff0c;那么还有什么和传统语言不一样的地方呢&#xff1f;
我们看一下这个代码。
var x &#61; 10;
function f(){var y&#61;15;function g(){var z&#61;25;alert(x&#43;y&#43;z);}g();
}
f();//显示50
这段代码在C语言中没法实现&#xff0c;原因是C语言的变量无法访问当前作用域外的变量。也就是说函数g里面访问不了y&#xff0c;函数f也访问不了x。但是js却能做到。我们看一下这样有什么强大的地方。
#include
#include
};// 函数指针&#xff0c;JS中的函数对象
typedef void (*func_t)(int); void foreach(struct node *list, func_t func){while(list){func(list->val);list &#61; list->next;}
}
void f(int n){printf("node(?)&#61;%d\n",n);
}
int main(){func_t pf &#61; f;f(1);// bool b &#61; false;// b &#61; true;// b &#61; "zifuchuan";struct node * list &#61; 0, *l;int i;for(i&#61;0; i<4; i&#43;&#43;){l &#61; malloc(sizeof(struct node));l->val &#61; i;l->next &#61; list;list &#61; l;}i&#61;0;l&#61;list;//这个循环可以打印出index&#xff0c;也就是i&#xff0c;如下是打印结果//node(0) &#61; 3//node(1) &#61; 2//node(2) &#61; 1//node(3) &#61; 0while(l){printf("node(%d) &#61; %d\n", i&#43;&#43;, l->val);l &#61; l->next;}//foreach里面再调用函数f&#xff0c;就不能访问i了&#xff0c;如下是打印结果//node(?)&#61;3//node(?)&#61;2//node(?)&#61;1//node(?)&#61;0foreach(list,f);
}
我们看到C语言的高阶函数&#xff08;函数调用函数&#xff09;是没法访问外部变量的。那么js写这段代码怎么弄呢&#xff1f;
function foreach(list, func){while(list){func(list.val);list&#61;list.next;}
}var i &#61; 0;
//这里可以使用变量i
foreach(list,function(n){console.log("node("&#43; i &#43;") &#61; " &#43;n);i&#43;&#43;;
});
我们发现js的变量作用域比C语言要牛逼一点吧。
下面再看看js闭包的更牛逼的地方吧。
function extent(){var n&#61;0;return function (){n&#43;&#43;;console.log("n&#61;"&#43;n);}
}
f &#61; extent();
f();//&#61;>n&#61;1
f();//&#61;>n&#61;2
这里&#xff0c;当extent函数执行完毕后&#xff0c;n变量应该挂了才对&#xff0c;但是&#xff0c;我们通过结果看到&#xff0c;n变量还活的好好的呢。
属于外部作用域的局部变量&#xff0c;被函数对象给“封闭”在里面了。闭包&#xff08;“closure”&#xff09;这个词原本就是封闭的意思。被封闭起来的变量的寿命&#xff0c;与封闭它的函数对象的寿命相等。也就是说&#xff0c;当封闭这个变量的函数对象不再被访问&#xff0c;被垃圾回收器回收掉时&#xff0c;这个变量才挂。
现在大家明白闭包的定义了吧。在函数对象中&#xff0c;将局部变量封闭起来的结构被叫做闭包。因此&#xff0c;C语言的函数指针并不是闭包&#xff0c;Javascript中的函数对象才是闭包。另外C&#43;&#43;等传统面向对象语言&#xff0c;也加入了对函数式编程的支持&#xff0c;其中一方面就是Lambda表达式&#xff0c;也有闭包的概念。
现在大家理解闭包的概念了&#xff0c;我们看看全局变量导入的问题。因为闭包中内部变量会一直持有外部变量&#xff0c;所以我们最好把外部变量当做参数传递给我们要使用的内部函数&#xff0c;这样会节省内存和查找变量的时间。因为找到一个外部变量&#xff0c;需要从内部往外部一层层的查找&#xff0c;很费时间&#xff08;对解析器来说&#xff09;。另外&#xff0c;一起开发代码的人也会很迷惑这个变量到底在那里定义的&#xff0c;容易出错&#xff0c;我们来看一个例子。
var globalVariable &#61; "我是外层变量,从disply函数找到我需要很久很久";var namespace1 &#61; (function(gv){var importGlobalVariable &#61; gv;var list &#61; [&#39;死亡骑士&#39;,&#39;巫妖&#39;,&#39;恐惧魔王&#39;];function display(){var mylist &#61; document.getElementById("mylist"), fragment &#61; document.createDocumentFragment(), element; for(var i &#61; 0, x &#61; list.length; i
})(globalVariable);
这个例子把globalVariable当成参数传入&#xff0c;这样他就在匿名function函数的作用域里面了。