Js进阶之作用域、解构、箭头函数
作用域
局部作用域
函数作用域
在函数内部声明的变量只能在函数内部访问,外部无法直接访问。
块作用域
用{}包裹的代码称为代码块,代码块内部声明的变量在外部 有可能 无法被访问。
注意:
- let声明的变量会产生块作用域,var不会产生块作用域。
- const声明的常量也会产生块作用域。
- 不同代码块之间的变量无法互相访问。
- 推荐使用let或者const。
全局作用域
script标签 和.js文件的最外层就是全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其他作用域都可以访问。
注意:
- 为window对象动态添加的属性默认也是全局的。
- 函数中未使用任何关键字声明的变量为全局变量。
- 尽可能少声明全局变量,防止变量污染。
作用域链
作用域链本质上是底层的变量查找机制
。
- 在函数被执行时,会
优先查找当前
函数作用域中查找变量。 - 如果当前作用域差找不到则会依次
逐级查找父级
作用域直至全局作用域。(有点像冒泡(?))
即:
- 嵌套关系的作用域串联起来形成了作用域链。
- 相同作用域链中按着从小到大的规则查找变量。
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域。
内存的生命周期
- 内存分配:声明变量、函数、对象时,系统自动分配内存。
- 内存使用:即读写内存,简而言之就是使用变量、函数。
- 内存回收:使用完毕,垃圾回收器自动回收不再使用的内存。
注意:
- 全局变量一般不会回收(关闭页面后回收
- 一般局部变量的值会被自动回收
内存泄漏
分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏。
JS垃圾回收机制
-
引用计数法:定义“内存不再使用”,即看一个对象是否有指向它的引用,没有就回收。 【已经很少使用】
例子:
// 声明了一个对象pa,地址存在栈中,内容存在堆中 let pa = { uname: 'jack', age: 18 } // 声明一个对象pb,将pa地址赋给pb let pb = pa // 此时,内容的引用次数为2 // 改变pa地址,使其不再指向内容 pa = 0 // 此时,内容的引用次数为1(pb) // 再改变pb地址 pb = null // 最后,内容的引用次数为0,被回收
但是,如果存在
嵌套引用
,两个对象相互引用,尽管它们已不再使用,垃圾回收器不会回收,导致内存泄漏。这是因为它们的引用次数永远不会是0。function fn(){ let a={} let b ={} a.p = b b.p = a return // return完后,a,b局部变量本应该销毁,但是a.p引用了b,b.p引用了a,二者的引用次数恒为1,造成了内存泄漏 } fn()
-
标记清除法 【现代浏览器使用的改进算法的基础】
- 将“不再使用的对象”定义为“
无法到达的对象
”。 - 从
根部
出发定时扫描内存中的对象。凡是能从根部到达的对象,都还是要使用的。 - 那些
无法
由根部出发触及到的对象被标记为不再使用
,稍后进行回收。
总的来说就是,从根部定期扫描,扫不到的就回收。
- 将“不再使用的对象”定义为“
闭包
定义:一个函数对周围状态的引用捆绑在一起,内层函数访问到其外层函数的作用域。
简单理解:闭包 = 内层函数 + 外层函数的变量 (内层函数用到了外层函数的变量(?))
可以用来让外部访问函数内部的变量。
// 闭包简单写法
function out(){
let a = 10
function fn(){
console.log(a)
}
fn()
}
out()
// 闭包常见写法
function pp(){
let a = 20
function fn(){
console.log(a)
}
return fn // 闭包可能引起内存泄漏,a没有被回收,一直能被查找到
}
const fu = pp()
fu() // 输出10
// 外面使用了10这个值
应用:实现数据的私有。
变量提升
允许在变量声明之前被访问(仅存在于var声明变量)。
- 把所有var声明的变量提升到当前作用域最前面。
- 只提升声明,不提升赋值。
【此处输出的是 undefined, 说明只提升声明不提升赋值的特性】
函数进阶
函数提升
- 把所有函数声明提升到当前作用域的最前面。
- 只提升函数声明,不提升函数调用。
fn()
function fn(){
console.log(1)
}
【特殊】函数表达式必须先声明和赋值,再调用
函数参数
-
动态参数
arguments
是函数内置的伪数组变量,它包含了调用函数时传入的所有实参。function getSum(){ let sum=0 for(let i=0;i<arguments.length;i++) sum+=arguments[i] console.log(sum) } getSum(1,2) getSum(1,2,3,4)
-
剩余参数
...变量名
允许将不定量的参数表示为一个数组。【获取多余的参数】function getSum(...arr){ console.log(arr) } getSum(1,2)
二者区别:
- 剩余参数是将没有形参对应的那些实参传入…中。
- 剩余参数是真数组,动态参数是伪数组。
展开运算符
...
可以把数组展开。
const arr = [1,2,3]
console.log(...arr) // 输出1,2,3
其典型运算场景:求数组最值,合并数组等。
// 求数组最值
const arr = [1,2,3]
console.log(Math.max(...arr))
console.log(Math.min(...arr))
// 合并数组
const arr2 = [4,5,6]
const arr3 = [...arr,...arr2]
console.log(...arr3) // 1,2,3,4,5,6
箭头函数
目的:更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁。
使用场景:更适用于本来需要匿名函数的地方
基本语法
const fn = (x) => {
console.log(x)
}
// x是参数,只有一个形参的时候,可以省略小括号
// 只有一行代码,省略大括号,并且无需写return,直接返回值
const fn2 = x => console.log(x)
const fn3 = x => x+x
fn(1) //1
fn2(2) //2
console.log(fn3(2)) //4
// 箭头函数可以直接返回一个对象
const fn4 = (uname) => ({ name:uname })
// 因为{}会分不清是函数体还是要返回对象,所以用()包起来
console.log(fn4('刘德华'))
箭头函数参数
箭头函数没有arguments
动态参数,但是有剩余参数...args
箭头函数this
箭头函数不会创建自己的this,它只会从自己的作用域链上一层沿用this。
在使用DOM事件回调函数,不推荐用箭头函数,this的值不指向该事件函数本身。
解构赋值
数组解构
将数组对的单元值快速批量赋值给一系列变量的简洁语法。
基本语法
- 赋值运算符 = 左侧的[]用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量。
- 变量的顺序对应数组单元的位置依次进行赋值操作。
const arr = [1,2,3]
const [a,b,c] = arr // a = 1 , b = 2, c = 3
注意:
-
单元值少,变量多的情况下:依次赋值后,多余的变量为undefined。
//防止undefined传递 const [a=0,b=0] = [1]
-
单元值多,变量少的情况下:利用剩余参数,但剩余参数只能放在最末位。
const [a,b,...c] = [1,2,3,4] // a = 1, b = 2, c = [3,4]
-
按需导入赋值
const [a,,b,c] = [1,2,3,4] // a = 1, b = 3, c = 4
-
支持多维数组的解构
const [a,b,c] = [1,2,[3,4]] // a=1, b=2, c=[3,4]
典型应用
变量交换
let a = 1
let b = 2; // 一定要加分号
[b,a] = [a,b] // a、b交换了值
JS前面一定要加分号的情况:
- 立即执行函数
- 直接使用数组的时候([1,2,3]、[a,b]……)
对象解构
基本语法
- 赋值运算符 = 左侧的{}用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量。
- 对象属性的值将被赋值给与属性名相同的变量。
- 注意解构的变量名不要和外面的变量名冲突。
- 对象中找不到与变量名一致的属性时变量值为undefined
const obj = {
uname:'小明',
age:20
}
const {uname,age} = obj
// 等价于 uname = obj.uname; age = obj.age;
注意:
【对象属性名更改】
const uname = 'jack'
const {uname:name,age} = {uname:'jack',age:19}
// 这个uname就被改名为name了,不会报错
// 用的时候就是用name了
【解构数组对象】
const obj = [
{
uname:'小明',
age:18
}
]
const [{uname,age}] = obj
【多级对象解构】
const pig = {
name:'佩奇',
family:{
mother:'猪妈妈',
father:'猪爸爸',
sister:'乔治'
},
age:6
}
// 语法
const {name,family:{mother,father,sister}} = pig
console.log(name) // 佩奇
console.log(mother) // 猪妈妈
console.log(father) // 猪爸爸
console.log(sister) // 乔治
数组遍历forEach方法
要遍历的数组.forEach(function(当前数组元素,当前元素下标){
//函数体
})
与map不同的是,forEach不返回数组,它的返回值是undefined。
const arr = ['red','green','blue']
const result = arr.forEach(function(item,index){
console.log(item) // 数组元素
console.log(index) //索引号
})
console.log(result) // undefined
注意:index可以省略,但是item不可以省略。
forEach方法适合用于数组对象。
数组筛选filter方法
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
- 筛选数组符合条件的元素,并返回筛选之后元素的新数组
- currentValue必须写,index可不写
- 返回的是新数组,不影响原数组
语法:
被遍历的数组.filter(function(currentValue,index){
return 筛选条件
})
例子:
// 筛选数组中大于30的元素
const score = [10,50,3,40,33]
const re = score.filter(function(item){
return item>30
})
// re [50,40,33]