- 作用域
- 函数进阶
- 解构赋值
一.作用域
- 局部作用域
- 全局作用域
- 作用域链
- JS垃圾回收机制
- 闭包
- 变量提升
1.1 作用域
① 概念:规定了变量能够被访问的“范围”,离开了这个"范围",变量不能被访问
② 分类
- 局部作用域
(1)函数作用域:在函数内部声明的变量,只能在函数内部被访问,外部无法直接访问
1.函数内部声明的变量,在函数外部无法被访问
2.函数的参数也是函数内部的局部变量
3.不同函数内部声明的变量无法互相访问
4.函数执行完毕后,函数内部的变量实际被清空了
(2)块作用域:在JavaScript中使用{ }包裹起来的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问
1.let声明的变量会产生块作用域,var不会产生块作用域
2.const声明的变量会产生块作用域
3.不同代码之间的变量无法相互访问
4.推荐使用let或const
- 全局作用域
① <script> 标签 和.js文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问
② 全局作用域中声明的变量,任何其他作用域都可以被访问
③ 为window对象动态添加的属性默认也是全局的,不推荐
④ 函数中未使用任何关键字声明的变量为全局变量
⑤ 尽可能少声明全局变量,防止全局变量被污染
1.2 作用域链
① 概念:本质是底层的变量查找机制
- 在函数被执行时,会优先在当前函数作用域中查找变量
- 如果当前作用域找不到会依次逐级查找父作用域直到全局作用域
总结
1.嵌套关系的作用域串联起来形成了作用域链
2.相同作用域链中按照从小到大的规则查找变量
3.子作用域能够访问父作用域,父级作用域无法访问子级作用域
1.3.垃圾回收机制
① 垃圾回收机制:简称GC, JS 中内存的回收和分配都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
② 内存的生命周期
- 内存分配:当我们声明变量,函数,对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量,函数
- 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
③ 说明
- 全局变量一般不会回收(关闭页面回收)
- 一般情况下,局部变量的值,不用了,会被自动回收掉
④ 内存泄漏:程序中分配的内存由于某种原因,程序未释放或无法释放叫做内存泄漏
⑤ 算法说明
堆和栈的分配说明:
1.栈(操作系统):由操作系统自动分配释放函数的参数值,局部变量等,基本数据类型放到栈里面
2.堆(操作系统):一般由程序员分配释放,如果程序员不释放,由垃圾回收机制回收,一般存放复杂数据类型
两种常见的垃圾回收算法:引用计数法和标记清除法
引用计数:
- IE浏览器采用的是引用计数法,定义“内存不再使用”,就是看一个对象是否有指向它的引用,如果没有就回收
- 跟踪记录被引用的次数
- 如果被引用了依次,就记录一次,多次引用会累加++
- 如果减少一个引用就减1
- 如果引用次数是0,就释放内存
致命问题:
嵌套引用(循环引用):如果两个对象下相互引用,垃圾回收器不会进行回收,就会导致内存泄漏,因为引用次数永远不会为0
标记清除法
- 现代浏览器已经不再使用引用计数算法了,大多是基于标记清除法的某些改进算法
- 标记清除算法将"不再使用的对象"定义为"无法达到的对象"
- 就是从根部(在JS中就是全局对象)触发定时扫描内存中的对象,凡是能够从根部到达的对象都是需要使用的
- 无法从根部触发触及到的对象被标记为不再使用,稍后进行回收
1.4.闭包
① 概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
② 闭包 = 内层函数 + 外层函数的变量
③ 闭包的作用:封闭数据,提供操作,外部也可以访问函数内部的变量
④ 基本格式:
⑤ 闭包的应用:实现数据的私有
function count () {
// 数据私有 i不会被回收, 可能会有内存泄漏的问题let i = 0
function fn () {i++console.log(i)
}return fn
}
const fun = count()
fun()
1.5.变量提升(不建议使用)
① 变量提升是JS中比较“奇怪”的现象,允许在变量声明之前被访问(仅存在于var声明变量)
- 把所有var声明的变量提升到当前作用域的最前面
- 只提升声明,不提升赋值
相当于:
var num
console.log(num + '件')
② 注意
- 变量在未声明即被访问时会报语法错误
- 变量在var声明之前即被访问,变量的值为undefined
- let/ const 声明的变量不存在变量提升
- 变量提升出现在相同作用域中
- 实际开发中推荐先声明再访问变量
二.函数进阶
- 函数提升
- 函数参数
- 箭头函数
2.1 函数提升
- 函数提升指的是把所有函数声明提升到当前作用域的最前面
- 只提升函数声明,不提升函数调用
- 函数表达式不支持提升
- 函数提升出现在相同作用域中
// 1.会把所有函数声明提升到当前作用域的最前面
// 2.只提升函数声明,不提升函数调用
fn()
function fn() {console.log('函数提升')
}
// 函数表达式不支持将调用写到声明前面
2.2 函数参数
- 动态参数
- 剩余参数
动态参数
① arguments是函数内部-内置的伪数组变量,它包含了调用函数时传入的所有实参
② 代码
function getSum() {console.log(arguments);let sum = 0for (let i = 0; i < arguments.length; i++) {sum += arguments[i]}console.log(sum)
}
getSum(1,2,3,4,5,6,7,8,9,10)
③ 注意
- arguments 是一个伪数组,只存在于函数中
- arguments 的作用是动态获取函数的实参
- 可以通过for循环依次得到传递过来的实参
剩余参数
① 剩余参数
- ...是语法符号,置于最末函数形参之前,用于获取多余的实参
- 借助...获取到的剩余实参,是个真数组
② 代码
function getSum (a, b, ...arr) {console.log(arr) // 使用的时候不需要使用...
}
getSum(2, 3)
getSum(1, 2, 3, 4, 5)
③ 展开运算符(...):将一个数组进行展开
- 展开数组,不会修改原数组
const arr1 = [1, 2, 3]
// 展开数组: 不会修改原数组 应用场景:求数组最大值(最小值), 合并数组
// 最大值
console.log(Math.max(...arr1))
console.log(Math.min(...arr1))
console.log(...arr1)
- 合并数组
const arr1 = [1, 2, 3]
const arr2 = [4, 5]
const arr = [...arr1, ...arr2]
④ 剩余参数和展开运算符的区别
剩余参数:在函数中使用,会得到一个真数组
展开运算符:数组中使用,数组展开
2.3 箭头函数
- 基本语法
- 箭头函数参数
- 箭头函数this
1.基本语法
① 目的:引入箭头函数的目的是更加简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
② 使用场景:箭头函数更适用于那些原本需要匿名函数的地方
③ 写法
(1)原本匿名函数写法
const fn = function () {console.log(123)
}
(2)箭头函数基本写法
const fn = () => {console.log(123)
}fn()
(3)箭头函数传参数
const fn = (x) => {console.log(x)
}
fn(1)
(4)只有一个参数的可以省略小括号
const fn = x => {console.log(x)
}
fn(1)
(5)只有一行代码,可以省略大括号
const fn = x => console.log(x)
fn(1)
(6)只有一行代码,箭头函数可以省略return
const fn = x => x + x
console.log(fn(1))
(7)箭头函数可以直接返回一个对象
const fn = (uname) => ({ uname: uname })
console.log(fn('hello'))
2.箭头函数参数
- 普通函数有arguments 动态参数
- 箭头函数没有 arguments 动态参数,但是有剩余参数 ...args
const getSum = (...arr) => {let sum = 0for (let i = 0; i < arr.length; i++) {sum += arr[i]}return sum
}
console.log(getSum(1,2,3))
3.箭头函数this
① this指向window
console.log(this)
② 普通函数this指向调用者
function fn () {
// this指向调用者console.log(this)
}
③ 对象里面的普通函数的this指向该对象
const obj = {name: 'andy',sayHi: function () {console.log(this)}}obj.sayHi()
④ 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
⑤ 普通函数箭头函数指向上一层作用域window
const fn = () => {console.log(this)}fn()
⑥ 对象方法中的箭头函数中的this指向window
const obj = {uname: 'pink老师',sayHi: () => {console.log(this) // 指向 window}}obj.sayHi()
⑦ 这里的this指向obj
const obj = {uname: 'pink老师',sayHi: function () {let i = 10const count = () => {console.log(this) // obj}count()}}obj.sayHi()
⑦ 注意:DOM事件回调函数为了方便,还是不太推荐使用箭头函数
2.4 解构赋值
- 数组解构
- 对象解构
1.数组解构
① 概念:将数组的单元值快速批量赋值给一系列变量的简洁语法
② 基本语法
- 赋值运算符 = 左侧的[ ] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
- 变量的顺序对应数组单元值的位置依次进行赋值操作
const arr = [100, 60, 80]
// 数组解构
const [max, min, avg] = arr
console.log(max, min, avg)
交换两个变量的值
let a = 1
// 必须加分号
let b = 2;
[b, a] = [a, b]
③ 必须加分号的两种情况
(1)立即执行函数
(function() {})();
(2)数组解构
const str = 'pink'
;[1, 2, 3].map(function (item) {console.log(item)
})
③ 数组解构细节
- 变量多,单元值少,多余的值为undefined
const [a, b, c, d] = [1, 2, 3]
console.log(a, b, c, d)
- 变量少,单元值多, 多余的参数没有值接
const [a, b] = [1, 2, 3, 4]
- 利用剩余参数解决
const [a, b, ...c] = [1, 2, 3, 4]
console.log(a, b, c)
- 防止undefined传递, 可以给默认参数
const [a = 0, b = 0] = []
console.log(a, b)
- 按需导入,忽略某些返回值
const [a, b, , c] = [1, 2, 3, 4]
console.log(a, b, c)
- 多维数组
// 多维数组
const arr = [1, 2, [3, 4]]
console.log(arr[0])
console.log(arr[2][0])// 多维数组解构
const [a, b, [c, d]] = [1, 2, [3, 4]]
console.log(a, b, c, d)
2.对象解构
① 对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
② 基本语法
- 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
- 对象属性的值将被赋值给与属性名相同的变量
- 解构的变量名不要和外面的变量名冲突报错
- 对象中找不到与变量名一致的属性时变量值为undefined
const obj = {uname: 'pink',age: 18
}const { uname, age } = obj
③ 给新的变量名赋值:可以从一个对象中提取变量并同时修改新的变量名, 新名字在后面
const uname = 'hello'
const { uname: username, age } = obj
console.log(username)
④ 解构数组对象
const pig = [{uname: '佩奇',age: 18
}]
const [{ uname, age }] = pig
console.log(uname)
console.log(age)
⑤ 多级对象解构
const pig = {name: '佩奇',family: {mother: '妈妈',father: '爸爸',sister: '乔治'},age: 6}const { name, family: { mother, father, sister } } = pigconsole.log(name, mother, father, sister)
⑥ 多级对象数组解构
const person = [{name: '佩奇',family: {mother: '妈妈',father: '爸爸',sister: '乔治'},age: 6}]const [{ name, family: { mother, father, sister } }] = personconsole.log(name, mother, father, sister)
3.forEach() 方法
① 作用:遍历数组中的每个元素,将元素传递给回调函数
② 场景:遍历数组的每个元素
③ 语法:
被遍历的数组.forEach(function (当前数组元素,当前元素引号) {
// 函数体
})
const arr = ['red', 'green', 'pink']
arr.forEach(function(item, index) {console.log(item)console.log(index)
})
④ 注意
- 主要用来遍历数组
- 参数里面当前元素是必须要写的,索引号是可选的