任务24 : JS高阶编程技巧(惰性函数、柯里化函数)
1.什么叫闭包?以及优缺点? 应用?
它是一种机制,函数执行形成一个私有上下文,它里面具备两大机制,第一个是保护,保护里面的私有变量不受外界影响,或者说保护里边的私有变量和外界不冲突,避免全局变量被污染。第二是保存,形成一个私有上下文,如果说它里边的某个东西被外界占用了,那么当前上下文是不会出栈释放的,不出栈释放那么当前上下文就被保存下来,保存下来那么它里面的变量就保存下来。一般来说市面上所谓的闭包就是函数执行形成一个不被释放的私有上下文,这个上下文里边的变量是自己的私有变量并且它里边的变量的值也被保留下来。它的体现形式很多,不一定是大函数执行返回小函数,小函数用到大函数里边的东西【闭包的体现形式之一】。
优点:保护和保存。
缺点:形成一个不被释放的私有上下文会消耗我们的栈内存。所以说我们项目中应该尽可能减少对闭包的使用.
应用:
①但是呢?减少使用不是不使用,比如jQuery部分源码里边写类库写插件的时候让别人用,我们基本上都要把我们的代码包起来,这样我们自己写的东西不会和外界冲突。早期的模块化编程思想
②闭包除了这些应用外,还有闭包深层次的三大应用处理方式,第一个是惰性思想,第二个是柯里化函数思想(curying函数),第三个是compose合并函数,看看这三个以后插件封装,阅读源码,以及我们自己在项目可能会用到的三大闭包的高级应用.[JS高阶编程技巧]
2.早期的模块化编程思想
①真实项目中的代码编写是按照模块划分编写的(早期的模块化思想其实就是利用闭包机制来实现的) .
②自己创建一个模块,里面有自己的私有方法func和queryElement,如果想让我的东西供别人来调用,可以通过return来暴露出来,需要哪些方法就暴露哪些API来,用统一的变量来接受这个对象,此时我把我模块下的方法都放searchModule这个对象下了,此时就把这个堆内存空间searchMoudle[这个searchMoudle就相当于给这个堆内存起了个名字]就叫命名空间.现在我把我们需要供别人用的东西放在我的命名空间下,别人通过指定命名空间访问到这个方法.
③单例设计模式:把描述自己特征的属性方法,放在自己的命名空间下,这样可以防止全局变量的污染(每一个命名空间都是一个单独的实例)=> 基于闭包把其进一步加强:用闭包保存/保护当前模块下私有的属性方法,把需要供别人调用的在暴露出去,形成最早期的模块化思想.
注意:
-
前端中的单例模式和后端中的单例模式是不一样的.
-
下面代码体现出闭包/单例/模块化思想.
// searchModule命名空间
let searchModule = (function () {
let n = 10;
function func() {}
function queryElement() {}
//window.xxx=xxx;相当于把这个东西挂到全局上了,如果往全局暴露太多也容易引发冲突
return {
func,
queryElement
};
})();
let mengModule = (function () {
let n = 10;
function sum() {}
searchModule.queryElement();
return {
sum
};
})();
/*真实项目中单例模式的应用*/
let listRender = (function () {
// ...
return {
init() {
// 控制板块方法的按顺序执行(入口)
}
};
})();
listRender.init();
3.通过浏览器兼容场景引出惰性函数
(1) 一般情况处理浏览器兼容的事件绑定
JS中的事件绑定 DOM2事件绑定:
①=>新版浏览器中: [元素].addEventListener([TYPE],[FUNC])
②=>老版浏览器中:[元素].attachEvent([on+TYPE],[FUNC])
③都不支持 [元素].onTYPE=[FUNC]
属性 in 对象:检测对象中是否存在这个属性
function handleEvent(element, type, func) {
if ('addEventListener' in element) {
element.addEventListener(type, func);
} else if ('attachEvent' in element) {
element.attachEvent('on' + type, func);
} else {
element['on' + type] = func;
}
}
handleEvent(document.body, 'click', function () {
console.log('BODY点击');
});
handleEvent(document.documentElement, 'mouseenter', function () {
console.log('HTML进入');
});
(2)基于惰性思想代码优化
以上代码处理浏览器兼容的事件绑定是没有问题的,但是呢?会有性能上的消耗,每一次执行handleEvent()都要去判断一下当前兼不兼容,handleEvent调10000次每一次进来都要判断一下兼不兼容,每一次都把一件事情重复做不好.这样是没有必要的,因为第一次已经知道浏览器兼不兼容,第二次就不用考虑了.惰性思想就是为了解决这种事情的.惰性思想:懒,能够执行一次搞定的,绝对不会重复干两次.通过这样提高代码的运行速度和简洁性,那么该怎么样基于惰性思想解决这种事情?
/* 惰性思想:懒,能够执行一次搞定的,绝对不会重复干两次 */
function handleEvent(element, type, func) {
if ('addEventListener' in element) {
handleEvent = function (element, type, func) {
element.addEventListener(type, func);
};
} else if ('attachEvent' in element) {
handleEvent = function (element, type, func) {
element.attachEvent('on' + type, func);
};
} else {
handleEvent = function (element, type, func) {
element['on' + type] = func;
};
}
// 第一次执行重写方法后,需要执行一次才能保证第一次事件也绑定了
handleEvent(element, type, func);
}
handleEvent(document.body, 'click', function () {
console.log('BODY点击');
});
handleEvent(document.documentElement, 'mouseenter', function () {
console.log('HTML进入');
});
代码原理:一个函数执行,把函数重新赋值,赋值一个新的函数.大函数执行形成一个私有上下文,在私有上下文里边创建一个小函数,把小函数地址给了当前这个大函数,相当于当前上下文中的某个东西被外面的作用域占用了,不能释放.形成这样的特点:这个函数再次执行,它的上级上下文,就是第一次执行的上下文.func方法重构就是利用这个闭包思想.第一次执行形成一个不释放的闭包.执行方法=>条件成立=>方法重写.第一次执行重写方法后,需要执行一次才能保证第一次事件也绑定了.当第二次及以后执行的时候就不再执行外边的大函数,而是执行里边重写后的handlEvent(),你会发现兼容处理和兼容判断只发生在第一次大的handleEvent()函数里.以后再次执行的时候直接使用重写后的小方法了,就不再考虑兼容判断了.
4.函数柯里化思想
柯理化函数编程思想:利用闭包的保存机制,事先把一些信息存储起来(存储到不释放的上下文中),这样可以供下级上下文中调用 => 我们把这种预先存储的思想叫做柯理化函数编程思想 .柯里化思想的应用:bind,redux,包括它的中间件的源码,jQuery中的很多源码,高阶组件,真实项目中等还有很多也是用柯里化实现的.
实现函数fn,让其具有如下功能(百度二面)
let res = fn(1,2)(3);
console.log(res); //=>6 1+2+3
/*代码详解*/
function fn(...outerArgs) {
// outerArgs = [1,2]
return function anonymous(...innerArgs) {
// innerArgs = [3]
let args = outerArgs.concat(innerArgs);
return args.reduce((sum, item) => {
return sum + item;
}, 0);
}
}
let res = fn(1, 2)(3);
console.log(res); //=>6 1+2+3
reduce
// reduce依次遍历数组中的每一项,每一次遍历都会触发回调函数执行
// => n:如果reduce不传递第二个参数,第一次获取的是数组第一项,其余每一次获取的值是上一次回调函数处理的结果(传递第二个参数,第一次获取的是第二个实参信息)
// => m:依次遍历的数组每一项
let arr = [10, 20, 30, 40];
let total = arr.reduce((n, m) => {
// 第一次: n=10 m=20
// 第二次:n=30 m=30
// 第三次:n=60 m=40
console.log(n, m);
return n + m;//100
});
console.log(total);
生活中离不开闭包,闭包,堆栈内存是最底层的中的东西,永远也离不开他的.