普通视图

发现新文章,点击刷新页面。
昨天 — 2025年6月8日首页

JavaScript篇:函数间的悄悄话:callee和caller的那些事儿

2025年6月8日 22:26

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

Snipaste_2025-06-03_13-45-06.png

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个做了6年前端的老司机。今天想和大家聊聊JavaScript中两个比较有意思的属性——arguments.calleefunction.caller。别看它们平时不显山不露水,但在某些场景下还真能帮上大忙!

一、arguments.callee:我是谁?

先说说arguments.callee,它指向当前正在执行的函数本身。这个特性在匿名函数中特别有用。

// 举个栗子:计算阶乘
function factorial(n) {
  if (n <= 1) return 1;
  return n * arguments.callee(n - 1);  // 这里用arguments.callee代替函数名
}

console.log(factorial(5)); // 输出120

以前我写递归函数时,如果函数名改了,还得手动修改递归调用的地方,用了arguments.callee就不用担心这个问题了。

不过要注意,在严格模式下('use strict')是不能使用arguments.callee的,ES6的箭头函数中也没有这个属性。

二、function.caller:谁在叫我?

再来说说function.caller,它指向调用当前函数的函数。这个属性可以帮助我们追踪函数的调用链。

function outer() {
  inner();
}

function inner() {
  console.log(inner.caller); // 会输出outer函数的代码
}

outer();

记得有一次我调试一个复杂项目时,有个函数莫名其妙被调用了,用console.trace()又觉得信息太多,这时候caller属性就帮了大忙,让我一眼就看出是谁调用了这个函数。

三、使用场景与注意事项

虽然这两个属性很方便,但现在已经不推荐使用了,原因有几点:

  1. 严格模式下会报错
  2. 影响JavaScript引擎的优化
  3. 现代JavaScript有更好的替代方案

比如递归函数可以用命名函数表达式:

const factorial = function me(n) {
  if (n <= 1) return 1;
  return n * me(n - 1);  // 使用命名函数表达式
};

而调用栈追踪可以用console.trace()或者Error对象的stack属性。

四、总结

虽然calleecaller曾经是JavaScript中的"黑科技",但随着语言发展,现在已经有更好的替代方案了。作为开发者,我们既要了解这些特性(说不定哪天维护老代码就用上了),也要知道更现代的解决方案。

你还知道哪些JavaScript中的"老古董"特性?欢迎在评论区分享你的经验!

JavaScript篇:回调地狱退散!6年老前端教你写出优雅异步代码

2025年6月8日 22:26

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

Snipaste_2025-06-03_13-45-06.png

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个干了6年前端的老油条。今天想和大家聊聊  “回调地狱”  这个让无数前端开发者头疼的问题。

曾经,我在维护一个老项目时,看到这样的代码:

getUser(userId, function(user) {
    getOrders(user.id, function(orders) {
        getOrderDetails(orders[0].id, function(details) {
            updateUI(details, function() {
                console.log("终于渲染完了!");
            });
        });
    });
});

一层套一层,像俄罗斯套娃一样,看得我头皮发麻。这就是典型的  “回调地狱”(Callback Hell) ,代码不仅难读,还容易出错,调试起来更是噩梦。

那么,怎么才能优雅地解决这个问题呢?今天我就分享几个 实战中常用的方案,让你告别回调地狱,写出更清爽的异步代码!


1. Promise:让异步代码更线性

ES6 引入的 Promise 是解决回调地狱的第一大利器。我们可以把上面的代码改写成这样:

function 我的获取用户数据(userId) {
    return new Promise((resolve, reject) => {
        getUser(userId, (user) => {
            if (user) resolve(user);
            else reject("用户不存在");
        });
    });
}

我的获取用户数据(userId)
    .then(user => getOrders(user.id))
    .then(orders => getOrderDetails(orders[0].id))
    .then(details => updateUI(details))
    .then(() => console.log("渲染完成!"))
    .catch(err => console.error("出错啦:", err));

