JS教程:没有Vue、没有任何框架,纯原生JS**,完整案例。
目标说明
需求:没有Vue、没有任何框架,纯原生JS,完整案例。
包含JS全部基础核心知识点,智慧城市大屏比喻,为后面学习Vue3+TS打基础。
业务场景:智慧小区住户登记大屏(登记住户、展示列表、删除住户、统计欠费人数、表单校验、网络模拟请求)
知识点覆盖:let/const、基础类型、数组、对象、函数、箭头函数、if判断、循环、DOM查询、事件、表单校验、this、try/catch、异步、JSON、console调试。
比喻回顾
HTML:公示大屏原始图纸
DOM:内存里建好的大屏楼房(Elements面板)
JS:大屏运维员
document:物业总管
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>原生JS智慧小区住户大屏(无Vue)</title>
<style>
.item {margin:4px 0;padding:4px;border:1px solid #aaa;}
.del-btn {margin-left:10px;color:red;cursor:pointer;}
</style>
</head>
<body>
<h2>🏘️ 阳光智慧小区 - 住户登记大屏(原生JS版本)</h2>
<!-- 输入表单 -->
<div>
姓名:<input id="nameInput" placeholder="输入住户姓名">
房号:<input id="roomInput" placeholder="输入房号数字">
<input type="checkbox" id="feeCheck">已缴物业费
<button id="addBtn">登记住户</button>
</div>
<div>
<h3>统计信息:<span id="statText"></span></h3>
</div>
<!-- 住户列表展示区域(DOM广告牌容器) -->
<div id="listWrap"></div>
<script>
'use strict'; // 严格模式:约束运维员写代码,禁止乱写,减少低级错误
// ===================== 1. 变量 let / const 收纳盒 =====================
// const:固定收纳盒,内容一旦赋值不能替换
const pageTitle = "阳光智慧小区公示屏";
// let:可修改收纳盒,里面内容后续可以更换
let residentList = []; // 数组:一排档案格子,存放所有住户档案卡片
// 基础数据类型(收纳盒存放的物品)
let demoName = "老王"; // String 文字字符串
let demoRoom = 1201; // Number 数字
let hasPay = true; // Boolean 布尔 true/false
let emptyCard = null; // null:收纳盒手动清空
let unknownVal; // undefined:收纳盒存在,从未放东西
// ===================== 2. 对象 Object:单张住户档案卡片 =====================
/**
* 创建住户档案卡片
* @param {string} name 住户姓名
* @param {number} room 房号
* @param {boolean} isPaid 是否缴费
* @returns 住户对象档案
*/
function createResident(name, room, isPaid) {
return {
name: name,
room: room,
isPaid: isPaid,
// 对象方法:卡片自带打招呼动作,this代表当前这张档案卡片
sayHello: function () {
console.log(`你好,住户【${this.name}】`);
}
}
}
// ===================== 3. 普通函数:打包一套固定任务【表单校验】 =====================
// 任务:校验市民在输入框填写的信息是否合法
function validateInput(nameStr, roomStr) {
if (!nameStr) {
return { ok: false, msg: "❌姓名不能为空!" };
}
if (isNaN(roomStr) || Number(roomStr) <= 0) {
return { ok: false, msg: "❌房号必须是大于0的数字!" };
}
return { ok: true, msg: "✅校验通过" };
}
// ===================== 4. 箭头函数:简短小任务【筛选欠费住户】 =====================
const getUnpaidResidents = (arr) => {
let unpaidArr = [];
// for循环:运维员逐个翻阅档案格子
for (let i = 0; i < arr.length; i++) {
if (arr[i].isPaid === false) {
unpaidArr.push(arr[i]); // push:档案格子末尾新增一张档案卡
}
}
return unpaidArr;
}
// ===================== 5. DOM查询:运维员指挥物业总管document找到大屏广告牌 =====================
const nameDom = document.querySelector("#nameInput");
const roomDom = document.querySelector("#roomInput");
const feeCheckDom = document.querySelector("#feeCheck");
const addBtnDom = document.querySelector("#addBtn");
const listWrapDom = document.querySelector("#listWrap");
const statTextDom = document.querySelector("#statText");
// ===================== 渲染函数:刷新整个住户公示大屏(核心!原生JS需要手动刷新页面) =====================
function renderList() {
listWrapDom.innerHTML = ""; // 清空广告牌容器
// 循环遍历所有住户档案,逐个生成DOM广告牌
for (let j = 0; j < residentList.length; j++) {
const item = residentList[j];
// 生成单条住户HTML
const div = document.createElement("div");
div.className = "item";
div.innerHTML = `
住户:${item.name},房号:${item.room},物业费:${item.isPaid ? "已缴" : "欠费"}
<span class="del-btn">【删除】</span>
`;
// 删除按钮点击事件
const delBtn = div.querySelector(".del-btn");
delBtn.onclick = function () {
// 删除当前这条住户数据
residentList.splice(j, 1);
renderList(); // 删除数据后,重新渲染大屏
}
listWrapDom.appendChild(div);
item.sayHello(); // 调用对象方法
}
// 更新统计文字
const unpaidList = getUnpaidResidents(residentList);
statTextDom.innerText = `总户数:${residentList.length},欠费户数:${unpaidList.length}`;
}
// ===================== 6. 事件监听:监听【登记按钮点击】(用户交互) =====================
addBtnDom.addEventListener("click", async function () {
try { // try/catch 异常捕获,防止代码出错直接让整个大屏崩溃
// 获取输入框里面用户填写的值
const inputName = nameDom.value.trim();
const inputRoom = roomDom.value.trim();
const isPaid = feeCheckDom.checked;
// 执行表单校验
const checkRes = validateInput(inputName, inputRoom);
if (!checkRes.ok) {
alert(checkRes.msg);
return;
}
// 创建住户档案对象
const newResident = createResident(inputName, Number(inputRoom), isPaid);
residentList.push(newResident);
// 【重点】原生JS:数据修改之后,必须手动调用渲染函数更新大屏广告牌!
renderList();
// ===================== 7. 异步请求:运维员呼叫城市数据中心API =====================
// 模拟:把住户档案上报远端服务器(城市数据中心档案室)
const res = await fetch("[https://jsonplaceholder.typicode.com/posts](https://jsonplaceholder.typicode.com/posts)", {
method: "POST",
body: JSON.stringify(newResident) // JSON:把JS档案打包成网络传输格式
})
const backData = await res.json();
console.log("✅总部收到登记,返回回执:", backData);
// 清空输入框
nameDom.value = "";
roomDom.value = "";
feeCheckDom.checked = false;
} catch (err) {
console.error("❌网络上报出错:", err);
alert("网络故障,住户信息保存到本地成功,上报总部失败!");
}
})
// 页面加载完成,初始渲染一次空列表
renderList();
</script>
</body>
</html>
✅ 这个原生案例核心逻辑(口述讲解,为Vue铺垫)
重点!这就是Vue出现之前前端的写法:
- 我们维护一份内存数据:
residentList(档案数组) - 数据变了之后,必须手动调用
renderList()去操作DOM,刷新大屏页面 - 每次增删住户,都要手动重新生成广告牌、手动操作innerHTML、createElement
- 所有DOM操作全部手写;数据和页面视图需要程序员手动同步
痛点(后面Vue3解决的问题,一定要给学员讲)
- 数据修改,不会自动更新页面,必须自己调用渲染函数刷新DOM;忘记调用,页面就和内存档案不一致。
- 页面复杂之后,DOM操作代码到处散落,维护麻烦。
- 每次更新列表,上面示例直接清空容器全部重绘(简单项目够用,复杂页面性能差)>
Vue3的核心改进:响应式数据,数据一变自动更新视图,不用手动写render渲染函数
📖 覆盖知识点清单(黑板板书)
【原生JS案例包含全部基础知识点】
1. 'use strict' 严格模式
2. let / const 变量,5大基础类型:String Number Boolean null undefined
3. Array数组,push、splice数组方法
4. Object对象,this,对象内方法
5. 普通function函数、箭头函数
6. if 判断、for循环
7. DOM:querySelector、createElement、appendChild、innerHTML
8. 事件:addEventListener、click点击事件
9. 表单取值、表单校验
10. try / catch 异常捕获
11. async / await 异步 + fetch网络请求
12. JSON.stringify、JSON解析
13. console.log调试、alert弹窗
📌 上机操作+F12演示步骤
- 复制全部代码保存html,浏览器打开
- F12打开Elements:新增住户时,会动态新增DOM节点(广告牌)
- F12 Console:可以看到打印日志;在这里可以手动修改
residentList数组,然后在控制台执行renderList(),页面才会更新```
residentList.push(createResident("小张",2002,false));
renderList();
>
> ✨关键实验:只修改数组,**不执行renderList(),页面不会变!**
> 这就是原生JS最大痛点,Vue自动帮你做这一步。
## 随堂口述总结(一段讲完)
这个案例是Vue诞生之前,前端开发的原生JS标准写法。
我们在内存里保存住户档案数组,当新增、删除住户修改数据后,**必须手动写渲染函数renderList,操作DOM广告牌刷新页面**。原生JS不会自动同步页面和数据。
所有DOM查询、事件绑定、页面更新都需要手写。
后面学习Vue3,就是把这套手动同步的工作交给框架。我们只需要修改响应式收纳盒里面的数据,Vue内部自动完成渲染、更新DOM,不用手写大量DOM操作和render渲染函数。
TS则后续给这些档案对象加上类型约束,防止档案格式写错。
## 课堂思考题
1. 原生JS中,如果修改residentList数组,但是不调用renderList(),页面会更新吗?>
> 不会,内存数据变了,但是DOM大屏不会自动刷新,必须手动渲染。
2. 原生JS最大麻烦是什么?>
> 数据和视图需要人工手动同步,每次数据改动都要手动操作DOM更新页面。
---
### 下一步对比演示(下一课,用来引出Vue3)
同一个业务,改成Vue3版本,对比两份代码,学员一眼看懂Vue解决了原生JS的什么麻烦。
要不要我直接写**等价Vue3版本(setup语法糖)**,方便课堂直接对比原生JS代码?

浙公网安备 33010602011771号