闭包作用
- 函数调用一旦结束,它内部创建的所有成员全部销毁
- 但是,由于返回一个子函数,而这个子函数又引用了父函数中的成员,会将它访问到的
- 所有成员,保存到它的”作用域链上”,只要记录到了作用域链上的成员,它的环境就不能被销毁
- 通过子函数将父函数中的成员, 返回或带出函数给外部
- 通过闭包,顺利的访问到了函数的内部成员
- 闭包的作用域是独立的,相当于其它语言中的”静态变量”的效果
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>闭包</title>
</head>
<body>
<script>
// 全局
let a = 100;
// 函数
function f1() {
let a = 200;
console.log(a);
}
f1();
/**
* js 与其它语言相比有一个重要区别
* 1. 可以不需要任何声明,直接访问外部成员,如变量
* 2. 因此,js需要维护一个"作用域链"对象,来保存(记录)全部可访问的变量
*/
// ================================
// 父函数
function outer() {
let a = 100;
// 子函数
function f1() {
// 子函数中,是可以访问到父函数中的(父作用域中)成员,如变量
// 就像: 单向玻璃
return a;
}
return f1;
}
// 调用 outer
const f2 = outer();
// console.log(f2)
// f2 仍然是一个函数
// 调用 f2()
console.log(f2());
// 当我们调用f2(),已经产生了闭包
let result = f2();
/**
* 闭包的作用
* 1. 函数调用一旦结束,它内部创建的所有成员全部销毁
* 2. 但是,由于返回一个子函数,而这个子函数又引用了父函数中的成员,会将它访问到的
* 所有成员,保存到它的"作用域链上",只要记录到了作用域链上的成员,它的环境就不能被销毁
* 3. 通过子函数将父函数中的成员, 返回或带出函数给外部
* 4. 通过闭包,顺利的访问到了函数的内部成员
* 5. 闭包的作用域是独立的,相当于其它语言中的"静态变量"的效果
*/
// =========================================
let count = 300;
function fun() {
let count = 0;
return function () {
return ++count;
};
}
const f3 = fun();
// 只要执行一次f3(),就是创建一个独立的闭包
console.log(f3());
console.log(f3());
console.log(f3());
</script>
</body>
</html>
投票系统

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>投票系统</title>
<style>
.box {
width: 16em;
padding: 1em;
display: flex;
border: 1px solid #ddd;
background-color: #eee;
box-shadow: 5px 5px 5px #888;
margin: auto;
color: #444;
border-radius: 0.5em;
}
.box .item {
flex: 1;
text-align: center;
}
.box .item img {
width: 4em;
border-radius: 50%;
box-shadow: 2px 2px 2px #aaa;
}
.box .item .vote {
padding: 0.1em 1em;
background-color: #fff;
border: none;
background-color: lightseagreen;
border-radius: 0.5em;
box-shadow: 2px 2px 2px #aaa;
}
.box .item .vote:hover {
background-color: lightseagreen;
color: white;
cursor: pointer;
transition: 0.3s;
}
</style>
</head>
<body>
<div class="box">
<div class="item">
<img src="images/xz.jpg" alt="" />
<h3>肖战</h3>
<button class="vote"></button>
</div>
<div class="item">
<img src="images/zsw.jpg" alt="" />
<h3>张颂文</h3>
<button class="vote"></button>
</div>
<div class="item">
<img src="images/yz.jpg" alt="" />
<h3>杨紫</h3>
<button class="vote"></button>
</div>
</div>
<script>
// 获取相关的DOM元素
// 抽票按钮
const voteBtns = document.querySelectorAll(".vote");
// 数组: 保存每个人的投票结果
const total = [0, 0, 0];
// 今晚就不用箭头函数了,全部使用经典传统函数语法
voteBtns.forEach(function (btn, index) {
// 初始化起始票数
btn.textContent = total[index] + " 票";
// 为每个投票键添加点击事件
btn.onclick = (function () {
// 创建一个临时的闭包(静态)变量
let count = 0;
return function () {
this.textContent = ++count + " 票";
// count : 闭包中的变量
total[index] = count;
console.log(total);
};
})();
});
//=======================================
// 相对于demo2(),demo1()就是它的外部环境,父函数
function demo1() {
let a = 500;
// 子函数: 相对于demo1()
return function () {
// 此时,如果子函数引用了父函数中的成员
// 就会创建一个闭包对象,内部保存着对父函数中的成员的引用 a
// 尽管 a 定义在父函数 demo1中, 但是在demo2是可访问的
console.log(a);
};
}
const demo3 = demo1(); // 返回的还是一个函数
// 外部调用 demo3(),相当于执行了子函数 demo2()
// 创建了一个闭包对象
demo3();
</script>
</body>
</html>
购物车
这个购物车示例大概就如下图所示,里面原本PHP中文网是使用json数据替换静态的html商品俩表,这边我修改成了根据商品数据data.js里的商品总数进行列出到前端,我也保留了中文网原来的html,学习提升最重要。
carts.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>购物车</title>
<link rel="stylesheet" href="carts.css" />
</head>
<body>
<table class="carts">
<caption class="carts-title">
我的购物车
</caption>
<!-- 1. 购物车头部 -->
<thead class="carts-head">
<tr>
<!-- 全选按钮 -->
<td>
<input
type="checkbox"
name=""
class="check-all"
aria-label="checkbox"
checked
/>
</td>
<td>编号</td>
<td>品名</td>
<td>单位</td>
<td>单价</td>
<td>数量</td>
<td>金额(元)</td>
</tr>
</thead>
<!-- 2. 购物车主体 -->
<tbody class="carts-body">
<!-- 下面是静态的数据模板,后续用js动态生成 -->
<!-- <tr class="item">
<td><input type="checkbox" name="" class="check" checked /></td>
<td class="id">xxx</td>
<td class="name">xxx</td>
<td class="unit">xxx</td>
<td class="price">xxx</td>
<td><input type="number" name="" class="num" value="1" min="1" /></td>
<td class="money">xxx</td>
</tr>
<tr class="item">
<td><input type="checkbox" name="" class="check" checked /></td>
<td class="id">xxx</td>
<td class="name">xxx</td>
<td class="unit">xxx</td>
<td class="price">xxx</td>
<td><input type="number" name="" class="num" value="1" min="1" /></td>
<td class="money">xxx</td>
</tr>
<tr class="item">
<td><input type="checkbox" name="" class="check" checked /></td>
<td class="id">xxx</td>
<td class="name">xxx</td>
<td class="unit">xxx</td>
<td class="price">xxx</td>
<td><input type="number" name="" class="num" value="1" min="1" /></td>
<td class="money">xxx</td>
</tr>
<tr class="item">
<td><input type="checkbox" name="" class="check" checked /></td>
<td class="id">xxx</td>
<td class="name">xxx</td>
<td class="unit">xxx</td>
<td class="price">xxx</td>
<td><input type="number" name="" class="num" value="1" min="1" /></td>
<td class="money">xxx</td>
</tr>
<tr class="item">
<td><input type="checkbox" name="" class="check" checked /></td>
<td class="id">xxx</td>
<td class="name">xxx</td>
<td class="unit">xxx</td>
<td class="price">xxx</td>
<td><input type="number" name="" class="num" value="1" min="1" /></td>
<td class="money">xxx</td>
</tr> -->
</tbody>
<!-- 3. 购物车尾部 -->
<tfoot class="carts-foot">
<tr>
<td colspan="5">总计:</td>
<td class="total">xxx</td>
<td class="total-money">xxx</td>
</tr>
</tfoot>
</table>
<script type="module">
// (一) 从购物车模块中获取数据
import carts from "./carts.js";
// 水果商品列表,json格式
const fruit_data = carts.data;
console.log(fruit_data);
console.log(carts.total);
console.log(carts.totalMoney);
// (二) 获取相关的dom元素
const items = document.querySelector(".carts-body");
const total = document.querySelector(".carts-foot .total");
const totalMoney = document.querySelector(".carts-foot .total-money");
console.log(items, total, totalMoney);
// (三) 将以上模块中的数据,渲染到页面中
// 1. 替换静态购物车的全部商品
// items.forEach(function (item, index) {
// item.querySelector(".id").textContent = carts.data[index].id;
// item.querySelector(".name").textContent = carts.data[index].name;
// item.querySelector(".unit").textContent = carts.data[index].unit;
// item.querySelector(".price").textContent = carts.data[index].price;
// item.querySelector(".num").value = carts.data[index].num;
// item.querySelector(".money").textContent = carts.data[index].money;
// });
// 1.将fruit_data水果商品的json列表循环渲染到carts-body里,动态生成tr列表和td内容
// 创建文档片断对象,防止页面抖动
const frag = new DocumentFragment();
for (let i = 0; i < fruit_data.length; i++) {
let temp_data = fruit_data[i];
let tr = document.createElement("tr");
let td_str = `
<td><input type="checkbox" name="" class="check" checked /></td>
<td class="id">${temp_data.id}</td>
<td class="name">${temp_data.name}</td>
<td class="unit">${temp_data.unit}</td>
<td class="price">${temp_data.price}</td>
<td><input type="number" name="" class="num" value="${temp_data.num}" min="1" /></td>
<td class="money">${temp_data.money}</td>`;
// 将td_str字符串转换为dom对象
tr.innerHTML = td_str;
// console.log(tr);
frag.append(tr);
}
// console.log(frag);
// 将文档片断对象frag添加到items中,并删除原本的tr静态数据
items.append(frag);
// 2. 总数量
total.textContent = carts.total;
// 3. 总金额
totalMoney.textContent = carts.totalMoney;
// ===========================================
// (四) 获取购物车商品的数量,单价,金额构成的数组
// 单价数组
const prices = document.querySelectorAll(".carts-body .price");
// 金额数组
const monies = document.querySelectorAll(".carts-body .money");
// console.log(monies);
// 数量数组
const nums = document.querySelectorAll(".carts-body .num");
// 是否选中: check数组
const checks = document.querySelectorAll(".carts-body .check");
/**
* 分析
* 1. 需要更新的数据: 每个商品金额,总数量,总金额
* 2. 以上的数据更新,全部依赖于商品数量的变化,进行动态计算
*/
// (五) 遍历所有的数量控件,为每个数量控件添加 change 事件,(num为当前的数量控件, index为当前的索引, arr为所有的数量控件)
nums.forEach(function (num, index, arr) {
num.onchange = function () {
// 检查当前商品有没有被选中,如果没有,就不要计算。
// 选中check的状态是true,未选中的状态是false
if (!checks[index].checked) {
return false;
}
// 1. 计算商品金额 = 数量 * 单价
monies[index].textContent = num.value * prices[index].textContent;
// 2. 计算总数量
// (1) 将每个数量控件中的数值取出来,但是value默认是字符,需要转换数值类型
const numArr = [...arr].map(function (item) {
//字符串转换为整数值
return parseInt(item.value);
});
// (2) 求和得到总数量
total.textContent = numArr.reduce(function (pre, cur) {
//累加器
return pre + cur;
});
// 3. 计算总金额
// (1) 将每个数量控件中的数值取出来,但是默认是字符,需要转换数值类型
const moneyArr = [...monies].map(function (money) {
//字符串转换为浮点数值
return parseFloat(money.textContent);
});
// (2) 求和得到总数量
totalMoney.textContent = moneyArr.reduce(function (pre, cur) {
//累加器
return pre + cur;
});
};
});
// (六) 全选 / 全不选
// 全选按钮
const checkAll = document.querySelector(".check-all");
// 缓存总数量和总金额
let totalTmp = total.textContent;
let totalMoneyTmp = totalMoney.textContent;
// 为全选添加change
checkAll.onchange = function () {
// 遍历所有商品的状态,并将当前全选按钮的状态赋值给它
checks.forEach(function (check) {
// 选中状态修改为全选按钮的状态
check.checked = checkAll.checked;
});
// 根据所有商品的状态,动态设置全选按钮的状态(如果所有商品都选中,全选按钮选中,否则不选中)
// this : 全选按钮
if (this.checked === false) {
// 检测每个商品的选中状态,只有全部未选中才返回 true
// every : 检测每个元素是否都符合条件,只有全部符合条件才返回 true
let itemStatus = [...checks].every(function (check) {
return check.checked === false;
});
if (itemStatus === true) {
total.textContent = 0;
totalMoney.textContent = 0;
}
} else {
// 用缓存的数组来填充
total.textContent = totalTmp;
totalMoney.textContent = totalMoneyTmp;
}
};
// 遍历每个商品的复选框,并添加change,动态的计算相关数据
checks.forEach(function (check, index) {
// 添加change
check.onchange = function () {
// 根据所有商品的复选框的状态,来动态的设置全选
// every : 检测每个元素是否都符合条件,只有全部符合条件才返回 true
checkAll.checked = [...checks].every(function (check) {
return check.checked;
});
// 如果商品未选中,应该减去相对应的总数量和总金额
if (check.checked === false) {
// 1. 总数量
total.textContent = total.textContent - nums[index].value;
// 2. 总金额
totalMoney.textContent =
totalMoney.textContent - monies[index].textContent;
} else {
// 如果商品选中,应该加上相对应的总数量和总金额
// 1. 总数量
total.textContent =
parseInt(total.textContent) + parseInt(nums[index].value);
// // 2. 总金额
totalMoney.textContent =
parseFloat(totalMoney.textContent) +
parseFloat(monies[index].textContent);
// ===================================================
// JS中, "+"用在字符串运算时,是字符串拼接,用在数值运算时,是数值相加
// 上面写法也可以写成,string->number的简便转换方法: 利用任何数乘以1, 都不变
total.textContent = total.textContent * 1 + nums[index].value * 1;
totalMoney.textContent =
totalMoney.textContent * 1 + monies[index].textContent * 1;
}
};
});
</script>
</body>
</html>
样式表carts.css:
/* 购物车 */
.carts {
width: 450px;
border-collapse: collapse;
text-align: center;
font-weight: lighter;
}
.carts th,
.carts td {
padding: 5px;
}
/* 购物车头部 */
.carts .carts-head {
background-color: lightseagreen;
color: white;
}
.carts .carts-body tr {
border-bottom: thin solid #ccc;
}
.carts input[type="number"] {
width: 3em;
color: seagreen;
border: none;
}
.carts input[type="number"]:focus {
outline: thin solid lightseagreen;
}
/* 购物车底部 */
.carts .carts-foot {
background-color: lightcyan;
}
.carts .pay {
display: grid;
grid: 1fr / auto-flow;
place-content: end;
gap: 10px;
}
商品数据data.js:
// 模块: 商品数据
export default [
{
id: 20,
name: "苹果",
unit: "Kg",
price: 10,
num: 5,
// 金额 = 数量 * 单价
// 适合用: 访问器属性来创建
get money() {
return this.num * this.price;
},
},
{
id: 22,
name: "草莓",
unit: "箱",
price: 10,
num: 3,
// 金额 = 数量 * 单价
// 适合用: 访问器属性来创建
get money() {
return this.num * this.price;
},
},
{
id: 66,
name: "香梨",
unit: "件",
price: 30,
num: 1,
get money() {
return this.price * this.num;
},
},
{
id: 85,
name: "西瓜",
unit: "个",
price: 40,
num: 5,
get money() {
return this.price * this.num;
},
},
{
id: 99,
name: "榴莲",
unit: "千克",
price: 50,
num: 2,
get money() {
return this.price * this.num;
},
},
];
购物车模块carts.js:
// 模块: 购物车
import data from "./data.js";
export default {
// 1. 商品数据
data,
// 2. 总数量(访问器属性)
get total() {
// (1) 获取商品数组中的数量橡构成的数组
const nums = data.map(function (item) {
return item.num;
});
//箭头函数的写法是
// const nums = data.map(item => item.num);
// (2) 对商品数量数组求和
return nums.reduce(function (pre, cur) {
return pre + cur;
});
},
// 3. 总金额(访问器属性)
get totalMoney() {
// (1) 获取商品金额数组
const monies = data.map(function (item) {
return item.money;
});
// (2) 对商品金额数组求和
return monies.reduce(function (pre, cur) {
return pre + cur;
});
},
};
附件
本文核心代码附件如下,自行下载。
0404.zip大小:46.05KB
已经过安全软件检测无毒,请您放心下载。
本文地址:https://www.mainblog.cn/334.html
版权声明:本文为原创文章,版权归 阁主 所有,欢迎分享本文,转载请保留出处!
免责申明:有些内容源于网络,没能联系到作者。如侵犯到你的权益请告知,我们会尽快删除相关内容。
版权声明:本文为原创文章,版权归 阁主 所有,欢迎分享本文,转载请保留出处!
免责申明:有些内容源于网络,没能联系到作者。如侵犯到你的权益请告知,我们会尽快删除相关内容。
黔ICP备19006353号-2
贵公网安备 52052102000042号