一、作用域
• 局部作用域
• 全局作用域
• 作用域链
• JS垃圾回收机制
• 闭包
• 变量提升
1. 作用域
⑴ 局部作用域
2.全局作用域
3. 作用域链
就近原则:输出为2
4.JS垃圾回收机制
⑴. 什么是垃圾回收机制?
⑵.内存的生命周期
⑶.拓展-JS垃圾回收机制-算法说明
①引用计数
但它却存在一个致命的问题:嵌套引用(循环引用)
这两个对象都有指向它的引用,即便let o1=null, let o2 =null ,依旧存在一个指向它的引用:o1.a=02, o2.a=01
②标记清除法
5. 闭包
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>// 简单的写法function outer() {let a = 10function fn() {console.log(a)}fn()}outer()</script></body></html>
闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量
闭包的基本格式:
闭包应用:实现数据的私有
代码逻辑概述
此代码定义了一个外部函数 count
,该函数内部定义了一个变量 i
和一个内部函数 fn
。fn
函数会让 i
的值加 1,并且输出当前函数被调用的次数。最后,count
函数返回 fn
函数。当你调用 count()
并把返回值赋给 fun
时,fun
就变成了 fn
函数,并且能持续追踪 i
的值。
内存泄漏原因
在 JavaScript 里,闭包是指有权访问另一个函数作用域中的变量的函数。在这个例子中,fn
函数形成了一个闭包,它能够访问 count
函数作用域中的 i
变量。由于 fun
引用了 fn
函数,fn
函数又引用了 count
函数作用域中的 i
变量,这就导致 count
函数执行完毕之后,其作用域不会被销毁,i
变量也会一直存在于内存中。
若这个闭包(也就是 fun
)在整个应用的生命周期内一直存在,那么 i
变量就会一直占用内存,无法被垃圾回收机制回收。要是在程序里大量使用这类闭包,或者闭包持有了大量的数据,就会造成内存占用不断增加,最终可能引发内存泄漏。
示例代码
下面是一段示例代码,用来展示闭包造成的内存占用情况:
function count() {let i = 0;function fn() {i++;console.log(`函数被调用了${i}次`);}return fn;
}const fun = count();
fun(); // 输出: 函数被调用了1次
fun(); // 输出: 函数被调用了2次
// 这里即使不再需要 fun 函数,它所引用的 i 变量依然占用内存
解决方案
若你想避免这种内存泄漏问题,在不再需要闭包的时候,要手动解除对闭包的引用。例如:
function count() {let i = 0;function fn() {i++;console.log(`函数被调用了${i}次`);}return fn;
}const fun = count();
fun(); // 输出: 函数被调用了1次
fun(); // 输出: 函数被调用了2次// 不再需要 fun 函数时,手动解除引用
fun = null;
// 此时,闭包和它所引用的变量就可以被垃圾回收机制回收
6. 变量提升
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>// 1. 把所有var声明的变量提升到 当前作用域的最前面// 2. 只提升声明, 不提升赋值var numconsole.log(num + '件')num = 10console.log(num)function fn() {console.log(num)var num = 10}fn()</script>
</body></html>
说明:
JS初学者经常花很多时间才能习惯变量提升,还经常出现一些意想不到的bug,正因为如此,ES6 引入了块级作用域, 用let 或者 const声明变量,让代码写法更加规范和人性化。
二、函数进阶
函数提升
函数参数
箭头函数
1. 函数提升
函数表达式 必须先声明和赋值, 后调用 否则 报错
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>var fun// 1. 会把所有函数声明提升到当前作用域的最前面// 2. 只提升函数声明,不提升函数调用fn()function fn() {console.log('函数提升')}fun()var fun = function () {console.log('函数表达式')}// 函数表达式 必须先声明和赋值, 后调用 否则 报错</script>
</body></html>
2.函数参数
⑴. 动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>function getSum() {// arguments 动态参数 只存在于 函数里面// 是伪数组 里面存储的是传递过来的实参console.log(arguments)let sum = 0for (let i = 0; i < arguments.length; i++) {sum += arguments[i]}console.log(sum)}getSum(2, 3, 4)getSum(1, 2, 3, 4, 2, 2, 3, 4)</script>
</body></html>
⑵. 剩余参数
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>function getSum(a, b, ...arr) {console.log(arr) // 使用的时候不需要写 ...}getSum(2, 3)getSum(1, 2, 3, 4, 5)</script>
</body></html>
⑶.展开运算符
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>const arr1 = [1, 2, 3]// 展开运算符 可以展开数组console.log(...arr1)console.log(Math.max(1, 2, 3))// ...arr1 === 1,2,3// 1 求数组最大值console.log(Math.max(...arr1)) // 3console.log(Math.min(...arr1)) // 1// 2. 合并数组const arr2 = [3, 4, 5]const arr = [...arr1, ...arr2]console.log(arr)</script>
</body></html>
⑷.展开运算符 or 剩余参数
3.箭头函数(重要)
⑴.基本语法
语法1:基本写法
语法2:只有一个参数可以省略小括号
语法3:如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值
语法4:加括号的函数体返回对象字面量表达式
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>const fn = function () {console.log(123)}// 1. 箭头函数 基本语法const fn1 = () => {console.log(123)}fn1()const fn2 = (x) => {console.log(x)}fn2(1)// 2. 只有一个形参的时候,可以省略小括号const fn3 = x => {console.log(x)}fn3(1)// // 3. 只有一行代码的时候,我们可以省略大括号const fn4 = x => console.log(x)fn4(1)//4. 只有一行代码的时候,可以省略returnconst fn5 = x => x + xconsole.log(fn5(1))// 5. 箭头函数可以直接返回一个对象const fn6 = (uname) => ({ uname: uname })console.log(fn6('刘德华'))</script>
</body></html>
总结:
1. 箭头函数属于表达式函数,因此不存在函数提升
⑵. 箭头函数参数
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>// 1. 利用箭头函数来求和const getSum = (...arr) => {let sum = 0for (let i = 0; i < arr.length; i++) {sum += arr[i]}return sum}const result = getSum(2, 3, 4)console.log(result) // 9</script>
</body></html>
⑶. 箭头函数 this
①普通函数
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>// 以前this的指向: 谁调用的这个函数,this 就指向谁console.log(this) // window// // 普通函数function fn() {console.log(this) // window}window.fn()// 对象方法里面的thisconst obj = {name: 'andy',sayHi: function () {console.log(this) // obj}}obj.sayHi()</script></body></html>
②箭头函数
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>// 2. 箭头函数的this 是上一层作用域的this 指向const fn = () => {console.log(this) // window}fn()// 对象方法箭头函数 thisconst obj = {uname: '老师',sayHi: () => {console.log(this) // this 指向谁? window}}obj.sayHi()</script></body></html>
③
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>const obj = {uname: 'pink老师',sayHi: function () {console.log(this) // objlet i = 10const count = () => {console.log(this) // obj }count()}}obj.sayHi()</script></body></html>
三、解构赋值
• 数组解构
• 对象解构
1. 数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>// const arr = [100, 60, 80]// 数组解构 赋值// // const [max, min, avg] = arrconst [max, min, avg] = [100, 60, 80]// // const max = arr[0]// // const min = arr[1]// // const avg = arr[2]console.log(max) // 100console.log(avg) // 80// 交换2个变量的值let a = 1let b = 2;[b, a] = [a, b]console.log(a, b)</script>
</body></html>
注意: js 前面必须加分号情况
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>// 1. 立即执行函数要加// (function () { })();// (function () { })();// 2. 使用数组的时候// const arr = [1, 2, 3]const str = 'pink';[1, 2, 3].map(function (item) {console.log(item)})let a = 1let b = 2;[b, a] = [a, b]console.log(a, b)</script>
</body></html>
⑴.练习:独立完成数组解构赋值
⑵. 变量多 单元值少的情况:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>// 1. 变量多, 单元值少 , undefinedconst [a, b, c, d] = [1, 2, 3]console.log(a) // 1console.log(b) // 2console.log(c) // 3console.log(d) // undefined</script></body></html>
⑶.变量少 单元值多的情况:
⑷.利用剩余参数解决变量少 单元值多的情况:
⑸. 防止有undefined传递单元值的情况,可以设置默认值:
⑹. 按需导入,忽略某些返回值:
⑺. 支持多维数组的结构:
2. 对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
⑴. 基本语法:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>// 对象解构// const obj = {// uname: 'pink老师',// age: 18// }// obj.uname// obj.age // const uname = 'red老师'// 解构的语法const { uname, age } = {age: 18, uname: '老师' }// 等价于 const uname = obj.uname// 要求属性名和变量名必须一直才可以console.log(uname)console.log(age)</script></body></html>
⑵.给新的变量名赋值:
可以从一个对象中提取变量并同时修改新的变量名
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>// 1. 对象解构的变量名 可以重新改名 旧变量名: 新变量名const { uname: username, age } = { uname: 'pink老师', age: 18 }console.log(username)console.log(age)</script></body></html>
⑶数组对象解构
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>const pig = [{uname: '佩奇',age: 6}]const [{ uname, age }] = pigconsole.log(uname)console.log(age)</script></body></html>
⑷.独立完成对象解构赋值
⑸. 多级对象解构:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>const pig = {name: '佩奇',family: {mother: '猪妈妈',father: '猪爸爸',sister: '乔治'},age: 6}// // 多级对象解构const { name, family: { mother, father, sister } } = pigconsole.log(name)console.log(mother)console.log(father)console.log(sister)</script></body></html>
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><script>const person = [{name: '佩奇',family: {mother: '猪妈妈',father: '猪爸爸',sister: '乔治'},age: 6}]const [{ name, family: { mother, father, sister } }] = personconsole.log(name)console.log(mother)console.log(father)console.log(sister)</script></body></html>
⑹.独立完成对象解构赋值
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>// 1. 这是后台传递过来的数据const msg = {"code": 200,"msg": "获取新闻列表成功","data": [{"id": 1,"title": "5G商用自己,三大运用商收入下降","count": 58},{"id": 2,"title": "国际媒体头条速览","count": 56},{"id": 3,"title": "乌克兰和俄罗斯持续冲突","count": 1669},]}// 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面// const { data } = msg// console.log(data)// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数// const { data } = msg// msg 虽然很多属性,但是我们利用解构只要 data值function render({ data }) {// const { data } = arr// 我们只要 data 数据// 内部处理console.log(data)}render(msg)// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myDatafunction render({ data: myData }) {// 要求将 获取过来的 data数据 更名为 myData// 内部处理console.log(myData)}render(msg)</script>
</body></html>
四、遍历数组 forEach 方法(重点)
注意:
1. forEach 主要是遍历数组
2. 参数当前数组元素是必须要写的, 索引号可选。
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>// forEach 就是遍历 加强版的for循环 适合于遍历数组对象const arr = ['red', 'green', 'pink']const result = arr.forEach(function (item, index) {console.log(item) // 数组元素 red green pinkconsole.log(index) // 索引号})// console.log(result)</script>
</body></html>
五、渲染商品列表案例
<!DOCTYPE html>
<html lang="en"><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>* {margin: 0;padding: 0;box-sizing: border-box;}.list {width: 990px;margin: 0 auto;display: flex;flex-wrap: wrap;padding-top: 100px;}.item {width: 240px;margin-left: 10px;padding: 20px 30px;transition: all .5s;margin-bottom: 20px;}.item:nth-child(4n) {margin-left: 0;}.item:hover {box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);transform: translate3d(0, -4px, 0);cursor: pointer;}.item img {width: 100%;}.item .name {font-size: 18px;margin-bottom: 10px;color: #666;}.item .price {font-size: 22px;color: firebrick;}.item .price::before {content: "¥";font-size: 14px;}</style>
</head><body><div class="list"><!-- <div class="item"><img src="" alt=""><p class="name"></p><p class="price"></p></div> --></div><script>const goodsList = [{id: '4001172',name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',price: '289.00',picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',},{id: '4001594',name: '日式黑陶功夫茶组双侧把茶具礼盒装',price: '288.00',picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg',},{id: '4001009',name: '竹制干泡茶盘正方形沥水茶台品茶盘',price: '109.00',picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',},{id: '4001874',name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',price: '488.00',picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',},{id: '4001649',name: '大师监制龙泉青瓷茶叶罐',price: '139.00',picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',},{id: '3997185',name: '与众不同的口感汝瓷白酒杯套组1壶4杯',price: '108.00',picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg',},{id: '3997403',name: '手工吹制更厚实白酒杯壶套装6壶6杯',price: '99.00',picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg',},{id: '3998274',name: '德国百年工艺高端水晶玻璃红酒杯2支装',price: '139.00',picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg',},]// 1. 声明一个字符串变量let str = ''// 2. 遍历数据 goodsList.forEach(item => {// console.log(item) // 可以得到每一个数组元素 对象 {id: '4001172'}// const {id} = item 对象解构const { name, price, picture } = itemstr += `<div class="item"><img src=${picture} alt=""><p class="name">${name}</p><p class="price">${price}</p></div>`})// 3.生成的 字符串 添加给 list document.querySelector('.list').innerHTML = str</script>
</body></html>
六、筛选数组 filter 方法(重点)
<!DOCTYPE html>
<html lang="en"><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>Document</title>
</head><body><script>const arr = [10, 20, 30]// const newArr = arr.filter(function (item, index) {// // console.log(item)// // console.log(index)// return item >= 20// })// 返回的符合条件的新数组const newArr = arr.filter(item => item >= 20)console.log(newArr)</script>
</body></html>
七、商品列表价格筛选
<!DOCTYPE html>
<html lang="en"><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>* {margin: 0;padding: 0;box-sizing: border-box;}.list {width: 990px;margin: 0 auto;display: flex;flex-wrap: wrap;}.item {width: 240px;margin-left: 10px;padding: 20px 30px;transition: all .5s;margin-bottom: 20px;}.item:nth-child(4n) {margin-left: 0;}.item:hover {box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);transform: translate3d(0, -4px, 0);cursor: pointer;}.item img {width: 100%;}.item .name {font-size: 18px;margin-bottom: 10px;color: #666;}.item .price {font-size: 22px;color: firebrick;}.item .price::before {content: "¥";font-size: 14px;}.filter {display: flex;width: 990px;margin: 0 auto;padding: 50px 30px;}.filter a {padding: 10px 20px;background: #f5f5f5;color: #666;text-decoration: none;margin-right: 20px;}.filter a:active,.filter a:focus {background: #05943c;color: #fff;}</style>
</head><body><div class="filter"><a data-index="1" href="javascript:;">0-100元</a><a data-index="2" href="javascript:;">100-300元</a><a data-index="3" href="javascript:;">300元以上</a><a href="javascript:;">全部区间</a></div><div class="list"><!-- <div class="item"><img src="" alt=""><p class="name"></p><p class="price"></p></div> --></div><script>// 2. 初始化数据const goodsList = [{id: '4001172',name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',price: '289.00',picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',},{id: '4001594',name: '日式黑陶功夫茶组双侧把茶具礼盒装',price: '288.00',picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg',},{id: '4001009',name: '竹制干泡茶盘正方形沥水茶台品茶盘',price: '109.00',picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',},{id: '4001874',name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',price: '488.00',picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',},{id: '4001649',name: '大师监制龙泉青瓷茶叶罐',price: '139.00',picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',},{id: '3997185',name: '与众不同的口感汝瓷白酒杯套组1壶4杯',price: '108.00',picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg',},{id: '3997403',name: '手工吹制更厚实白酒杯壶套装6壶6杯',price: '100.00',picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg',},{id: '3998274',name: '德国百年工艺高端水晶玻璃红酒杯2支装',price: '139.00',picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg',},]// 1. 渲染函数 封装function render(arr) {// 声明空字符串let str = ''// 遍历数组 arr.forEach(item => {// 解构const { name, picture, price } = itemstr += `<div class="item"><img src=${picture} alt=""><p class="name">${name}</p><p class="price">${price}</p></div> `})// 追加给list document.querySelector('.list').innerHTML = str}render(goodsList) // 页面一打开就需要渲染// 2. 过滤筛选 document.querySelector('.filter').addEventListener('click', e => {// e.target.dataset.index e.target.tagNameconst { tagName, dataset } = e.target// 判断 if (tagName === 'A') {// console.log(11) // arr 返回的新数组let arr = goodsListif (dataset.index === '1') {arr = goodsList.filter(item => item.price > 0 && item.price <= 100)} else if (dataset.index === '2') {arr = goodsList.filter(item => item.price >= 100 && item.price <= 300)} else if (dataset.index === '3') {arr = goodsList.filter(item => item.price >= 300)}// 渲染函数render(arr)}})</script>
</body></html>