Web 前端学习笔记(四):JavaScript 基础
系列最后一篇。HTML 管结构、CSS 管样式,JavaScript 负责行为——响应用户操作、修改页面内容。每节同样附了可运行的 demo。
一、三种导入方式
和 CSS 类似,JS 也有三种写进页面的方式:
1 | <!-- 1. 写在 head 里的内联脚本 --> |
外部的 js/myscript.js 内容也很简单:
1 | console.log("Hello,外联样式"); |
打开 demo 后按 F12 打开控制台,就能看到四行输出(console.log 的内容不会显示在页面上,只在控制台里)。
script 为什么要放在 body 末尾
demo 里的脚本只是打印日志,所以写在 head 里没问题。但一旦脚本要操作页面元素,就必须放在 body 末尾:
1 | <body> |
浏览器是从上往下解析 HTML 的。放在 head 的脚本执行时,<body> 里的元素还没被创建出来,自然找不到。放在末尾就能保证元素都已就绪。
另一种做法是给 <script> 加 defer 或 async 属性,或者把代码包进 DOMContentLoaded 事件里。但放进 body 末尾是最简单、最容易理解的方式,初学阶段推荐就用这个。
二、基本语法
声明变量:var / let / const
1 | var x; // undefined(未赋值) |
| 关键字 | 能否重新赋值 | 说明 |
|---|---|---|
var |
能 | 老写法,作用域规则容易出问题,现在基本不用 |
let |
能 | 现代写法的默认选择 |
const |
不能 | 声明常量,值不会变就用它 |
选择顺序很简单:先想 const,确实需要改再换 let,var 就别用了。这样能避免不小心覆盖变量的值。
数据类型
1 | let name = "何意味"; // 字符串 |
undefined 和 null 容易混:undefined 是”声明了但没赋值”,null 是”我主动把它设成空”。上面 var x; 打印出来是 undefined,而 empty_value 是 null。
条件判断
1 | let age = 9; |
需要多个分支时用 else if:
1 | let time = 22; |
if 的判断条件会从上往下依次检查,命中一个就跳出,后面的即使也成立也不会执行。
循环
1 | // for 循环:适合次数已知的场景 |
for 的三个部分用分号隔开:初始化;条件;每轮结束后执行。while 则需要自己在循环体里改变条件变量,忘了 index++ 就会把浏览器卡死。
break 和 continue
1 | for (let i = 0; i < 7; i++) { |
这段代码的输出是 0 1 3 4:
i=2时被continue跳过,所以没有 2;i=5时被break终止,所以 5 和 6 都不输出。
一句话区分:continue 是”这一轮不干了”,break 是”全都不干了”。
三、函数与作用域
三种常见形态
1 | // 1. 无参数、无返回值 |
关键区别:return 会把结果交回给调用处,所以能赋值给变量;而 console.log 只是打印,函数本身的返回值是 undefined。
作用域
1 | let global_var = '全局变量'; |
规则是单向的:
- 函数内部可以访问外面的全局变量;
- 函数外部访问不到函数内部的局部变量。
因为局部变量只在函数执行期间存在,函数一结束就销毁了。这就是”作用域链”最基本的表现——由内向外查找,不能反向。
原练习文件里最后一行写的是 console.log("全局打印局部变量==报错")——它只是打印了一句字符串,并没有真的访问 local_var,所以不会报错。想复现 local_var is not defined,必须真的去引用它:
1 | console.log(local_var); // ReferenceError: local_var is not defined |
四、事件
事件就是”用户做了什么”,绑定事件就是告诉浏览器”发生这件事时执行哪个函数”。
1 | <button onclick="click_event()">这是一个点击事件按钮</button> |
三种常用事件:
| 事件 | 触发时机 |
|---|---|
onclick |
鼠标点击 |
onfocus |
元素获得焦点(光标进入) |
onblur |
元素失去焦点(光标离开) |
onfocus / onblur 是表单校验的常用时机——比如用户离开输入框时立刻检查格式对不对。
demo 里把 onclick="..." 直接写在 HTML 标签上,这叫内联事件绑定,优点是直观,缺点是 HTML 和 JS 混在一起不好维护。下一篇 demo 会看到更规范的做法:addEventListener。
五、DOM 查找与修改
DOM(文档对象模型)把 HTML 页面变成一棵可以用 JS 操作的对象树。要改页面,第一步永远是先找到那个元素。
三种查找方式
1 | <div id="box1">这是一个id选择器标签</div> |
| 方法 | 返回 | 注意 |
|---|---|---|
getElementById |
单个元素 | 参数不带 # |
getElementsByClassName |
集合(类数组) | 参数不带 .;要加索引取值 |
getElementsByTagName |
集合(类数组) | 要加索引取值 |
后面两个返回的不是数组而是集合,所以不能直接当元素用。忘记加 [0] 是新手最常见的错误之一。
修改内容:innerHTML 和 innerText
1 | var element_id = document.getElementById('box1'); |
这是两者最重要的差别:
innerHTML会解析 HTML,所以第一个 div 里真的出现了一个可点击的链接;innerText只当普通文字,第二个 div 里会原样显示<a href="#">...</a>这串字符。
innerHTML 用起来方便,但不要用它插入用户输入的内容。用户如果输入 <img src=x onerror="恶意代码">,浏览器会真的执行——这是 XSS 攻击的经典入口。要插入用户内容,请用 innerText 或 textContent。
修改样式
1 | element_tag.style.color = "red"; |
注意 CSS 属性名要转成小驼峰:CSS 里的 font-size 在 JS 里写成 fontSize,background-color 写成 backgroundColor。原因是 font-size 里的横线会被当成减号。
绑定事件的两种方式
1 | var button_element = document.getElementsByTagName('button')[0]; |
addEventListener 的优势是同一个元素可以绑定多个同类型处理函数,它们会依次执行;而 onclick = ... 是赋值操作,再赋一次就把前一个覆盖了。
补充:现在更常用 querySelector 和 querySelectorAll,它们直接支持 CSS 选择器语法,比上面三个方法灵活得多:
1 | document.querySelector('#box1'); // 按 id 找,返回第一个 |
六、DOM 节点增删改查
这是一个把前面的知识串起来的综合练习:用按钮动态给表格加行、改内容、删行。
HTML 部分,每行末尾放两个按钮,并且把 this 传进去:
1 | <button onclick="addRow()">新增数据</button> |
按钮上的 this 很关键——它把被点击的那个按钮元素作为参数传给函数,函数才能知道”是哪一行要操作”。
对应的 js/table.js:
1 | // 新增一行 |
三个关键 API
| API | 作用 |
|---|---|
table.insertRow(index) |
在指定位置插入一行 |
row.insertCell(index) |
在行内指定位置插入一个单元格 |
parentNode.removeChild(child) |
从父节点中移除子节点 |
用 parentNode 往回找
deleteRow 和 editRow 里都有一句 button.parentNode.parentNode,这是从按钮反推所在行的固定套路:
1 | button(按钮) |
因为点击事件只能拿到按钮本身,要操作整行就必须往上爬两层。DOM 树是双向的:既能用 children 往下找,也能用 parentNode 往上找。
prompt 在用户点”取消”时返回的是 null,直接赋值会把单元格内容变成字符串 "null"。实际项目里应该加个判断:
1 | if (inputname !== null) { |
七、移动端 rem 适配
这是系列里最”实用”的一个练习,解决的是同一套代码如何适配不同宽度的手机屏幕。
px 的问题
1 | .box-px { |
px 是绝对单位。在 375px 宽的手机上,300px 占了 80% 的宽度;换到 414px 的手机上就只占 72%——比例变了,看起来就不一致。
rem 是什么
rem 是相对于根元素 html 的 font-size 的单位:
1 | html { |
只要改 html 的 font-size,所有用 rem 写的尺寸会整体等比缩放。这就给了我们一个可调节的”缩放旋钮”。
动态设置缩放基准
把屏幕宽度平均分成 10 份,让 1rem 等于其中一份:
1 | // 根据设备宽度计算 html 标签的 font-size 属性值 |
这样在 375px 宽的设备上,1rem = 37.5px;在 414px 上 1rem = 41.4px。设计稿上量出多少 rem,在任何手机上占的比例都一样。
前面 Flex 那篇里写的 html { font-size: 10px; } 是把基准写死(1rem = 10px,纯粹为了好算);这里则是跟着屏幕宽度动态变。两者用途不同:前者方便换算,后者用于适配。
原文件标题里的 “rom” 是 rem 的笔误。
另外这段代码用的是 screen.width(设备屏幕宽度),它不会随浏览器窗口大小改变。所以在电脑上拖动窗口时 onresize 触发的重算结果其实没变化,看不出效果——真机或手机模拟器下才明显。
如果想用视口宽度(更符合直觉,桌面端也能看到效果),应该改成:
1 | document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px'; |
rem 需要手动换算,写起来略麻烦,所以现在更流行 vw 方案(1vw 就等于视口宽度的 1%,不需要 JS 参与),或者用 PostCSS 插件自动把设计稿的 px 转成 rem。理解了 rem 的原理,这些方案都是同一个思路的变体。
小结
| 知识点 | 核心记忆点 |
|---|---|
| 导入方式 | 操作 DOM 的脚本放 body 末尾;用 F12 控制台看输出 |
| 基本语法 | 优先 const,其次 let;continue 跳过本轮,break 结束循环 |
| 函数作用域 | 函数内能访问外面的变量,外面访问不到函数内的 |
| 事件 | onclick / onfocus / onblur;推荐 addEventListener |
| DOM 查找 | getElementById 返回单个,另外两个返回集合要加索引 |
| innerHTML | 会解析 HTML,有 XSS 风险,不要用于用户输入 |
| 增删改查 | insertRow / insertCell / removeChild;parentNode 往上找 |
| rem 适配 | 1rem = html 的 font-size,改基准即可整体缩放 |
四篇总结
整个系列走下来,前端的三个层次已经很清楚了:
| 层 | 负责 | 核心内容 |
|---|---|---|
| HTML | 结构 | 标签、属性、表单 |
| CSS | 样式 | 选择器、盒子模型、布局 |
| JavaScript | 行为 | 语法、事件、DOM 操作 |
这三者里,CSS 布局和 DOM 操作是新手最容易卡住的地方——浮动的高度坍塌、定位的参照物、集合忘记加索引,都是必踩的坑。把 demo 挨个改一改、故意改坏再修好,比只看代码印象深得多。
本系列的 demo 我都放在博客里可以直接运行,见每节开头的链接。
本系列其他文章
