js-闭包(closure)&this&arguments对象&懒加载(lazyload)

来源:互联网 发布:hystrix 源码分析 编辑:程序博客网 时间:2024/06/08 18:11

这一篇讲解一下js中乱起八糟却特别有用的东西:

1、闭包

2、this作用域

3、arguments对象

4、懒加载


闭包:

闭包这个东西,或者说这个概念,如果要在网上找到特别清晰明朗的解释是不容易的,因为自从jQuery把闭包发扬光大后,大家才逐渐开始使用起这个技术。

这是我从一本书上摘抄的一句话:在函数内部定义了其他函数时,就创建了闭包。

是的,简单、直接却很明了的解释了什么是闭包。有人会提自执行的匿名函数了,我们暂时把它称之为"较为特殊的闭包"或"块级作用域"。

来看一段代码明白我们为什么要使用这个技术:

function foo(){  var results=[];  for(var i=0;i<3;i++){    results[i]=function(){      console.log(i);    }  }  return results;}var results=foo();results[0]();//3results[1]();//3results[2]();//3

可以看出console.log(i);中的i保存的仅仅是一个引用,而不是真正的值。

解决的办法是构建一个闭包:

function foo(){  var results=[];  for(var i=0;i<3;i++){    results[i]=(function(i){      return function(){console.log(i);      }    })(i);  }  return results;}var results=foo();results[0]();//0results[1]();//1results[2]();//2

我们把重点放在这一句话:

results[i]=(function(i){  return function(){    console.log(i);  }})(i);
(function(i){})(i);就是一个闭包,或者说是一个块级作用域,(i)代表调用,i是传入的参数,所以这是一个自执行的函数。

return function(){...}是返回一个function,将一个function赋给results[i]。同时将传入的i参数保存下来。

闭包的原理如下:

1、在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
2、通常,函数的作用域及其变量都会在函数执行完成后销毁。但是,当函数返回了一个闭包时,这个函数的作用域会一直在内存中保存到闭包不存在为止。


顺便多提以下闭包模仿js中的块级作用域:

1、创建自执行的函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
2、结果就是函数内部的所有变量都被销毁——除非把变量赋值给了包含作用域中的变量。

使用闭包时需要注意:闭包是极其有用的特性。但是创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。


this作用域&arguments对象:

js方法中的this可以参考这一篇文章:js-静态、原型、实例属性。

看下面的例子:

function foo(){console.log(this==window);}var obj=new Object();Object.foo=function(){return this;}obj.foo=function(){this.fn=function(){console.log(this==obj);};}obj.fun=function(){return function(){console.log(this==window);}}var name="window";obj.myfun=function(){this.name="myfun";function closure(){console.log(this.name);}return closure.call(this);}var obj1=new Object();obj1.foo=Object.foo;console.log(obj1.foo()==obj1);//true 1console.log(Object.foo()==Object);//true 2foo();//true 3obj.foo();//给obj添加fn方法 4obj.fn();//true 5obj.fun()();//true 6obj.myfun();//myfun 7

由1、2、3句的结果可以得出this对象取决于调用方式的不同:

1、当作为obj1的实例方法调用时,this指向的是obj1

2、当作为静态方法调用时,this指向的是Object

3、当在window作用域下执行时,this执行的是window

5的执行结果可以看出给对象添加方法时,this执行的是当前对象。

6句返回window,可以看出返回函数时,函数的定义不是在当前对象上,而是在window中,所以this指向window。

最后一句obj.myfun函数展示了将函数绑定到特定作用域下执行的方式:使用call方法,第一个参数指定执行的作用域。


arguments对象:

刚才提到了call方法,同样函数还存在一个apply方法,他们的作用都是将函数绑定到指定的作用域,也就是纠正this指针,唯一不同的是传参的方式不同:call接收多个参数,第一个参数是指定的作用域,后面可以指定多个参数。而apply只接受两个参数,第一个参数也是作用域,第二个参数是一个数组。

来看一个例子:

function bind(fn,context){if(arguments.length<2)return fn;context=context||window;var args=Array.prototype.slice.call(arguments,2);return function(){       var innerArgs=Array.prototype.slice.call(arguments,0),   finalArgs=args.concat(innerArgs);       return fn.apply(context,finalArgs);}}

