前端笔记:递归与闭包

递归与闭包

闭包总结(觉得重要放在开头了)

  1. 闭包的原理其实就是对作用域链的依赖,闭包中的作用域链包含着它自己的作用域,包含函数的作用域以及全局作用域
  2. 通常,函数的作用域及其变量都会在函数执行结束后被销毁,但是函数返回闭包时,函数的作用域会一直在内存中保存知道闭包不存在为止
  3. 当我们创建并立即调用一个函数时,既可以执行其中的代码,又不会在内存中留下对该函数的引用
  4. js中没有官方的私有对象属性概念,但闭包可以实现公有方法,通过公有方法可以访问在包含作用域中定义的变量,也就是私有变量
  5. 有权访问私有变量的公有方法叫做特权方法,可以使用构造函数模式,原型模式来实现自定义类型的特权方法,也可以使用模块模式实现单例的特权方法

递归

递归函数是在一个函数通过名字调用自身的情况下构成的

function fff(num){
	if(num <=1){
		return 1;
	} else {
		return num*fff(num-1);
	}
}

常见错误:如果上面例子fff函数执行中,fff不再是函数,这就会执行错误

arguments.callee

arguments.callee是一个指向正在执行的函数的指针

function fff(num){
	if(num < = 1){
		return 1;
	} else {
	 	return num * arguments.callee(num-1);   
	}     //arguments.callee()指向正在执行的fff()
}

这样即使函数名变化,依旧不会有此类错误产生
但在严格模式下,不能通过脚本访问arguments.callee(),这时可以使用命名函数表达式来达成相同的结果

var fff = (function f (num) {
	if (num <=1){
		return 1;
	} else {
		return num * f(num-1);
	}
});

这里创建了一个名为f()的命名函数表达式,然后将他赋值给变量fff,这样函数即使赋值给了其他变量,里面的函数名f依旧有效,所以递归依旧可以执行

闭包

闭包是指有权访问另一个函数作用域中的变量的函数
创建闭包的常用方式,就是在一个函数内部创建另一个函数
根据作用域链,内部函数的作用域链中会包含外部函数的活动对象,也就是外部函数的变量对象,根据这个变量对象,内部函数可以访问外部函数的自定义变量以及参数,当然随着作用域链向上一级查找,函数最终可以访问全局变量(这点跟闭包没关系)

function art(num){
	return function(tm1,tm2){
		var val1 = tm1*num;
        var val2 = tm2*num;
        return val1+val2;
	} 
}
var start = art(10)   //把函数art结果赋给变量start,因为return返回,所以调用外部函数,内部函数不会执行,只是一个返回结果
var result = start(3,7)    
console.log(result)  //最后打印结果是100
start = null //解除对匿名函数的引用,释放内存

虽然最后外部函数被解除,但是他的活动对象会被保存到内部函数的作用域链中,只要内部函数作用域链不销毁,内部函数就可以接着使用外部函数的活动对象

闭包与变量

作用域链的配置机制有一个副作用,闭包只能取得包含函数中任何变量的最后一个值,因为闭包保存的是整个变量对象,不是单独特殊的变量

function creates(){
	var end = new Array();
	for (var i=0;i<10;i++){
		end[i] = function (){
			return i;
		}
	}
	return end;
}

函数creates会返回一个函数数组,从代码上来看,每个匿名函数返回的都应该是自己的索引值,即位置0范围0,但实际上所有的函数都会返回10,因为每个函数的作用域链中都保存了creates()的活动对象,所以他们引用的i都是一个值,在我们调用匿名函数时,循环已经结束,所有i都是10
解决方法,再创建一个匿名函数

function creates(){
	var end = [];
	for(var i=0;i<10;i++){
		end[i] = function (num){
			return function(){
				return num;
			}
		}(i);
	}
	return end;
}

上面代码通过定义一个带有参数的匿名函数,并且在赋值给数组时立即调用,此时实参i就是数组当中函数的索引值,传递给形参num,然后再这个匿名函数内部,再创建并返回了一个访问num的闭包,这样,返回的num其实就是在循环中得到的每一个i,每个num对应相应的i,所以每个数组中的每个函数都能返回自己的索引值

关于this对象

在闭包中使用this对象也可能导致一些问题,this对象是在运行时基于函数的执行环境绑定的,在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于调用的对象,匿名函数的执行环境具有全局性,因此它的this对象通常指向window,但有时候由于编写闭包的方式不同,这一点特性表现得并不明显。

var name = 'the window';
var obj = {
	name : 'my obj',
	getnamefunc : function (){
		return function(){
			return this.name;
		}
	}
}
var end = obj.getnamefunc();
console.log(end())  //结果是the window

