闭包与封装:让代码学会"社恐"和"自律"的艺术

70 阅读6分钟

今天在JavaScript世界里,我见证了代码如何像人类一样学会"社交恐惧"和"自律"的神奇技能。私有变量像社恐患者一样躲着外部世界,节流函数像自律达人一样控制行为频率,而闭包则是它们的秘密基地。让我们一起来看看这些有趣的编程技巧吧!

一、私有变量:代码中的"社恐患者"

在下述代码中,我们看到了一位资深社恐患者——私有变量的精彩表演:

function Book(title, author, year) {
            // 对内 私有
            // 对外
            let _title = title; // _开始的变量 叫做私有变量 内部 有利于可读性的编程风格
            let _author = author;
            let _year = year;
            this.getTitle = function () { // 对外的方法
                return _title; // 可以访问私有变量
            }
            // 私有方法
            function getFullTitle() {
                return `${_title} by ${_author} `;
            }

            this.getFullInfo = function () {
                return `${getFullTitle()},published in ${_year}`
            }

            this.getAuthor = function () {
                return _author;
            }
            this.getYear = function () {
                return _year;
            }
            // 类的声明者,和类的使用者,可能是两拨人
            
        }
        let book = new Book('JavaScript高级程序设计', 'Nicholas C.Zakas', '2011')

这个Book类就像个社交恐惧症患者:

  • 私有变量(_title_author_year)是它的内心世界,外面无法直接访问
  • 公共方法(getTitlegetFullInfo)是它对外沟通的有限渠道
  • 尝试直接访问私有属性?book._title只会得到undefined,就像对社恐患者问隐私问题只会得到沉默

更有趣的是,我们还能给私有属性设置"门禁系统":

this.updateYear = function(newYear) {
    if (typeof newYear === 'number' && newYear > 0) {
        _year = newYear; // 只有符合条件的访客才能进入
    } else {
        console.error('Invalid year'); // 其他人吃闭门羹
    }
}
console.log(book._title); // 私有属性不能通过实例访问 undefined
        console.log(book.getTitle()); // 可以通过实例访问 私有属性
        console.log(book.getFullInfo()); // 可以通过实例访问 私有方法
        book._year = '2025' // 私有属性不能通过实例修改
        book.updateYear('1234')
        book.updateYear(2015)
        console.log(book.getYear());

这种封装的好处显而易见:

  1. 保护内部状态:外部无法直接修改关键数据
  2. 隐藏实现细节:用户只需关心接口,不必了解内部复杂逻辑
  3. 数据验证:确保输入符合预期,避免脏数据

当我们试着访问的时候,我们是访问不到我们的私有变量的,当然也不能修改,只能通过对象返回的对象从而进行对私有变量进行操作

image.png

二、节流函数:代码中的"自律达人"

继昨天学习了防抖函数分享了一下自己学习看法,今天再来分享一下今天所学的节流函数,这是昨天关于防抖的文章分享大家可以看看闭包与防抖:代码里的“拖延症患者”和“隐私守护者

那么什么是节流呢?简单来说:就是每隔单位时间内一定执行一次

在下述代码中,我们遇到了一位极度自律的节流函数,它严格控制着自己的行为频率:

function throttle(fn, delay) {
    let last, deferTimer;
    
    return function(...args) {
        let that = this;
        let now = +new Date();
        
        if (last && now < last + delay) {
            clearTimeout(deferTimer);
            deferTimer = setTimeout(() => {
                last = now;
                fn.apply(that, args);
            }, delay);
        } else {
            last = now;
            fn.apply(that, args);
        }
    };
}

这位自律达人的行为准则:

  1. 首次见面热情响应(立即执行)
  2. 频繁接触保持距离(延迟执行)
  3. 最后请求必定回应(确保最终执行)

在输入框搜索建议中的应用:

let throttleAjax = throttle(ajax, 2000);
inputC.addEventListener('keyup', (e) => {
    throttleAjax(e.target.value);
});

