闭包的理解和应用-面试必问题-提高逼格的回答

  • 谈谈你对闭包的理解,以及在项目中的应用!
    1. 阐述闭包是什么?(引申:堆栈、EC、AO、VO、SCOPE…)
    2. 闭包的作用及在真实项目中的应用场景,以及所带来的问题!
    3. 由闭包引发的高阶编程技巧
    4. 突出自己在分析研究框架的源码(逼格更高的是自己写类库、插件的时候)是怎么应用这些东西的!

回答如下:
// 建议:浏览器加载页面会把代码放到栈内存中执行(也就是ECStack),函数进栈执行会产生一个私有的上下文(也就是EC),此上下文能保护里面的私有变量(也就是AO)不受外界的干扰,并且如果当前上下文中的某些内容,被上下文以外的内容所占用,当前上下文是不会出栈释放的,这样可以保存里面的变量和变量值,所以我认为闭包是一种保存和保护内部私有变量的机制…在真实的项目中,其实我应用闭包的场景还是很多的,例如:

// 1. 我会基于闭包把自己编写的模块内容包起来,这样自己编写的代码都是私有的,防止和全局变量或者别人的代码冲突,这一点利用的是闭包的保护机制

// 2. 在没有用LET之前,我们循环处理事件绑定,在事件触发需要用到索引值的时候,我们基于闭包,把每一轮循环的索引值保存起来,这样来实现我们的需求,只不过现在都是基于LET来完成,因为LET会产生块级作用域来保存需要的内容(机制和闭包类似)

// 但是不建议过多使用闭包,因为形成不被释放的上下文,是占用栈内存空间的,过多使用会导致页面渲染变慢,所以要合理应用闭包

// 除了这些传统的业务开发中会应用闭包,我之前在研究别人源码和自己写一写插件的时候,往往会利用一些JS高阶编程技巧来实现代码的管理和功能的开发,他们的底层机制其实就是闭包,例如:
// 1. 惰性函数
// 2. 柯理化函数
// 3. compose函数
// …

惰性函数

  • DOM0事件绑定:xxx.οnclick=function(){}

  • DOM2事件绑定:xxx.addEventListener(‘click’,function(){}),只不过不兼容IE6~8,低版本浏览器中是基于
    xxx.attachEvent(‘onclick’,function(){}) 实现的

  • 这种写法虽然可以实现我们的需求和效果,但是在相同页面中,每一次执行函数,进来后都要重复的兼容判断(但是理论上第一次执行,我们就知道兼容性了,后期再执行,没必要每一次都判断兼容,也就是把兼容处理只处理一次 =>“懒”)

function observerEvent(element, type, func) {
	if (element.addEventListener) {
		element.addEventListener(type, func);
	} else if (element.attachEvent) {
		element.attachEvent('on' + type, func);
	} else {
		element['on' + type] = func;
	}
}

利用闭包机制=>做如下处理,这样处理之后以后再执行函数就不用再判断兼容性了=》我们利用这种思想

function observerEvent(element, type, func) {
	// 第一次执行observerEvent,根据兼容判断,重构了这个函数,重构后的小函数是不需要做兼容验证处理的
	if (element.addEventListener) {
		observerEvent = function (element, type, func) {
			element.addEventListener(type, func);
		};
	} else if (element.attachEvent) {
		observerEvent = function (element, type, func) {
			element.attachEvent('on' + type, func);
		};
	} else {
		observerEvent = function (element, type, func) {
			element['on' + type] = func;
		};
	}
	// 第一次也需要执行重构后的方法,实现事件绑定
	observerEvent(element, type, func);
}

// observerEvent(xxx,‘click’,function(){});

// observerEvent(xxx,‘click’,function(){});

// observerEvent(xxx,‘click’,function(){});

// …

//=>JS中一个最基础,但是也是业务逻辑开发中常用到的设计思想:“单例设计模式”(框架开发模式下无需用单例处理)最早的模块开发思想(AMD/CMD/CommonJS/ES6Module)

// A开发天气版块

