一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

JavaScript虚拟入口函数Main的方法

时间:2015-09-11 编辑:简简单单 来源:一聚教程网

本文我们虚拟一个JavaScript的入口main函数作为程序的起点,这样可以让我们对JS有更深入的学习和理解。

一、JavaScript文件实际入口

JavaScript文件交给JS引擎运行时,JS引擎是从JavaScript文件的顶部由上到下逐条执行语句,直到文件尾部执行完每一条js代码。

二、JavaScript的作用域链、全局作用域和全局对象

JavaScript中的每个函数在执行时都会产生一个新的作用域。具体来说,在执行流程进入函数时会建立一个新的作用域,在函数执行完成退出时会销毁这个作用域。函数的形参、局部变量都会绑定到这个作用域里,当函数调用完成作用域销毁时,它们随之被销毁。当然在特殊情况下,如果函数返回时作用域中的某些变量仍然被引用,那么作用域以及这些被引用的变量就不会被销毁,从而形成所谓的闭包。

另一方面,我们知道函数是可以嵌套的,因而作用域也是可以嵌套的。函数在定义的时候,JS引擎会给每个函数设置一个称为[[scope]]内置属性,它指向外部函数的词法作用域。通过这种方式,多个作用域形成了链式结构,称为作用域链。通常情况下,在任意时刻只存在一条作用域链,即从正在执行的函数的作用域开始,层层上溯,直到最外层的全局作用域。

[注]:作用域链上的函数就是JS源码里的层层嵌套的函数,跟函数执行时的顺序或函数调用栈无关,这也是词法作用域这个称呼的由来。

全局作用域是一个特殊的作用域,它不是一个函数作用域,但它是所有函数作用域的外层作用域,也是所有作用域链的终点。因此只要程序没有退出,全局作用域总是存在的,全局作用域内的变量也是一直有效的。

[函数3的作用域]-->[函数2的作用域]-->[函数3的作用域]-->[全局作用域]

 另外,对应于全局作用域,还有一个全局对象。在浏览器中,全局对象就是window对象。全局对象是个特殊的对象:

    在全局作用域中定义的变量,都会绑定到全局对象。
    在任意作用域中定义的变量,如果定义时没有用 var 关键字,都会绑定到全局对象。
    在全局作用域中, this 指向全局对象。

从上面列举的这些特性可以看出,如果把全局作用域当成一个对象的话,那么实际上它就是全局对象。另外,这也解释了在全局作用域中,下面的四条语句为什么是等价的:

var a = 1;
a = 1;
window.a = 1;
this.a = 1;
 
三、虚构的main入口函数

既然都是作用域,为什么要有一个特殊的全局作用域呢?我们总是喜欢简单化、一致性,而尽量避免复杂化、特殊性。所以很自然地,我们会想能否让全局作用域看起来跟函数作用域没什么区别?答案是肯定的。我们可以做这样的构想:

我们想象,在JS引擎执行源文件时,会将文件中的代码包装到一个叫做main的函数中。然后把这个main函数作为程序的入口。

也就是说,假设一个JS文件中有这样的代码:

var a = 1;
var b = 2;
function add(x, y) {
    var z = x + y;
    return z;
}
console.log(add(a, b));


JS引擎在程序开始执行前会把它包装成一个main函数:

// 虚构的main函数
function main() {
    var a = 1;
    var b = 2;
    function add(x, y) {
        var z = x + y;
        return z;
    }
    console.log(add(a, b));
}


然后,调用这个main函数:

main._current_scope_ = window; // 将全局作用域(对象)设为window
main.call(window) // 将this指向window

四、这样做有什么意义?

(1) JS也有了入口函数main,跟其他语言一致了。
(2) 省去了全局作用域的概念,或者说全局作用域也成了函数作用域。
(3) 通过上面对main函数的调用过程,可以明白全局作用域中的那些特殊性质的由来。
(4) 最后一点,将所有JS源码当成一个函数,是为了后面讲事件队列、事件循环做铺垫。



给JavaScript程序一个统一的入口

JavaScript是一种脚本语言,浏览器下载到哪儿就会执行到那儿,这种特性给我们编程带来方便,但也很容易使得程序支离破碎,过于分散。当我们网页中需要用到的JS功能非常多时,各种JS标签零散的分布到网页里,网页加载到哪里执行到哪里,各自独立运行,虽然能满足需求,但是给我们的维护带来极大的麻烦,毫无组织性!为解决这一问题,我们为JS代码做一个统一的入口:

网页中的JavaScript从功能上,应该分为两大部分:


      (1)框架部分:该部分的作用是对JavaScript代码的组织作用,包括定义全局变量和命名空间方法等。它和具体应用无关,每个页面都需要包含相同的框架。
      (2)应用部分:提供页面的功能逻辑,不同页面会有不同的功能,所以不同页面应用部分的代码也不相同。
通过下面一段代码,让我们更深刻的了解一下JS代码的框架部分和应用部分:


   xxxxxxxxxxxxxxxx
