🤙《中级前端进阶方向指南》

《中级前端进阶方向 第一步)JavaScript 微任务 / 宏任务机制》

《中级前端进阶方向 第二步)深入事件循环(Event Loop)》

《中级前端进阶方向 第三步)深入javascript原型链,附学习代码》

《中级前端进阶方向 第四步)深入javascript 闭包、this 、作用域提升,附学习案例源码》

《​​中级前端进阶方向 第五步)深入 javascript高级特性Proxy,附学习案例源码》

《​​中级前端进阶方向 第六步)深入 javascript 高级特性Generator生成器,附案例源码》

1. 模块模式(私有状态)——IIFE / Revealing Module(经典写法)

// 经典 IIFE 模块:私有变量在闭包内,外部只能通过暴露的 API 访问
const CounterModule = (function () {
  // 私有状态(外部无法直接访问)
  let count = 0;
  // 私有方法
  function log() { console.log('count =>', count); }

  // 对外暴露的 API(revealing pattern)
  return {
    increment() {
      count += 1;
      log();
    },
    decrement() {
      count -= 1;
      log();
    },
    getCount() {
      return count; // 只读访问
    }
  };
})();

// 使用
CounterModule.increment(); // count => 1
console.log(CounterModule.getCount()); // 1
// 不能直接访问 count(undefined)
console.log(CounterModule.count); // undefined

变体:传参注入依赖(避免全局)

const AjaxModule = (function (fetchFn) {
  let token = null;
  return {
    setToken(t) { token = t; },
    async get(url) {
      return fetchFn(url, { headers: { Authorization: token }});
    }
  };
})(window.fetch.bind(window)); // 把依赖注入模块

2. ES6 模块(文件级) —— 私有状态天然实现

// file: store.js
let state = { count: 0 }; // module scope 私有
export function increment(){ state.count++; }
export function getState(){ return { ...state }; } // 返回副本以避免外部修改

// file: main.js
// import { increment, getState } from './store.js'

在 ES6 模块中,模块内部的变量就是私有(文件边界),只有被 export 的才是公共的。


3. 工厂函数(Factory Function)——创建具有私有状态的对象

// 简单工厂函数:每个实例都有自己的私有变量
function createCounter(initial = 0) {
  let count = initial; // 私有
  return {
    increment() { count++; return count; },
    decrement() { count--; return count; },
    get() { return count; }
  };
}

const a = createCounter(1);
const b = createCounter(10);
console.log(a.increment(), a.get()); // 2,2
console.log(b.get()); // 10

工厂函数的内存问题与优化(方法共享)

工厂函数每次返回的对象方法都会重新创建,若需要大量实例且方法相同,可以把方法放外部并用 Object.create 共享原型,但仍需私有状态:可用 WeakMap 存私有数据。

// 使用 WeakMap 存储私有数据以实现方法共享
const _private = new WeakMap();
const proto = {
  increment() {
    const p = _private.get(this);
    p.count++;
    return p.count;
  },
  get() {
    return _private.get(this).count;
  }
};

function createCounterShared(initial = 0) {
  const obj = Object.create(proto);
  _private.set(obj, { count: initial });
  return obj;
}

const c1 = createCounterShared(5);
console.log(c1.increment(), c1.get()); // 6,6

4. 回调中保持局部状态(保持计数 / 重试次数 / 上下文)

// 场景:为某个按钮创建事件处理器,内部保持点击次数状态
function bindButtonWithLocalState(btn) {
  let clicks = 0; // 闭包私有
  function handler(e) {
    clicks++;
    console.log('clicked times:', clicks, 'this:', this);
  }
  btn.addEventListener('click', handler);
  // 返回移除函数,避免内存泄漏
  return () => btn.removeEventListener('click', handler);
}

// 假设有 <button id="btn">
const remover = bindButtonWithLocalState(document.getElementById('btn'));
// later...
// remover(); // 卸载处理器并释放闭包对 DOM 的隐式引用(有助防止泄漏)

回调中保持重试计数(例如网络请求)

function fetchWithRetries(url, maxRetries = 3) {
  let attempts = 0;
  return new Promise((resolve, reject) => {
    function attempt() {
      attempts++;
      fetch(url).then(res => {
        if (!res.ok && attempts <= maxRetries) {
          attempt();
        } else if (!res.ok) {
          reject(new Error('Failed after ' + attempts));
        } else {
          resolve(res);
        }
      }).catch(err => {
        if (attempts <= maxRetries) attempt(); else reject(err);
      });
    }
    attempt();
  });
}