上面代码创建了一个全局变量name和一个包含name属性的对象,这个对象还包含一个方法getnamefunc(),在这里匿名函数没有取得其包含作用域的this对象
每个函数在被调用时都会自动取得两个特殊变量 : this和arguments ,内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此内部函数不可能直接访问外部函数中的这两个变量,之前的例子是外部函数传入了参数。不过,我们可以把外部作用域的this对象保存到内部函数可以访问到的变量中,那么闭包就可以访问这个对象了
上述代码中,我们只需要在getnamefunc()方法中加入一个变量 var that = this
这里的this就是调用该方法的对象中的this,这是我们在 内部函数中返回 that.name
返回结果就是my obj
当然这里我们也可以使用bind()方法,也就是在闭包内部函数之后.bind(this),改变this指向为外部执行环境的this或者也可以使用call()apply()方法改变this指向
ES6提供的箭头函数可以直接改变this指向为外部执行环境的this

模仿块级作用域

javascript没有块级作用域的概念,除了函数中定义的变量,其余{}中的变量都是可以被访问到的
可以使用匿名函数来充当块级作用域(通常也称为私有作用域):

(function (){
	//这里就是块级作用域
})();

其实就是函数自调用,在函数中声明的变量都只能在函数内部访问
这样可以减少闭包占用的内存问题,因为没有指向匿名函数的应用,只要函数执行完毕,就可以立即销毁其作用域链了

私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量
私有变量包括函数的参数,局部变量和在函数内部定义的其他函数
但是如果在函数内部创建一个闭包,那么闭包就可以通过自己的作用域链访问到这些变量,所以我们可以创建用于访问私有变量的公有方法

特权方法

这个公有方法被称为特权方法,两种创建特权方法的方式,第一种是在构造函数中定义特权方法

function myobj(){
     var privateNum = 10;
     function privateFun (){
         console.log("可以访问")
     }
     console.log(this);   //这里面的this其实就是window,所以下面写window.publicFun也可以
     this.publicFun = function(){
          privateNum++;
          console.log(privateNum);
          return privateFun();
	}
}
myobj();   
publicFun();    //可以访问私有变量

这个方式有一个缺点就是必须使用构造函数模式来达到这个目的,然而构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题
静态私有变量
通过在私有作用域定义私有变量或函数,同样也可以用来创建特权方法
在这里不能使用函数声明,函数声明只能创建局部函数

//var Myobj='';    这一步不是严格模式可以不写
(function (){
    var privatNum = 10;
    function privatFun(){
        console.log(privatNum);
    }
    Myobj = function(){};    //不带var  创建隐式全局变量
    Myobj.prototype.publicFun = function (){
        privatNum++;
        return privatFun();
    };
})();
var ooo = new Myobj();
ooo.publicFun();

因为构造函数是一个全局变量,所以可以在私有作用域之外被访问到
这个模式与在构造函数中定义特权方法得主要区别在于私有变量和函数是由实例共享的,而不是每个实例都有一个新方法,由于特权方法实在原型上定义的,因此所有的实例都使用同一个函数,这就跟继承有相似之处。而这个方法,作为一个闭包,总是保存着对其包含作用域的引用。

模块模式

模块模式是为单例创建私有变量和特权方法,所谓单例,指的就是只有一个实例对象
JavaScript是以对象字面量的方式来创建单例对象的
单例对象:

var danli = {
	name:'value',
	metbod : function (){
	     //代码
	}
}

模块模式通过为单例添加私有变量和特权方法能够使其得到增强

var danli = function (){
	//私有变量和私有函数
	var privateNum = 10;
	function privateFun(){
		console.log(privateNum);
	}
	//特权/公有方法和属性
	return {
		publicProp : true ,
		publicMethod : function (){
			privateNum++;
			return privateFun();
		}
	}
}();
//调用
danli.publicMethod();     //返回的是一个对象赋值给了变量danli

这个模块模式使用了一个返回对象的匿名函数,由于里面的返回对象实在匿名函数内部定义的,因此它可以访问函数内部的私有变量和函数,从本质上来讲,这个对象字面量定义的是单例的公共接口。这种模式在需要对但单例进行某些初始化,同时又需要维护其私有变量时是非常有用的

var application =function (){
	var comps = new Array();
	comps.push(new BaseComponent());
	return {
		getComp : function(){
			return comps.length;
		},
		registerComp : function (comp){
			if(typeof comp == 'object'){
				comps.push(comp);
			}
		}
	};
}();

上面代码既创建了一个对象并且对某些数据进行了初始化,也公开了一些能够访问这些私有数据的方法,我个人理解为,函数中的私有变量是不想被外界随意访问,所以需要一个特权方法,刚好我们需要创建一个新对象并对其初始化,这是就可以使用模块模式,一举两得

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值