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