优点:
✅ 代码结构更清晰,链式调用让逻辑更线性
✅ 错误处理更简单,一个 .catch 就能捕获所有异常


2. Async/Await:让异步代码像同步一样写

如果你觉得 .then() 还是不够直观,async/await 可以让异步代码看起来和同步代码一样:

async function 我的渲染流程() {
    try {
        const user = await 我的获取用户数据(userId);
        const orders = await getOrders(user.id);
        const details = await getOrderDetails(orders[0].id);
        await updateUI(details);
        console.log("渲染完成!");
    } catch (err) {
        console.error("出错啦:", err);
    }
}

我的渲染流程();

优点:
✅ 代码更符合直觉,就像写同步代码一样
✅ try/catch 错误处理更自然


3. 终极方案:Promise + Async/Await + 解构

如果多个请求可以并行,我们可以结合 Promise.all 进一步提升效率:

async function 我的高效数据加载() {
    try {
        const [user, config] = await Promise.all([
            我的获取用户数据(userId),
            fetchConfig()  // 假设另一个接口
        ]);
        
        const orders = await getOrders(user.id);
        const { details, related } = await getFullOrderInfo(orders[0].id);
        
        await updateUI({ user, orders, details, related });
        console.log("所有数据加载完成!");
    } catch (err) {
        console.error("加载失败:", err);
    }
}

我的高效数据加载();

优点:
✅ 并行请求,减少等待时间
✅ 数据组合更灵活,避免层层嵌套


4. 额外技巧:命名函数代替匿名回调

有时候,回调地狱是因为 匿名函数嵌套太深,我们可以用 命名函数 来优化:

async function 加载用户(userId) {
    return await 我的获取用户数据(userId);
}

async function 加载订单(user) {
    return await getOrders(user.id);
}

async function 加载详情(order) {
    return await getOrderDetails(order.id);
}

async function 我的优化版流程() {
    const user = await 加载用户(userId);
    const orders = await 加载订单(user);
    const details = await 加载详情(orders[0]);
    await updateUI(details);
    console.log("优化版流程完成!");
}

我的优化版流程();

优点:
✅ 每个步骤独立,方便复用和测试
✅ 代码可读性更高,调试更方便


总结:如何避免回调地狱?

方案 适用场景 优点
Promise 需要链式调用时 比回调更清晰,支持 .catch
Async/Await 需要更直观的代码 让异步代码像同步一样
Promise.all 多个并行请求 提升加载速度
命名函数 复杂逻辑拆分 提高可读性和复用性

回调地狱不是不能写,而是 有更好的写法。掌握这些技巧后,你的异步代码会变得更优雅、更易维护!

如果你有更好的方案,欢迎在评论区交流~觉得有用的话,别忘了 点赞 & 收藏,我们下期见! 🚀

eval:JavaScript里的双刃剑,用好了封神,用不好封号!

2025年6月7日 20:55

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

Snipaste_2025-06-03_13-45-06.png

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个写了6年前端的老司机。今天要聊一个让人又爱又恨的JavaScript特性——eval()。这玩意儿就像编程界的"瑞士军刀",功能强大但危险系数极高,新手容易滥用,老手又避之不及。到底该不该用?怎么安全地用?今天我就用几个血泪教训带大家彻底搞懂它!


一、eval是什么?代码里的"魔术师"

简单说,eval()把字符串当代码执行

const codeStr = 'console.log("Hello eval!")';
eval(codeStr); // 输出:Hello eval!

我第一次见到这功能时惊为天人:"这不就是动态执行代码的黑科技吗?!" 于是兴冲冲地写了个"万能计算器":

function calculate(expr) {
  return eval(expr); // 千万别学我!
}

console.log(calculate('2 + 2 * 3')); // 8
console.log(calculate('Math.pow(2, 10)')); // 1024

结果上线三天就被安全团队约谈了... (后面会讲为什么)


二、为什么说eval是"危险分子"?

