文章目录
作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问
局部作用域
局部作用域声明的变量外部不能使用
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
块作用域
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将有可能无法被访问
- let 声明的变量会产生块作用域,var 不会产生块作用域
- const 声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用 let 或 const
全局作用域
script标签和 .js 文件的最外层就是所谓的全局作用域,在此声明的变量在此声明的变量在函数内部也可以被访问。全局作用域中声明的变量,任何其它作用域都可以被访问
- 为 window 对象动态添加的属性默认也是全局的,不推荐!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
作用域链
作用域链本质上是底层的变量查找机制。
-
在函数被执行时,会优先查找当前函数作用域中查找变量
-
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
JS垃圾回收机制
垃圾回收机制(Garbage Collection) 简称 GC
-
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
-
但如果不了解JS的内存管理机制,同样非常容易成内存泄漏(内存无法被回收)的情况
不再用到的内存,没有及时释放,就叫做内存泄漏
内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
内存使用:即读写内存,也就是使用变量、函数等
内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
说明:全局变量一般不会回收(关闭页面回收);
一般情况下局部变量的值, 不用了, 会被自动回收掉
垃圾回收算法说明
所谓垃圾回收, 核心思想就是如何判断内存是否已经不再会被使用了, 如果是, 就视为垃圾, 释放掉
引用计数法
IE采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用
算法:
- 跟踪记录每个值被引用的次数。
- 如果这个值的被引用了一次,那么就记录次数1多次引用会累加。
- 如果减少一个引用就减1。
- 如果引用次数是0 ,则释放内存。
但它却存在一个致命的问题:嵌套引用
如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露
标记清除法
核心:
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”;
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的;
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进 行回收
闭包
闭包可能会引起内存泄漏
一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
闭包 = 内层函数 + 外层函数的变量
闭包作用:封闭数据,实现数据私有,外部也可以访问函数内部的变量
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
格式:
function outer(){
//将这个变为局部变量,无法从外部修改(全局变量太容易被修改了)
let i = 1
function fn(){
console.log(i)
}
return fn //当然也可以直接return一个function函数
}
const fun = outer()
fun() //1
//外层函数使用内部函数的变量
变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问
- 变量在未声明即被访问时会报语法错误
- 变量在var声明之前即被访问,变量的值为 undefined
- let/const 声明的变量不存在变量提升
- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
变量提升流程:
先把var 变量提升到当前作用域于最前面
只提升变量声明, 不提升变量赋值
然后依次执行代码
函数
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
函数参数
动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
arguments 是一个伪数组,只存在于函数中
arguments 的作用是动态获取函数的实参
可以通过for循环依次得到传递过来的实参
//求和函数,无论传入多少参数都可以进行求和
function sum(){
let s = 0
for(let i=0;i<arguments.length;i++){
s += arguments[i]
}
return s
}
console.log(sum(5,10)) //15
console.log(sum(1,2,3)) //6
剩余参数
写法:…参数名
剩余参数允许我们将一个不定数量的参数表示为一个数组(是个真数组)
//求和函数,无论传入多少参数都可以进行求和
function sum(...arrs){
let s = 0
for(let i=0;i<arrs.length;i++){
s += arrs[i]
}
return s
}
console.log(sum(5,10)) //15
console.log(sum(1,2,3)) //6
展开运算符
展开运算符(…),可以将一个数组进行展开
且不会修改原数组
典型运用场景: 求数组最大值(最小值)、合并数组等
//求最大值,最小值
const 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]
const arr3 = [...arr1,...arr2]
console.log(arr3) //[1,2,3,4,5,6]
展开运算符和剩余参数的区别:
- 展开运算符主要是 数组展开
- 剩余参数 在函数内部使用
箭头函数
箭头函数更适用于那些本来需要匿名函数的地方
引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
() => {}
- 如果只有一个参数,可以不写小括号
- 如果只有一行代码,可以不写大括号,并自动做为返回值被返回
- 加括号的函数体返回对象是一个字面量,如下所示
const fn = uname => ({uname:uname})
console.log(fn('小明')) //{uname:'小明'}
箭头函数的参数
箭头函数没有 arguments 动态参数,但是有 剩余参数 …args
//求和函数,无论传入多少参数都可以进行求和
const sum = (...arrs) => {
let s = 0
for(let i=0;i<arrs.length;i++){
s += arrs[i]
}
return s
}
console.log(sum(5,10)) //15
console.log(sum(1,2,3)) //6
箭头函数的this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
console.log(this) //此处为window
const say = () => {
console.log(this) //此处为window
}
btn.addEventListener('click',() => {
console.log(this) //当前this指向window
})
- 所以DOM事件回调函数不推荐使用箭头函数,特别是需要用到this的时候
- 事件回调函数使用箭头函数时,this 为全局的 window
解构赋值
数组解构
主要用于快速给变量赋值(右边的赋给左边的)
const [max,min,avg] = [100,60,80]
console.log(max) //100
console.log(min) //60
console.log(avg) //80
变量交互
let a = 1
let b = 3; //这里不加分号会视为和下方是一体的
[b,a] = [a,b]
console.log(a) //3
console.log(b) //1
- 如果左边的参数多,那多余的值为undefined
- 如果右边的变量多,只会按参数的数量来存储
- 利用剩余参数可以把多余的变量存成一个数组( 只能置于最末位)
防止有undefined传递单元值的情况,可以设置默认值:
const [a = '手机' , b = '华为'] = ['小米']
console.log(a) //小米
console.log(b) //华为
按需导入,忽略某些返回值
const [a,,c,d] = ['小米','苹果','华为','格力']
console.log(a) //小米
console.log(c) //华为
console.log(d) //格力
支持多维数组的结构
const [a,b] = ['苹果',['小米','华为']]
console.log(a) //苹果
console.log(b) //['小米','华为']
//想单独拿到小米和华为
const [a,[b,c]] = ['苹果',['小米','华为']]
console.log(a) //苹果
console.log(b) //小米
console.log(c) //华为
对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
const user = {
name:'小明',
age:'20'
}
//批量声明变量
const {name,age} = user
console.log(name) //小明
console.log(age) //20
- 对象属性的值将被赋值给与属性名相同的变量
- 注意解构的变量名不要和外面的变量名冲突否则报错
- 对象中找不到与变量名一致的属性时变量值为 undefined
2.给新的变量名赋值
冒号表示“什么值:赋值给谁”
const user = {
name:'小明',
age:'20'
}
//批量声明变量
const {name:uname,age} = user
console.log(uname) //小明
console.log(age) //20
3.数组对象解构
const pig = [
{
name:'佩奇',
age:6
}
]
const [{name,age}] = pig
console.log(name) //佩奇
console.log(age) //6
4.多级对象解构
const pig = {
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
sister: '乔治'
},
age: 6
}
const {name,family:{mother,father,borther}} = pig
console.log(name) //佩奇
console.log(mother) //猪妈妈
console.log(father) //猪爸爸
console.log(borther) //乔治
数组遍历
forEach方法
forEach 主要是遍历数组
参数当前数组元素是必须要写的, 索引号可选
被遍历的数组.forEach(function(当前数组元素,当前元素索引号){
//函数体
})
例:
const arr = ['pink','red','green']
arr.forEach((item,index) => {
//一次打印数组的每一个元素
console.log(`当前数组的元素是:${item}`)
//一次打印数组的每一个元素的索引号
console.log(`当前数组元素的索引是${index}`)
})
筛选数组filter()
- filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
- 主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
被遍历的数组.filter(function(value,index){
return 筛选条件
})
例:
const arrs = [1,2,3,4,5,6]
//过滤
let filt = arrs.filter( item => item>3)
console.log(filt); //4,5,6