JS闭包全解

文章目录

  • 闭包原理
    • 链接:浏览器的底层机制
    • 什么是闭包
  • 闭包的优缺点和作用
    • 作用
    • 缺点
  • 闭包的应用
    • 循环事件绑定
    • 高阶单例模式
    • 惰性函数
    • 科里化函数
    • compose:组合函数
    • 防抖函数
      • 最初级:防止频繁点击触发
      • 升级版:保证上一个逻辑处理完成后再执行下一次
    • 节流
  • 闭包梳理,你是怎么理解闭包的:

闭包原理

  1. 堆栈内存
  2. ECStack 、EC、VO、AO、Scope、ScopeChain
  3. 浏览器垃圾回收机制

链接:浏览器的底层机制

什么是闭包

  1. 闭包是一种机制:只要函数执行,形成自己的私有上下文,保护了自己的私有变量,不被外界干扰就会形成闭包
  2. 当外界上下文占用了函数执行形成的私有上下文中的变量时,私有上下文得不到出栈释放,使得函数中的变量得以保留不被浏览器的垃圾回收机制销毁

闭包的优缺点和作用

作用

  1. 保护:保护私有变量不受外界干扰
  2. 保存:当私有变量被外界占用时,形成一个不被释放的上下文

缺点

  • 大量应用闭包肯定会导致内存的消耗,所以需要学会 “合理使用闭包”
  • 闭包大量使用时就通过手动释放占用,来管理内存

闭包的应用

循环事件绑定

高阶单例模式

基于闭包的高阶单例模式

  • 用单独的实例来管理当前事物的相关特征[属性和方法](类似于实现一个分组的特点),而此时obj1/obj2不仅仅叫做一个对象,也被成为命名空间
  • 基于闭包管控的单例模式称为:高级单例设计模式,以此来实现模块划分(最早的模块化思想)
let module1 = (function () {
	function query() {}

	function tools() {}

	return {
		name: 'AREA',
		tools
	};
})();
module1.tools(); 

惰性函数

惰性思想:懒,执行过一遍的东西,如果第二遍执行还是一样的效果,则我们就不想让其重复执行第二遍了

function getCss(element, attr) {
	if ('getComputedStyle' in window) {
		return window.getComputedStyle(element)[attr];
	}
	return element.currentStyle[attr];
}
function getCss(element, attr) {
	if ('getComputedStyle' in window) {
		getCss = function (element, attr) {
			return window.getComputedStyle(element)[attr];
		};
	} else {
		getCss = function (element, attr) {
			return element.currentStyle[attr];
		};
	}
	// 为了第一次也能拿到值
	return getCss(element, attr);
}
getCss(document.body, 'margin');
getCss(document.body, 'padding');
getCss(document.body, 'width'); 

科里化函数

柯理化函数思想:利用闭包保存机制,把一些信息预先存储下来(预处理的思想)

题目

let res = fn(1, 2)(3);
console.log(res); //=>6  1+2+3
function fn(...outerArgs) {
	return function anonymous(...innerArgs) {
		// ARGS:外层和里层函数传递的所有值都合并在一起
		let args = outerArgs.concat(innerArgs);
		return args.reduce((n, item) => n + item);
	};
}
let f = fn(10, 20, 30);
console.log(f(40))	// 100
console.log(f(100))	// 160

let res = fn(1, 2)(3);
console.log(res); //=>6  1+2+3
  • 函数内部返回一个函数
  • 通过解构与concat合并里外函数参数
  • reduce累加所有参数
  • 上下文不销毁,形成闭包
  • 这种闭包事先存储值,后期用的机制就叫科里化函数

compose:组合函数

把多层函数嵌套调用扁平化展开

const fn1 = (x, y) => x + y + 10;
const fn2 = x => x - 10;
const fn3 = x => x * 10;
const fn4 = x => x / 10;
function compose(...funcs) {
	// FUNCS:存储按照顺序执行的函数(数组) =>[fn1, fn3, fn2, fn4]
	return function anonymous(...args) {
		// ARGS:存储第一个函数执行需要传递的实参信息(数组)  =>[20]
		if (funcs.length === 0) return args;
		if (funcs.length === 1) return funcs[0](...args);
		return funcs.reduce((N, func) => {
			// 第一次N的值:第一个函数执行的实参  func是第一个函数
			// 第二次N的值:上一次func执行的返回值,作为实参传递给下一个函数执行
			return Array.isArray(N) ? func(...N) : func(N);
		}, args);
	};
}
let res = compose(fn1, fn3, fn2, fn4)(20, 30);
console.log(res);