1. 安全漏洞:XSS攻击直通车

如果执行用户输入的字符串:

const userInput = "alert('你的cookie是:'+document.cookie)";
eval(userInput); // 完蛋,用户脚本被执行了!

2. 性能杀手:引擎优化全失效

JS引擎原本可以预编译和优化代码,但遇到eval()就不得不:

  • 启动解释器
  • 创建新作用域
  • 放弃静态分析

3. 调试噩梦

错误堆栈会显示eval at <anonymous>,根本找不到问题源头!


三、安全使用eval的三大法则

虽然危险,但在某些场景下不得不用(比如解析JSON的老浏览器环境)。这时要遵守:

法则1:永远不要直接执行用户输入

// 错误示范
eval(req.body.userCode);

// 正确做法
function safeEval(code) {
  if (/alert|document|window/.test(code)) {
    throw new Error('危险代码!');
  }
  return eval(code);
}

法则2:用Function构造器替代

const calculator = new Function('expr', 'return (' + expr + ')');
console.log(calculator('2 + 2')); // 4

优点

  • 只在全局作用域执行
  • 稍微安全一丢丢

法则3:严格模式限制

"use strict";
eval = 1; // 报错!严格模式下不能覆盖eval

四、真实案例:我踩过的三个坑

案例1:动态生成函数

// 需求:根据API返回的函数名执行对应方法
const funcName = apiResponse.method; // 比如"showDialog"

// 菜鸟时期的我:
eval(funcName + '()'); // 可能执行任意代码!

// 现在的我:
const allowedMethods = { showDialog: true };
if (allowedMethods[funcName]) {
  window[funcName]?.();
}

案例2:JSON解析(上古时期)

// 2008年的老代码(那时候没有JSON.parse):
const data = eval('(' + jsonStr + ')');

// 2023年的正确姿势:
const data = JSON.parse(jsonStr);

案例3:沙箱环境

// 用Proxy做个简单沙箱
function safeEval(code) {
  const sandbox = new Proxy({}, {
    has: () => true, // 欺骗in操作符
    get: (target, key) => {
      if (['window','document'].includes(key)) 
        throw new Error(`禁止访问 ${key}`);
      return target[key];
    }
  });
  return (new Function('with(this){return ' + code + '}')).call(sandbox);
}

五、现代替代方案

场景 eval做法 更优方案
动态执行代码 eval(str) Function构造函数
JSON解析 eval('('+json+')') JSON.parse
动态属性访问 eval('obj.'+key) obj[key]
模板引擎 eval拼接字符串 模板字面量${}
数学表达式计算 eval('1+1') 第三方库(如math.js)

六、什么时候非用eval不可?

  1. 开发调试工具(如浏览器控制台本身)
  2. 编写DSL语言(如某些低代码平台)
  3. 教学演示(比如教人理解AST解析)

我在写在线代码编辑器时,最终选择了new Function()+Web Worker的方案,既安全又不会阻塞主线程。


总结:eval如老虎,摸前要三思

  • ✅ 能用别的方案就别用eval
  • ✅ 必须用时严格过滤输入
  • ✅ 优先考虑Function构造函数

你们在项目里用过eval吗?有没有因此翻过车?欢迎在评论区分享你的"惊魂时刻"~

我是小杨,下期可能会讲《如何安全地动态执行代码》,感兴趣的话点个关注不迷路! 🔐

JavaScript篇:前端定时器黑科技:不用setInterval照样玩转循环任务

2025年6月7日 20:34

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

Snipaste_2025-06-03_13-45-06.png

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个干了快6年的前端老司机。今天要和大家分享一个特别实用的定时器技巧——用setTimeout实现setInterval。这个方案不仅能解决setInterval的一些痛点,还能让我们的定时任务更加可控。

一、为什么不用setInterval?

先说说我为什么研究这个方案。去年在做一个大屏数据实时刷新功能时,发现直接用setInterval会有个恶心的问题:

