先码后看 JS变量作用域与生命周期 侵立删



转自:https://segmentfault.com/a/1190000002960647

Javascript 这门语言与其他的大部分语言相比,有很多特殊性,这是很多人喜欢它或者讨厌它的原因。其中变量的作用域问题,对很多初学者来说就是一个又一个「坑」。

变量的作用域在编程技能中算是一个基本概念,而在 Javascript 中,这一基本概念往往挑战者初学者的常识。

基本的变量作用域

先上例子:

var scope = 'global';
function checkScope(){
    var scope = 'local';
    console.log(scope); // local
}
checkScope();
console.log(scope); // global

上面的例子中,声明了全局变量 scope 和函数体内的局部变量 scope。在函数体内部,局部变量的优先级比通明的全局变量要高,如果一个局部变量的名字与一个全局变量相同,那么,在声明局部变量的函数体范围内,局部变量将覆盖同名的全局变量。

下面再看一个例子:

scope = 'global';
function checkScope(){
    scope = 'local'; 
    console.log(scope); // local
    myScope = 'local';
    console.log(myScope); // local
}
checkScope();
console.log(scope); // local
console.log(myScope); // local

对于初学者来说,可能会有两个疑问:为什么在函数体外,scope 的值也变成了 local ?为什么在函数体外可以访问 myScope 变量?

这两个问题都源于一个特性。在全局作用域中声明变量可以省略 var 关键字,但是如果在函数体内声明变量时不使用 var 关键字,就会发生上面的现象。首先,函数体内的第一行语句,把全局变量中的 scope 变量的值改变了。而在声明 myScope 变量时,由于没有使用 var 关键字,Javascript 就会在全局范围内声明这个变量。因此,在声明局部变量时使用 var 关键字是个很好的习惯。

在 Javascript 中,没有「块级作用域」一说

在 C 或者 Java 等语言中,iffor 等语句块内可以包含自己的局部变量,这些变量的作用域是这些语句的语句块,而在 Javascript 中,不存在「块级作用域」的说法。

看下面的例子:

function checkScope(obj){
    var i = 0;
    if (typeof obj == 'object') {
        var j = 0;
        for (var k = 0; k < 10; k++) {
            console.log(k);
        }
        console.log(k);
    }
    console.log(j);
}
checkScope(new Object());

在上面的例子中,每一条控制台输出语句都能输出正确的值,这是因为,由于 Javascript 中不存在块级作用域,因此,函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的

如果要更加强调上文中 函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的 这句话,那么还可以在后面跟一句话:函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的,即使是在声明之前。对于这句话,有个经典的困扰初学者的「坑」。

var a = 2;
function test(){
    console.log(a);
    var a = 10;
}
test();

上面的例子中,控制台输出变量 a 的值为 undefined,既不是全局变量 a 的值 2,也不是局部变量 a 的值 10。首先,局部变量在整个函数体内都是有定义的,因此,局部变量 a 会在函数体内覆盖全局变量 a,而在函数体内,在 var 语句之前,它是不会被初始化的。如果要读取一个未被初始化的变量,将会得到一个默认值 undefined

所以,上面示例中的代码与下面的代码时等价的:

var a = 2;
function test(){
    var a;
    console.log(a);
    a = 10;
}
test();

可见,把所有的函数声明集合起来放在函数的开头是个良好的习惯

变量的真相

可能很多人已经注意到,在 Javascript 当中,一个变量与一个对象的一个属性,有很多相似的地方,实际上,它们并没有什么本质区别。在 Javascript 中,任何变量都是某个特定对象的属性

全局变量都是全局对象的属性。在 Javascript 解释器开始运行且没有执行 Javascript 代码之前,会有一个「全局对象」被创建,然后 Javascript 解释器会给它与定义一些属性,这些属性就是我们在 Javascript 代码中可以直接使用的内置的变量和方法。之后,每当我们定义一个全局变量,实际上是给全局对象定义了一个属性。

在客户端的 Javascript 当中,这个全局变量就是 Window 对象,它有一个指向自己的属性 window ,这就是我们常用的全局变量。

对于函数的局部变量,则是在函数开始执行时,会有一个对应的「调用对象」被创建,函数的局部变量都作为它的属性而存储。这样可以防止局部变量覆盖全局变量。

作用域链

如果要深入理解 Javascript 中变量的作用域,那就必须拿出「作用域链」这个终极武器。

