作用域
局部作用域
局部作用域分为函数作用域和块作用域
函数作用域
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
块作用域
在 JavaScript 中使用 { }
(如 if
、for
、while
等代码块)包裹的代码称为代码块,代码块内部声明的变量外部可能无法访问
- 对象本身并不算作一个作用域
- let 声明的变量会产生块作用域,var 不会产生块作用域
- const 声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用 let 或 const
全局作用域
<script> 标签和 .js 文件 的最外层就是全局作用域,在此声明的变量在任何其它作用域都可以被访问
- 为 window 对象动态添加的属性默认也是全局的,不推荐
- 函数中未使用任何关键字声明的变量为全局变量,不推荐
- 尽可能少的声明全局变量,防止全局变量被污染
作用域链
作用域链本质上是底层的变量查找机制
- 在函数被执行时,会优先在当前函数作用域中查找变量
- 如果当前作用域查找不到则会逐级查找父级作用域直到全局作用域
垃圾回收机制
生命周期
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
全局变量一般不会回收(关闭页面回收),一般情况下局部变量不用了后会被自动回收掉
内存泄漏:不再用到的内存,没有及时释放,就叫做内存泄漏
算法
两种常见的浏览器垃圾回收算法:引用计数法 和 标记清除法
引用计数法
IE 采用的引用计数算法, 定义 “ 内存不再使用 ”,就是看一个对象是否有指向它的引用,没有引用了就回收对象
算法:
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数 1,多次引用会累加 ++
- 如果减少一个引用就减 1 –
- 如果引用次数是 0 ,则释放内存
缺点:嵌套引用(循环引用),如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,因为他们的引用次数永远不会是 0,这样会导致内存泄露
标记清除法
现代的浏览器已经不再使用引用计数算法了,现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的
核心:
- 标记清除算法将 " 不再使用的对象 " 定义为 " 无法达到的对象 "
- 从根部(在 JS 中就是全局对象)出发定时扫描内存中的对象, 凡是能从根部到达的对象,都是还需要使用的
- 无法由根部出发触及到的对象被标记为不再使用,稍后进行回收
闭包
闭包 = 内层函数 + 外层函数的变量
作用:使外部也可以访问函数内部的变量,示例代码如下:
function outer() {
let a = 100
function fn() {
console.log(a)
}
return fn
}
// outer() === fn === function fn() { console.log(a) }
const fun = outer()
fun() // 调用函数,实现外部(fun())调用函数内部的变量(a)
闭包应用:实现数据的私有,比如,要做个统计函数调用次数,函数调用一次,就 ++,代码如下:
function fn() {
let count = 1
function fun() {
count++
console.log(`函数被调用了${count}次`)
}
return fun
}
const result = fn()
result() // 2
result() // 3
闭包可能引起内存泄漏
变量提升
变量提升的流程:
- 先把 var 变量提升到当前作用域的最前面
- 只提升变量声明, 不提升变量赋值
- 然后依次执行代码
实际开发中不建议使用 var 声明变量
函数进阶
函数提升
函数提升指的是函数在声明之前可以被调用,会把所有函数声明提升到当前作用域的最前面,示例代码如下:
<script>
fn()
function fn(){
console.log('校花好美')
}
</script>
函数参数
动态参数
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参,示例代码如下:
function getSum() {
let sum = 0
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
console.log(sum)
}
getSum(1, 2, 3, 4, 5)
使用场景:当不确定传递多少个实参的时候
剩余参数
...
是语法符号,放在函数形参的最后一个位置,用于获取多余的实参,是个真数组,示例代码如下:
function getSum(a, ...arr) {
console.log(arr) // [2,3]
}
getSum(1, 2, 3)
使用场景和动态参数相同,但是剩余参数更加灵活(真数组),因此开发中尽量使用剩余参数
展开运算符
展开运算符( ...
),跟剩余参数的语法相同,作用是将一个数组进行展开,不会修改原数组,示例代码如下:
const arr1 = [1, 2, 3]
console.log(...arr1) // 1 2 3
// 求最大值 ...arr === 1,2,3
console.log(Math.max(...arr1)) // 3
// 合并数组
const arr2 = [3, 4, 5]
const arr = [...arr1, ...arr2]
console.log(arr) // [ 1, 2, 3, 3, 4, 5 ]
箭头函数
使用场景:箭头函数适用于本来需要使用匿名函数的地方
基本语法
基本写法:
const fn = () => {
console.log(123)
}
-
箭头函数属于表达式函数(赋值操作),因此不存在函数提升
-
只有一个参数可以省略小括号
-
箭头函数函数体只有一行代码时可以省略大括号
{}
,并自动做为返回值 -
加括号的函数体返回对象字面量,示例代码如下:
const fn5 = uname => ({uname: uname}) console.log(fn5('校花'))
参数
- 普通函数有
arguments
动态参数和剩余参数...args
- 箭头函数没有
arguments
动态参数,但是有剩余参数...args
this
- 箭头函数不会创建自己的
this
,它只会从自己的作用域链的上一层沿用this
(注意对象本身并不算作一个作用域) - 事件回调函数使用箭头函数时,
this
为全局的window
,所以不推荐 DOM 事件回调函数使用箭头函数
解构赋值
数组解构
数组解构是将数组的元素快速批量赋值给一系列变量的简洁语法
基本语法
左侧的 []
用于批量声明变量,右侧数组的元素将被依次赋值给左侧的变量,示例代码如下:
const arr = [100, 60, 80]
// 数组解构
const [max, min, avg] = arr
console.log(max) // 100
console.log(min) // 60
console.log(avg) // 80
-
可以配合剩余参数使用,注意剩余参数只能放在左侧
[]
的最后一个位置 -
可以设置默认值,示例代码如下:
const [aa = '校花', bb = '好美'] = ['校花校花'] console.log(aa) // 校花校花 console.log(bb) // 好美
-
可以忽略某些值,示例代码如下:
const [a, b, , d] = [1, 2, 3, 4] console.log(a) // 1 console.log(b) // 2 console.log(d) // 4
-
支持多维数组,示例代码如下:
const [a, b, c] = [1, 2, [3, 4]] console.log(a) // 1 console.log(b) // 2 console.log(c) // [3, 4] console.log(c[0]) // 3
应用场景
交换 2 个变量,示例代码如下:
let a = 1
let b = 2; // 注意要加分号
[b, a] = [a, b]
console.log(a, b) // 2 1
js 前面必须加分号情况:
- 立即执行函数前
- 使用数组字面量前
对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
基本语法
- 左侧的
{}
用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 - 对象属性的值将被赋值给与属性名相同的变量
- 解构的变量名不要和外面的变量名冲突否则报错,除非重新命名变量名
- 对象中找不到与变量名一致的属性时变量值为
undefined
示例代码如下:
const {uname, age} = {
uname: '校花',
age: 18
}
console.log(uname) // 校花
console.log(age) // 18
修改变量名
可以从一个对象中提取变量并修改变量名,示例代码如下:
const uname = '程序员校花'
const {uname: name, age} = {
// 把原来的 uname 变量重新命名为 name
uname: '校花',
age: 18
}
console.log(name) // 校花
console.log(uname) // 程序员校花
console.log(age) // 18
数组对象解构
解构数组对象,示例代码如下:
const pig = [
{
uname: '佩奇',
age: 6
}
]
const [{uname, age}] = pig
console.log(uname) // 佩奇
console.log(age) // 6
多级对象解构
const pig = {
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
sister: '乔治',
}
}
const { name, family: { mother, father, sister } } = pig
console.log(name) // 佩奇
console.log(mother) // 猪妈妈
const pig = [
{
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
sister: '乔治',
},
age: 6
}
]
const [ { family: { sister } } ] = pig
console.log(sister) // 乔治
函数形参解构
const msg = {
"code": 200,
"msg": "获取新闻列表成功",
"data": [
{
"id": 1,
"title": "5G商用自己,三大运用商收入下降",
"count": 58
},
{
"id": 2,
"title": "国际媒体头条速览",
"count": 56
},
{
"id": 3,
"title": "乌克兰和俄罗斯持续冲突",
"count": 1669
},
]
}
function render({data}) {
// const {data} = arr
console.log(data)
}
render(msg)
数组常用方法
方便解构赋值后对数组或者数组对象的操作
forEach
forEach 主要是遍历数组
- 参数当前数组元素是必须要写的, 索引号可选
- 没有返回值
示例代码如下:
const arr = ['red', 'green', 'pink']
arr.forEach(function (item, index) {
console.log(item) // red green pink
console.log(index) // 0 1 2
})
filter
筛选数组
- 返回新数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组
- 参数:
item
必写,index
可选
示例代码如下:
const newArr = arr.filter(item => item >= 20)
console.log(newArr) // [20 ,30]
map
map 可以处理数据,并且返回新的数组,示例代码如下:
const arr = ['red', 'green', 'pink']
const newArr = arr.map(function (item, index) {
console.log(item) // red green pink
console.log(index) // 0 1 2
return item + '老师'
})
console.log(newArr)
join
用于把数组中的所有元素转换一个字符串,通过参数里面指定的分隔符进行分隔,示例代码如下:
const arr = ['red', 'green', 'pink']
console.log(arr.join('')) // redgreenpink