setInterval(() => {
  // 模拟网络请求
  console.log('执行任务', new Date().getSeconds());
}, 1000);

看起来每1秒执行一次对吧?但如果网络卡顿导致函数执行超过1秒呢?这时候就会发现多个任务挤在一起执行,就像早高峰的地铁一样让人崩溃。

二、setTimeout的救场方案

后来我改用setTimeout递归调用的方式,完美解决了这个问题:

function 我的循环任务() {
  console.log('执行任务', new Date().getSeconds());
  
  // 在函数末尾重新调用自己
  setTimeout(我的循环任务, 1000);
}

// 启动任务
setTimeout(我的循环任务, 1000);

这个方案的精妙之处在于:每次都是等上次任务完全执行完,才重新计时。就像排队上厕所,必须等前一个人出来,下个人才能进去。

三、升级版:可控定时器

后来我又做了个加强版,加上了启动/停止功能:

let timer = null;
let count = 0;

function 我的可中断任务() {
  console.log(`执行第${++count}次`, new Date().getSeconds());
  
  if(count < 5) { // 只执行5次
    timer = setTimeout(我的可中断任务, 1000);
  }
}

// 启动
timer = setTimeout(我的可中断任务, 1000);

// 随时可以停止
// clearTimeout(timer);

这样写有三个好处:

  1. 避免任务堆积
  2. 可以精确控制执行次数
  3. 随时能终止任务

四、实战中的应用场景

这个技巧在我工作中帮了大忙,比如:

  1. 轮询接口:检查订单状态,直到支付成功
  2. 动画序列:实现复杂的多段动画效果
  3. 倒计时:更精准的秒表功能
// 倒计时示例
function 倒计时(剩余秒数) {
  console.log(`剩余:${剩余秒数}秒`);
  
  if(剩余秒数 > 0) {
    setTimeout(() => 倒计时(剩余秒数 - 1), 1000);
  }
}

倒计时(10); // 开始10秒倒计时

五、注意事项

虽然这个方案很香,但也要注意:

  1. 记得保存timer变量,否则没法清除
  2. 递归调用要注意停止条件,避免内存泄漏
  3. 长时间运行的任务可能会造成调用栈过深

六、总结

setTimeout实现setInterval的方案,就像是用乐高积木拼出了现成玩具的功能,虽然多写几行代码,但获得了更大的灵活性和可控性。特别适合需要精确控制执行时机的场景。

大家如果有更好的实现方案,欢迎在评论区交流~如果觉得有用,别忘了点赞收藏!

JavaScript篇:自定义事件:让你的代码学会'打小报告'

2025年6月7日 20:05

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

Snipaste_2025-06-03_13-45-06.png

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个在前端界摸爬滚打6年的老油条。今天我要和大家聊聊怎么让代码组件之间"说悄悄话"——没错,就是自定义事件!

一、为什么需要自定义事件?

想象一下这个场景:

// 传统写法:直接调用
function updateUser() {
    updateProfile();
    updateAvatar();
    updateSettings();
    // 我加了新功能还得回来改这里!
}

// 事件驱动写法
document.dispatchEvent(new CustomEvent('userUpdated'));

小杨解说:自定义事件就像办公室里的广播系统,谁想听就自己接,不用挨个通知!

二、基础用法:创建和监听

1. 创建自定义事件

// 简单版
const event = new Event('myEvent');

// 高级版(可以带数据)
const event = new CustomEvent('myEvent', {
    detail: {
        name: '我',
        age: 18
    }
});

2. 监听事件

document.addEventListener('myEvent', function(e) {
    console.log(`收到事件!数据:${e.detail.name} ${e.detail.age}`);
});

三、实战案例:购物车系统

// 商品组件
class Product {
    addToCart() {
        document.dispatchEvent(new CustomEvent('cartAdd', {
            detail: { id: 123, name: '前端秘籍' }
        }));
    }
}

// 购物车组件
document.addEventListener('cartAdd', function(e) {
    console.log(`把 ${e.detail.name} 加入购物车`);
});

