作用域
局部作用域
函数作用域(一直 存在)
块作用域(ES6,只有let和const有块级作用域,var没有)
块就是一对大括号,比如{ }、if(){ }、for(…){ }
使用var则失去块级作用域
//例如
for(var i=1;i<=3;i++)
{console.log(i)}
console.log(i);//正确,var定义的i是全局变量
全局作用域
script标签和.js文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可访问。
全局作用域声明的变量,任何其他作用域都可访问
注意
1.为window对象动态添加的属性默认是全局的,不推荐!
2.函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
3.尽可能少的声明全局变量,防止全局变量被污染
作用域链
作用域链本质上是底层的变量查找机制
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域找不到则会依次逐级查找父级作用域直到全局作用域
垃圾回收机制(GC)
JS中内存的分配和回收都是自动完成的。内存在不使用的时候会被垃圾回收器自动回收
但不了解JS的内存管理机制,我们同样非常容易造成内存泄漏(内存无法回收)的情况
不在用到的内存,没有及时释放,就叫做内存泄漏
内存的生命周期
JS环境中分配的内存,一般有如下生命周期
1.内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
2.内存使用:即读写内存,也就是使用变量、函数等(函数一调用。临时为函数再分配一小块内存,用于运行函数中的局部变量,函数执行完毕,函数中的局部变量就被释放)
3.内存回收:使用完毕,由垃圾回收自动回收不在使用的内存
4.说明:
a)全局变量一般不会回收(关闭页面回收)
b)一般情况下局部变量的值,不用了会被自动回收掉
垃圾回收算法说明
所谓垃圾回收,核心思想就是如何判断内存是否已经不会再被使用了,如果是,就视为垃圾,释放掉
下面介绍两种常见的垃圾回收算法:引用计数法和标记清除法
引用计数法(算法固定,有缺陷被淘汰)
IE采用的引用计数算法,定义“内存不在使用”的标准很简单,就是看一个对象是否有指向它的引用
算法:
1.跟踪记录每个值被引用的次数
2.如果这个值被引用了一次,那么就记录次数1
3.多次引用会累加
4.如果减少一个引用就减1
5.如果引用次数是0,则释放内存
//声明变量,代码运行的时候,就会分配内存,存储对象的内容
//同时,有一个变量指向这个对象,所以这个对象的引用次数为1
let obj={uname:'zzs',gae:20}
//下面的代码意思是让obj2也指向这个对象,所以这个对象的引用次数为2
let obj2=obj
//下面让obj=null ,也就是说obj不再指向对象了,则这个对象的引用次数减1
obj=null
//下面让obj2=null,也就是说obj2不再指向对象了,则这个对象的引用次数减1
obj2=null
//到这里,对象的引用次数为0,则垃圾回收器会自动回收对象占用的内存
引用计数的问题
function fn(){
let o1={}
let o2={}
//给o1增加属性
o1.a=o2;
o2.a=o1
}
fn()
//相互引用,造成对象(o1、o2)的引用次数始终为1,这两个对象占用的内存始终不会释放
//这样就造成了内存的泄漏
标记清除(主流浏览器都在使用)
现在浏览器通用的大多是基于标记清除算法的某些改进算法,总体的思想都是一致的
核心:
1.标记清除算法将“不在使用的对象”定义为“无法达到的对象”
2.就是从根部(在js中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的
3.那些无法由根部出发触及到的对象标记为不再使用,稍后进行回收
闭包(有了let、const之后应用比较少,应付面试)
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
简单理解:闭包=内层函数+外层函数的变量
function outer()
{
let a=10 //构成闭包的要素之一,外层函数中得有一个变量
function fn()
{
console.log(a) //构成闭包的要素之二,内存函数中使用外层函数的变量
}
fn()
}
outer()
//常用闭包写法(防止全局变量经常被修改,闭包函数中的内部变量不受到全局变量的影响)
function outer()
{
let a=10 //构成闭包的要素之一,外层函数中得有一个变量
function fn()
{
console.log(a) //构成闭包的要素之二,内存函数中使用外层函数的变量
}
return fn
}
let xx=outer() // xx=function fn(){console.log(a)}
xx() //调用xx 相当于调用了fn //外层函数使用内部函数的变量a
变量提升
//代码运行的时候,会把函数的声明、创建提升到当前作用域的最开头
//代码运行的时候,会把用var声明变量(let和const声明的变量不会提升)的声明过程(没有赋值过程)提升到最开头(在函数之后)
console.log(a)
fn()
var a=10
function fn(){
console.log(123)
}
提升的特点
提升的时候,把函数提升到最前面,其次是用var声明的变量
提升只会把var声明的变量提升到当前作用域的最前面
var a=1000
function fn()
{
var a
console.log(a) //undefined
}
fn()
let和const的提升问题
变量的创建分为三步:创建—>声明—>赋值
var会把创建和声明,提升到最开头
let会把创建提升到最开头(let创建的变量,在声明之前使用是不行的)
let如果在创建之后,声明之前使用,就进入了“暂时性死区”。
let a=100
function fn(){
//这里有一个隐式的创建过程,会提升到当前作用域的最开头,否则将会输出a=100,不能使用外层的a,let a叫声明
console.log(a)
let a=20
}
fn()
<script>
var a = 100
var a //只有重新赋值会覆盖,声明不会覆盖
console.log(a) //100
</script>
<script>
console.log(a) //先把函数a提升,后续Var a提升,只是声明,没有覆盖,则a还是函数
var a = 10 //只把声明var a提升到前面
function a() { //函数提升到最前面
console.log(1111)
}
console.log(a) //a=10
var a = 20
//提升只是把声明提升了,赋值的位置不变
console.log(a) //a=20
</script>
函数
函数参数
动态参数
function sum(){
//实参不固定的时候,干脆形参直接不写
//这样的普通函数中有一个固定对象叫arguments(不能出现在箭头函数中),是一个伪数组(不能调用数组方法),本质上是一个对象,保存传递进来的实参
let num=0
for(let i=0;i<arguments.length;i++)
{num=num+arguments[i]}
}
//实参有几个不固定
sum(2,3)
sum(2,3,4,5)
sum(4,1,8,2,9)
}
剩余参数
function fn(a,b,...c)
{
//a接收了4,b接收了1,...c接收了剩余所有实参
console.log(a,b,c)
}
fn(4,1,5,6,2)
展开运算符(类似剩余参数)
1.在构造数组时,能够将其他数组和字符串展开
let arr1=[3,4]
let arr2=[5,6]
let arr=[100,200,...arr1] //将arr1展开放到数组arr中
在为函数传递参数的时候,能够将其他数组或字符串展开
let arr3=[2,8,6,5,9]
function fn(...a) //形参中的...是剩余参数
{}
fn(...arr3) //相当于fn(2,8,6,5,9) 实参中的...是展开运算发
在构造字面量对象的时候,能够展开其他对象
let obj1={uname:'zs',age:20}
let obj2={sex:'男',height:180}
//运用,加入obj的属性
let obj={id:100,...obj1....obj2,weight:75}
函数参数的默认值
//如果实参未传入参数,则当undefined处理
function fn(a,b=10)
{
}
fn(3,4) //传递两个参数,优先使用我们传递进去的实参,也就是a=3,b=4
fn(6) //传递了一个参数,则a=6,没有给b传递实参,则b使用默认的10
箭头函数
箭头函数是ES6中的新语法,是新的声明函数的方式,其目的是为了简化函数的写法
语法
箭头函数属于表达式函数,因此不存在函数提升
简化写法:
箭头函数只有一个参数时可以省略圆括号()
箭头函数函数体只有一行代码时可以省略花括号{},并自动作为返回值被返回
箭头函数中没有arguments,只能使用…动态获取实参
//写法
// (形参)=>{函数体}
let fn=(a,b)=>{
console。log(12345)
}
let fn=(x)=>{return x*x}
//简写
let fn=x=>x*x
//箭头函数的使用。例如当作其他函数的参数,举例
setTimeout(()=>{},1000)
//特别注意
let fn=()=>{return {uname:'zs,age:20}}
//简写不能写成let fn=()=>{uname:'zs,age:20} 因为无法分清这个大括号{}是函数的大括号还是对象的大括号
//写成
let fn=()=>({uname:'zs,age:20})//额外价格小括号,以免引起歧义
箭头函数内部没有自己的this,箭头函数中的this,把它当作普通变量,使用的时候按照作用域去查找
document.querySelector('button').addEventListener('click',function(){
console.log(this) //这里的this是事件源button
})
//换成箭头函数
document.querySelector('button').addEventListener('click',()=>{
console.log(this) //这里的this为普通变量,函数中没有则到全局作用域去查找
})
解构
数组的解构
解构赋值
解:展开
构:结构的意思(在js中有结构的变量是数组和对象)
赋值:把一个值赋值给一个变量
总的来说:就是展开数组或对象,将里面的值取出,赋值给一些变量
//数组的解构
//相当于将几个值赋值给几个变量
let [a,b,c,d]=[1,2,3,4] //变量和值的数量不对等的时候,按顺序赋值
//另一个写法
let [,a,,b]=['老段','琪琪‘,'小军','小名']
//为了给变量a赋值琪琪。变量b赋值小名,又不想创建多余的变量,则可以用逗号代替
let [a,[b,c,d]]=[1,[3,4,5]] //前后对应,严格保证顺序
对象的解构(用的超级多)
对象的解构不看顺序和顺序无关,
保证 变量名===对象的键 即可实现解构
let {uname,sex}={uname:'zs',age:20.sex:'男'}
//需要什么变量,写什么变量名即可
let person={
uname:'zs',
age:34,
dog:{dname:'旺财',dage:3},
cat:{cname:'小花',cage:2}}
let {uname,dog:{dname}}=person //前后对应
//举例,在传参的时候直接结构出来
function fn({age}){console.log(age)}
fn(person)
对象解构时为变量定义别名
let age=20;
//不能重复定义同一个名字的变量
let {uname,age:其他的名字}={uname:'zs',age:100}