作用域
作用域:非常重要,很多代码都不知道是为什么这么运行的。变量的生效范围。
原理:变量在那个作用域被声明,就拥有那个范围的作用域,作用域目前不能被延伸。
一。全局作用域:整个页面中js代码都可以访问到的。
let a=1
alert(a)
二。块级作用域:代码块,如果这个变量只能在代码块中生效,那么这个变量拥有块级作用域。
{
let a = 1
alert(a)
}
alert(a) //这会报错
三。函数作用域:在函数范围之内生效的变量,拥有函数作用域。函数内部声明的变量,只能在函数内有效
function foo(){
let a = 1
console.log(a)
}
foo() //1
console.log(a) //报错 ,外部的没声明a
let a = 1 //变量在全局声明,所以作用域在全局,
function foo(){
console.log(a)
}
foo() //1
console.log(a) //1
四。1,全局作用域 > 块级作用域和函数作用域
2.块级作用域和函数作用域具体谁包裹谁,看情况讨论
function val(){
let a = 1
{
console.log(a)
}
}
val() //1
{
let b = 2
function val(){
console.log(b)
}
val() //2
}
五。变量在作用域中的特点
1.let 和val和const之间的区别:
val:(ES5)会忽略块级作用域,自动上升到上一级作用域,但是还是受制于函数作用域
let与const :(ES6)会识别块级作用域和函数作用域。不会做出出格的事情
var a=123
{
console.log(a) //123
}
let a=123
{
console.log(a) //123
}
{
var var_a=123
let var_a=456
}
console.log(var_a) //123
console.log(let_a) //报错
在(块级作用域中),var会忽略块级作用域,所以会导致val变量的提升,到上一级作用域
function foo(){
var var_a=123
let var_a=456
}
console.log(var_a) //报错
console.log(let_a) //报错
在函数作用域中,声明的变量,作用域只是函数,不能进行穿透,不能让外面的访问到。
六。作用域的访问:
变量的访问角度,作用域是有很多的,一层包裹一层的,变量会在作用域中一层一层往上寻找变量,那么寻找的方式就是作用域链。
当代码中、出现一个变量的时候,js是怎么找到这个变量以及他的值的。首先会在自己的作用域中查询,如果查询到就用这个值,如果查询不到,回去上一级找,重复直到全局作用域,如果还没找到,抛出一个错误。
(重点) 作用域是在声明阶段就已经确定下来了,函数声明的时候写在哪里,作用域就确定在了哪里,和函数的执行位置没有关系
function foo(){
let val = 123
{
console.log(val) //123 //在自己块级作用域中查找,如果没有找上一级
}
}
function foo(){
let val = 123
{
let val = 456
console.log(val) //456 //先找自己作用域
}
console.log(val) //123 //先找自己作用域
}
案例题。
1.let a = 1
function foo1(){
function foo2(){
console.log(a)
}
foo2()
}
foo1(1)
//结果为1
foo2() //报错,因为foo2不在全局,外面无法访问到里面的东西
2.let a = 1
function foo1(){
let a = 3
function foo2(){
console.log(a)
}
foo2()
}
foo1()
//结果为3
3.let a = 1
function foo2(){
console.log(a)
}
function foo1(){
let a = 3
foo2()
}
foo1()
//结果1
//因为foo2是在全局,作用域是在声明阶段就已经确定下来了,和函数的执行位置没有关系
4.var a = 6
function foo3(){
let a = 3
function foo4(){
console.log(a)
}
return foo4
}
let foo5 = foo3()
foo5()
//结果3
5. var a = 6
function foo3(){
let a = 3
function foo4(a){
console.log(a)
}
return foo4
}
let foo5 = foo3() //f005=f004
foo5(a) //这个实参a的作用域是全局
//结果6
6.var a = 6
function foo3(){
let a = 3
function foo4(a){ //函数形参等价于在当前作用域声明了一个参数接受传入的值
console.log(a)
}
return foo4
}
let foo5 = foo3()
foo5()
//结果undefined
7. var a = 6
function foo3(){
let a = 3
function foo4(){
console.log(a)
}
return foo4
}
let foo5 = foo3() //f005=f004
foo5(a)
//结果3
8.var a = 6
function foo3(){
let a = 3
function foo4(){
function foo5(){
console.log(a)
}
return foo5(a)
}
return foo4(a)
}
let foo5 = foo3(a) //foo5就是foo4,foo3(a)执行就是f004(a)执行,函数foo4里没有形参,所以可以把foo3(a)把a删了。最终f005函数找最近的即a = 3.
foo5(a) //里面最终对应的函数f005没形参可接受,所以可以省略
//结果3
七。变量提升。代码执行之前会在各自的作用域最开始的位置声明变量(为赋值阶段),直到真正复制的时候,才会执行赋值操作
本质是怎么杨执行的:两个阶段
1.预编译阶段,开辟内存空间,分配变量存储位置,确定代码是否出现语法错误等等内容。
console.log(a) //只有这行代码,则报错
(1)开辟内存空间,分配变量存储位置,安排作用域之间的关系,已近在读取变量的声明了。预编译阶段,把变量提升到最前面
console.log(a)
var a = 3
结果:undefined
//等价于
var a //预编译阶段,把变量提升到最前面
console.log(a)
a = 3
console.log(a) //undefined
var a = 3
console.log(a) //3
函数是js的一等公民:函数在变量声明(预编译阶段)就直接赋值了。
函数声明,函数整体直接提升到最前面。没遇到代码块的时候
foo()
function foo(){
console.log(666)
} //666
预编译:function foo(){
console.log(666)
}
foo() //666
console.log(foo)
function foo(){
console.log(666)
}
var foo = 3
结果;打印出函数本身
预编译阶段;函数提升
function foo(){
console.log(666)
}
var foo
console.log(foo)
foo = 3
2.执行阶段:读取一行代码,解析执行一行代码
var foo = 3
console.log(foo)
function foo(){
console.log(3)
}
结果:3
3.let和const是怎么提升的。ES6新特性(相对于合理),var 不合理
提升了没有,其实提升了,不然不可能知道在后面才能访问
let和const 他们在真正初始化的阶段才能被访问,在这之前都是被锁死的状态:叫做暂时性死区
console.log(a) //光有这一行的时候,报错内容为a没有被定义
let a = 3 在这里,let a 提升了 ,正式因为提升了所以后面报错内容不一样 ,提升了但是不能用
//报错 在a初始化之前不能访问他
虽然变量提升了,但是不能被使用,处于一个假死区域,只有当执行阶段,执行到他的时候才可以访问
{
console.log(a)
const a = 1
}
//报错 在a初始化之前不能访问他
5.变量提升的位置。
var提升的时候会跳过块级作用域,不会跳过函数作用域
console.log(a)
{
var a = 3
}
//结果 undefined
预编译,var 跳过代码块
var a
console.log(a)
a = 3
let 和const提升的时候不会跳过块级作用域和函数作用域,
特例:在代码块中function foo(){ }中函数的变量提升到了全局,但是函数的内容还停留在代码块中,但是后续如果离开了代码块,函数就全部出来了。(比如在代码块外console.log一下),可以跳到后面,挑不到外面去。
console.log(a)
{
let a = 3
}
//结果 报错:a is a not defined
console.log(foo)
{
function foo(){ //foo这个单词会提升,但是内容不会提升
console.log(666)
}
}
//undefined
另一个:foo
{
function foo(){
console.log(666)
}
}
//报错,foo is not a function
{
function foo(){
console.log(666)
}
}
console.log(foo)
//结果:打印出函数本身
foo()//不可以执行,报错
{
foo()//可以执行,666
function foo(){
console.log(666)
}
foo() //可以执行 。666
}
foo() //可以执行,666
6.函数声明和函数表达式的区别
函数声明可以提升上去,函数表达式不会提升
if(false){
function foo(){ //foo提升到全局,函数内容提升不到全局
}
}else{
console.log(foo)
}
//undefined
if(true){
function foo(){
}
}else{
console.log(foo)
}
console.log(foo) //打印出函数本身
相当于 var foo 提升出来了,内容出不来
console.log(foo) //也是undefined
if(false){
function foo(){ foo会提升到全局,内容不会提升
}
}
console.log(foo) //undefined
if(function foo(){console.log(666)}){ // 因为这是表达式(有括号),预编译不会提升,只有函数声明才有变量提升
foo()
}
//会报错。 foo is undefined
7.作用域的生命周期。作用域在什么时候出现。什么时候消失。
进入作用域的一瞬间作用域出现,离开作用域的时候消失。作用域里面的所有内容(声明的变量都会)消失,这种操作会让程序时时刻刻都有足够的空间执行代码
这个执行完的作用域会立马消失吗?不会,需要判断。垃圾回收机制:在作用域是否消失的时候的判断法则。(自动的)。当前作用域里面声明的变量,是否还能被其他地方用到,如果没有用到,那就该消失了。
一般情况下,函数内部声明的变量是没有办法直接保留下来,所以一般情况下作用域会直接执行完消失。
作用域的查找位置,永远都是在变量的申明位置
特殊的:闭包会保留这个作用域,不会让它消失
function foo(){
let a = 3 //
a++
console.log(a)
}
//a++ //这个会报错
document.onclick = foo
var a = 3
//这里var a = 3的作用域不会消失,只要页面不关闭,就不会消失
function foo(){
let a = 1
function foo1(){
console.log(a) //只要一个作用域还会被需要,那么这个作用域就不会消失
}
foo1()
}
foo()
(重要)只要一个作用域还会被需要,那么这个作用域就不会消失.作用域如果没人需要了,那么他就消失了
let fn = null
function foo(){ //A
let a = 1
function foo1(){ //B
console.log(a)
}
fn = foo1 //将函数内部的函数放到了全局中,那么foo1可以随时被执行,因此foo的作用域就一直存在
}
foo()
//fn就是这个foo里面的函数foo1,foo()执行了一次,那么foo的作用域就出现了,foo就执行完了
//foo的作用域A消失了没有?没有消失,因为foo的作用域被foo1需要了,而foo就是fn,fn是全局变量
//fn是全局变量,所以有可能在任何时候执行,所以foo不敢删除自己的作用域A,所以foo的作用域就被保留了下来。
8.闭包:用一个函数A保存了另一个函数B的作用域,使得B这个作用域能在B以外被访问,就是闭包
function chejian(){ //B
let title = "哈哈"
function cidai(){ //A
console.log(title)
}
return cidai
}
let baoshe = chejian()
baoshe()
一个函数A保留了另一个函数B的作用域,并且A还能被访问,B的作用域就保留了下来,此时函数A称为闭包函数。
function foo(){
let a = 3
function foo1(){
console.log(a)
}
window.foo1 = foo1 //window.foo1 在浏览器里添加一条对象属性
}
foo() //只有函数执行了才会出现作用域
window.foo1()
闭包的特性:优点和缺点
优点:1.函数的作用域在函数执行完成之后被保留,某些特殊的转态不会消失。
2.没有形成关于数据的全局变量,变量就没有办法修改或者读取。
1.//生成一个密码管理的工具对象
function managePassword(){
let password = "9527"
let obj = {
getPassword:function(){
return password
},
sendPassword:function(){
//写私有的发送地址,转码工具
console.log(`密码${password}已经发送了`)
}
}
return obj
}
let passwordTool = managePassword
console.log(passwordTool)
//密码是不会被更改的
3.形成私有变量,各自之间会使用自己保持的作用域,相互没有任何干扰
//加法器
function createAdder(){
let count = 0
return function (num){
if(num<0){
console.log("不能加负数")
return count
}else{
count += num
return count
}
}
}
//下面这两行代码直接彼此不会有影响,因为只执行了两次createAdder,每次执行都会count为0,变成一个新的函数
let adder1 = createAdder() //adder1(2) adder1(3) //5
let adder2 = createAdder() //adder2(2) adder2(3) //5
缺点:既然作用域没有消失,那么内存就被占用下来了。每形成一个闭包都会保留各自那一次执行的所有转态,内存消耗会随着代码执行的过程中逐渐变大,内存不足。
怎么解决:首先少用,知道自己写的代码是闭包。其次:手动释放内存(把函数引用去掉)
function createAdder(){
let count = 0
return function (num){
if(num<0){
console.log("不能加负数")
return count
}else{
count += num
return count
}
}
}
let add = createAdder()
adder = null //手动清除引用关系,让内部函数没办法被外部使用,剩下的交给垃圾回收
闭包的使用:写法:大函数套小函数,小函数在外部可以被访问,小函数使用的变量在大函数中。