// 用户组件
document.addEventListener('cartAdd', function() {
    console.log('更新用户购物车数量');
});

小杨踩坑记:曾经没加detail导致数据传丢,debug到怀疑人生!

四、高级技巧

1. 事件命名空间

// 避免冲突
document.dispatchEvent(new CustomEvent('me:cartAdd'));

2. 事件冒泡控制

const event = new CustomEvent('bubbleEvent', {
    bubbles: true,  // 允许冒泡
    cancelable: true // 允许取消
});

3. 移除监听

function handleEvent() {
    console.log('我只执行一次!');
    document.removeEventListener('oneTimeEvent', handleEvent);
}
document.addEventListener('oneTimeEvent', handleEvent);

五、Vue/React中的自定义事件

1. Vue版

// 子组件
this.$emit('me-event', { data: 123 });

// 父组件
<Child @me-event="handleEvent" />

2. React版

// 父组件
<Child onMeEvent={handleEvent} />

// 子组件
props.onMeEvent({ data: 123 });

六、性能优化

  1. 避免滥用:太多事件会让代码变成"广播体操"
  2. 及时销毁:SPA记得在组件卸载时移除监听
  3. 事件池:高频事件考虑复用事件对象

七、与原生事件的区别

特性 自定义事件 原生事件
触发方式 手动dispatch 浏览器自动触发
事件类型 任意自定义名称 click/keydown等
数据传递 通过detail 有限的事件对象

八、总结

  • 自定义事件是解耦神器
  • 适合组件通信、插件开发等场景
  • 记得给事件起个清晰的名字
  • 移除不需要的监听防止内存泄漏

思考题

const event = new CustomEvent('meetup', {
    detail: { time: new Date() }
});

document.addEventListener('meetup', function(e) {
    console.log(e.detail.time.toLocaleString());
});

setTimeout(() => {
    document.dispatchEvent(event);
}, 1000);
// 1秒后事件触发时,输出的时间是创建时还是触发时的时间?

欢迎在评论区讨论你的答案!下期我会分享更多前端设计模式的实战技巧。

昨天以前首页

JavaScript篇:"闭包:天使还是魔鬼?6年老司机带你玩转JS闭包"

2025年6月5日 21:12

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

Snipaste_2025-06-03_13-45-06.png

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个被JS闭包折磨了6年又爱又恨的前端工程师。今天我要带大家深入理解闭包这个让人又爱又恨的特性,分享那些年我踩过的坑和总结的优化技巧。

一、闭包是什么?一个简单的例子

function outer() {
    let me = '小杨';
    return function inner() {
        console.log(`大家好,我是${me}`);
    };
}
const sayHello = outer();
sayHello(); // "大家好,我是小杨"

看到没?inner函数记住了outer函数的me变量,这就是闭包!

二、闭包的三大妙用(天使面)

1. 创建私有变量

