JavaScript的三座大山--(2)--作用域和闭包

文章可能有点长,但  栗子 都很经典,简单易懂,一起学习吧!

壹 ❀ 作用域

1、作用域就是一个变量或者函数的有效作用范围,在JS中作用域一共有三种,分别是全局作用域、局部作用域(函数作用域)、块级作用域;

2、变量声明的三种方式:varletconst 

1. 全局作用域

1、全局作用域:声明在函数外部的变量(声明在script标签中的变量和函数),在代码中任何地方都能访问到的对象拥有全局作用域;var和let变量在全局作用域中都是全局变量;

注意:所有没有var直接赋值的变量都属于全局变量;

2、全局作用域中声明的变量和函数会作为window对象的属性和方法保存,可以通过 window.变量名 去调用它;

3、全局作用域在页面打开时被创建,页面关闭时被销毁;

(1)最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:
var authorName="波妞";
function doSomething(){
    var blogName="中介";
    function innerSay(){
        console.log(blogName);
    }
    innerSay();
}
console.log(authorName); //波妞
doSomething(); //中介
console.log(blogName); //错误
innerSay() //错误
(2)所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:
function doSomething(){
    var authorName="波妞";
    blogName="中介";
    console.log(authorName);
}
doSomething(); // 波妞
console.log(blogName); //中介
console.log(authorName); //错误

2. 局部作用域(又称 函数作用域)

 在函数中用 var 、let 、const 声明的所有变量,都是函数的局部变量,作用范围为局部作用域,即:只能在函数内部使用,函数外部使用是不行的。无论是通过var还是let定义在局部作用域的变量都是局部变量;

1、调用函数时,函数作用域被创建;函数执行完毕,函数作用域被销毁;

2、每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的;

3、在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量;

4、在函数作用域内访问变量/函数时,会现在自身作用域中查找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域;

注意:所有没有var直接赋值的变量都属于全局变量 

3. 块级作用域

 

 任何一对花括号{ }中的语句集都属于一个,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域

在ES6中只要 { } 没有函数结合在一起,那么就是“块级作用域”。ES6之前没有块级作用域。使用 let 关键字或者 const 关键字来实现块级作用域。

let 和 const声明的变量只在 let 或 const命令所在的代码块 { } 内有效,在 { } 之外不能访问。

 注意:

  • 块级作用域中,var定义的变量是全局变量,let定义的变量是局部变量;
  • 块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。但函数funtion(){ }里面的{ }不属于块级作用域,而是局部作用域(函数作用域);

4.块级作用域和局部(函数)作用域区别 

        4.1、在块级作用域中通过var定义的变量是全局变量

{
    //块级作用域
    var num = 123;//全局变量
}
console.log(num);  // 123

        4.2、在局部作用域中通过var定义的变量是局部变量

function test() {
     var value = 666;//局部变量
     console.log(value);
}
test(); // 666
console.log(value);//value is not defined

5、无论是在块级作用域还是局部作用域,省略变量前面的let或者var就会变成一个全局变量。

         5.1、验证在块级作用域中

{
     var num1 = 678;//全局变量
     num2 = 678;//全局变量
     let num3 = 678;//局部变量
}
console.log(num1);//678
console.log(num2);//678
console.log(num3);//num3 is not defined

        5.2、验证在局部作用域中

function f() {
      num1 = 456;//全局变量
      var num2 = 678;//局部变量
      let num3 = 123;//局部变量
}
f();
console.log(num1);//456
console.log(num2);//num2 is not defined
console.log(num3);//num3 is not defined

❀加餐❀ 

1. 通过这个 栗子加深一下理解

        var num1 = 123;//**全局变量**
        function f() {
            var num2 = 456//局部变量
        }
        function test() {
            num3 = 666;//局部作用域,没有var或者let修饰。**全局变量**
        }
        {
            var num4 = 789;//块级作用域、**全局变量**
        }
        {
            let num5 = 789;//块级作用域、let定义变量。局部变量
        }
        {
            let value7 = 123;
            {
                //注意点:在不同的作用域范围中,可以有同名变量
                let value7 = 456;//不会报错。
            }
        }
     

 2. 为什么需要块级作用域

 ES5只有全局作用域和函数作用域,没有块级作用域,会带来以下问题:

1) 变量提升导致内层变量可能会覆盖外层变量

var i = 5;  
function func() {  
    console.log(i);  
    if (true) {  
        // var i = 6;  
    }  
}  
func(); // 5



var i = 5;  
function func() {  
    console.log(i);  
    if (true) {  
        let i = 6;  
    }  
}  
func(); // 5
var i = 5;  
function func() {  
    console.log(i);  
    if (true) {  
        var i = 6;  
    }  
}  
func(); // undefined

2) 用来计数的循环变量泄露为全局变量

for (var i = 0; i < 10; i++) {    
        console.log(i);    
}    
console.log(i);  // 10 
for (let i = 0; i < 10; i++) {    
        console.log(i);    
}    
console.log(i);  // i is not defined

贰 ❀ 变量提升 

首先我们要知道,js的执行顺序是由上到下的,但这个顺序,并不完全取决于你,因为js中存在变量的声明提升。

栗子1

console.log(a)  //undefined
var a = 100

fn('zhangsan')
function fn(name){
    age = 20
    console.log(name, age)  //zhangsan 20
}

结果:

打印a的时候,a并没有声明,为什么不报错,而是打印undefined。

