原生JS作用域

作用域

作用域:非常重要,很多代码都不知道是为什么这么运行的。变量的生效范围。

原理:变量在那个作用域被声明,就拥有那个范围的作用域,作用域目前不能被延伸。

一。全局作用域:整个页面中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 //手动清除引用关系,让内部函数没办法被外部使用,剩下的交给垃圾回收

闭包的使用:写法:大函数套小函数,小函数在外部可以被访问,小函数使用的变量在大函数中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值