JS递归、闭包、作用域链
递归
常规的递归函数
function func(number) {
if (number <= 1) {
return 1;
}
else {
return number * func(number - 1);
}
}
console.log(func(4)); // 24
这是一个非常常见的递归函数,但是有一种情况会使得此方法出现错误。
var func2 = func;
func = null;
console.log(func2(4));
将递归函数赋值给一个变量,然后把原函数赋值为null,这时候再调用就会报错
这是因为function属于引用类型,赋值时通过指针指向原函数,原函数发生改变,调用时就会出错
使用callee
在上面的情况下,使用arguments.callee
就可以解决,arguments.callee
是一个指向正在执行函数的指针,将经典递归做以下改动
function func(number) {
if (number <= 1) {
return 1;
}
else {
return number * arguments.callee(number - 1);
}
}
console.log(func(4)); //24
使用指针进行递归,无论怎么调用函数都不会出错,但是,在严格模式下使用arguments.callee会报错
命名函数表达式
以上两种方法都存在这不同程度的缺陷,使用命名函数表达式可以达到指针的效果,亦可以同时运行在严格模式和非严格模式下
var func2 = (function func(number) {
if (number <= 1) {
return 1;
}
else {
return number * func(number - 1);
}
});
var func3 = func2;
func2 = null;
console.log(func3(4));
这时依旧可以正常显示结果
闭包
闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。
上面的递归最后一种方法就可以理解为一个闭包
闭包与变量
经典的示例
function newArray() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
}
}
return result;
}
var newArr = newArray();
for (var i = 0; i < 10; i++) {
console.log(newArr[i]());
}
// 10,10,10,10,10,10,10,10,10,10
乍一看好像是for循环中每次循环将0-9赋值给数组的每一项,其实并不是,因为当前的newArray()正在执行,每个函数作用域链都保存着newArray()的活动对象,所以for循环中每次都是引用的同一个变量i,for循环结束之后,i是10,所以每个函数内部的i都是10。
再将返回i的函数包在一个匿名函数中,就可以达到互不影响的效果
function newArray() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function (num: number) {
return function () {
return num;
};
}(i)
}
return result;
}
var newArr = newArray();
for (var i = 0; i < 10; i++) {
console.log(newArr[i]());
}
结果如下
在for循环中,自调用函数立即执行,获取实时地i赋值给num. 循环结束之后,返回result,在自调用函数执行之后,他的作用域就被销毁了,不会再被其他操作影响
this对象
// 非严格模式
var arg = 'window';
var object = {
arg: 'object',
getArg: function () {
return function () {
return this.arg;
};
}
};
console.log(object.getArg()()); //window
为什么结果会是window呢,来看一下最后一行
// object.getArg()()相当于
var val = object.getArg();
val();
把getArg返回的方法赋值给了val变量,然后执行函数。因为val在全局中,所以获得的arg是全局中的arg
还有一种情况会出现问题
var arg = 'window';
var object = {
arg: 'object',
getArg: function () {
return this.arg;
}
};
- object.getArg() // object
- object.getArg() // object
- (object.getArg = object.getArg)() // window
第一种方法简单的调用了object的方法;第二种方法引用了object的方法,this对象能够维持;但是第三种是一个赋值语句,赋值语句是有返回值的,也就是等号右边,所以这里引用了方法,但不是object内部而是指向window
从这张图就可以看出赋值语句其实是有返回值的
解决方法
1 传递this指向
var arg = 'window';
var object = {
arg: 'object',
getArg: function () {
var _this = this;
return function () {
return _this.arg;
};
}
};
console.log(object.getArg()());
将object的this传递到getArg之内,然后使用这个引用值获取arg
2 箭头函数
也就是lambda表达式
var arg: string = 'window';
var object = {
arg: 'object',
getArg: function () {
return () => {
return this.arg
}
}
}
console.log(object.getArg()());
箭头函数的一大特点就是不改变this的指向
内存泄漏
闭包的缺陷在于会造成内存泄露,当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,比如通过node结点给html元素添加点击事件的时候,当事件添加完之后,存储node结点的变量会一直占用内存,造成内存泄漏
var divNode = document.getElementById('box');
divNode.onclick = function () {
alert('click');
};
存储divNode的内存将会无法释放也无法使用,怎么解决呢
在添加完事件之后将divNode手动赋值为空
var divNode = document.getElementById('box');
divNode.onclick = function () {
alert('click');
};
divNode = null;
作用域链
作用域
作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。
全局作用域
一般全局作用域有以下几种情况
- 最外层函数和最外层函数外面定义的变量拥有全局作用域
- 所有未直接定义的变量
- 所有window对象
函数作用域
声明在函数内部的变量,函数作用域一般只有代码片段中可以访问,比如函数内部
例如
function func1(a) {
var b = a * 2;
function func2(c) {
console.log(a, b, c);
}
func2(b * 2);
}
func1(2)
这段代码中存在3个作用域
- 全局作用域,标识符func1
- 作用域func1,标识符a,b,func2
- 作用域func2,标识符c
作用域链
由多级作用域连续引用形成的链式结果,掌管一切变量的使用顺序: 先在局部找,没有,就延作用域向父级作用域找。
可以向上搜索,但不可以向下搜索
这种一层一层的关系,就是 作用域链 。