执行fn的时候fn并没有声明,为什么fn的语句会执行?

 这就是变量的声明提升,代码虽然写成这样,但其实执行顺序是这样的。

var a

function fn(name){
    age = 20
    console.log(name, age)
}

console.log(a) 
a = 100

fn('zhangsan')

 js会把所有的声明提到前面,然后再顺序执行赋值等其它操作,因为在打印a之前已经存在a这个变量了,只是没有赋值,所以会打印出undefined,为不是报错,fn同理。

变量提升:JS在解析代码时,会将所有的声明提前到所在作用域的最前面

  栗子2

console.log(name);      //undefined
var name = '波妞';
console.log(name);      //波妞
function fun(){
    console.log(name)   //undefined
    console.log(like)   //undefined
    var name = '大西瓜';
    var like = '宗介'
}
fun();

相当于

var name;
console.log(name);      //undefined
name = '波妞';
console.log(name);      //波妞
function fun(){
    var name;
    var like;
    console.log(name)   //undefined
    console.log(like)   //undefined
    name = '大西瓜';
    like = '宗介'
    
    // 此时再打印
    console.log(name)   //大西瓜
    console.log(like)   //宗介
}
fun();

注意:是提前到当前作用域的最前面

这里也要注意函数声明和函数表达式的区别。上例中的fn是函数声明。接下来通过代码区分一下。

  栗子3

// 函数声明
fn1('abc')
function fn1(str){
    console.log(str)
}

// 函数表达式
fn2('def')
var fn2 = function(str){
    console.log(str)
}

结果:

可以看到fn1被提升了,而fn2的函数体并没有被提升。

效果等同于:

var fn2
fn2('def')
fn2 = function(str){
    console.log(str)
}

这下大家应该就明白报错原因了吧! 

好了,上面我们已经了解了作用域与变量提升的概念,下面我们来看一下作用域链;


叁 ❀ 作用域链

 很简单,直接看 栗子

console.log(name);      //undefined   (1)
var name = '波妞';
var like = '宗介'
console.log(name);      //波妞         (2)
function fun(){
    console.log(name);  //波妞         (3)
    console.log(eat)    //ReferenceError: eat is not defined    (4)
    (function(){
        console.log(like)   //宗介      (5)
        var eat = '肉'
    })()
}
fun();
  1. name定义在全局,在全局可以访问到,所以 (2) 打印能够正确打印;
  2. 在函数fun中,如果没有定义name属性,那么会到它的父作用域去找,所以 (3) 也能正确打印。
  3. 定义:内部环境可以访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。类似单向透明,这就是作用域链,所以 (4) 不行而 (5) 可以。

前后呼应:为什么第一个打印是"undefined",而不是"ReferenceError: name is not defined"。原理就是JS的变量提升


肆 ❀ 闭包

JavaScript高级程序设计中对闭包的定义:闭包是指有权访问另外一个函数作用域中变量的函数。

从概念上,闭包有两个特点

1、函数嵌套;(有外部函数, 有内部函数)

2、内部函数可以引用外部函数的变量/函数;

以下是我在学习中做的笔记:

1. 引入----代码有点长,但很经典,可以自己运行理解一下哦








 2. 理解闭包

1. 如何产生闭包?
  * 当一个被嵌套的内部(子) 函数引用了嵌套其的外部(父) 函数的变量(函数)时, 就产生了闭包
2. 闭包到底是什么?
  * 使用chrome调试查看
  * 理解一: 闭包是嵌套的内部函数(绝大部分人)
  * 理解二: 包含被引用变量(函数)的对象(极少数人)
  * 注意: 闭包存在于被嵌套的内部函数中
3. 产生闭包的条件?
  * 函数嵌套   (有外部函数,有内部函数)
  * 内部函数引用了外部函数的变量/函数
  * 执行外部函数;(必须执行外部函数才会产生闭包)  ---产生几个闭包要看执行多少次外部函数

栗子

由于种种原因,我们有时候需要得到函数内的局部变量。但是,在正常情况下,这是办不到的,那么怎么才能得到函数内部的局部变量呢?

那就是闭包,在函数的内部,再定义一个函数,然后把这个函数返回。

function F1(){
    var a = 100
    //返回一个函数 (函数作为返回值)
    return function (){
        console.log(a)
    }
}

//f1得到一个函数
var f1 = F1()
var a = 200
f1()  // 100

在本中就实现了闭包,简单的说,闭包就是能够读取其他函数内部变量的函数。 

下面解释一下为什么打印的是100,

看这句 var f1 = F1(); F1这个函数执行的结果是返回一个函数,所以就相当于把F1内的函数付给了f1变量,类似于这样:

var f1 = function(){
  console.log(a)    //这里的a是一个自由变量
}

 这里的a是一个自由变量,所以根据作用域链的原理,就应该去上一级作用域去找。之前说过,作用域链在定义时确定,和执行无关,那就去想上找,这个函数定义定义在F1中,所以会在F1中找a这个变量,所以这里会打印的100。

通过这种方式,我们在全局下就读取到了F1函数内部定义的变量,这就是闭包。谈到这里那就说说闭包的作用吧!

3. 闭包的作用




 4. 闭包的两种应用场景




 5.  闭包的生命周期





好了,就到这里,我这只是冰山一角,一点皮毛而已,如果哪里有问题,希望各位大佬指出DayDayUp!!!

你可能感兴趣的:(JavaScript的三座大山,javascript)