首先要理解的一个名词就是「执行环境」,每当 Javascript 执行时,都会有一个对应的执行环境被创建,这个执行环境中很重要的一部分就是函数的调用对象(前面说过,调用对象是用来存储相应函数的局部变量的对象),每一个 Javascript 方法都是在自己独有的执行环境中运行的。简而言之,函数的执行环境包含了调用对象,调用对象的属性就是函数的局部变量,每个函数就是在这样的执行环境中执行,而在函数之外的代码,也在一个执行环境中,这个执行环境包含了全局变量。

在 Javascript 的执行环境中,还有一个与之对应的「作用域链」,它是一个由对象组成的列表或链。

当 Javascript 代码需要查询一个变量 x 的时候,会有一个被称为「变量名解析」的过程。它会首先检查作用域链的第一个对象,如果这个对象包含名为 x 的属性,那么就采用这个属性的值,否则,会继续检查第二个对象,依此类推。当检查到最后一个对象的时候仍然没有相应的属性,则这个变量会被认定为是「未定义」的。

在全局的 Javascript 执行环境中,作用域链中只包含一个对象,就是全局对象。而在函数的执行环境中,则同时包含函数的调用对象。由于 Javascript 的函数是可以嵌套的,因此每个函数执行环境的作用域链可能包含不同数目个对象,一个非嵌套的函数的执行环境中,作用域链包含了这个函数的调用对象和全局对象,而在嵌套的函数的执行环境中,作用域链包含了嵌套的每一层函数的调用对象以及全局变量。

我们可以用一个图来直观地解释作用域链和变量名解析的过程:

Javascript 作用域链he变量名解析




转自:https://blog.csdn.net/obkoro1/article/details/70948239

写在前面:

是想写一个闭包的,因为写的比较细,基于篇幅,所以闭包前面关于变量的部分就单独发出来,到时候放个链接引进来,js闭包虽然是一个被讲烂的东西,但其实很多人刚接触这个概念也不太懂,所以希望写一篇接地气,能够让一个从前不知道这个内容的小伙伴能够清楚的理解闭包这个东西。so,本文是基于闭包的变量部分。

首先需要理解变量的作用域(变量的有效范围):

变量的作用域有两种:全局变量和局部变量。

全局变量很好理解:就是我们平时没有再函数内部声明的那些变量,在全局中任何地方(函数,对象等)都可以被引用。

栗子:

var n=999;
  function f1(){
    alert(n);
  }
  f1(); // 输出999,说明全局变量n在函数内部被读取

局部变量:在函数内部声明的变量。函数内部的变量,外部无法读取。
栗子:

 function f1(){
    var n=999;
  }
  alert(n); // 没有定义,说明函数内部的变量,外部无法读取。

在函数内部声明变量没有使用var,实际上是声明了一个全局变量,前提是需要先调用函数(闭包函数中,也是全局变量。)!

栗子:

    var func1=function(){
         a1=1;
        console.log(a1); //输出:1
        function func2() {//func2()是一个闭包
            a2=2;
            console.log(a2);
        }
        func2();//闭包函数中的变量在调用函数之后,变量也是全局变量
        console.log(a2);//输出2
    };
    func1();//调用函数,才会使变量生效
    console.log(a1);//输出1
     console.log(a2);//输出2,说明闭包中没有用var声明的变量也是全局变量。

上面栗子中在函数里面声明变量没有用var,在调用函数之后,a1,a2都能有效输出,看完栗子之后,相信你们对变量的作用域也有一些了解了,我们总结一下:


变量作用域,js高级程序设计(红宝书)中的解释:

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境是线性、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名;

意思就是:比如函数的局部环境可以访问到外界所有的变量的函数,而且环境的搜索是先从自己本身的环境开始,一级一级往上搜索,这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

关于查找变量,红宝书中有一句话是这样的:

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级的向后查找,直至找到标识符位置(如果找不到标识符,通常会导致错误的发生、)

ps:标识符指的是变量的名字,作用域链的前端指的是当前执行代码所在环境的变量对象。

作用域链的用途:

保证对执行环境有权访问的所有变量和函数的有序访问

红宝书中的作用域链栗子:

var color = 'blue';

function text1(){
    var anotherColor = 'red';

    function text2(){
        var tempCplor = anotherColor;
        anotherColor = color;
        //这里可以访问到color、anotherColor和tempColor
    }
    //这里可以访问color和anotherColor,但不能访问到tempColor
    text2();
}
//这里只能访问到color
text1();

解析(画重点,这里关于作用域讲的比较详细,看完这个作用域就了解差不多):

以上代码一共涉及三个执行环境:全局环境,text1()的局部环境和text2()的局部环境。

1.全局环境中有一个变量color和一个函数text1()。

