进阶
作用域
- 作用域(scope)规定了变量能够访问的 ‘范围’ ,离开了这个 ‘范围’ 变量便不能被访问
全局作用域
<script>
标签和 .js
文件 的【最外层】就是所谓的全局作用域,全局作用域中声明的变量,在其他作用域都可以被访问。
注意:
- 为 window 对象动态添加的属性默认也是全局的,不推荐!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
局部作用域
局部作用域分为函数作用域和块作用域。
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问
函数作用域
一般在函数体内的作用域叫做函数作用域
块作用域
if for switch 这类用{} 包裹的语句叫做块级作用域
注意:
- let 声明的变量会产生块作用域,var 不会产生块作用域
- const 声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用 let 或 const
作用域链
作用域链本质上是底层的变量查找机制。
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
JS垃圾回收机制
内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
说明:
- 全局变量一般不会回收(关闭页面回收)
- 一般情况下局部变量的值, 不用了, 会被自动回收掉
垃圾回收机制算法
1.引用计数法
IE采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。 算法:
- 跟踪记录每个值被引用的次数。
- 如果这个值的被引用了一次,那么就记录次数1
- 多次引用会累加。
- 如果减少一个引用就减1。
- 如果引用次数是0 ,则释放内存。
const person = {
age: 18,
name: 'pink老师'
}
const p = person
person = 1
p = null
-----------------------------
由上面可以看出,引用计数算法是个简单有效的算法。
但它却存在一个致命的问题:“嵌套引用”。
如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
2.标记清除法
现代的浏览器已经不再使用引用计数算法了。 现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。 核心:
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进 行回收。
function fn() {
let o1 = {}
let o2 = {}
o1.a = o2
o2.a = o1
return '引用计数无法回收'
}
fn()
-------------------------
根部已经访问不到,所以自动清除
回收机制总结
- 1.什么是垃圾回收机制?
- 简称 GC
- JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
- 2.什么是内存泄漏?
- 不再用到的内存,没有及时释放,就叫做内存泄漏
- 3.内存的生命周期是什么样的?
- 内存分配、内存使用、内存回收
- 全局变量一般不会回收; 一般情况下局部变量的值, 不用了, 会被自动回 收掉
闭包
- 概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
- 简单理解:闭包 = 内层函数 + 外层函数的变量
- 先看个简单的代码:
闭包应用实现全局私有
比如,我们要做个统计函数调用次数,函数调用一次,就++
总结
- 1.怎么理解闭包?
- 闭包 = 内层函数 + 外层函数的变量
- 2.闭包的作用?
- 封闭数据,实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
- 3.闭包可能引起的问题?
- 内存泄漏(原因:将局部函数赋给了全局变量)
变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
注意:
- 变量在未声明即被访问时会报语法错误
- 变量在var声明之前即被访问,变量的值为 undefined
- let/const 声明的变量不存在变量提升
- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
函数进阶
函数提升
1.命名函数
//调用函数
fn()
//声明函数
function fn(){
console.log('声明之前即被调用')
}
2.函数表达式
//不存在变量提升
bar() //错误
var bar =function (){
console.log('函数表达式不存在提升现象')
}
总结:
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
函数参数
1.动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
//求和函数,计算所有参数的和
function sum(){
console.log(arguments) //以伪数组的形式打印传进来的参数
let s = 0
for(let i =0;i<arguments.length;i++){
s += arguments[i]
}
console.log(s)
}
//调用求和函数
sum(5,10) //两个参数
sum(1,2,4,5,9)//五个参数
总结:
- arguments 是一个伪数组,只存在于函数中
- arguments 的作用是动态获取函数的实参
- 可以通过for循环依次得到传递过来的实参
2.剩余参数
function config(baseURL, ...other){
console.log(barURl) //得到 'http://baidu.com'
console.log(other) //other 得到 ['get','json']
}
//调用函数
config('http://baidu.com','get','json')
总结:
- ... 是语法符号,置于最末函数形参之前,用于获取多余的实参
- 借助 ... 获取的剩余实参,是个真数组
- 开发中,还是提倡多使用 剩余参数
展开运算符
展开运算符(…),将一个数组进行展开 (不会修改原数组)
- 典型运用场景: 求数组最大值(最小值)、合并数组等
//求数组最大值(最小值)
const arr =[1,5,3,8,2]
cosole.log(...arr) //1 5 3 8 2
console.log(Math.max(...arr))// 8
console.log(Math.min(...arr))// 1
//合并数组
const arr1 =[1,2,3]
const arr2 =[4,5,6]
cosnt newArr=[.arr1,...arr2]
console.log(newArr) //[1,2,3,4,5,6]
总结:
- 展开运算符主要的作用是?
- 可以把数组展开,可以利用求数组最大值以及合并数组等操作
- 展开运算符和剩余参数有什么区别?
- 展开运算符主要是 数组展开
- 剩余参数 在函数内部使用
箭头函数
通过箭头函数返回对象字面量表达式 需要加上括号
const fn =uname =>({uname:uname})
console.log(fn('坤坤')) // {uname:'坤坤'}
箭头函数参数
- 普通函数有arguments 动态参数
- 箭头函数没有 arguments 动态参数,但是有 剩余参数
...
const getSum =(...args)=>{
let sum = 0
for(let i = 0;i<args.length;i++){
sum+=args[i]
}
console.log(sum)
}
getSum(1,2,3)
箭头函数的 this
1.在箭头函数出现之前,每一个新函数根据它是被如何被调用的来定义这个函数的this值的。
2.箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
btn.addEventListener('click', function () {
console.log(this) //指向调用者btn
})
btn.addEventListener('click', () => {
console.log(this) // 指向 window
})
解构赋值
数组结构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:
赋值运算符 = 左侧的 [ ] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
变量的顺序对应数组单元值的位置依次进行赋值操作
1. 将 声明变量指向 arr
const arr =[1,2,3]
const [a,b,c]=arr
console.log(b) // 2
2.同时将数组单元值4 5 6 ,依次赋值给变量 E F G
const [E,F,G] =[4,5,6]
consoloe.log(f) // 5
3.典型应用交互2个变量的值
let a =1
let b =3; //这里必须加分号否则报错 原因 3[b,a]
[b,a]=[a,b]
console.log(a) // 3
console.log(b) // 1
利用展开运算符解决变量少 单元值多的情况:
利用展开运算符 解决变量少,单元值多的问题
const [a,b,...tel] =['小米','苹果','华为','魅族','中兴']
console.log(a) //小米
console.log(b) //苹果
console.log(tel) //['华为','魅族','中兴']
为了防止有 undefined 传递参数的情况,可以设置默认值
const [a, b, c = '小米'] = ['华为']
console.log(a) //华为
console.log(b) //undefined
console.log(c) //小米
按需导入忽略某些值
cosnt [a, ,c,d]=['小米','苹果','华为','魅族']
console.log(a) //小米
console.log(c) //华为
console.log(d) //魅族
支持多维数组解构
1.
const [a,b]=['小米',['苹果','华为']]
console.log(a) // 小米
console.log(b) //['苹果','华为']
2.想拿到苹果和华为怎么办?
const [a,[b,c]]=['小米',['苹果','华为']]
console.log(b) // 苹果
console.log(c) // 华为
对象结构
- 基本语法:
- 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
- 对象属性的值将被赋值给与属性名相同的变量
- 注意解构的变量名不要和外面的变量名冲突否则报错 4.对象中找不到与变量名一致的属性时变量值为 undefined
- 给变量重命名 使用
:
冒号表示“什么值:赋值给谁”
// 普通对象
const user ={
name:'小明',
age:18
}
// 批量声明变量 name age
const {name,age} = user
console.log(name)//小明
console.log(age)// 18
数组对象结构
const goods = [{
goodsName: '小米',
price: 1999
}]
const [{ price }] =goods
console.log(price) //1999
多级对象解构
const pig = {
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
brother: '乔治'
},
age: 6
}
————————————————————————
const {name,family:{mother,father,brother}}=pig
console.log(name) //佩奇
console.log(brother) //乔治
const dataJson ={
'code':200,
'msg':'获取列表成功'
data:{
'id':1
'count':'58'
}
}
————————————————————————
const {data:res} =dataJson
console.log(res)