5. 防抖(debounce)实现与变体 —— 代码 + 说明

基本防抖(只在停止触发后执行)

function debounce(fn, wait = 200) {
  let timer = null;
  return function (...args) {
    const ctx = this;
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(ctx, args);
    }, wait);
  };
}

// 使用:输入框实时搜索时防抖
const input = document.querySelector('#search');
input.addEventListener('input', debounce(function (e) {
  console.log('search for', e.target.value);
}, 300));

支持立即触发(leading)与 cancel / flush

function debounceWithOptions(fn, wait = 200, options = { leading: false, trailing: true }) {
  let timer = null;
  let lastArgs, lastThis;
  let result;
  const { leading, trailing } = options;

  function invoke() {
    result = fn.apply(lastThis, lastArgs);
    lastArgs = lastThis = null;
  }

  const debounced = function (...args) {
    lastArgs = args;
    lastThis = this;
    const callNow = leading && !timer;
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
      if (trailing && lastArgs) invoke();
    }, wait);
    if (callNow) invoke();
    return result;
  };

  debounced.cancel = function () {
    clearTimeout(timer);
    timer = null;
    lastArgs = lastThis = null;
  };

  debounced.flush = function () {
    if (timer) {
      clearTimeout(timer);
      timer = null;
      if (lastArgs) invoke();
    }
  };

  return debounced;
}

// usage
const fn = debounceWithOptions((v) => console.log('do:', v), 300, { leading: true, trailing: true });
fn('a'); // immediate since leading true
fn('b'); // will schedule trailing
setTimeout(() => fn.flush(), 100); // force flush

rAF 防抖(适合渲染/滚动)

function rafDebounce(fn) {
  let id = null;
  return function (...args) {
    const ctx = this;
    if (id) cancelAnimationFrame(id);
    id = requestAnimationFrame(() => {
      id = null;
      fn.apply(ctx, args);
    });
  };
}

防抖注意事项

  • 若需要最后一次调用结果(例如提交最后的输入),确保 trailing 为 true 或使用 flush()

  • 防抖会保留 lastArgs/lastThis,因此可能会延长闭包中的数据生命周期(注意内存)。

  • 对 promise-returning fn,可以在 invoke 中返回并保留 Promise 以便 debounced 返回值为 Promise(可选扩展)。


6. 节流(throttle)实现与变体 —— 代码 + 说明

时间戳法(leading 即刻触发)

function throttleTimestamp(fn, wait = 200) {
  let previous = 0;
  return function (...args) {
    const now = Date.now();
    const ctx = this;
    if (now - previous >= wait) {
      previous = now;
      fn.apply(ctx, args);
    }
  };
}

定时器法(trailing 执行)

function throttleTimer(fn, wait = 200) {
  let timer = null;
  let lastArgs, lastThis;
  return function (...args) {
    lastArgs = args;
    lastThis = this;
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        fn.apply(lastThis, lastArgs);
      }, wait);
    }
  };
}

lodash 风格(leading/trailing 可选)——综合版

function throttle(fn, wait = 200, options = { leading: true, trailing: true }) {
  let timer = null;
  let previous = 0;
  let lastArgs, lastThis;

  const later = () => {
    previous = options.leading === false ? 0 : Date.now();
    timer = null;
    if (lastArgs) {
      fn.apply(lastThis, lastArgs);
      lastArgs = lastThis = null;
    }
  };

  return function (...args) {
    const now = Date.now();
    if (!previous && options.leading === false) previous = now;
    const remaining = wait - (now - previous);
    lastArgs = args;
    lastThis = this;

    if (remaining <= 0 || remaining > wait) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      previous = now;
      fn.apply(lastThis, lastArgs);
      lastArgs = lastThis = null;
    } else if (!timer && options.trailing !== false) {
      timer = setTimeout(later, remaining);
    }
  };
}

节流注意事项

  • timestamp 法更适合需要最小延迟的场景(即时响应),timer 法适合保证间隔内至少执行一次(最后结尾)。

  • leading/trailing 组合常用:leading 控制是否立即触发,trailing 控制是否在间隔结束时再次触发。

  • 对于需要保持 this 与参数的情况,请用 apply 并保留 lastArgs/lastThis


