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

这一篇讲解一下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]();//3
results[1]();//3
results[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]();//0
results[1]();//1
results[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 1
console.log(Object.foo()==Object);//true 2
foo();//true 3
obj.foo();//给obj添加fn方法 4
obj.fn();//true 5
obj.fun()();//true 6
obj.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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值