这篇,咱们来学习下自定义函数以及即时函数的内容。浏览器
4、自定义函数闭包
函数能够动态定义,也能够分配给变量。若是建立了一个新函数,而且将其分配给保存了另外函数的同一个变量,那么就以一个新函数覆盖了旧函数。在某种程度上,回收了旧函数指针以指向一个新函数。而这一切发生在旧函数体的内部。在这种状况下,该函数以一个新的实现覆盖并从新定义了自身。函数
var scareMe = function() { alert("Boo!"); scareMe = function() { alert("Double Boo!"); }; }; // 使用自定义函数 scareMe(); scareMe(); scareMe();
当您的函数有一些初始化准备工做要作,而且仅须要执行一次,那么这种模式就很是有用。由于并无理由去执行本能够避免的重复工做,即该函数的一些部分可能并再也不须要。在这种状况下,自定义函数(self-defining function)能够更新自身的实现。性能
这里多说下,我的以为,这里的自定义,并不是单纯的指开发者建立的非内置函数,而是指本身定义本身的函数,也就是self-defining。因此,这里要尤为注意一下。学习
使用此模式能够显著地帮助您提高应用程序的性能,这是因为从新定义的函数仅执行了更少的工做。this
这种函数的另外一个名称是“惰性函数定义”(lazy function definition),由于该函数直到第一次使用时才被正确的定义,而且其具备后向惰性执行了更少的工做。spa
该模式的其中一个缺点在于,当它重定义自身时已经添加到原始函数的任何属性都会丢失。此外,若是该函数使用了不一样的名称,好比分配给不一样的变量或者以对象的方法来使用,那么重定义部分将永远不会发生,而且将会执行原始函数体。指针
下面的例子,咱们将上面的scareMe()函数以第一类对象的使用方式来使用:code
var scareMe = function() { alert("Boo!"); scareMe = function() { alert("Double Boo!"); }; }; // 一、添加一个新的属性 scareMe.property = "propertly"; // 二、赋值给另外一个不一样名称的变量 var prank = scareMe; // 三、做为一个方法使用 var spooky = { boo:scareMe }; // calling with a new name prank(); //输出“Boo!” prank(); //输出“Boo!” console.log(prank.property); // 输出“properly” // 做为一个方法来调用 spooky.boo(); //输出“Boo!” spooky.boo(); //输出“Boo!” console.log(spooky.boo.property); //输出“properly” // 使用自定义函数 scareMe(); //输出“Double Boo!” scareMe(); //输出“Double Boo!” console.log(scareMe.property); //输出undefined
正如上面代码所示,当将该函数分配给一个新的变量时,如预期的那样,函数的自定义(self-definition)并无发生。每次当调用prank()时,它都通知"Boo!"消息,同时它还覆盖了全局scareMe()函数,可是prank()自身保持了旧函数的可见,其中还包括属性。当该函数以spooky对象当boo()方法使用时,也发生了一样的状况。全部这些调用不断的重写全局scareMe()指针,以致于当它最终被调用时,他才第一次具备更新函数主体并通知“Double boo”消息的权利。此外,它也不能访问scareMe.property属性。对象
再多说两句,我的理解:
// 咱们先来看,为何上面的代码访问不到property属性。 // 咱们把代码简化一下: var scareMe = function() { alert("Boo!"); scareMe = function() { alert("Double Boo!"); }; }; scareMe.property = "propertly"; console.log(scareMe.property); //输出“propertly” scareMe(); //输出“Boo!” console.log(scareMe.property); //输出“undefined” scareMe(); //输出“Double Boo!” console.log(scareMe.property); //输出undefined
这是为何呢?在第一次执行scareMe()方法后,就找不到property属性了。由于第一次执行后,绑定的是外层变量的指针,此时在绑定属性的时候,是绑定在这个指针上的。而当函数执行了一次后,内部的scareMe()函数,替换了原来的函数指针。它已经不是曾经的它了!因此property属性是绑定在外层的,那固然再就找不到了被。
那么,因为它被覆盖了。而且,之后不会再有新的东西覆盖掉这个“新函数指针”,因此,之后每次执行都不会执行旧的内容。因此,之后每次的执行都会打印"Double Boo!"。那么,咱们再看代码:
// 咱们先来看,为何上面的代码访问不到property属性。 // 咱们把代码简化一下: var scareMe = function() { alert("Boo!"); scareMe = function() { alert("Double Boo!"); scareMe = function () { alert("Third Boo!"); } }; }; scareMe.property = "propertly"; console.log(scareMe.property); //输出“propertly” scareMe(); //输出“Boo!” console.log(scareMe.property); //输出“undefined” scareMe(); //输出“Double Boo!” console.log(scareMe.property); //输出undefined scareMe(); //输出“Third Boo!” scareMe(); //输出“Third Boo!” scareMe(); //输出“Third Boo!”
咱们来看这段代码,我自觉得是的又加了一层,因而,我但愿不用我说,你也已经懂了。
最后,再说一下,为何赋值给一个其它名字的变量以及用对象的方法来使用的时候,重定义永远没有发生。我的理解,由于你每次在执行的时候,赋值的动做是有的,可是并无把我覆盖,因此,每次都是重定义,每次都没法执行新的内部逻辑。因此,在最开始的那个例子里,当你第一次调用scareMe()的时候,就走了Double Boo!语句。由于前面prank()或者spooky.boo()的每一次执行,都从新定义了scareMe()。但愿我说的,你理解了。
5、即时函数
即时函数模式(Immediate Function pattern)是一种能够支持在定义函数后当即执行该函数的语法。
(function() { alert('watch out!'); }());
这种模式本质上只是一个函数表达式(不管是命名仍是匿名的),该函数会在建立后马上执行。在ECMAScript标准中并无定义术语“即时函数(immediate function)”,可是这种模式很是简洁。
该模式由一下几部分组成:
(function() { alert('watch out!'); })();
这样的语法也能够,可是JSLint偏好第一种。
这种模式是很是有用的,由于它为初始化代码提供了一个做用域沙箱。好比:当页面加载时,代码必须初始化执行一些设置任务,好比附加事件处理程序、建立对象等诸如此类的任务。全部这些工做仅须要执行一次,所以没有理由去建立一个可复用的命名函数。可是代码也还须要一些临时变量,而在初始化阶段完成后就再也不须要这些变量。然而,以全局变量形式建立全部哪些变量是一个差劲的方法。这就是为何须要一个即时函数的缘由,用以将全部代码包装到它的局部做用域中,且不会将任何变量泄露到全局做用域中;
(function () { var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], today = new Date(), msg = 'Today is' + days[today.getDay()] + ', ' + today.getDate(); alert(msg); }());
若是上面的代码没有包装在即时函数中,那么days、today和msg等变量将会成为全局变量,并遗留在初始化代码中。
即时函数的参数
也能够将参数传递到即时函数中:
(function (who,when) { console.log("I met " + who + " on " + when); }("Zaking",new Date()));
通常状况下,全局对象是以参数方式传递给即时函数的,以便于在不使用window指定全局做用域限定的状况下能够在函数内部访问该对象,这样将使得代码在浏览器环境以外时具备更好的操做性。
(function (global) { // 经过global访问全局变量 }(this));
请注意,通常来讲,不该该传递过多的参数到即时函数中,由于这样将迅速成为一种阅读负担,致使在理解代码运行流程时须要不断地滚动到该函数的顶部和底部。
即时函数的返回值
正如任何其余函数同样,即时函数能够返回值,而且这些返回值也能够分配给变量:
var result = (function() { return 2 + 2; }());
另外一种方式也能够达到效果,即忽略包装函数的括号,由于将即时函数的返回值分配给一个变量时并不须要这些括号:
var result = function() { return 2 + 2; }();
这个语法虽然比较简单,可是看起来可能有点使人误解。在没有注意到该函数尾部的括号时,一些阅读代码的人可能会认为result变量指向一个函数。实际上,result指向由即时函数返回的值。
另外一种语法也能够获得一样的结果:
var result = (function() { return 2 + 2; })();
实际上,即时函数不只能够返回原始值,还能够返回任意类型的值,包括另一个函数。所以,可使用即时函数的做用域以存储一些私有数据,而这特定于返回的内部函数。
var getResult = (function() { var res = 2 + 2; return function () { return res; } }());
上面这段代码,即时函数返回的值是一个函数,它将分配给变量getResult,而且将简单的返回res值,该值被预计算并存储在即时函数的闭包中。
当定义对象属性时也可使用即时函数。想象一下,若是须要定义一个在对象生存期内永远都不会改变的属性,可是在定义它以前须要执行一些工做以找出正确的值。此时,可使用一个即时函数包装这些工做,而且即时函数的返回值将会成为属性值。
var o = { message:(function () { var who = "me", what = "call"; return what + " " + who; }()), getMsg:function () { return this.message; } }; console.log(o.getMsg()) console.log(o.message)
这个例子中,message是一个字符串属性,而不是一个函数,可是它须要一个在脚本加载时执行的函数来帮助定义该o.message属性。
优势和用法
即时函数模式获得了普遍的使用。它能够帮助包装许多想要执行的工做,且不会在后台留下任何全局变量。定义的全部这些变量将会是用于自调用函数的局部变量,而且不用担忧全局空间被临时变量所污染。
还可使用即时函数模式来定义模块(固然ES6中以及由模块的概念了,可是这样的方法仍旧有学习的地方):
// 文件module1.js中定义的模块module1 (function() { //模块1的全部代码 }());
使用这种方式,能够编写其余模块。而后,将该代码发布到在线站点时,能够决定哪些功能准备应用于黄金时间,而且使用构建脚本将对应文件合并。
这篇文章就到这里了。后面还有...