这是个经典的函数绑定的方式,将函数fn绑定到context上执行。

Array.prototype.slice.call(arguments,2)
这句话可能会有很多人搞不懂为什么用这样的写法。

首先arguments对象并不是一个真正的数组,只不过它的数据格式比较像数组而已,也有length属性。而Array.protoype.slice方法可以根据对象的length属性进行处理,然后返回一个数组。

arguments对象还有一个成员:callee

function count(n){if(n==0)return 0;return arguments.callee(n-1)+n;}

上面是一个典型的递归,arguments.callee代表对自身的调用,所以这种写法也常见于递归。但是这种写法更加解耦,同样可以对匿名函数进行更好的处理。

arguments.callee.caller表示了当前函数的调用者,如果没有调用者返回null。

arguments、call、apply、callee、caller这些可能经常被混淆,但他们都有各自的应用场合,只要明白各自的概念也就可以很好的使用。


懒加载:

来看一下一个创建xhr的函数:

function createXHR(){if (typeof XMLHttpRequest != "undefined"){return new XMLHttpRequest();} else if (typeof ActiveXObject != "undefined"){if (typeof arguments.callee.activeXString != "string"){var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i, len;for (i=0,len=versions.length; i < len; i++){try {new ActiveXObject(versions[i]);arguments.callee.activeXString = versions[i];} catch (ex){//skip}}}return new ActiveXObject(arguments.callee.activeXString);} else {throw new Error("No XHR object available.");}}

可以看出,每次调用createXHR方法的时候,它都要对浏览器的支持能力进行检查。首先检查内置的XHR,然后检查有没有基于ActiveX的XHR,最后都没有就抛出一个异常。

每次调用都做相同的检查,如果浏览器内置支持XHR,就一直支持,那么这样检查就没有必要了。解决方法就称之为惰性加载的技巧。

懒加载表示函数执行的分支仅执行一次。

有两种懒加载的实现方式:

1、在函数被调用时再处理函数——在第一次执行的过程中,该函数被覆盖成另一个按合适方式执行的函数,这样对原函数的执行就不需要再经过任何分支了。

function createXHR(){if (typeof XMLHttpRequest != "undefined"){createXHR=function(){return new XMLHttpRequest();};} else if (typeof ActiveXObject != "undefined"){createXHR=function(){if (typeof arguments.callee.activeXString != "string"){var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i, len;for (i=0,len=versions.length; i < len; i++){try {new ActiveXObject(versions[i]);arguments.callee.activeXString = versions[i];} catch (ex){//skip}}}return new ActiveXObject(arguments.callee.activeXString);};} else {createXHR=function(){throw new Error("No XHR object available.");};}return createXHR();}

在这个懒加载的createXHR函数中,每一个if分支都会为变量createXHR赋值,有效覆盖了原有函数。下一次再调用该函数时,就会直接调用被分配好的函数,这样就不用再执行if语句了。

2、在声明函数时就指定适当的函数——这样在首次调用函数时就不会损失性能了,而在代码首次加载时损失一点性能。

var createXHR=(function(){if (typeof XMLHttpRequest != "undefined"){return function(){new XMLHttpRequest();};} else if (typeof ActiveXObject != "undefined"){return function(){if (typeof arguments.callee.activeXString != "string"){var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i, len;for (i=0,len=versions.length; i < len; i++){try {new ActiveXObject(versions[i]);arguments.callee.activeXString = versions[i];} catch (ex){//skip}}}return new ActiveXObject(arguments.callee.activeXString);};} else {return function(){throw new Error("No XHR object available.");};}})();

该方式使用的技巧是创建一个匿名、自执行的函数,用以确认改实现哪一个函数实现。

懒加载函数的优点是只在执行分支代码时牺牲一点性能。至于哪种方式更合适,就需要根据你的具体需求而定了。不过两种方式都能避免执行不必要的代码。


js的大杂烩到此结束了,有疑问或错误之处欢迎指出,请大家多多赐教。

预告:下一篇待定,这次留点神秘感。好吧,我这次还没想好再见
0 0
原创粉丝点击