简介:《JS公开课资料及笔记》由邵山欢整理,面向零基础初学者,系统讲解从语法基础到ES6新特性的完整知识链,涵盖变量与数据类型、运算符、DOM操作、BOM处理、事件监听、函数与闭包、异步编程、面向对象、Node.js基础及前端框架入门,其中每个主题都配有简明示例,适合想要快速上手Web前端开发的学习者。压缩包内共16个文件,以12个HTML实战案例为主,搭配1份PDF课堂笔记和3张辅助图片,整体仅1.49MB,便于直接查看和运行;HTML示例包括点击盒子变蓝、仿网易选项卡、水平菜单、鼠标移入移出等交互场景,笔记则对各章节要点做了梳理。目前已有529人学习下载。这套资料的价值在于用可运行的小案例串联知识点,从第一个程序到综合实例循序渐进,覆盖从入门到进阶的常见问题,方便边看边练,为后续深入学习Vue、React等框架打下扎实基础。
1. 从一份JavaScript公开课笔记说起:为什么我拿它当索引而不是从头读
如果你手头有一份叫“[邵山欢]javascript公开课资料及笔记”的文档,我的建议是先别从头读到尾。这份资料看起来是按公开课集数整理的讲义和代码,但JavaScript知识点太散,从第一集顺序往下看,很容易出现“函数刚讲到一半被事件打断、事件还没吃透又开始讲框架”的混乱感。更好的用法是把它当成一本能按问题定位的索引:想弄清“javascript判断数据类型”就翻类型章节,想弄懂“javascript函数”为什么这么设计就翻函数章节,遇到“javascript运行时报错”就去错误排查部分。
这份资料适合谁?新手需要一个能把语言基础、浏览器交互、常见库串起来的顺序;熟手需要一个方便回查的笔记结构,用来准备分享或做内部培训。我这次把整理思路和踩坑经验完整写出来,你可以直接照着搭自己的学习环境。
2. 先把学习路线理清楚:公开课资料该按什么顺序吃透JavaScript
2.1 拿到资料后先建一个“可检索的本地笔记仓库”
公开课资料和笔记经常是几十个 Markdown 文件加一堆示例代码,如果随手塞进“桌面/新建文件夹”,后面想找一个概念只能凭记忆翻。我拿到手会先花十分钟建一个按主题分类的本地仓库,把所有笔记都放进去,并用命令行检索。
mkdir -p js-notes/{01-type,02-function,03-event,04-error,05-browser,06-lib,07-tools} cd js-notes git init cat > README.md <<'EOF' # JavaScript 公开课资料与笔记 按主题整理,不按课时整理。每个主题一个目录。 EOF find . -name "*.md" -exec grep -n "闭包" {} +这段命令做的事情不复杂但很有用:mkdir -p后面的花括号会展开成 7 个目录,分别对应类型、函数、事件、错误、浏览器、框架和工具链;git init是给自己的“后悔药”,哪天删错笔记或改乱代码,可以回到上一个提交;最后一条find加grep在所有 Markdown 文件里搜“闭包”,输出文件名和行号。我不用数据库,也不用在线笔记,就是本地纯文本加全文检索,因为 JS 笔记要不停粘贴代码块,纯文本格式最省心。
为什么按主题而不是按课时?公开课通常一集会同时讲函数、对象和事件,如果按集数整理,“this 指向”会散在十几集里,到后面根本想不起来在哪见过。按主题整理,一份资料就变成了一张地图:一个主题一个目录,目录里只放和该主题相关的讲义摘录、代码示例、踩坑记录。这样学习时不会迷路,回查时也只要一个grep就能定位。
2.2 学习路线排序:类型、函数、事件、框架/库,而不是跟着视频集数走
把资料整理好之后,下一个问题是按什么顺序学。我的排序是固定的:先类型,再函数,再事件,再错误排查,然后才轮到浏览器 API 和框架/库。这个顺序和视频集数没关系,而是按“依赖关系”排的:后面的知识点几乎都依赖前面的基础。
| 学习顺序 | 主题 | 为什么放在这里 |
|---|---|---|
| 1 | javascript判断数据类型 | 几乎所有代码都会用到类型判断,而且这里最容易踩坑 |
| 2 | javascript函数 | 闭包、作用域、this 是语言特性核心,不啃透后面寸步难行 |
| 3 | javascript事件 | 浏览器交互的入口,事件委托和冒泡是高频面试点 |
| 4 | javascript运行时报错 | 学会读错误堆栈,才能把前面学的东西用在真实项目里 |
| 5 | Canvas、FullCalendar 等浏览器场景 | 有语言基础后,再学具体 API 才不会一头雾水 |
| 6 | OC和JavaScript互相调用、Kettle等扩展 | 工作里遇到了再补,纯前端笔记不必全列 |
我一般不会在刚开始就钻进框架或库。很多公开课会提前放一段 FullCalendar 或某个组件库的演示,看起来效果很好看,但如果没有函数和作用域的基础,你连this指向都看不清。框架或库本质上就是一组能帮你生成跨浏览器兼容代码的工具和函数,它们建立在原生语言之上,所以把原生基础打牢,再看框架就是看“它帮我做了什么”。
扩展内容更不需要进主链路。就像“kettle中javascript代码”,那是 ETL 工具里内嵌的 JavaScript 环境,语法相近但运行环境和浏览器完全不同;“OC和javascript互相调用”更是要原生环境配合。这些我都建议单独建目录,什么时候工作需要什么时候再看,绝不放进主学习路线里。
3. 亲手记一遍JavaScript四块硬骨头:类型判断、函数、事件、运行时报错
3.1 数据类型判断:typeof 会骗你,笔记里要写“边界”
数据类型判断是资料里第一个值得认真记的章节。最朴素的反应是直接用typeof,但typeof有很多反直觉的地方,比如typeof null返回的是"object",数组返回的也是"object",typeof NaN返回的又是"number"。如果笔记里只写一句“typeof 能判断类型”,那这段笔记基本是废纸。
真正稳妥的通用方法是借助Object.prototype.toString,它会返回每个内置类型的标准标记。
function typeName(value) { return Object.prototype.toString.call(value).slice(8, -1); } console.log(typeName(null)); // "Null" console.log(typeName([])); // "Array" console.log(typeName(new Date())); // "Date" console.log(typeName(() => {})); // "Function"这段代码的逻辑是:Object.prototype.toString调用后,会返回"[object Null]"、"[object Array]"这样的字符串。slice(8, -1)就是从第 8 个字符开始取,去掉开头的"[object ",再去掉末尾的"]",最后只剩下类型名。参数 8 是因为"[object "恰好 8 个字符,-1表示截到倒数第一个字符之前。
笔记里我会专门留一个边界区,把容易判断错的例子写清楚:
typeof null === 'object',而不是'null';typeof [] === 'object',判断数组要用Array.isArray;typeof function(){} === 'function',这是函数和对象的明显区别;instanceof适合判断对象是否属于某个构造函数的实例,但跨iframe场景会失效,因为不同全局环境里的构造函数不是同一个。
这段话的意思是:typeof只适合快速判断原始类型,遇到null、数组、日期和自定义对象,要继续用Object.prototype.toString或instanceof。公开课资料里通常只讲一个笼统的“判断类型方法”,我会在笔记旁边补上这些边界,因为面试和项目里考的就是边界。
3.2 函数笔记:作用域、闭包、this 指向要分开记
函数是 JavaScript 里信息密度最大的一块,也是最容易“听得懂、写不对”的地方。我的笔记会把它拆成作用域、闭包和this三部分,每一部分都配一个能独立运行的例子。
闭包最经典的开场是计数器:
function createCounter() { let count = 0; return function () { count += 1; return count; }; } const counter = createCounter(); counter(); // 返回 1 counter(); // 返回 2这里的逻辑是:createCounter执行时创建了一个局部变量count,然后返回一个匿名函数。这个匿名函数本身没有count,但它定义在createCounter内部,所以它会把外层作用域里的count留在自己的作用域链上。即使createCounter已经执行完,count也不会被回收,这就是闭包。笔记要写一句最关键的话:闭包让函数记住了定义时所在作用域,而不是调用时所在作用域。
this就正好相反,普通函数的this由调用方式决定,而不是定义位置决定。
const obj = { name: 'public-course', getName: function () { return this.name; } }; console.log(obj.getName()); // "public-course" const getter = obj.getName; console.log(getter()); // 这里 this 不是 objgetName作为obj.getName()调用时,this指向obj;一旦把它赋值给getter再单独调用,this就变成undefined或全局对象,取决于运行环境。箭头函数则相反,它不绑定自己的this,会按定义位置向外层找。所以处理回调时,我常用箭头函数来“锁死”外层this:
class Button { constructor() { this.count = 0; this.click = this.click.bind(this); } click() { this.count += 1; } }bind会返回一个永久改变this的新函数,这在事件回调里特别有用。笔记里要写清楚的选型规则是:普通函数适合做方法,因为它能拿到调用者;箭头函数适合做回调,因为它不需要自己的this;当两者都不方便时,用bind显式绑定。函数这块没有捷径,只能在笔记里反复记“调用方式决定 this”这一句话,并配合实验。
3.3 事件笔记:监听、冒泡、委托,用例子代替概念
事件是浏览器开发的骨架。公开课资料里经常会讲冒泡和捕获,但只讲概念的话,新手还是不知道为什么页面上明明监听了父容器,结果绑定在子元素上的处理器没生效。我会把事件笔记的重点放在“事件委托”上,因为这是一个能直接减少代码量的实用技巧。
const list = document.querySelector('#list'); list.addEventListener('click', (e) => { const li = e.target.closest('li'); if (li) { console.log('点击了:', li.textContent); } });这段代码的逻辑是:只在#list上绑定一次click监听,事件发生时通过e.target拿到真正被点击的元素,再用closest('li')向上找最近的li。这样以后往列表里新增li,不需要再给每个新元素单独绑定监听,因为事件会冒泡到list,由挂在上面的处理器统一处理。
参数这块要注意三点:e.target是实际触发事件的元素,不是绑定监听的元素;closest会从当前元素一路向上找匹配的祖先,找不到就返回null;如果直接写e.target.matches('li'),心里要清楚它只检查“当前元素”本身,一旦点到li里的span就会漏掉。用closest更稳。
事件流顺序也写进笔记:捕获阶段从根节点往下到目标,目标阶段触发目标上的监听,冒泡阶段从目标往上回到根节点。日常开发里 90% 的监听都在冒泡阶段处理。e.stopPropagation()能阻止事件继续向上传播,e.preventDefault()阻止默认行为,比如阻止表单提交或链接跳转。这两个方法经常被一起混用,笔记里必须分开记:一个管传播路径,一个管默认行为。
3.4 运行时报错:把错误当线索,不要当黑匣子
学习 JavaScript 到一定程度,代码一定会跑不出预期结果。公开课资料如果不能教会你读错误,那后面的 Canvas、FullCalendar 都学不踏实。我的错误笔记会用一个标准模板记录,每次遇到报错都尽量按模板走。
try { const data = JSON.parse('{"name": "js-course"}'); processData(data); } catch (err) { console.error('错误名:', err.name); console.error('错误信息:', err.message); console.error('错误堆栈:', err.stack); }这里try模拟一个可能出错的区域,catch里的err是错误对象。err.name告诉我们错误类型,err.message是给人读的说明,err.stack是调用堆栈。排查时我只看两个东西:错误名是TypeError、ReferenceError还是SyntaxError,以及堆栈里第一次出现自己项目文件的行号。
常见的对应关系是:读取undefined的属性会抛TypeError;使用没有声明的变量会抛ReferenceError;数组下标越界或递归太深会抛RangeError;JSON.parse遇到不合法字符串会抛SyntaxError。笔记里要加一个“运行时报错自查清单”:先看错误名,再看 message,最后顺着 stack 找自己的代码。最怕的就是只截图 error.message 然后到处问,因为 message 常常只能说明是哪类错误,不能说明是哪一行。
这段笔记的价值在于,它把“运行时报错”从不可控的黑匣子变成了可检索的线索。后面只要再遇到相似报错,我会立刻查自己的错误笔记,而不是重新搜索一遍全部资料。
4. 把公开课里的代码变成自己的笔记:Canvas、小数保留、FullCalendar 这些经典场景
4.1 保留两位小数:浮点数精度问题不是玄学,是二进制浮点
在很多格式化场景里都要求保留两位小数,最常见的就是价格和百分比。公开课里通常简单一句toFixed(2)带过,但真要拿它去处理1.005时,结果可能会让你意外:二进制浮点数在十进制小数的转换上天然有误差,直接乘 100 再四舍五入未必能得到你想要的“四舍五入”。
我的笔记里会放一个带方案的例子:
function round2(value) { return Math.round((value + Number.EPSILON) * 100) / 100; } console.log(round2(2.675)); // 常见做法:加一个最小单位再乘 100这里的Number.EPSILON是浮点数能识别的最小差值,加上它能修正一部分“刚好差一点点”的情况;* 100是为了把两位小数变成整数再取整,/ 100再还原。但我会在注释旁边写清楚:这不算银弹,不同数值组合下仍然可能出现边界误差。
公开课资料教到这里其实是合格的,但真实项目里我更保守。要展示时用toFixed,要计算时先把金额转成“分”这样的整数再运算:
const yuan = 1.005; const cents = Math.round(yuan * 100); // 先转成整数分 console.log(cents); // 可能得到 100,而不是 101这个现象会让你觉得“JavaScript 在针对我”,其实是十进制小数在二进制里很多都不能精确表示。笔记里必须记录一条结论:不要拿浮点数直接做金额计算,保留两位小数之前,先确认自己是“只用来显示”还是“还要继续算”。显示用toFixed,计算用整数分,这样才能避免后面返工。
4.2 Canvas 与素材:公开课里那朵云彩,自己画一遍
Canvas 是公开课里视觉效果最明显的一章,但也是最容易“照着画一遍就以为会了”的章节。资料里经常给一张图,可能是云彩素材、粒子动画或者折线图。我记 Canvas 笔记时会专门拆解一次绘图命令,而不是整段复制。
const canvas = document.querySelector('#sky'); const ctx = canvas.getContext('2d'); canvas.width = 640; canvas.height = 400; ctx.fillStyle = '#87CEEB'; ctx.fillRect(0, 0, canvas.width, canvas.height); function drawCloud(x, y, scale) { ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(x, y, 30 * scale, 0, Math.PI * 2); ctx.arc(x + 35 * scale, y - 15 * scale, 40 * scale, 0, Math.PI * 2); ctx.arc(x + 70 * scale, y, 30 * scale, 0, Math.PI * 2); ctx.fill(); } drawCloud(120, 100, 1);getContext('2d')是拿到 2D 绘图上下文,后续所有绘图命令都作用在这个上下文上;fillRect(x, y, width, height)从左上角开始填充矩形;ctx.arc(x, y, r, startAngle, endAngle)的五个参数分别是圆心横坐标、圆心纵坐标、半径、起始角和结束角,Math.PI * 2代表整个圆。beginPath()很重要,它告诉 canvas“下面要画一个新的图形”,不然多个云朵会粘连在一起。
我把这个云彩函数当成“素材生成器”,以后想在页面上加点氛围感,不需要去网上找图片,直接用函数画就可以。真正做动画时再配合requestAnimationFrame:每帧先clearRect清空画布,再按新坐标重新画一次。这份笔记写清楚后,Canvas 就从“看一遍视频”变成“自己手里的一套小工具”。
4.3 FullCalendar 这类框架/库:先跑官方示例,再记 API
FullCalendar 是公开课资料里很常见的一个日历框架。框架或库本质上是一组能帮你生成跨浏览器兼容代码的工具和函数,它把很多重复的月份计算、星期排列、事件渲染工作封装好。但框架也有一个麻烦:版本更新快,API 经常变,公开课里抄下来的老代码换到新版可能直接报错。
我记 FullCalendar 笔记时,会先写一个“最小可跑示例”,再往上加自己的需求:
const calendarEl = document.getElementById('calendar'); const calendar = new FullCalendar.Calendar(calendarEl, { initialView: 'dayGridMonth', locale: 'zh-cn', headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay' }, events: [] }); calendar.render();这里关键是搞清楚四个配置:initialView是首次渲染的视图;locale控制中文文案;headerToolbar左边是上一页/下一页按钮和今天按钮,中间是大标题,右边是月/周/日视图切换;events用来传事件数据。要记清楚的是,这段代码只描述“结构”,不能保证不同版本都跑通,所以笔记顶部要写“记录当时所用的版本号”,遇到报错先问自己“是不是版本不匹配”。
我的习惯是先把官方示例原样跑通,再改成自己的数据。不要一上来就把几十个配置项全复制到项目里,那样出错时根本分不清是数据问题、配置问题还是版本问题。这个笔记习惯对所有 JavaScript 框架/库都适用:先最小化复现,再逐项加参数;记笔记时只记“为什么配这个参数”,不记“配了以后看起来怎么样”。
5. 避坑:公开课笔记最常见的 5 个翻车现场
5.1 现象:只抄代码不抄边界,类型判断写到一半就翻车
只看公开课里的typeof示例,就会把数组、null和对象全判断成"object"。原因不是代码写错,而是笔记只记了“能用的场景”,没记“不能用的边界”。解决方法是每段笔记后面加一个“边界提醒”,把null、数组、instanceof跨环境问题都写上去。我后来回查笔记都会先看边界区域,因为那里才是真正区分懂不懂的地方。
5.2 现象:函数笔记里闭包会写,但用事件回调时 this 又丢了
闭包只是函数的一半,另一半是this。现象是对象方法被当作事件回调时,方法里的this拿不到对象属性。原因很简单:普通函数的this由调用方式决定,回调时调用者变成了事件系统,this自然就变了。解决方法是直接记住:需要对象的方法时用bind固定this,或者用箭头函数引用外层this。笔记里每个函数示例后,我都会补一行“这里的 this 是什么”,这一行已经帮我避免好几次线上事故。
5.3 现象:事件委托点不到具体的 li
事件委托代码看起来没问题,但点击列表里的文字时,e.target指向的是span,不是li,于是条件判断失败。原因是我只用了e.target.matches('li'),没有考虑事件目标可能是更深层的子元素。解决方法是改用e.target.closest('li'),让匹配逻辑向上找最近的li。这个坑是公开课里讲得最少、实际项目里出现最多的,必须单独记。
5.4 现象:看到运行时报错就慌,只会截图 error.message,不会看堆栈
报错信息一长串,经常淹没在第三方库的调用栈里。原因是没有建立“错误对象三要素”的意识,其实err.name是类型、err.message是描述、err.stack是定位线索。解决方法是把console.error(err.name, err.message, err.stack)这个组合写进笔记,遇到报错先看stack里第一行是自己的文件:那个文件行号就是出问题的地方。这个习惯能省掉大半调试时间。
5.5 现象:把 OC 和 JavaScript 互相调用的代码直接搬进 Web 项目
公开课有一段“OC和javascript互相调用”的演示,表面看是 JavaScript 代码,实际依赖 iOS WebView 注入的桥对象。纯 Web 项目里根本没有这个对象,一调用就报not a function。原因是没有区分运行环境。解决方法是把这类代码放到“原生桥接”单独目录,笔记头标注“需要原生环境”,同时提醒自己“kettle中javascript代码”也是一样,Kettle 环境有自己的 API,不能拿浏览器那套直接套。环境区分,永远是 JavaScript 笔记的第一原则。
6. 用“回写”验证笔记:把公开课资料变成自己的速查手册
我整理这套资料的过程中,走过一段弯路:笔记越抄越多,但过两周再看,只记得“我没有记熟”。后来改成一个笨办法,叫“回写”——先拼记忆,再开卷对照。
做法很简单。拿到一份公开课笔记,先只看章节标题,把代码块遮住,凭记忆写一遍核心示例。写完之后再打开资料逐行对比,把对不上的地方用另一种颜色标记到笔记最前面。这些“对不上的地方”不是抄错了,而是我真实的理解缺口,比资料里所有知识点都更值得反复看。
下面的自测代码就是我平时用来检查基础笔记的工具:
function typeName(value) { return Object.prototype.toString.call(value).slice(8, -1); } console.assert(typeName([]) === 'Array', '数组类型判断失败'); console.assert(typeName(null) === 'Null', 'null 类型判断失败'); console.assert(typeName(new Date()) === 'Date', 'Date 类型判断失败');这段代码没有依赖任何框架,直接在浏览器控制台或 Node 里都能跑。console.assert的第一个参数是断言条件,第二个参数是失败时打印的信息。我习惯把当天刚学的函数也放进这种自测函数里,比如事件委托的closest写法、闭包的计数器,每个主题至少补一条断言。
回写最大的好处是:它逼我放弃“看过就会”的错觉。以前我会觉得公开课老师讲得我都懂,一旦让我合上资料写,才意识到漏掉了多少个参数。现在我的每份 JavaScript 笔记末尾都留着一个“回写自查区”,只列标题和自测用例,不看答案地过一遍,过不了就回到对应章节补锅。
这个习惯是我自己踩了半年坑才捡回来的,现在每次学新库、新框架,都会先造一份类似的速查脚本,而不是直接开项目。希望帮到你,也希望你的资料笔记能真的变成知识,而不是一堆躺在硬盘里的 Markdown 文件。
本文还有配套的精品资源,点击获取