JavaScript设计模式之基础知识:闭包、高阶函数

闭包和高阶函数是JS学习中非常难懂的概念,是步入中高级程序员的门槛。下面是我在学习中的一些心得:

一、闭包

1、闭包的实现依赖JS中变量的作用域(即作用域链)和生存周期:

1) 全局变量:在全局环境下声明的变量,或者在函数中不使用关键词var、let、const声明的变量。全局变量的特性是:在所有的函数中均可访问;如果不主动销毁该变量,其生存周期是永久的。全局变量拥有全局作用域,在代码的任何地方都可以访问。

2) 局部变量:在函数中使用var、let、const声明的变量。局部变量的特性是:只有声明该变量的函数内部才能访问,在函数外部无法访问;退出该函数时,即函数执行完毕后,局部变量即被销毁。局部变量的优先级高于同名的全局变量,如果在函数内部声明的一个局部变量与全局变量重名,那么在函数内部访问到的是局部变量。

var a = 1;  // 全局变量
function fn(){
	var b = 2;  // 局部变量
	console.log(a);  // 1
	console.log(b);  // 2
}
fn();
console.log(a);  // 1 全局变量a,始终保存
console.log(b);  // b is not defined 退出函数后,局部变量b被销毁

3) 作用域链:作用域链是一个对象列表,能保证作用域范围内的变量按由内到外的顺序访问。

作用域链的创建规则:作用域链是函数定义时保存的。当调用该函数时,会创建一个对象存储其内部的局部变量,并将其添加到作用域链上。

嵌套函数的作用域链:当每次调用外部函数时,内部嵌套函数会重新定义。每调用一次外部函数,内部函数的代码都是相同的,但是关联这段代码的作用域链是不同的。

4) 垃圾回收:作用域链是一个对象,每次调用函数时,都会创建一个新的对象保存局部变量,并把这个对象添加到作用域链上。当函数返回时,这个函数及其内部的局部变量在其他地方没有再引用了,就会把这个对象删除,即被当做垃圾回收。但是如果是这个函数内有一个嵌套函数,并且嵌套函数在该函数外部保存了下来,即有一个外部的引用指向这个嵌套函数,那么这个对象就不会被当作垃圾回收。

2、闭包

闭包可以理解为一种特殊的函数写法:当一个函数嵌套了另一个函数,外部函数将嵌套的函数对象作为返回值返回的时候,就形成了闭包。闭包利用的是词法作用域,即函数定义时的作用域链到函数执行时依然有效。

闭包的作用:可以把不需要暴露在全局的变量封装成“私有变量”;延续局部变量的寿命。

1) 把不需要暴露在全局的变量封装成私有变量,这一闭包的特性,在封装模块时经常使用到。它可以摒弃全局变量的使用,把状态和实现隐藏起来,只对外提供一个接口。

var deentityify = (function (){
	var entity = {
		quot: '"',
		lt: '<',
		gt: '>'
	};

	return function (str){  // 这是真正的deentityify方法
		return str.replace(/&([^&;]+);/g, function (a,b){
			var r = entity[b];
			return typeof r === 'string' ? r : a;
		})
	}
})();
deentityify('&lt;&quot;&gt;');  // <">


var deentityify2 = function (str){
	var entity = {
		quot: '"',
		lt: '<',
		gt: '>'
	};

	return str.replace(/&([^&;]+);/g, function (a,b){
		var r = entity[b];
		return typeof r === 'string' ? r : a;
	})
};
deentityify2('&lt;&quot;&gt;');  // <">

// 这个方法是用来查找字符串中的html字符实体,并替换为对应的字符。
// 在方法中使用的变量entity用来保存字符映射关系,也可以把它放在全局变量中,但是全局变量容易引起命名冲突,尽量不使用全局变量.
// deentityify和deentityify2的功能相同,写法稍有不同。可以比较一下区别:
// deentityify2中把变量entity定义在deentityify2函数内部,每次执行函数的时候,该变量都会被求值一次,会带来运行时的损耗。
// deentityify中,变量entity定义在真正的deentityify方法之外,相当于是公用的变量,只是在初始化时定义了一次,后续调用中都不会再求值。

 

2) 延续局部变量的寿命

var scope = 'window';
function fn(){
	var scope = 'local';
	function foo(){
		return scope;
	}
	return foo;
}
var foo = fn();
foo(); // local

在这个例子中,fn函数的返回值是foo,foo在全局环境中执行时,其所引用的scope变量,依然是按定义时的作用域来查找,即local。这样就可以在fn函数的外部访问到局部变量scope。

二、高阶函数

1、高阶函数需要满足以下两个条件之一:函数可以作为参数被传递;函数可以作为返回值输出。

1) 函数作为参数被传递,平时应用很多,像ajax中经常使用的callback函数,另外还有数组的sort、every之类的方法。

2) 函数作为返回值输出,也就是return 一个函数:

var single = function (fn){
	var res;
        // return 一个函数
	return function (){
		return res || (res = fn.call(this, arguments));
	}
}

var getScript = single(function (){
	return document.createElement('script');
})

var s1 = getScript();
var s2 = getScript();

console.log(s1 === s2);  // true

在这个例子中,single方法的参数是一个函数,并且把一个函数return出去(这也是单例模式的实现)。

2、高阶函数最重要的一个应用就是函数柯里化。函数柯里化,又称部分求值,即一个柯里化的函数会接受一些参数,但是不会立即求值,而是返回另一个函数,而那些传入的参数会在闭包中保存起来,等到函数真正求值的时候,再一起使用。

柯里化的核心,一个是保存传入的参数,这是利用闭包延续局部变量的寿命来实现的;另外就是返回一个新的函数,这是利用高阶函数中,函数作为返回值输出来实现的。

function add(){
	var self = this;
	var args = Array.prototype.slice.call(arguments);

	function fn(){
		var _args = Array.prototype.slice.call(arguments);
		return add.apply(self, args.concat(_args));
	}

	fn.valueOf = function (){
		return args.reduce((a,b) => {
			return a+b;
		})
	}
	return fn;
}

add(1)(2,3)(4)(5);  // 15

这个面试题应该见的比较多,就是使用局部变量args来存储传入的参数。

三、闭包和高阶函数的应用很多,当然有些场景完全可以通过其他方式来实现,但是在某些情况下,使用闭包和高阶函数会使代码更加优化。要想深入的理解闭包和高阶函数,还是要多用,尤其是在一些实际场景中的应用,简单的写几个demo很难理解其中的精髓。后边在设计模式中,涉及到闭包和高阶函数的,再详加说明。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值