7. IIFE 捕获(立即执行函数表达式)——循环捕获经典问题与解决

问题:var 循环中的闭包捕获

for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log('var i =>', i); // 3 3 3(预期 0 1 2)
  }, 0);
}

解决方法一:使用 IIFE 捕获每次的 i

for (var i = 0; i < 3; i++) {
  (function (j) {
    setTimeout(function () {
      console.log('i with IIFE =>', j); // 0 1 2
    }, 0);
  })(i);
}

解决方法二:使用 let(块级作用域)

for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log('let i =>', i); // 0 1 2
  }, 0);
}

IIFE 的常见用法

// 1) 创建私有块,避免污染全局
(function () {
  const secret = 'x';
  console.log('IIFE', secret);
})();

// 2) 注入依赖以利于压缩和测试
(function (window, document) {
  // use window, document as local vars -> 压缩时能缩短名称
})(window, document);

// 3) 立即初始化单例
const singleton = (function () {
  const state = {};
  return {
    set(k,v){ state[k]=v; },
    get(k){ return state[k]; }
  };
})();

8. 内存泄漏注意(闭包持有 DOM 的风险)

function attach() {
  const big = document.getElementById('big'); // large DOM subtree
  function onClick() { console.log(big); }
  window.addEventListener('click', onClick);
  // 如果页面上卸载 big,但没有移除 listener,big 会因为闭包而被保留
  return () => window.removeEventListener('click', onClick);
}
const remove = attach();
// later when unmounting:
remove(); // 释放闭包对 big 的引用,允许回收

建议:绑定事件时返回移除函数;在组件卸载时调用;避免闭包持有大型外部资源。


