尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JS函数与jQuery核心知识梳理:从this指向到事件委托与实战

JS函数与jQuery核心知识梳理:从this指向到事件委托与实战 很多前端新手学到 JS 函数的时候第一反应通常是这玩意儿到底在什么时候用学到 jQuery 又觉得现在都 React、Vue 满天飞了还学这老古董干嘛我见过不少准备前端面试的人被“函数声明和函数表达式的区别”“this指向”“jQuery 的first-child怎么选”这类基础题问懵。其实不管是原生 JS 还是 jQuery核心思路都是那几条把逻辑拆成函数、把 DOM 操作变得可控、把事件处理理顺。这篇文章我就结合自己带新人和写业务页面的经验把 JS 函数和 jQuery 里最常考、最常用的知识点一次梳理清楚不讲虚的直接给能落地的结论和代码。1. 先把JS函数这层窗户纸捅破1.1 三种函数写法的区别声明、表达式、箭头面试里最常问的一个问题就是“JS 里有哪些定义函数的方式”很多人能背出答案但真让手写却分不清该用哪种。实际上日常开发中就三种函数声明、函数表达式和箭头函数。// 函数声明 function add(a, b) { return a b; } // 函数表达式 const add function(a, b) { return a b; }; // 箭头函数 const add (a, b) a b;这三者的第一个区别是提升机制。函数声明会有“声明提升”你在定义之前调用它也没问题JavaScript 引擎在解析阶段就把整个函数挂到了当前作用域顶部。函数表达式不行它像一个变量赋值定义之前调用会报Cannot access add before initialization。箭头函数本质也是表达式同样没有提升而且它没有自己的arguments对象也没有自己的this。我在实际项目里有一个很简单的选型标准如果这个函数不需要访问调用者的this优先用箭头函数如果需要动态this比如给对象方法、事件回调里要拿当前触发元素就用普通函数或函数表达式。箭头函数写法简洁但正因为简洁很多人误以为它只是“少打几个字”忽略了这个“没有自己的 this”的隐藏属性。这块不理解透后面处理 jQuery 事件回调时会踩不少坑。1.2 this指向与闭包看似玄学背后的两条铁律this指向问题是前端面试的保留节目也是很多新手最头疼的部分。我自己总结了两条非常粗鲁但有效的铁律。第一条看函数怎么被调用。如果是obj.method()这种形式this就指向obj如果是普通调用fn()this就是window严格模式下是undefined。第二条箭头函数不看调用方式它看定义位置定义在哪一层作用域this就继承哪一层。闭包是另一个绕不开的核心概念。一句话解释函数记住并访问了它定义时的作用域即使这个函数在外面执行也能用那份“旧”环境里的变量。闭包最常见的用途是封装私有变量其次是做防抖节流、缓存数据。function createCounter() { let count 0; return function() { count; return count; }; } const counter createCounter(); counter(); // 1 counter(); // 2这个count在外面拿不到只能通过返回的函数修改和读取这就是闭包的经典场景。理解闭包不仅仅是为了面试更关键的是理解事件回调里常见的“循环里用var导致点击事件全是最后一个值”这个问题根本原因就是变量共享了同一个作用域。1.3 回调函数为什么jQuery时代到处是回调JS 是单线程语言很多耗时操作不能傻等于是回调函数成了异步处理的基本单元。回调函数就是“你把一个函数传给另一个函数让它在合适的时机回头调用你”。jQuery 时代最典型的就是 Ajax$.ajax({ url: /api/user, success: function(data) { console.log(data); }, error: function(err) { console.error(err); } });这里success和error都是回调函数jQuery 在请求成功或失败后调用它们。虽然现在大家多用 Promise 和async/await但回调思想依然是理解事件驱动、事件监听、定时器的基础。setTimeout(fn, 1000)里的fn也是回调本质上都是同一件事。2. jQuery核心知识选择器、事件与DOM操作2.1 选择器与节点定位first-child和name属性的高频实战jQuery 最吸引人的地方就是选择器它把document.querySelectorAll的能力提前普及了。在 jQuery 里$(#id)、$(.class)、$(input[nameusername])这些都是常规操作。有个高频搜索词是“jquery 第一个子元素”这个对应的是:first-child选择器。比如$(.list li:first-child).addClass(active);这里选中的是每个.list下第一个li。注意它和:first的区别:first是“所有匹配元素中的第一个”:first-child是“每个父元素下的第一个子元素”。这个区别面试时经常被拿来当陷阱题。另一个高频需求是“jquery根据name获取对象”。如果你知道某个表单控件的name属性可以直接用属性选择器$(input[nameusername]).val();或者更精准一点限定类型再加属性$(input:text[nameusername]).val();这样能避免误选中同名的hidden或radio。实际开发中尤其是做老项目维护时没有统一的id规范靠name和父容器定位是最稳妥的方案。我习惯在封装表单组件时统一用$([name])先过滤出所有带name的表单项再通过each遍历取值这样无论页面结构怎么改只要name不变代码就还能跑。2.2 事件绑定、事件委托与链式调用jQuery 的事件绑定比原生addEventListener好上手的地方在于它把所有事件统一成.on()并且天然支持多个事件的链式处理。$(#btn).on(click, function() { $(this).addClass(active); });这里的$(this)是新手最容易丢的一环。原生事件回调里this是当前 DOM 元素但如果你想调用 jQuery 的方法必须先把this包一层成$(this)否则this.addClass直接报错。事件委托也是 jQuery 用得非常多、但很多新人用不明白的点。所谓事件委托就是把子元素的事件挂到父元素上利用事件冒泡机制去判断实际触发元素。它的好处在于动态添加的子元素不需要重新绑定事件。$(#list).on(click, li, function() { console.log($(this).text()); });如果直接把click绑到li上新增的li是没有事件的。但绑到#list上只要点击的是符合li选择器的元素事件就会被捕获并触发。这招在渲染列表、动态加载表格时特别省心。链式调用是 jQuery 的另一个标志性特性本质就是每个方法返回this所以可以一路点下去$(#box).css(color, red).fadeIn(300).find(span).text(ok);链式调用写起来爽但排查问题的时候建议拆开写。我踩过一次坑一个.css()方法写错了属性名整个链后面全部失效控制台又不报错找问题花了不少时间。后来我只能在项目里立个规矩超过三个链式操作中间拆成变量方便 debug。2.3 遍历与DOM批量操作each、map和三级联动jQuery 的each是处理批量 DOM 操作的利器和原生forEach的用法非常像$(.item).each(function(index, element) { console.log(index, element); });回调里的this是当前 DOM 元素element也是当前 DOM 元素。要操作它同样要$(this)。三级联动下拉菜单是遍历操作的一个经典场景。无论是省市区还是商品分类核心思路都一样用一个数据结构存好全集选中上级后通过each筛选并重建下级选项。数据设计比代码技巧更关键我一般这样组织const data { 北京市: [朝阳区, 海淀区], 上海市: [浦东新区, 杨浦区] };然后监听省级下拉的change事件拿到当前值清空市级下拉再遍历data[当前省]把每一项通过option字符串拼进select。这个过程中用到$.each遍历数组也用到了事件绑定和 DOM 操作是一个把函数、jQuery、数据结构结合起来的小型实战强烈建议新手手写一遍比单纯记 API 有用得多。3. 从函数到jQuery三个拿来即用的实战场景3.1 搜索框防抖与节流函数思维拯救体验搜索框的实时搜索是面试里很喜欢问、业务里也很常见的场景。如果你每次keyup都发请求用户打“前端开发”四个字就会发四次请求既浪费资源又容易因返回顺序不一致导致数据错乱。解决办法就是防抖。防抖的思路很简单函数被连续触发时只执行最后一次。每次触发先清掉上一次的定时器再重新设置一个新的定时器。function debounce(fn, delay) { let timer null; return function(...args) { clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; } $(#search).on(input, debounce(function() { // 发送搜索请求 }, 300));这里用到了闭包去保存timer也用到了apply去修正this。如果不做防抖高频操作页面会非常卡做了防抖以后用户停顿 300 毫秒才触发一次体验立刻顺滑。节流则是另一套思路固定时间间隔内只执行一次适合滚动加载、拖拽场景。这两个函数建议背下来面试手写防抖节流基本是必考。3.2 表单校验把逻辑拆成函数用jQuery收尾表单校验是一个特别适合练“函数拆分”的场景。以前很多人写表单校验是一个submit回调里堆上百行 if后来被同事 review 骂了一顿才改成函数拆分的做法。核心套路是每个校验规则一个函数返回错误信息没有错误就返回空字符串。function validateUsername(value) { if (!value) return 用户名不能为空; if (value.length 3) return 用户名至少3个字符; return ; } function validateEmail(value) { const reg /^[^\s][^\s]\.[^\s]$/; return reg.test(value) ? : 邮箱格式不正确; }提交时用each遍历所有需要校验的字段逐个调用对应校验函数收集错误$(#submit).on(click, function() { let errors []; $(.required).each(function() { const name $(this).attr(name); const value $(this).val().trim(); const error validateField(name, value); if (error) errors.push(error); }); if (errors.length 0) { alert(errors.join(\n)); return; } // 提交逻辑 });这样写的最大好处是每个校验函数可以独立测试需求变更多少都只用改对应函数不用动总控逻辑。我在真实项目里用过这套代码结构维护过三套不同业务线的表单体验非常舒服。3.3 三级联动下拉数据结构设计比代码更重要三级联动在上一节提过这里我想展开讲一下它的函数拆分思路。当时做这个功能我最开始写的版本是把三级逻辑全部塞进一个change回调里结果后面要求“根据省市区回显已有数据”“切换省时保留已选市”改得我头大。后来我换了一种抽象方式把“获取下一级数据”单独提成纯函数把“渲染一个下拉框”提成纯函数把“级联关系清空策略”也提成函数。回调函数只负责“调用它们”。function getCities(province) { return CITY_DATA[province] || []; } function renderSelect($select, options) { $select.empty(); options.forEach(function(item) { $select.append(option value item.value item.name /option); }); }change事件里只需要判断当前变更的是哪一级然后决定清空哪些下级再调用对应的渲染函数。这看起来像过度设计但实际上当项目从两个下拉变四个下拉时这套拆分能救你一命。数据驱动页面函数去处理变化DOM 操作收敛到几个渲染函数里这是我认为 jQuery 项目比较理想的组织方式。4. 前端面试高频题与避坑实录4.1 高频面试题函数提升、字符串包含与忽略大小写先看一道经典面试题console.log(foo); // ? var foo function() {};答案是undefined因为var声明提升了但赋值没有。如果改成函数声明function foo() {}控制台就会打印整个函数。这就是声明提升和变量提升的区别面试官很喜欢拿这个考基本功。字符串相关的题目也很高频比如“js判断字符串是否包含某个字符”。老项目里常见做法是indexOfES6 之后可以用includesconst str Hello World; str.includes(World); // true但如果题目要求“忽略大小写”最稳的办法是统一转成小写再比较const key world; str.toLowerCase().includes(key.toLowerCase()); // true这个简单需求其实非常贴前沿端开发的日常因为很多业务场景里用户输入的大小写是不可控的比如邮箱、验证码、车牌号都可能在服务端或前端先做归一化。4.2 高频面试题jQuery操作细节面试题里经常会有“jQuery 里如何选择第一个子元素”和“根据 name 获取对象”这种操作细节我在 2.1 已经讲过这里再补两个容易错的点。第一个是$(this)和this的混淆。很多人面试手写 jQuery 事件回调时直接写this.text()报错了才反应过来要$(this)。第二个是val()和text()的区别。前者拿表单值后者拿 DOM 文本。如果用来取input的当前值却用了text()会拿到空字符串这种 bug 隐藏得很深。还有一道高频题是“jQuery 对象和 DOM 对象怎么转换”。jQuery 对象是类数组原生 DOM 方法是数组原型链上的两者方法不通用。转换方式很简单$(#id)[0]拿到原生 DOM$(domElement)包成 jQuery 对象。理解这一点在处理混合原生 JS 和 jQuery 的老页面时特别重要。4.3 工程化踩坑为什么“无法将pnpm识别为cmdlet”热搜词里有一条很经典“pnpm : 无法将‘pnpm’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”。这个报错不是 JS 函数层面的问题而是环境变量没有配置好。很多新手在 Windows 下装了 Node.js 和 pnpm然后一执行pnpm就报这个错第一反应是重新安装其实完全没必要。这个报错本质是系统在PATH环境变量指向的目录里找不到pnpm这个可执行文件。解决方案有三步确认 pnpm 是否真的安装了在命令行执行npm ls -g pnpm或pnpm -v如果提示没有命令就用npm install -g pnpm重新安装。找到全局安装路径执行npm prefix -g把输出目录添加到系统环境变量的PATH中。添加完环境变量后新开一个终端窗口再试。类似的报错还出现在执行make、claude等命令时排查思路一样。这条经验放在 JS 函数话题下有点偏但我想强调一个点当你看到“无法识别为 cmdlet、函数、脚本文件”时先不要慌着乱改代码先去查命令是否存在、PATH 是否配置好。很多所谓“环境问题”不是代码问题排查顺序搞对了能省很多时间。4.4 关于IDE和代码跳转的坑VSCode 里“函数变量没办法跳转”这个问题也经常有人搜特别是前端项目里如果你用的别名路径没有配置jsconfig.json或tsconfig.json编辑器是没法解析模块路径的跳转会失效。比如你在项目里用/utils/api这样的别名但 VSCode 不知道指向哪个目录自然跳转不到函数定义。解决方法是检查项目根目录下的 jsconfig/tsconfig确认paths配置{ compilerOptions: { baseUrl: ., paths: { /*: [src/*] } } }还有一类情况是装了太多插件比如某个格式化插件和代码补全插件打架导致跳转功能被禁用。这种情况下可以先禁用所有扩展逐个启用排查。排查工具问题最忌讳一上来就重装系统或重装 IDE先试“最小化复现”也就是把项目、插件、配置逐步去掉变量很快就能定位。我在实际带新人的过程中发现能把 JS 函数和 jQuery 同时讲清楚的人往往对“调用时机”和“作用域”有很直观的理解。函数这东西学明白了再看 jQuery 的每个 API本质上就是一个固定回调、固定参数、固定返回值的地图任务你只是在用自己已经掌握的函数思维去拼装页面逻辑。老项目里拿 jQuery 处理表格、表单、三级联动是常态新项目里函数拆分的思路同样适用于 Vue、React 的组合式函数和自定义 Hook。后面如果再遇到“这个按钮点了没反应”“这个动态加出来的行怎么绑不上事件”这种问题先回到函数和事件委托这两个核心点上去想比满世界搜答案有用得多。
返回列表