目录
[1.1 局部作用域](#1.1 局部作用域)
[1.2 全局作用域](#1.2 全局作用域)
[1.3 作用域链](#1.3 作用域链)
[1.4 JS垃圾回收机制](#1.4 JS垃圾回收机制)
[1.5 闭包](#1.5 闭包)
[1.6 变量提升](#1.6 变量提升)
[2.1 函数提升](#2.1 函数提升)
[2.2 函数参数](#2.2 函数参数)
[1. 动态参数](#1. 动态参数)
[2. 剩余参数](#2. 剩余参数)
[展开运算符 or 剩余参数](#展开运算符 or 剩余参数)
[2.3 箭头函数(重要)](#2.3 箭头函数(重要))
[2. 箭头函数参数](#2. 箭头函数参数)
[3. 箭头函数 this](#3. 箭头函数 this)
[3.1 数组解构](#3.1 数组解构)
[1. 立即执行函数](#1. 立即执行函数)
[2. 数组解构](#2. 数组解构)
[3.2 对象解构](#3.2 对象解构)
[遍历数组 forEach 方法(重点)](#遍历数组 forEach 方法(重点))
[步骤 渲染商品列表案例](#步骤 渲染商品列表案例)
[筛选数组 filter 方法(重点)](#筛选数组 filter 方法(重点))
[案例 商品列表价格筛选编辑](#案例 商品列表价格筛选编辑)
本章节用到的所有资源材料都可以自取:素材自取
1、作用域
1.1 局部作用域
局部作用域分为函数作用域和块作用域。
1. 函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
局部作用域分为函数作用域和块作用域。
2. 块作用域:
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【 有可能 】无法被访问。
总结:
- let 声明的变量会产生块作用域,var 不会产生块作用域
- const 声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用 let 或 const
1.2 全局作用域
<script> 标签 和 .js 文件 的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问
注意:
- 为 window 对象动态添加的属性默认也是全局的,不推荐!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
1.3 作用域链
作用域链本质上是底层的 变量查找机制 。
Ø 在函数被执行时,会 优先查找当前 函数作用域中查找变量
Ø 如果当前作用域查找不到则会依次 逐级查找父级作用域 直到全局作用域
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
1.4 JS垃圾回收机制
1. 什么是垃圾回收机制?
垃圾回收机制(Garbage Collection) 简称 GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况
不再用到的内存,没有及时释放,就叫做 内存泄漏
2.内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
- 说明:
Ø 全局变量一般不会回收(关闭页面回收);
Ø 一般情况下局部变量的值, 不用了, 会被自动回收掉
总结:
- 什么是垃圾回收机制?
Ø 简称 GC
Ø JS中内存的分配和回收都是自动完成的, 内存 在不使用的时候会被垃圾回收器 自动回收
- 什么是内存泄漏?
Ø 不再用到的内存,没有及时释放,就叫做内存泄漏 - 内存的生命周期是什么样的?
Ø 内存分配、内存使用、内存回收
Ø 全局变量一般不会回收; 一般情况下局部变量的值, 不用了, 会被自动回收掉
拓展-JS垃圾回收机制-算法说明
堆栈空间分配区别:
- 栈(操作系统): 由 操作系统自动分配释放 函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由 垃圾回收机制 回收。 复杂数据类型 放到堆里面。
下面介绍两种常见的浏览器 垃圾回收算法 : 引用计数法 和 标记清除法
引用计数
IE采用的引用计数算法, 定义" 内存不再使用 ",就是看一个 对象 是否有指向它的引用,没有引用了就回收对象
算法:
- 跟踪记录被 引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会 累加 ++
- 如果减少一个引用就 减1 --
- 如果引用次数是 0 ,则释放内存

但它却存在一个致命的问题: 嵌套引用 (循环引用)
如果两个对象 相互引用 ,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露
标记清除法
现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于 标记清除算法 的某些改进算法,总体思想都是一致的。
核心:
- 标记清除算法将"不再使用的对象"定义为" 无法达到的对象 "。
- 就是从 根部 (在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从 根部到达 的对象,都是还 需要使用 的。
- 那些 无法 由根部出发触及到的 对象被标记 为不再使用,稍后进行 回收 。
标记所有的引用

还是拿这个互相指引的函数为例:
当函数执行结束的时候,此时已经没有入口能够找到这个互相指引的函数里的对象,所以会自动被垃圾回收机制清除!

总结:
- 标记清除法 核心思路是什么?
Ø 从 根部 扫描对象,能查找到的就是使用的,查找不到的就要回收
1.5 闭包
目标: 能说出什么是闭包,闭包的作用以及注意事项
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
简单理解: 闭包 = 内层函数 + 外层函数的变量
先看个简单的代码:


内层函数跟外层函数的变量产生捆绑关系!
闭包作用: 封闭数据,提供操作,外部也可以访问函数内部的变量
闭包的基本格式:

闭包应用: 实现数据的私有
比如,我们要做个统计函数调用次数,函数调用一次,就++

但是有的人就可以直接对我的全局变量进行修改!这样是很不安全的:

此时函数却直接被修改成调用了1000次大大增加了程序运行的负担!
所以,此时就是需要闭包来对数据进行封闭,来实现数据的私有化!
这样实现了数据私有,无法直接修改count

此时能发现,虽然i在函数内部,但是fun却指向fn函数能够找到i,i又是外部函数的变量被引用,所以变相来说,i是会根据全局变量fun的生存周期来决定的,fun在页面关闭时被回收,i变量也是!
这里函数被调用完后,此时里面的变量应该是要被回收的,但是跟着fun的全局周期来进行生成,也就是说,这个i变量应该要被回收的,但是没有被回收,就会存在内存泄漏的问题!!!
总结
- 怎么理解闭包?
Ø 闭包 = 内层函数 + 外层函数的变量 - 闭包的作用?
Ø 封闭数据,实现数据私有,外部也可以访问函数内部的变量
Ø 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
- 闭包可能引起的问题?
Ø 内存泄漏
1.6 变量提升
目标:了解什么是变量提升
变量提升是 JavaScript 中比较"奇怪"的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
注意:
- 变量在未声明即被访问时会报语法错误
- 变量在var声明之前即被访问,变量的值为 undefined
- let/const 声明的变量不存在变量提升
- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
说明:
JS初学者经常花很多时间才能 习惯变量提升 ,还经常出现一些意想不到的bug,正因为如此,ES6 引入了块级作用域,用let 或者 const声明变量,让代码写法更加规范和人性化。
2、函数进阶
2.1 函数提升
目标:能说出函数提升的过程
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。

函数的调用同样也能被执行!就是因为在代码执行的过程中,首先就是将所有的函数声明都提升到了当前作用域的最前面~
2.2 函数参数
产品需求: 写一个求和函数
不管用户传入几个实参,都要把和求出来

1. 动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
总结:
- arguments 是一个伪数组,只存在于函数中
- arguments 的作用是动态获取函数的实参
- 可以通过for循环依次得到传递过来的实参
2. 剩余参数
目标: 能够使用剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组


- ... 是语法符号,置于最末函数形参之前,用于获取 多余 的实参
- 借助 ... 获取的剩余实参,是个 真数组
开发中,还是提倡多使用 剩余参数。
总结:
- 剩余参数主要的使用场景是?
Ø 用于获取多余的实参 - 剩余参数和动态参数区别是什么?开发中提倡使用哪一个?
Ø 动态参数是伪数组
Ø 剩余参数是真数组
Ø 开发中使用剩余参数想必也是极好的
展开运算符
典型运用场景: 求数组最大值(最小值)、合并数组等

展开运算符 or 剩余参数
剩余参数: 函数参数使用,得到真数组
展开运算符: 数组中使用, 数组展开
2.3 箭头函数(重要)
javascript
const fn = function () {
console.log(123)
}
// 箭头函数 基本语法
const fn = () => {
console.log(123)
}
fn()
const fn = (x) => {
console.log(x)
}
fn(1)
// 1. 只有一个形参的时候 可以省略小括号
const fn = x => {
console.log(x)
}
fn(2)
// 2.只有一行代码的时候 可以省略大括号
const fn = x => console.log(x)
fn(11)
const fn = x => {
return x + x
}
console.log(fn(1))
// 等价
const fn = x => x + x
console.log(fn(2))
// 直接返回一个对象
const fn = (uname) => ({ uname: uname })
//因为对象是要用{}包括起来 所以用()包起来
console.log(fn('溜得滑'))
总结:
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号 ()
- 箭头函数函数体只有一行代码时可以省略花括号 {},并自动做为返回值被返回
- 加括号的函数体返回对象字面量表达式
2. 箭头函数参数
- 普通函数有arguments 动态参数
- 箭头函数 没有 arguments 动态参数,但是 有 剩余参数 ..args

- 箭头函数里面有arguments动态参数吗?可以使用什么参数?
Ø 没有arguments动态参数
Ø 可以使用剩余参数
3. 箭头函数 this
在箭头函数出现之前,每一个新函数根据它是被 如何调用的 来定义这个函数的this值, 非常令人讨厌。
箭头函数不会创建自己的this ,它只会从自己的作用域链的上一层沿用this。
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此 DOM事件回调函数为了简便,还是不太推荐使用箭头函数

3、解构赋值
3.1 数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:
- 赋值运算符 = 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
- 变量的顺序对应数组单元值的位置依次进行赋值操作
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:典型应用交互2个变量
注意: js 前面必须加分号情况
1. 立即执行函数

2. 数组解构

数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法:
javascript
// 1.变量多 单元值少 undefined
const [a, b, c] = [1, 2]
console.log(a)
console.log(b)
console.log(c) //undefined
// 2.变量少 单元值多
const [a, b] = [1, 2, 3]
console.log(a)
console.log(b)
// 3.变量少 单元值多
const [a, b, ...c] = [1, 2, 3, 4]
console.log(a)
console.log(b)
console.log(c) //[3, 4]
// 4.变量给初始值
const [a = 0, b = 0] = [1, 2]
console.log(a) // 1
console.log(b) // 2
const arr = [1, 2, [3, 4]]
console.log(arr[0]) // 1
console.log(arr[1]) // 2
console.log(arr[2]) // [3,4]
console.log(arr[2][0]) // 3
3.2 对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
1. 基本语法:
- 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
- 对象属性的值将被赋值给与属性名 相同的 变量
- 注意解构的变量名不要和外面的变量名冲突否则报错
4.对象中找不到与变量名一致的属性时变量值为 undefined

2.给新的变量名赋值:
可以从一个对象中提取变量并同时修改新的变量名
冒号表示"什么值:赋值给谁"
2. 数组对象解构

3. 多级对象解构:
数组多级对象解构:

多级对象解构案例:
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
// function render(arr) {
function render({ data }) {
// 我们只要 data 数据
// 内部处理
// const { data } = msg
console.log(data)
}
render(msg)
// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
</script>
</body>
</html>
遍历数组 forEach 方法(重点)
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
主要使用场景: 遍历数组的每个元素
注意:
- forEach 主要是遍历数组
- 参数当前数组元素是必须要写的, 索引号可选。
步骤 渲染商品列表案例
案例自取:素材自取
核心思路:有多少条数据,就渲染多少模块,然后 生成对应的 html结构标签, 赋值给 list标签即可
①:利用forEach 遍历数据里面的 数据
②:拿到数据,利用 字符串拼接 生成结构添加到页面中
③:注意:传递参数的时候,可以使用对象解构

活动栏切换:纯css 不需要js

筛选数组 filter 方法(重点)
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素 ,并返回筛选之后元素的新数组
javascript
const arr = [10, 20, 30]
// 筛选数组filter
const newArr = arr.filter(function (item, index) {
console.log(item)
console.log(index)
return index >= 20
})
console.log(newArr) //[20,30]
// 返回符合条件的新数组
const newArr = arr.filter(item => item >= 20)
console.log(newArr)
filter() 筛选数组
返回值: 返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组
参数: currentValue 必须写, index 可选
因为返回新数组,所以不会影响原数组
案例 商品列表价格筛选
案例自取:素材自取 
需求:
①:渲染数据列表
②:根据选择不同条件显示不同商品
点击不同需求,显示不同页面内容
(1) 点击采取事件委托方式 .filter
(2) 利用过滤函数 filter 筛选出符合条件的数据,因为生成的是一个数组,传递给渲染函数即可
(3) 筛选条件是根据点击的 data-index 来判断
(4) 可以使用对象解构,把 事件对象 解构
(5) 因为 全部区间不需要筛选,直接 把goodList渲染即可
第一步:创建渲染函数,获取goodList 数组后,取到所有的数据存入str内!
将str追加给list进行渲染!
第二步,进行筛选过滤
通过不同点击的data-index 然后来进行筛选商品的价格分别存入arr内,然后再次进行渲染
JavaScript:
javascript
function render(goodsList) {
let str = ''
goodsList.forEach(item => {
const { id, name, price, picture } = item
// console.log(id)
str += `
<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.data.index e.target.tagName
const { tagName, dataset } = e.target
// 判断
if (tagName === 'A') {
// console.log(11)
let arr = goodsList
if (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)
}
// console.log(arr)
render(arr)
}
})