// weatherModule除了是全局变量、模块名称、对象名,更专业的叫法是“命名空间”,这样单例设计模式就是把描述相同事务(相同版块)中的属性和方法,归拢到相同的命名空间下,实现分组管理(既可以避免全局变量污染、也可以实现模块之间的相互调用)
 let weatherModule = (function () {
	let index = 0;
	function queryData() {}
	function getElement() {}
	
	// 想让闭包之外的东西调用方法,可以基于WINDOW.XXX把其暴露到全局上(如果向全局暴露的东西过多,也会存在冲突的问题)
	// window.getElement = getElement;

	// 建议使用RETURN的方式
	return {
		// getElement: getElement
		getElement
	};
})();




// B开发资讯版块
let informationModule = (function () {
	let index = 0;
	function queryData() {}
	function bindHTML() {}
	function handleEvent() {}
	// ......

	return {
		// init:function(){}
		init() {
			// 在单例设计模式的基础上,增加一个命令模式,init作为当前模块业务的入口,以后只需要执行informationModule.init(),我们在init中根据业务需求,把编写的方法按照顺序依次调用执行即可
			queryData();
			bindHTML();
			handleEvent();
		}
	};
})();
informationModule.init(); 

柯理化函数

// 预先存储或者叫做预先处理的概念

function fn(x, y) {
	// 第一次执行函数,形成一个临时不被释放的上下文(闭包),在闭包中我们保存下来传递的参数信息,当后期执行小函数的时候,可以基于作用域链机制,找到闭包中存储的信息,并且拿来使用,所以形成的闭包类似于预先把一些信息进行存储
	return function (z) {
		// 最后小函数执行的时候,需要把之前传递的值和最新传递的值进行累加
		return x + y + z;
	}
}
let res = fn(1, 2)(3);
console.log(res); //=>6  1+2+3

// Function.prototype.bind 预先处理THIS的,利用的就是柯理化的思想

// React中的redux/react-redux源码以及vue中的很多核心源码都是这样处理的

COMPOSE函数

  • 在函数式编程当中有一个很重要的概念就是函数组合,
    实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
const add1 = (x) => x + 1;
    const mul3 = (x) => x * 3;
    const div2 = (x) => x / 2;
    div2(mul3(add1(add1(0)))); //=>3
  • 而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
const operate = compose(div2, mul3, add1, add1)
    operate(0) //=>相当于div2(mul3(add1(add1(0)))) 
    operate(2) //=>相当于div2(mul3(add1(add1(2))))
  • 简而言之:compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x),请你完成 compose函数的编写
const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;

function compose(...funcs) {
	// funcs接收的就是所有传递进来的函数
	return function anonymous(val) {
		// val第一个函数执行时候需要的实参  0
		if (funcs.length === 0) return val;
		if (funcs.length === 1) return funcs[0](val);
		// return funcs.reverse().reduce((N, item) => {
		// 	return typeof N === "function" ? item(N(val)) : item(N);
		// });
		return funcs.reverse().reduce((N, item) => {
			return item(N);
		}, val);
	}
}
let result = compose(div2, mul3, add1)(5);
console.log(result);
  • reduce

  • reduce只传递一个回调函数

let arr = [10, 20, 30, 40]; 
result = arr.reduce((N, item) => {
	// 第一次:10 20
	// 第二次:30 30
	// 第三次: 60 40
	// ...
	// reduce只传递一个回调函数,那么N第一次默认是第一项,后续的N是上一次函数执行的处理结果
	console.log(N, item);
	return N + item;
});
console.log(result);
  • REDUCE的第二个参数就是给N赋值的初始值
result = arr.reduce((N, item) => {
	console.log(N, item);
	return N + item;
}, 0); //=>REDUCE的第二个参数就是给N赋值的初始值 ITEM从数组第一项开始遍历
console.log(result);
function compose(...funcs) {
	if (funcs.length === 0) {
		return arg => arg
	}

	if (funcs.length === 1) {
		return funcs[0]
	}

	return funcs.reduce((a, b) => (...args) => a(b(...args)))
	
//	return funcs.reduce(function(a,b){
//	    return function(...args){
//	        return a(b(...args))
//	    }
//	})
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值