function createCounter() {
    let count = 0;
    return {
        increment() { count++ },
        getCount() { return count }
    };
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined

2. 实现函数柯里化

function multiply(a) {
    return function(b) {
        return a * b;
    };
}
const double = multiply(2);
console.log(double(5)); // 10

3. 事件处理中的妙用

function setupButtons() {
    for(var i = 1; i <= 3; i++) {
        (function(index) {
            document.getElementById(`btn-${index}`)
                .addEventListener('click', function() {
                    console.log(`我是按钮${index}`);
                });
        })(i);
    }
}

三、闭包的三大坑(魔鬼面)

1. 内存泄漏

function leakMemory() {
    const bigData = new Array(1000000).fill('*');
    return function() {
        console.log('我还记得bigData');
    };
}
const leaked = leakMemory();
// bigData本应该被回收,但闭包让它一直存在

2. 性能问题

function slowPerformance() {
    const data = {}; // 大对象
    return function(key, value) {
        data[key] = value;
        // 每次调用都要访问闭包变量
    };
}

3. 意外的变量共享

function createFunctions() {
    let funcs = [];
    for(var i = 0; i < 3; i++) {
        funcs.push(function() {
            console.log(i); // 全是3!
        });
    }
    return funcs;
}

四、闭包优化六大法则(6年经验总结)

1. 及时释放引用

function createHeavyObject() {
    const heavy = new Array(1000000).fill('*');
    return {
        useHeavy: function() {
            // 使用heavy
        },
        cleanup: function() {
            heavy = null; // 手动释放
        }
    };
}

2. 使用块级作用域

// 修复前面的共享变量问题
function createFixedFunctions() {
    let funcs = [];
    for(let i = 0; i < 3; i++) { // 使用let
        funcs.push(function() {
            console.log(i); // 0,1,2
        });
    }
    return funcs;
}

3. 避免不必要的闭包

// 不好的写法
function unneededClosure() {
    const data = {};
    return function() {
        // 根本不使用data,却形成了闭包
        console.log('Hello');
    };
}

// 好的写法
function noClosure() {
    console.log('Hello');
}

4. 使用WeakMap管理私有变量

const privateData = new WeakMap();

class MyClass {
    constructor() {
        privateData.set(this, {
            secret: '我是私有数据'
        });
    }
    
    getSecret() {
        return privateData.get(this).secret;
    }
}

5. 合理使用IIFE

// 立即执行函数减少闭包生命周期
(function() {
    const tempData = processData();
    // 使用tempData
})(); // 执行完立即释放

6. 使用模块化

// 模块化天然适合管理闭包
const counterModule = (function() {
    let count = 0;
    
    return {
        increment() { count++ },
        getCount() { return count }
    };
})();

五、真实案例分享

案例1:我曾经在项目中遇到一个页面卡顿问题,最后发现是因为一个事件处理函数形成了闭包,持有了一个大DOM树的引用。解决方案是:

// 修复前
function setup() {
    const bigElement = document.getElementById('big');
    button.addEventListener('click', function() {
        // 持有bigElement引用
        console.log(bigElement.id);
    });
}

// 修复后
function setup() {
    const id = 'big';
    button.addEventListener('click', function() {
        // 只存储需要的id
        console.log(id);
    });
}

六、总结

闭包就像一把双刃剑:
✅ 优点:实现私有变量、函数柯里化、模块化等
❌ 缺点:可能导致内存泄漏、性能问题

记住我的6年经验总结:

  1. 及时释放不再需要的引用
  2. 优先使用块级作用域
  3. 避免不必要的闭包
  4. 合理使用WeakMap和模块化
  5. 善用开发者工具检查内存

最后留个思考题:

function createFunctions() {
    let funcs = [];
    for(var i = 0; i < 3; i++) {
        funcs.push(function(j) {
            return function() {
                console.log(j);
            };
        }(i));
    }
    return funcs;
}
const funcs = createFunctions();
funcs[0](); // 输出什么?为什么?

欢迎在评论区讨论你的答案!下期我会分享更多JS高级技巧。

JavaScript篇:解密JS执行上下文:代码到底是怎么被执行的?

2025年6月5日 20:49

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

Snipaste_2025-06-03_13-45-06.png

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个和JS相爱相杀6年的前端工程师。今天我要带大家揭开JavaScript代码执行的神秘面纱,保证让你看完后恍然大悟:"原来我的代码是这样跑的!"

一、执行上下文:代码的"舞台"

想象一下,JS引擎就像个剧场,每次函数调用就像一场新的演出。而执行上下文就是这个演出的"舞台",决定了哪些"演员"(变量和函数)可以上场。

先看个简单例子:

function sayHello() {
    let me = '小杨';
    console.log(`大家好,我是${me}`);
}
sayHello();

当调用sayHello()时,JS就会创建一个新的执行上下文。

二、执行上下文的"人生三阶段"

每个执行上下文都会经历三个阶段:

  1. 创建阶段:准备舞台

    • 创建变量对象(VO)
    • 建立作用域链
    • 确定this指向
  2. 执行阶段:正式演出

    • 变量赋值
    • 函数调用
    • 执行代码
  3. 销毁阶段:演出结束

    • 出栈等待垃圾回收

三、变量提升的真相

来看个经典例子:

console.log(me); // undefined
var me = '小杨';
console.log(me); // '小杨'

为什么不会报错?因为在创建阶段,变量声明会被提升,但赋值不会。

小杨踩坑记

function test() {
    console.log(me); // undefined
    if(false) {
        var me = '小杨';
    }
}
test();

即使if条件为false,变量声明依然会被提升!

四、作用域链:变量的"寻亲记"

let name = '全局小杨';

function outer() {
    let name = '外层小杨';
    
    function inner() {
        console.log(name); // '外层小杨'
    }
    inner();
}
outer();

JS会沿着作用域链一层层往上找变量,就像寻亲一样。

五、this指向:最难捉摸的"演员"

this的指向总让人头大,记住几个规则:

  1. 普通函数调用:this指向window(严格模式undefined)
  2. 方法调用:this指向调用对象
  3. new调用:this指向新创建的对象
let obj = {
    me: '小杨',
    say: function() {
        console.log(this.me);
    }
};

obj.say(); // '小杨'
let fn = obj.say;
fn(); // undefined (this指向window)

六、闭包:执行上下文的"遗产"

function createCounter() {
    let count = 0;
    return function() {
        count++;
        console.log(count);
    };
}

let counter = createCounter();
counter(); // 1
counter(); // 2

即使createCounter的执行上下文已经销毁,内部函数依然能访问count变量,这就是闭包的魔力。

七、实战应用:避免常见坑

  1. 避免变量污染
// 错误做法
for(var i=0; i<5; i++) {
    setTimeout(() => {
        console.log(i); // 全是5
    }, 100);
}

// 正确做法
for(let i=0; i<5; i++) {
    setTimeout(() => {
        console.log(i); // 0,1,2,3,4
    }, 100);
}
  1. 合理使用闭包
// 缓存计算结果
function createCache() {
    let cache = {};
    return function(key, value) {
        if(value !== undefined) {
            cache[key] = value;
        }
        return cache[key];
    }
}

八、总结

  • 执行上下文是JS代码执行的环境
  • 变量提升和作用域链是理解JS的关键
  • this指向需要根据调用方式判断
  • 闭包可以让函数"继承"执行上下文的变量

最后留个思考题:

let obj = {
    me: '小杨',
    say: () => {
        console.log(this.me);
    }
};
obj.say(); // 输出什么?为什么?

欢迎在评论区讨论你的答案!下期我会详细讲解箭头函数的this指向问题。

JavaScript篇:如何实现add(1)(2)(3)()=6?揭秘链式调用的终极奥义!

2025年6月3日 08:16

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

技术qq交流群:906392632

大家好,我是小杨,一个沉迷于JavaScript各种骚操作的前端老司机。今天咱们来玩点有意思的——如何实现一个可以无限链式调用的add函数,最终在空调用时返回累加结果?听起来是不是很酷?让我们一步步揭开这个技巧的神秘面纱!

一、先看看我们要实现的效果

add(1)(2)(3)();      // 期望输出 6
add(1,2,3)(4)();     // 期望输出 10
add(1)(2,3)(4,5)();  // 期望输出 15

这种函数调用方式在函数式编程中被称为柯里化(Currying) ,但比普通柯里化更灵活,因为它支持:

  1. 单参数或多参数调用
  2. 无限链式调用
  3. 空调用时返回计算结果

二、基础版:单参数链式调用

我们先从简单的单参数版本来理解核心思路:

function add(num) {
  let sum = num;
  
  const innerAdd = (nextNum) => {
    if (nextNum === undefined) {
      return sum;
    }
    sum += nextNum;
    return innerAdd;
  };
  
  return innerAdd;
}

console.log(add(1)(2)(3)()); // 输出 6

关键点解析

  1. add函数初始化累加值

  2. 返回的innerAdd函数可以:

    • 接收新数字并累加,然后返回自身(继续链式调用)
    • 无参数调用时返回累加结果
  3. 通过闭包保持对sum的引用

三、升级版:支持多参数调用

现在我们来增强功能,支持每次调用传入多个参数:

function add(...args) {
  let sum = args.reduce((acc, val) => acc + val, 0);
  
  const innerAdd = (...nextArgs) => {
    if (nextArgs.length === 0) {
      return sum;
    }
    sum += nextArgs.reduce((acc, val) => acc + val, 0);
    return innerAdd;
  };
  
  return innerAdd;
}

console.log(add(1,2,3)(4)()); // 输出 10
console.log(add(1)(2,3)(4,5)()); // 输出 15

改进点

  1. 使用剩余参数...args接收任意数量参数
  2. reduce计算参数总和
  3. 同样通过闭包保持sum的状态

四、终极版:支持直接取值和链式调用

有时候我们可能想直接获取当前值而不需要空调用:

function add(...args) {
  let sum = args.reduce((acc, val) => acc + val, 0);
  
  const innerAdd = (...nextArgs) => {
    sum += nextArgs.reduce((acc, val) => acc + val, 0);
    return innerAdd;
  };
  
  // 添加valueOf方法,可以在需要原始值时自动调用
  innerAdd.valueOf = () => sum;
  
  // 添加toString方法,方便输出查看
  innerAdd.toString = () => sum.toString();
  
  return innerAdd;
}

// 使用方式1:传统空调用
console.log(add(1)(2)(3)()); // 输出 6

// 使用方式2:直接参与运算(自动调用valueOf)
const result = add(1)(2)(3) + 4; // 6 + 4 = 10
console.log(result); // 输出 10

// 使用方式3:直接输出(自动调用toString)
console.log(add(1)(2)(3)); // 输出 6

高级技巧

  1. 实现valueOf方法让对象在需要原始值时自动转换
  2. 实现toString方法让对象在被当作字符串时友好显示
  3. 这样函数既可以被链式调用,也可以直接参与运算

五、原理深度剖析

这个实现的魔法主要依赖于几个JavaScript特性:

  1. 闭包(Closure) :内部函数保持对外部变量的引用
  2. 高阶函数(Higher-order Function) :函数返回函数
  3. 剩余参数(Rest Parameters) :处理不定数量参数
  4. 对象原始值转换:通过valueOftoString控制对象到原始值的转换

六、实际应用场景

虽然这种写法看起来很炫酷,但在实际项目中要谨慎使用。适合的场景包括:

  1. 构建数学计算库的流畅接口
  2. 创建DSL(领域特定语言)
  3. 函数式编程工具函数
  4. 面试时展示JS功底(笑)

七、扩展思考:如何实现减法?

基于同样思路,我们可以扩展出支持加减乘除的链式计算器:

function calc(initial = 0) {
  let result = initial;
  
  const methods = {
    add: (...args) => {
      result += args.reduce((a, b) => a + b, 0);
      return methods;
    },
    subtract: (...args) => {
      result -= args.reduce((a, b) => a + b, 0);
      return methods;
    },
    valueOf: () => result
  };
  
  return methods;
}

const total = calc()
  .add(1,2).subtract(3)
  .add(4).add(5,6) + 7;
console.log(total); // 输出 16

八、总结与最佳实践

  1. 核心模式:函数返回函数 + 闭包保存状态

  2. 参数处理:使用剩余参数处理多参数情况

  3. 终止条件:空调用或隐式转换触发结果返回

  4. 注意事项

    • 这种模式可能降低代码可读性
    • 在团队项目中要确保大家都理解这种写法
    • 考虑使用TypeScript添加类型提示

记住,强大的JavaScript特性是一把双刃剑,用得好能让代码更优雅,用不好会让同事抓狂。关键是找到平衡点!

❌
❌