9. 小结(实践建议)

  • 模块私有状态:IIFE / ES6 模块 / WeakMap / 私有字段(#)都可实现,优先使用模块或私有字段(更语义化)。

  • 工厂函数:简单、直观;若要节省内存可用 WeakMap + prototype 共享方法。

  • 回调局部状态:用闭包保存状态(计数、重试、节流状态等),并提供取消 API 以避免泄漏。

  • 防抖/节流:理解 leading/trailing 行为,提供 cancel/flush 接口;留意 this 与参数传递。

  • IIFE:在没有 let 的旧环境或需要立即作用域隔离时非常有用;也常用于参数注入与单例初始化。

  • 内存安全:给长期闭包(事件监听、长定时器)提供卸载/取消逻辑,避免保留对大对象或 DOM 的引用。


10. 源文件案例代码:

1. 模块模式(私有状态)

  • 使用IIFE创建了一个计数器模块

  • 内部状态count是私有的,只能通过公共方法访问

  • 演示了如何封装私有变量和提供公共接口

2. 工厂函数

  • createUser函数返回一个新用户对象

  • 每个对象都有自己的私有状态(创建时间)

  • 展示了如何创建具有封装状态的对象而不使用类

3. 回调中保持局部状态

  • setupCounter函数返回一个闭包

  • 返回的函数保持对count变量的访问

  • 演示了如何在异步回调中保持状态

4. 防抖和节流

  • 实现了防抖和节流函数

  • 防抖:输入框输入时延迟处理,避免频繁触发

  • 节流:按钮点击时限制执行频率

5. IIFE捕获

  • 展示了经典的IIFE在循环中捕获变量值的问题

  • 使用IIFE为每个迭代创建新的作用域,保存正确的值

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>JavaScript 高级特性详解</title>
        <style>
            body {
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                line-height: 1.6;
                color: #333;
                max-width: 1000px;
                margin: 0 auto;
                padding: 20px;
                background-color: #f5f7fa;
            }
            h1 {
                color: #2c3e50;
                text-align: center;
                margin-bottom: 30px;
            }
            h2 {
                color: #3498db;
                border-bottom: 2px solid #3498db;
                padding-bottom: 5px;
                margin-top: 30px;
            }
            .section {
                background-color: white;
                border-radius: 8px;
                padding: 20px;
                margin-bottom: 30px;
                box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            }
            pre {
                background-color: #f8f9fa;
                padding: 15px;
                border-radius: 5px;
                overflow-x: auto;
                border-left: 4px solid #3498db;
            }
            code {
                font-family: 'Consolas', 'Monaco', monospace;
                font-size: 14px;
            }
            .example {
                margin-top: 15px;
                padding: 15px;
                background-color: #e8f4fc;
                border-radius: 5px;
            }
            button {
                background-color: #3498db;
                color: white;
                border: none;
                padding: 10px 15px;
                border-radius: 4px;
                cursor: pointer;
                margin: 5px;
                transition: background-color 0.3s;
            }
            button:hover {
                background-color: #2980b9;
            }
            .output {
                margin-top: 10px;
                padding: 10px;
                background-color: #f0f0f0;
                border-radius: 4px;
                min-height: 20px;
            }
        </style>
    </head>
    <body>
        <h1>JavaScript 高级特性详解</h1>
    
        <div class="section">
            <h2>1. 模块模式(私有状态)</h2>
            <p>模块模式利用闭包创建私有变量和方法,只暴露公共接口。</p>
            <pre><code>const CounterModule = (function() {
        // 私有变量
        let count = 0;
        
        // 私有方法
        function logChange(action) {
            console.log(`Count ${action}. New value: ${count}`);
        }
        
        // 公共接口
        return {
            increment: function() {
                count++;
                logChange('incremented');
            },
            decrement: function() {
                count--;
                logChange('decremented');
            },
            getValue: function() {
                return count;
            },
            reset: function() {
                count = 0;
                logChange('reset');
            }
        };
    })();</code></pre>
            
            <div class="example">
                <button onclick="counterIncrement()">增加</button>
                <button onclick="counterDecrement()">减少</button>
                <button onclick="counterGetValue()">获取值</button>
                <button onclick="counterReset()">重置</button>
                <div class="output" id="counter-output"></div>
            </div>
        </div>
    
        <div class="section">
            <h2>2. 工厂函数</h2>
            <p>工厂函数是返回新对象的函数,用于创建相似对象而不使用new关键字。</p>
            <pre><code>function createUser(name, age, email) {
        // 私有变量
        const createdAt = new Date();
        
        // 公共接口
        return {
            getName: () => name,
            getAge: () => age,
            getEmail: () => email,
            getCreatedAt: () => createdAt,
            setName: (newName) => {
                if (newName && newName.length > 0) {
                    name = newName;
                    return true;
                }
                return false;
            },
            getInfo: () => `${name} (${age}), ${email}`
        };
    }</code></pre>
            
            <div class="example">
                <button onclick="createUserExample()">创建用户示例</button>
                <div class="output" id="user-output"></div>
            </div>
        </div>
    
        <div class="section">
            <h2>3. 回调中保持局部状态</h2>
            <p>使用闭包在回调函数中保持对局部变量的访问。</p>
            <pre><code>function setupCounter() {
        let count = 0;
        const outputElement = document.getElementById('callback-output');
        
        return function() {
            count++;
            outputElement.textContent = `按钮被点击了 ${count} 次`;
            
            // 模拟异步操作
            setTimeout(() => {
                outputElement.textContent += ` | 异步更新: ${count}`;
            }, 500);
        };
    }</code></pre>
            
            <div class="example">
                <button id="callback-button">点击我</button>
                <div class="output" id="callback-output"></div>
            </div>
        </div>
    
        <div class="section">
            <h2>4. 防抖和节流</h2>
            <p>防抖和节流是控制函数执行频率的技术。</p>
            
            <h3>防抖 (Debounce)</h3>
            <pre><code>function debounce(func, delay) {
        let timeoutId;
        return function(...args) {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => func.apply(this, args), delay);
        };
    }</code></pre>
            
            <h3>节流 (Throttle)</h3>
            <pre><code>function throttle(func, limit) {
        let inThrottle;
        return function(...args) {
            if (!inThrottle) {
                func.apply(this, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }</code></pre>
            
            <div class="example">
                <h3>防抖示例</h3>
                <input type="text" id="debounce-input" placeholder="输入内容(防抖500ms)">
                <div class="output" id="debounce-output"></div>
                
                <h3>节流示例</h3>
                <button id="throttle-button">频繁点击我(节流1000ms)</button>
                <div class="output" id="throttle-output"></div>
            </div>
        </div>
    
        <div class="section">
            <h2>5. IIFE捕获</h2>
            <p>立即调用函数表达式(IIFE)可以捕获变量在特定时刻的状态。</p>
            <pre><code>// 经典问题:循环中的闭包
    for (var i = 0; i < 5; i++) {
        (function(j) {
            setTimeout(function() {
                console.log('IIFE捕获的值:', j);
            }, j * 500);
        })(i);
    }</code></pre>
            
            <div class="example">
                <button onclick="runIIFEExample()">运行IIFE示例</button>
                <div class="output" id="iife-output"></div>
            </div>
        </div>
    
        <script>
            // 1. 模块模式示例
            const CounterModule = (function() {
                let count = 0;
                
                function logChange(action) {
                    console.log(`Count ${action}. New value: ${count}`);
                    document.getElementById('counter-output').textContent = 
                        `Count ${action}. New value: ${count}`;
                }
                
                return {
                    increment: function() {
                        count++;
                        logChange('incremented');
                    },
                    decrement: function() {
                        count--;
                        logChange('decremented');
                    },
                    getValue: function() {
                        return count;
                    },
                    reset: function() {
                        count = 0;
                        logChange('reset');
                    }
                };
            })();
    
            function counterIncrement() { CounterModule.increment(); }
            function counterDecrement() { CounterModule.decrement(); }
            function counterGetValue() { 
                document.getElementById('counter-output').textContent = 
                    `当前值: ${CounterModule.getValue()}`;
            }
            function counterReset() { CounterModule.reset(); }
    
            // 2. 工厂函数示例
            function createUser(name, age, email) {
                const createdAt = new Date();
                
                return {
                    getName: () => name,
                    getAge: () => age,
                    getEmail: () => email,
                    getCreatedAt: () => createdAt,
                    setName: (newName) => {
                        if (newName && newName.length > 0) {
                            name = newName;
                            return true;
                        }
                        return false;
                    },
                    getInfo: () => `${name} (${age}), ${email}`
                };
            }
    
            function createUserExample() {
                const user = createUser('张三', 28, 'zhangsan@example.com');
                let output = `初始信息: ${user.getInfo()}<br>`;
                
                user.setName('李四');
                output += `修改后: ${user.getInfo()}<br>`;
                output += `创建时间: ${user.getCreatedAt().toLocaleString()}`;
                
                document.getElementById('user-output').innerHTML = output;
            }
    
            // 3. 回调中保持局部状态
            const counterCallback = setupCounter();
            document.getElementById('callback-button').addEventListener('click', counterCallback);
    
            function setupCounter() {
                let count = 0;
                const outputElement = document.getElementById('callback-output');
                
                return function() {
                    count++;
                    outputElement.textContent = `按钮被点击了 ${count} 次`;
                    
                    setTimeout(() => {
                        outputElement.textContent += ` | 异步更新: ${count}`;
                    }, 500);
                };
            }
    
            // 4. 防抖和节流
            // 防抖实现
            function debounce(func, delay) {
                let timeoutId;
                return function(...args) {
                    clearTimeout(timeoutId);
                    timeoutId = setTimeout(() => func.apply(this, args), delay);
                };
            }
    
            // 节流实现
            function throttle(func, limit) {
                let inThrottle;
                return function(...args) {
                    if (!inThrottle) {
                        func.apply(this, args);
                        inThrottle = true;
                        setTimeout(() => inThrottle = false, limit);
                    }
                };
            }
    
            // 防抖示例
            const debounceInput = document.getElementById('debounce-input');
            const debounceOutput = document.getElementById('debounce-output');
            
            const updateDebounceOutput = debounce(function(e) {
                debounceOutput.textContent = `防抖结果: ${e.target.value}`;
            }, 500);
            
            debounceInput.addEventListener('input', updateDebounceOutput);
    
            // 节流示例
            const throttleButton = document.getElementById('throttle-button');
            const throttleOutput = document.getElementById('throttle-output');
            let throttleCount = 0;
            
            const updateThrottleOutput = throttle(function() {
                throttleCount++;
                throttleOutput.textContent = `节流点击次数: ${throttleCount}`;
            }, 1000);
            
            throttleButton.addEventListener('click', updateThrottleOutput);
    
            // 5. IIFE捕获示例
            function runIIFEExample() {
                const outputElement = document.getElementById('iife-output');
                outputElement.innerHTML = '';
                
                for (var i = 0; i < 5; i++) {
                    (function(j) {
                        setTimeout(function() {
                            outputElement.innerHTML += `IIFE捕获的值: ${j}<br>`;
                        }, j * 500);
                    })(i);
                }
            }
        </script>
    </body>
    </html>

更多推荐