zoukankan      html  css  js  c++  java
  • JavaScript-变量与作用域链

    jQuery片段: 

     1 var   

     2     // Will speed up references to window, and allows munging its name.  

     3     window = this,  

     4     // Will speed up references to undefined, and allows munging its name.  

     5     undefined,  

     6     // Map over jQuery in case of overwrite  

     7     _jQuery = window.jQuery,  

     8     // Map over the $ in case of overwrite  

     9     _$ = window.$,  

    10  

    11     jQuery = window.jQuery = window.$ = function( selector, context ) {  

    12         // The jQuery object is actually just the init constructor 'enhanced'  

    13         return new jQuery.fn.init( selector, context );  

    14     },  

    15  

    16     // A simple way to check for HTML strings or ID strings  

    17     // (both of which we optimize for)  

    18     quickExpr = /^[^<]*(<(.|s)+>)[^>]*$|^#([w-]+)$/,  

    19     // Is it a simple selector  

    20     isSimple = /^.[^:#[.,]*$/;  

    从这一节开始,我们剥掉jQuery的外衣,看看里面藏着些什么。前一节中曾经提及,如果单看外层的匿名函数,不看里面的实现的话,这个实现肯定不是闭包。但是,如果把jQuery的实现加上的话,这个肯定就是一种闭包应用。万丈高楼平地起,要了解闭包应用,就首先要了解它的基础。而这一节,我们遇到的片段,就是这个基础的所在——变量。(虽然这个片段包含众多知识点,但请容许我一个个慢慢分说。)

    声明变量 

    变量的英文名为variable,其前三个字母正是我们在JS声明变量的关键字——var。那么,我们先来看一下如何去声明一个变量:

     1 /* 

     2  * 声明变量的格式为 

     3  * var 变量名 初始化变量表达式列表(可选) 

     4  */ 

     5 var a=1, b, c="test", d=a+b;// 虽然b还没有初始化,但是声明是合法的  

     6 alert(a);// "1"  

     7 alert(b);// "undefined"  

     8 alert(c);// "test"  

     9 alert(d);// "NaN"  

    10 alert(e);// 这里将引发编译错误:"e"未定义  

    11 // 同样地,如果在初始化中使用未定义的变量,也会引发编译错误。

    如上例所示,声明变量需要使用var关键字,然后在空格后紧跟变量的名字。在声明变量的同时,我们也可以选择帮变量初始化。初始化的值可以是任何类型的值或表达式,但是,如果你尝试使用未定义的变量名来初始化,JS的编译器将会判定发生编译错误,并阻止程序继续往下运行。无论你是否对声明的变量进行初始化,你都可以继续声明第二个变量而无须使用var关键字。你所需要的只是运算符“,”。(关于运算符将在稍后章节详细讨论。)但当你没有对声明的变量进行初始化时,变量将会被赋予值“undefined”——undefined也是JS的固有类型之一。PS:JS中使用的运算符必须是半角的英文字符,甚至空格也一样。

    重复声明的变量?! 

    当我们声明了一个变量,而又在后续的代码中再次对他进行声明,结果会怎么样呢?或许在很多其他语言中,这都会引起重复定义的错误,但在JS中,这完全是合法的。并且,由于JS是弱数据类型,所以变量能被赋予任何类型的值。请看以下例子:

    1 var a=1;

    2 alert(typeof a); // "number"

    3 var a;

    4 alert(typeof a); // "number"

    5 var a="1";

    6 alert(typeof a); // "string"

    7 a=new String("1");

    8 alert(typeof a); // "object" 

    看完上面的例子,你可能会产生两个疑问:

    a)为什么第二个a还是number?

    b)为什么第四个a是object?

    为了解答第一个问题,我们首先要了解声明一个变量到底是怎么运作的。而第二个问题,我们将他放到下一节再讨论。

    var 变量声明的工作步骤 

    当我们使用var关键字去声明变量的时候,JS解释器将会进行如下操作:

    1)预编译javascript代码块中所有非函数块内的var关键字;

    2)生成变量名标识并在其所在作用域分配空间;

    3)按代码顺序运行至第一个var关键字所在行;

    4)按变量声明列表表达式次序计算初始化表达式的值;

    5)每计算完一条初始化表达式,就将其计算结果赋予给对应的声明变量;

    6)继续运行后续代码至下一var关键字;

    7)重复4-7步到代码块结束;

    8)继续编译运行下一个代码块。

    PS:JS将以一个代码块,也就是一个script标签为单位去运行一段JS代码。

    正是因为var的工作方式,实际上程序执行时,解释器是根本看不到var关键字的。他执行的只是初始化表达式的赋值语句而已——所以问题a的答案就是例子中的第三句实际上什么事也没有做。所以,你一点也不用为代码中会否出现重复定义的变量名而烦恼。你真正需要担心的是,初始化语句所产生的变量的值的变化是否如你预期。除此之外,请不要尝试使用保留字作为变量名。这几乎在所有语言中都必须遵循的规范。

    另外,在函数块中声明变量的工作步骤也是类似的,但不同的是,他们是在函数运行时才创建的。

    没有var的变量声明?! 

    很多朋友都应该有这个经验——“我们根本不需要使用var来声明变量也能直接赋值啊!”。这是因为JS解释器在遇到赋值表达式的时候,会先在作用域链中寻找这个变量是否已经声明。如果这个变量没有声明,则隐式强制为其在全局(Global)作用域中声明,并将表达式的值赋予给该变量。

    但究竟为什么会这样呢?其实一切都源自于变量的获取规则和作用域链的化合作用外加赋值运算符的催化作用。

    作用域链 

    每个运行时的上下文都有与其对应的一个作用域。而作用域链正是把这些作用域连接起来的桥梁。它的作用与程序寻找某一变量标识有关:

    1)JS解释器会按调用的顺序把作用域加进作用域链(像栈般早进入的作用域会在作用域链的底部);

    2)然后在程序寻找某一变量标识时进入作用域链中的第一个索引,并在其中寻找该变量标识;

    3)如果没有找到该标识,则前往下一个索引继续寻找;

    4)如果已经找到该标识,则将该标识及其值返回;

    5)当搜索到最后一个索引仍未能找到该标识,则在最后的索引上创建该标识,并使其值为null,最后返回该标识与值。

    PS:而上述的第5步发生的前提是该标识处于赋值运算符表达式左侧。

    因此,当你没有使用var声明变量而直接使用对该变量作初始化操作(简单赋值)时,JS会自动为你创建该空值标识,并让它可以顺利执行赋值语句。

    变量与作用域链 

    从上面的描述,我们可以很轻易的看到变量与作用域链的关系。因为只要程序需要寻找变量,就必须通过作用域链。而前面所谈及的闭包问题正是由此而来的。回想一下我们前面的示例代码:

     1 var abc=function(y){  

     2 var x=y;// 这个是局部变量  

     3 return function(){  

     4   alert(x++);// 就是这里调用了闭包特性中的一级函数局部变量的x,并对它进行操作  

     5   alert(y--);// 引用的参数变量也是自由变量  

     6 }}(5);// 初始化  

     7 abc();// "5" "5"  

     8 abc();// "6" "4"  

     9 abc();// "7" "3"  

    10 alert(x);// 报错!“x”未定义! 

  • 相关阅读:
    浅析Vue CompositionAPI和React Hooks对比:hook的意义、两者差别(原理链表/Proxy、代码执行每次渲染都执行/组件创建时运行、声明响应式状态、如何跟踪依赖、生命周期、自定义hook、Ref获取元素、计算属性附加函数、Context和provide/inject、在渲染上下文中暴露值)
    算法设计和数据结构学习_1(一道堆排序作业题)
    总结系列_14(OpenCV2.4.3的新特征以及安装方法)
    Kinect+OpenNI学习笔记之8(Robert Walter手部提取代码的分析)
    基础学习笔记之opencv(21):一个简单有趣的皮肤检测代码
    基础学习笔记之opencv(16):grabcut使用例程
    Eigen初步1:初步体验Eigen库
    基础学习笔记之opencv(19):有关图像序列的直方图计算
    基础学习笔记之opencv(17):皮肤检测类CvAdaptiveSkinDetector的使用
    ChaLearn Gesture Challenge_4:one shot learning比赛结果简单分析
  • 原文地址:https://www.cnblogs.com/ranran/p/3965757.html
Copyright © 2011-2022 走看看