2.text1()的局部环境中有一个anotherColor变量和一个text2()的函数,但它可以访问到全局环境中的变量color。

3.text2()的局部环境有一个变量tempColor,该变量只能在text2()自身的环境中被访问到。这是一个闭包,无论是全局环境还是text1()的局部环境都无权访问tempColor。但是在text2()的内部环境中,可以访问到全局环境和text1()中的所有变量,因为这两个环境是text2()的父执行环境。

这个作用域链为:


只是把函数的名字改了,图方便

ps:函数参数也被当做变量来对待,因此其访问规则与执行函数中的其他变量相同。

以上出自红宝书,自己修改了一丢丢,红宝书还是神器啊,常看常新,每次都会有新发现。


关于变量的生存周期:

1.全局变量的生存周期是永久的,除非我们主动销毁。

ps:变量永久生存,且可以随时调用,但是使用的时候要适度,正是因为它的生命周期长,所以将占据更多的内存,如果声明的变量都是全局变量,当项目比较大的时候,就可能出现性能问题,养成一个好的习惯还是有必要的。

2.而对于在函数内用 var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数的调用的结束而销毁。

ps:调用函数结束,局部变量确实会销毁。但并不是完全销毁,而是一直函数的内部环境中存活着,当函数再度被调用时,变量就“复活”了,所以局部变量还是非常方便的,不会影响二次使用。

值得注意的是:在局部环境中,出现全局变量与局部变量重名的时候,起作用的是局部变量,全局变量被屏蔽掉。这是因为上文说过作用域链的原因,先由局部开始搜索变量,当局部找到该变量的时候,就不会再我继续往父级找变量了。



转自:https://www.cnblogs.com/junwuyao/p/7674753.html

一、 JS中的作用域

1、全局变量:函数外声明的变量,称为全部变量
 局部变量:函数内部使用var声明的变量,称为局部变量

在JS中,只有函数作用域,没有块级作用域!!!也就是说,if/for等有{}的结构体,并不能具备自己的作用域。

所以,函数外部不能访问函数内部局部变量(私有属性)。因为,函数内部的变量,在函数执行完毕以后,就会被释放掉
2、使用闭包,可以访问函数的私有变量!
JS中,提供了一种“闭包”的概念:在函数内部,定义一个子函数,子函数可以访问父函数的私有便利。可以在子函数中进行操作,最后将子函数通过return返回

复制代码
function func1(){
var num = 1;
  function func2(){
   return num;
  }
  return func2;
}

var num = func1()();

console.log(num);
复制代码

 

3、闭包的作用:
①可以在函数外部访问函数的私有变量
②让函数内部的变量可以始终存在于内存中,不会再函数调用完成后立即释放。

复制代码
function func1(){
var num = 1;
function func2(){
return num;
}
return func2;
}

var num = func1()();

console.log(num);
复制代码

 结果为1

 

二、 全局变量的问题

 

【错误原因!!】
代码从上自下,执行完毕后,li的onclick还没有触发,for循环已经转完!
而for循环没有自己的作用域!所以循环5次,用的是同一个全局变量i!也就是在for循环转完后,这个全局变量已经变成了5,那么在怎点li,点第几个都会是5

复制代码
var lis = document.getElementsByTagName("li");
for(var i=0; i<lis.length;i++){
lis[i].onclick = function(){
alert(i);
}
}
复制代码

 

 

有三种办法解决上述问题:

1、【使用闭包解决上述问题】
解决关键:函数具有自己的作用域!!在for循环转一次,创建一个自执行函数。在美国自执行函数中,都有自己独立的i,而不会被释放掉。
所以for循环转完以后,创建的5个自执行函数的作用域中,分别储存了5个不同的i变量,也就解决了问题

复制代码
var lis = document.getElementsByTagName("li");
for(var i=0; i<lis.length;i++){
!function(i){
lis[i].onclick = function(){
alert(i);
}
}(i);
}
复制代码

 

 

2、【使用let解决】
解决原理:let具有自己的块级作用域,所以for循环转一次,创建一个块级作用域,思路与闭包相同

复制代码
var lis = document.getElementsByTagName("li");
for(let i=0; i<lis.length;i++){
lis[i].onclick = function(){
alert(i);
}
}
复制代码

 

 

3、【使用this解决】
解决原理:出错的原则在于全局变量i在多次循环后被污染。那么在点击事件中,就可以不使用i变量,而用this代替lis[i],这样也就不会出现错误!

复制代码
var lis = document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++){
lis[i].onclick = function(){
console.log(this)
}
}
复制代码

 

 




猜你喜欢

转载自blog.csdn.net/qq_22167989/article/details/80140041