react中的redux源码中的compose函数用的是另外思想实现的

防抖函数

防抖:在用户频繁触发的时候,我们只识别一次(识别第一次/识别最后一次)

最初级:防止频繁点击触发

    function func() {
        console.log('OK');
    }
    //  防止频繁点击触发:设置标识进行判断 
    let isClick = false;
    document.body.onclick = function () {
        if (isClick) return;
        isClick = true;
        setTimeout(() => {
            console.log('OK');
            isClick = false;
        }, 1000);
    };

存在问题:只能控制执行间隔

升级版:保证上一个逻辑处理完成后再执行下一次

  1. 第一次点击,没有立即执行,等待500MS,看看这个时间内有没有触发第二次,
  2. 有触发第二次说明在频繁点击,不去执行我们的事情(继续看第三次和第二次间隔…)
  3. 如果没有触发第二次,则认为非频繁点击,此时再去触发;
  4. 科里化函数思想
  5. 或者只执行第一次,接下来都不执行,通过immediate 参数来控制
function debounce(func, wait = 500, immediate = false) {
	let timer = null;
	return function anonymous(...params) {
		let now = immediate && !timer;	// timer设置了定时器,说明就不是第一次执行了
		clearTimeout(timer);	// wait 时间内,如果再次触发就清除上一次的定时器
		timer = setTimeout(() => {
			timer = null;
			// 执行函数:注意保持THIS和参数的完整度
			!immediate ? func.call(this, ...params) : null;
		}, wait);
		// 只执行第一次
		now ? func.call(this, ...params) : null;
	};
}

function func() {
    console.log("点击了");
}

document.body.onclick = debounce(func, 1000, true);
  • debounce:实现函数的防抖(目的是频繁触发中只执行一次)
  • @params
    • func:需要执行的函数
    • wait:检测防抖的间隔频率
    • immediate:是否是立即执行(如果为TRUE是控制第一次触发的时候就执行函数,默认FALSE是以最后一次触发为准
  • @return
    • 可被调用执行的函数

节流

  • 比如浏览器滚动事件,浏览器检测到滚动就会触发很多次绑定的事件,也就是说函数的执行频率很高

  • 而节流就是为了缩减这个频率而诞生的

  • 和防抖的区别就在于节流是缩减频率而不是限定为只执行一次

实现

  • throttle:实现函数的节流(目的是频繁触发中缩减频率)
  • @params
    • func:需要执行的函数
    • wait:自己设定的间隔时间(频率)
  • @return
    • 可被调用执行的函数
  • remaining 上次执行后剩下的间隔时间
function throttle(func, wait = 500) {
    let timer = null,
        previous = 0; //记录上一次操作时间
    return function anonymous(...params) {
        let now = new Date(), //当前操作的时间
            remaining = wait - (now - previous);
        if (remaining <= 0) {
            // 两次间隔时间超过频率:把方法执行即可
            clearTimeout(timer);
            timer = null;
            previous = now;
            func.call(this, ...params);
        } 
        if (remaining > 0 && !timer) {	// 如果有定时器就不需要再定时,先把上一次的执行完成再说
            // 两次间隔时间没有超过频率,说明还没有达到触发标准,设置定时器等待即可(还差多久等多久)
            timer = setTimeout(() => {
                clearTimeout(timer);
                timer = null;
                previous = new Date();
                func.call(this, ...params);
            }, remaining);
        }
    };
}

function func() {
    console.log("滚动了");
}

document.body.onscroll = throttle(func, 500);

闭包梳理,你是怎么理解闭包的:

  1. 什么是闭包以及它的产生
    1. 堆栈内存
    2. ECStack 、EC、VO、AO、Scope、ScopeChain
    3. 浏览器垃圾回收机制
  2. 优缺点和作用
    1. 保护
    2. 保存
  3. 分析应用场景 => 实战应用和处理思维
    1. 循环事件绑定
    2. let
    3. 单例设计模式
    4. 高阶函数:惰性函数、柯理化函数、compose函数
  4. 源码分析 => 阅读源码的能力
    1. JQ源码
    2. 函数的防抖和节流
    3. bind
    4. …REACT中的高阶组件/redux源码/react-redux源码)

你可能感兴趣的:(前端技术笔记)