作用域
局部作用域
局部作用域分为函数作用域和块作用域
函数作用域
(在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。)
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了。
块作用域
(使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。)
- let 声明的变量会产生块作用域,var 不会产生块作用域(var可以先使用后使用)。
- const 声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用 let 或 const
全局作用域
<script>标签和.js的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
- 为 window 对象动态添加的属性默认也是全局的,不推荐!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有 助于规范代码书写习惯,避免因作用域导致的语法错误。
作用域链
作用域链本质上是底层的变量查找机制
- 在函数被执行时,会优先查找当前函数作用域中查找变量。
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域。
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量 g
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
<script> //全局作用域 let a = 1 let b = 2 //局部作用域 function fn() { let a = 3 //局部作用域 function gn() { let a = 4 console.log(a); } gn() } fn() //4 </script>
JS垃圾回收机制(简称GC)
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
不再用到的内存,没有及时释放,就叫做内存泄漏。
内存的生命周期
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存。
- 内存使用:即读写内存,也就是使用变量、函数等 。
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存。
说明:
- 全局变量一般不会回收(关闭页面回收);
- 一般情况下局部变量的值, 不用了, 会被自动回收掉。
拓展-JS垃圾回收机制-算法说明
堆栈空间分配区别:
栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
垃圾回收算法: 引用计数法 和 标记清除法
引用计数法:IE采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象。
算法:
1. 跟踪记录被引用的次数
2. 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
3. 如果减少一个引用就减1 --
4. 如果引用次数是0 ,则释放内存
数组是复杂数据类型,值存放在堆里面,栈里面存放的是地址,此时该地址指向该数组,数组被引用;当arr=null时,该地址为空无法指向该数组,数组不被引用,被回收。
引用计数存在一个致命的问题:嵌套引用(循环引用)
如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
function fn() { let obj1 = {} let obj2 = {} obj1.a = obj2 obj2.a = obj1 return '引用计数无法回收' } fn()
因为他们的引用次数永远不会是0,这样的相互引用如果说很大量的存在就会导致大量的内存泄露。但是如果采用的是标记清除法,因为根部已经访问不到,所以会自动清除。
标记清除法:现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
2. 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,都是还需要使用的。
3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
闭包
使用闭包函数创建隔离作用域避免全局变量污染
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域。
简单理解:闭包 = 内层函数 + 外层函数的变量
作用:封闭数据,提供操作,外部也可以访问函数内部的变量。(实现数据的私有)
//外层函数使用内层函数的变量 function outer() { const a = 2 function fn() { console.log(a); } return fn } const fun=outer() fun() //2
let count = 1 function fn() { count++ console.log(`函数被调用了${count}次`); } fn() //函数被调用了2次 fn() //函数被调用了3次 //当count=1000时,此时控制台输出的是'函数被调用了1001次',外部的count是全局变量,很容易被修改。 function fn(){ let count = 1 function fun() { count++ console.log(`函数被调用了${count}次`); } return fun } const result = fn() result() //函数被调用了2次 result() //函数被调用了3次 //这样实现了数据私有,因为内部声明在外部无法修改,所以无法直接修改count。
闭包可能引起的问题:内存泄漏
变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)。
//访问变量str console.log(str + 'nihao'); //undefinednihao //声明变量str var str = 'hahaha' //相当于 var str console.log(str + 'nihao'); var str = 'hahaha'
- 把所有var声明的变量提升到当前作用域的最前面
- 只提升声明,不提升赋值。
注意:
1. 变量在未声明即被访问时会报语法错误
2. 变量在var声明之前即被访问,变量的值为 undefined。
3. let/const 声明的变量不存在变量提升
4. 变量提升出现在相同作用域当中
5. 实际开发中推荐先声明再访问变量
函数进阶
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
//调用函数 fn() //声明函数 function fn() { console.log(1); } //不存在提升现象 gn() //错误 var gn=function(){ console.log(1); }
总结:
1. 函数提升能够使函数的声明调用更灵活
2. 函数表达式不存在提升的现象
3. 函数提升出现在相同作用域当中
函数参数
函数参数的使用细节,能够提升函数应用的灵活度。
动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
//求所有参数的和 function sum() { let s = 0 for (let i = 0; i < arguments.length; i++) { s += arguments[i] } console.log(s); } sum(1, 2) //3 sum(1, 2, 3, 4, 5) //15
总结:
1. arguments 是一个伪数组,只存在于函数中。
2. arguments 的作用是动态获取函数的实参
3. 可以通过for循环依次得到传递过来的实参
剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组
function fn(...arr) { console.log(arr); } fn(1, 2, 3) //[1, 2, 3]
总结:
1. ... 是语法符号,置于最末函数形参之前,用于获取多余的实参。
2. 借助 ... 获取的剩余实参,是个真数组。
3.开发中,还是提倡多使用 剩余参数。
展开运算符
展开运算符(…),将一个数组进行展开。
//典型运用场景: 求数组最大值(最小值)、合并数组等 const arr = [1, 2, 3, 4, 5, 6] console.log(Math.max(...arr)); //6 console.log(Math.min(...arr)); //1 //合并数组 const arr1 = [1,2,3] const arr2 = [5,6,7] const arr3 = [...arr1,...arr2] console.log(arr3) //[1,2,3,5,6,7]
展开运算符只展开数组,不会修改原数组。
展开运算符和剩余参数的区别:
- 剩余参数:函数参数使用,得到真数组。
- 展开运算符:数组中使用,数组展开。
箭头函数
目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁。
使用场景:箭头函数更适用于那些本来需要匿名函数的地方。
语法
语法1:基本写法
语法2:只有一个参数可以省略小括号
语法3:加括号的函数体返回对象字面量表达式
语法4:如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值。
const form = document.querySelector('form')
form.addEventListener('submit',ev=>ev.preventDefault())
//整个表达式 ev => ev.preventDefault() 表示一个箭头函数,它接受一个事件对象 ev 作为参数,并调用 preventDefault() 方法来阻止该事件的默认行为。这种用法通常出现在事件处理程序中。
箭头函数参数
- 普通函数有arguments 动态参数
- 箭头函数没有arguments 动态参数,但是有剩余参数 ..args
const getSum = (...arr) => { let sum = 0 for (let i = 0; i < arr.length; i++) { sum += arr[i] } return sum } console.log(getSum(1, 2, 3)); //6
箭头函数 this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此 DOM事件回调函数为了简便,还是不太推荐使用箭头函数
箭头函数的几种常见用法和场景
1.作为函数表达式
这是箭头函数最常见的用法,通常用来定义回调函数或简化代码。
const add = (a, b) => a + b;
2.作为参数传递给其他函数
箭头函数可以直接作为参数传递给其他函数,特别是在使用高阶函数(如
map
、filter
、reduce
等)时。const numbers = [1, 2, 3, 4]; const doubled = numbers.map(num => num * 2); // 使用箭头函数作为参数
3.作为立即调用的函数表达式(IIFE)
虽然不太常见,但箭头函数也可以用作立即调用的函数表达式。
(() => { console.log("这是一个立即调用的箭头函数") })();
4.在类中的方法
箭头函数可以用作类的方法,尤其是在需要保留
this
上下文时。class Counter { constructor() { this.count = 0; } increment = () => { this.count++; }; }
在这个例子中,
increment
是一个箭头函数,它会保留this
的上下文,即指向类实例。5.在对象字面量中
在对象字面量中定义箭头函数,但在这种情况下,它们不能使用对象的属性作为
this
上下文,因为箭头函数不绑定自己的this。
const obj = { value: 42, getValue: () => this.value // 注意:这里的 `this` 不会指向 `obj` };
解构赋值
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值。
数组解构
基本语法
变量的顺序对应数组单元值的位置依次进行赋值操作
const [a, b, c] = [1, 2, 3] console.log(a); //1 console.log(b); //2 console.log(c); //3
基本语法:典型应用交互2个变量
let a=1 let b=2; //这里必须有分号,不然会报错。 [a,b] = [b,a] console.log(a) //2 console.log(b) //1
js 前面必须加分号情况
- 立即执行函数
(function fn(){})(); //或者 ;(function fn(){})()
- 数组解构
//数组开头的,特别是前面有语句的一定要加分号。 //如果不加分号,系统会认为const a=1[1,2,3].....从而报错 const a = 1; [1, 2, 3].map(function (item) { console.log(item); })
数组解构几种常见用法和场景
1.变量多 单元值少的情况:变量的数量大于单元值数量时,多余的变量将被赋值为 undefined。
const [a, b, c,d] = [1, 2, 3] console.log(a); //1 console.log(b); //2 console.log(c); //3 console.log(d); //undefined
2. 变量少 单元值多的情况:
const [a, b, c] = [1, 2, 3, 4] console.log(a); //1 console.log(b); //2 console.log(c); //3
3. 利用剩余参数解决变量少 单元值多的情况:剩余参数返回的还是一个数组,只能置于最末位。
const [a, b, ...arr] = [1, 2, 3, 4, 5, 6] console.log(a); //1 console.log(b); //2 console.log(arr); // [3,4,5,6]
4. 防止有undefined传递单元值的情况,可以设置默认值:允许初始化变量的默认值,且只有单元值为 undefined 时默认值才会生效。
const [a = 0, b = 0] = [1] console.log(a); //1 console.log(b); //0
5. 按需导入,忽略某些返回值:
const [a, , c, d] = [1, 2, 3, 4] console.log(a); //1 console.log(c); //3 console.log(d); //4
6. 支持多维数组的结构:
const [a, b] = [1, [2, 3, 4]] console.log(a); //1 console.log(b); //[2,3,4] const [a, [b, c]] = [1, [2, 3, 4]] console.log(a); //1 console.log(b); //2 console.log(c); //3
练习 数组解构赋值
<script>
// const pc = ['海尔', '联想', '小米', '方正']
const [hr, lx, mi, fz] = ['海尔', '联想', '小米', '方正']
console.log(hr); //海尔
console.log(lx); //联想
console.log(mi); //小米
console.log(fz); //方正
// 请将最大值和最小值函数返回值解构 max 和min 两个变
function getValue() {
return [100, 60]
}
const [max, min] = getValue()
console.log(max); //100
console.log(min); //60
</script>
对象解构
基本语法
1. 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量。
2. 对象属性的值将被赋值给与属性名相同的变量
3. 注意解构的变量名不要和外面的变量名冲突否则报错
4.对象中找不到与变量名一致的属性时变量值为 undefined
//普通对象 const person = { name: '小明', age: 18, gender: '男' }; //批量声明变量 const { name, age, gender } = person console.log(name);//小明 console.log(age);//18 console.log(gender);//男 // 给新的变量名赋值 const { name: uname } = person console.log(uname);//小明
多级对象解构
//第一种情况 const person = { name: '小明', family: { mother: '妈妈', father: '爸爸', sister: '姐姐' }, age: 13 } //解构 const { name, family: { mother, father, sister }, age } = person console.log(name);//小明 console.log(mother);//妈妈 console.log(age);//13 //第二种情况 const people = [ { uname: '小华', family: { mother: '妈妈', father: '爸爸', sister: '姐姐' }, age: 13 } ] const [{ uname, family: { mother, father, sister } }] = people console.log(uname);//小华 console.log(father);//爸爸
<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 // const { data } = msg // console.log(data) // 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数 function render({ data }) { console.log(data); } render(msg) // const { data } = msg // msg 虽然很多属性,但是我们利用解构只要 data值 // function render({ data }) { // // const { data } = arr // // 我们只要 data 数据 // // 内部处理 // console.log(data) // } // render(msg) // // 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData function render({ data: myData }) { console.log(myData); } render(msg) // function render({ data: myData }) { // // 要求将 获取过来的 data数据 更名为 myData // // 内部处理 // console.log(myData) // } // render(msg) </script>
渲染商品列表案例
//主要部分
<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',
},
]
//声明一个字符串变量
let str = ''
//遍历数据
goodsList.forEach(item => {
//console.log(item) 得到每一个数组元素对象
const { name, price, picture } = item
str += `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div>
`
})
//循环得到的是8个拼接的字符串,要用innerHTML进行解析。
document.querySelector('.list').innerHTML = str
</script>
</body>
补充:forEach filter
遍历数组 forEach 方法(重点)
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。(加强版for循环,没有返回值。)
const arr = [1, 2, 3, 4] arr.forEach(function (item, index) { console.log(item);//1 2 3 4 console.log(index);//0 1 2 3 })
注意:
1. forEach 主要是遍历数组
2. 参数当前数组元素是必须要写的, 索引号可选。
筛选数组 filter 方法(重点)
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组。
const num = [1, 2, 3, 4] const re = num.filter(function (item, index) { return item > 2 //map不能比大小,但是可以返回item算数计算值,如item+3。 }) console.log(re); //[3,4]
注意:
1.返回值:返回数组,包含了符合条件的所有元素,如果没有符合条件的元素则返回空数组
2. 参数:currentValue 必须写, index 可选 。
3.因为返回新数组,所以不会影响原数组
综合案例
<!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 => {
//console.log(item) 得到每一个数组元素对象
const { name, price, picture } = item
str += `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div>
`
})
//循环得到的是8个拼接的字符串,要用innerHTML进行解析。
//添加给list
document.querySelector('.list').innerHTML = str
}
render(goodsList)
//过滤筛选 事件委托
document.querySelector('.filter').addEventListener('click', e => {
//e.target.dataset.id e.target.tagName 都是e.target对象里的
const { tagName, dataset } = e.target
if (tagName === 'A') {
// console.log(12);
//当dataset.index不是123时,就是所有区间,不需要筛选。
let arr = goodsList
if (dataset.index === '1') {
//filter进行筛选,筛选后返回一个新数组,得到区间对象
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 && item.price <= 600)
}
//渲染函数
render(arr)
}
})
</script>
</body>
</html>