javascript">  //定义命名空间方法  var GLOBAL={};  GLOBAL.namespace = function(str){   for(i=(arr[0]=="GLOBAL") ? 1 : 0; i  function init(){   //功能:A   (function(){    var a = 111; b="hello";    GLOBAL.namespace("A.CAT");    GLOBAL.namespace("A.DOG");    GLOBAL.A.CAT.name = "xiaomao";    GLOBAL.A.DOG.name = "wangcai";    GLOBAL.A.CAT.eat = function(){         }    GLOBAL.A.DOG.eat = function(){    }    GLOBAL.A.str2 = a;    GLOBAL.A.str = b;   })();   //功能:B   (function(){    var a, c="efg";    GLOBAL.namespace("B");    GLOBAL.B.str = c;   })();   //功能:C   (function(){    var a=GLOBAL.A.str2, b=GLOBAL.A.str;    var d="this is a demo";    ......   })();   //功能:D   (function(){    var test=GLOBAL.B.str;    alert(test);   })  }
   xxxxxxxxxxxxxxxx
在上面这段代码中,定义命名空间的方法就是我们JS代码的框架部分,这部分代码是我们每个页面中的JS代码都要用到的。功能A、B、C、D四段代码就是我们的应用部分,在不同页面实现不同的功能。
可能你已经发现,在上面这段代码中,我们与上一讲中最大的区别就是多了一个init的函数,这个函数就是我们今天主讲的入口函数了。在避免JS冲突一讲,我们不同的功能代码段是零散的分布到整个html代码中的,而这里我们将所用的功能代码统一放到init函数中。
接下来,我们需要在适当的时候调用这个入口函数(init),完成页面程序的初始化。那么什么时候调用才是最合适的时间呢?首先,我们已经知道,javascript是脚本语言,其最大的特点是加载到哪儿执行到那儿,如果程序控制某个DOM节点,而该DOM节点还没有加载,程序就会报错。如下面这段代码:

 alert(document.getElementById("demo").innerHTML);


 

Hello ITLee

在这段代码中,正常情况下JS代码应该弹出id为demo的DOM节点的内容,但实际情况却是,在执行脚本的时候,demo节点还没有加载,所以document.getElementById(“demo”)不能找到demo节点。为了解决类似于这样的问题,我们必须保证js脚本加载的时候其所需的节点必须加载完成。

怎样才能保证在js脚本执行的时候,DOM节点都已加载完毕呢?当页面中所有的节点加载完毕后会触发onload事件,这样我们就可以监听window对象的onload事件,当window触发onload事件后才调用脚本就可以解决我们的问题。看下面这段代码:


window.onload = function(){
 alert(document.getElementById("demo").innerHTML);
}


 

Hello ITLee

      这样,当页面加载到JS脚本时,脚本不会立即执行,而是等到window.onload之后才会去执行,这样就保证了脚本中所需的节点全部加载完毕。按照我们前面所说,应用部分的代码最好放在统一的入口函数中,那么现在完整的代码应该是这样的:


function init(){
 alert(document.getElementById("demo").innerHTML);
}
window.onload = init;


 

Hello ITLee

      现在看来,目前我们的问题已经得到解决,但是还不算完美,因为window的onload事件要求网页内所有的元素全部加载完毕后才会触发,如果网页中有大量图片的话,加载时间会非常长,那么我们的初始化函数会延长很久才会执行,这样也是一中非常不好的用户体验方式。那么怎么解决这个问题呢?
      DOMReady和window.onload的作用非常像,但是前者只判断页面内所有的DOM节点是否已经全部生成,至于节点的内容是否加载,它并不关心。所有,DOMReady有更快的触发速度,也更能满足我们的需求。需要我们注意的是,DOMReady并不是原生的Javascript支持的事件,不能像window.load那样直接调用,一般我们需要结合JS框架来使用它。下面我们以JQuery为例:

 

function init(){
 alert(document.getElementById("demo").innerHTML);
}
$(document).ready(init);


 

Hello ITLee

      现在我们按照刚才讲的这些思路来完善我们的第一段代码,完成后的代码清单:


   xxxxxxxxxxxxxxxx
 //定义命名空间方法  var GLOBAL={};  GLOBAL.namespace = function(str){   for(i=(arr[0]=="GLOBAL") ? 1 : 0; i  function init(){   //功能:A   (function(){    var a = 111; b="hello";    GLOBAL.namespace("A.CAT");    GLOBAL.namespace("A.DOG");    GLOBAL.A.CAT.name = "xiaomao";    GLOBAL.A.DOG.name = "wangcai";    GLOBAL.A.CAT.eat = function(){         }    GLOBAL.A.DOG.eat = function(){    }    GLOBAL.A.str2 = a;    GLOBAL.A.str = b;   })();   //功能:B   (function(){    var a, c="efg";    GLOBAL.namespace("B");    GLOBAL.B.str = c;   })();   //功能:C   (function(){    var a=GLOBAL.A.str2, b=GLOBAL.A.str;    var d="this is a demo";    ......   })();   //功能:D   (function(){    var test=GLOBAL.B.str;    alert(test);   })  }
   xxxxxxxxxxxxxxxx
   xxxxxxxxxxxxxxxx
......

这样我们的JS代码就趋于完美了,所有全局作用域的函数都放在了GLOBAL命名空间下,有效控制了window作用域下函数的数量,减小了JS冲突的隐患。然后提供了一个应用部分JS的统一入口函数init,最后在DOMReady的时候调用它。


 


 

相关文章

热门栏目