在这里我们假设了一个AJAX请求,模拟一下向后端请求数据,可以知道的,如果我们不设置节流,就会频繁触发请求,会对我们的后端服务器造成极大的性能消耗,如果用户一多,就有种像隐形的DDOM攻击

1.gif

效果就像个贴心的助手:

  • 你疯狂输入时,它不会频繁打扰服务器
  • 你停止输入后,它才优雅地发送最后一次请求
  • 避免像过度热情的推销员那样狂轰滥炸

我们来看看防抖和节流的对比

项目节流(Throttling)防抖(Debouncing)
中文名节流防抖
英文名ThrottleDebounce
核心思想在一段时间内只执行一次函数在一段时间内没有再次触发才执行函数
举例滚动事件中每隔一定时间检查位置输入框输入时,停止输入后才触发搜索
执行时机触发后,每隔固定时间执行一次触发后,等待指定时间不再触发才执行

三、闭包:代码中的"记忆大师"

闭包就像拥有超强记忆力的朋友,它能记住创建时的环境。在下述代码中,我们看到三种解决this丢失问题的巧妙方法:

我们先来看看,下述代码

 const obj = {
            message: "hello from the object!",
            init: function () {
                const button = document.getElementById('myButton');
                // const that = this
                button.addEventListener('click', function () {
                    console.log(this.message);
                })
            }
        }
        obj.init();

执行结果为什么是undefined呢?

image.png

原因就在于我们在点击事件中,this是指向我们事件本身的,所以得到了undefined

1. "老派绅士" - that = this

init: function() {
    const button = document.getElementById('myButton');
    const that = this; // 记住当下的自己
    button.addEventListener('click', function() {
        console.log(that.message); // 还是原来的我
    })
}

2. "时尚达人" - 箭头函数

sayHello: function() {
    setTimeout(() => {
        console.log(`${this.name} says hello!`); // 自带记忆功能
    }, 1000)
}

3. "专业绑定师" - bind

sayHello: function() {
    setTimeout(function() {
        console.log(`${this.name} says hello!`);
    }.bind(this), 1000) // 绑定专属身份
}

这三种方法本质上都是闭包的应用,它们记住了创建时的上下文,避免在回调中迷失自我。

这里还是我们老生常谈的this的问题,在事件和延迟函数,我们的this都会丢失,这时候我们上诉三种方法就防止了我们的this丢失问题

四、闭包应用全景图

闭包在JavaScript中扮演着多重角色:

应用场景作用类比
防抖(debounce)确保只执行最后一次操作耐心等待说完最后一句话
节流(throttle)单位时间只执行一次自律的会议发言者
绑定上下文解决this丢失问题身份认证系统
事件监听维持回调函数上下文记忆增强芯片
记忆函数缓存计算结果提升性能过目不忘的天才
柯里化分步传递参数分步烹饪指南
模块模式创建私有作用域秘密基地建设者

五、闭包的双面人生

闭包虽好,但也像爱吃内存的小怪兽:

function createHeavyObject() {
    const hugeData = new Array(1000000).fill("🍔"); // 巨型汉堡数组
    
    return {
        eatBurger: function() {
            return hugeData.pop(); // 每次只吃一个汉堡
        }
    };
}

const burgerMachine = createHeavyObject();

这里即使我们只需要eatBurger方法,整个hugeData数组依然会留在内存中,因为闭包记住了它的存在!

解决方案:

  1. 避免不必要的闭包
  2. 及时解除引用:burgerMachine = null
  3. 模块化设计,限制作用域

结语:闭包与封装的艺术

今天的学习旅程让我们看到:

  • 私有变量如社恐患者,精心保护自己的内心世界
  • 节流函数如自律达人,优雅控制行为频率
  • 闭包如记忆大师,忠实地保存执行环境
  • IIFE如秘密基地,创造安全的私有空间