js 面试的坑:变量提升

全局中的解析和执行过程

预处理:创建一个词法环境(LexicalEnvironment,在后面简写为LE),扫描JS中的用声明的方式声明的函数,用var定义的变量并将它们加到预处理阶段的词法环境中去。

一、全局环境中如何理解预处理

比如说下面的这段代码:

var a = 1;//用var定义的变量,以赋值
var b;//用var定义的变量,未赋值
c = 3;//未定义,直接赋值
function d(){//用声明的方式声明的函数
    console.log(‘hello‘);
}
var e = function(){//函数表达式
    console.log(‘world‘);
}

在预处理时它创建的词法作用域可以这样表示:

LE{        //此时的LE相当于window
    a:undefined
    b:undefined
    没有c
    d:对函数的一个引用
    没有e
}

强调:1、预处理的函数必须是JS中用声明的方式声明的函数(不是函数表达式),看例子:

d();
e();
function d(){//用声明的方式声明的函数
    console.log(‘hello‘);
}
var e = function(){//函数表达式
    console.log(‘world‘);
}

输出结果分别是:hello;报错e is not a function
2、是用var定义的变量,看例子:

console.log(a);//undefined
console.log(b);//undefined
console.log(c);//报错:c is not defined
var a = 1;
var b;
c = 3;

二、命名冲突的处理

来看下面的代码:你觉得输出结果是什么?

console.log(f);
var f = 1;
function f(){
    console.log(‘foodoir‘);
}
console.log(f);
function f(){
    console.log(‘foodoir‘);
}
var f = 1;
console.log(f);
var f = 1;
var f = 2;

console.log(f);
function f(){
    console.log(‘foodoir‘);
}
function f(){
    console.log(‘hello world‘);
}

你可能跟我开始一样,觉得输出的是foodoir,这样你就错了,你应该继续看看前面讲的预处理的问题。第一段代码输出的结果应该是:function f(){console.log(‘foodoir‘);}。

看到第二段代码,你可能想都没想就回答结果是1,并且你还告诉原因说javascript里面的函数没有传统意义的重载。是的javascript里面的函数是没有重载,但是第二段代码的运行结果仍然是:function f(){console.log(‘foodoir‘);}。(原因后面作解释)

如果你还觉得第三段代码的结果是2或者是1,那么我建议你回到前面看看关于预处理的例子。第三段的结果为:undefined。

第四段代码的结果为function f(){console.log(‘hello world‘);}

原因:处理函数声明有冲突时,会覆盖;处理变量声明有冲突时,会忽略。在既有函数声明又有变量声明的时候,你可以跟我一样像CSS中的权重那样去理解,函数声明的权重总是高一些,所以最终结果往往是指向函数声明的引用。

三、全局函数的执行

来看下面的例子:

 1 console.log(a);
 2 console.log(b);
 3 console.log(c);
 4 console.log(d);
 5 var a = 1;
 6 b = 2;
 7 console.log(b);
 8 function c(){
 9     console.log(‘c‘);
10 }
11
12 var d = function(){
13     console.log(‘d‘);
14 }
15 console.log(d);

1、我们先分析全局预处理的情况,结果如下:

LE{
    a:undefined
    没有b
    c:对函数的一个引用
    d:undefined
}

此时,我们可以得到前四行代码得到的结果分别为:
  undefined
  报错
  function c(){console.log(‘c‘);
  undefined

2、当执行完预处理后,代码开始一步步被解析(将第二行报错的代码注释掉)

在第6行代码执行完,LE中的a的值变为1;

LE{
    a:1
    没有b
    c:对函数的一个引用
    d:undefined
}

第7行代码执行完,LE中就有了b的值(且b的值为2,此时b的值直接变为全局变量);

LE{
    a:1
    b:2
    c:对函数的一个引用
    d:undefined
}

第10行代码执行完,

LE{
    a:1
    b:2
    c:指向函数
    d:undefined
}

第14行代码执行完,此时

LE{
    a:1
    b:2
    c:指向函数
    d:指向函数
}

关于b变为全局变量的例子,我们在控制台中输入window.b,可以得到b的结果为2。结果如图:

补充:运用词法的作用域,我们可以很好的解释一个带多个参数的函数只传递一个参数的例子。

function f(a,b){

}
f(1);

它的词法作用域可以这样解释:

LE{
    a:1
    b:undefined
}

函数中的解析和执行过程

函数中的解析和执行过程的区别不是很大,但是函数中有个arguments我们需要注意一下,我们来看下面的例子:

function f(a,b){
    alert(a);
    alert(b);

    var b = 100;
    function a(){}
}
f(1,2);

我们先来分析函数的预处理,它和全局的预处理类似,它的词法结构如下:

LE{
    b:2
    a:指向函数的引用
    arguments:2
}
//arguments,调用函数时实际调用的参数个数

再结合之前的那句话:处理函数声明有冲突时,会覆盖;处理变量声明时有冲突,会忽略。
故结果分别为:function a(){}和2

当传入的参数值有一个时:

function f(a,b){
    alert(a);
    alert(b);

    var b = 100;
    function a(){}
}
f(1);

这个时候的词法结构如下:

LE{
    b:undefined
    a:对函数的一个引用
    arguments:1
}

故结果分别为:function a(){}和undefined
还有一个需要注意的地方有:如果没有用var声明的变量,会变成最外部LE的成员,即全局变量

function a(){
    function b(){
        g = 12;
    }
    b();
}
a();
console.log(g);//12

控制台结果:

有了前面的基础,我们就可以对JS的作用域和作用域链进行深入的了解了。

关于JS作用域和作用域链

console.log(a);//undefined
console.log(b);//undefined
console.log(c);//c is not defined
console.log(d);//d is not defined

var a = 1;
if(false){
    var b = 2;
}else{
    c = 3;
}
function f(){
    var d = 4;
}

有了前面的基础我们很容易就可以得到前三个的结果,但是对于第四个却很是有疑问,这个时候,你就有必要看一看关于javascript作用域的相关知识了。
  在编程语言中,作用域一般可以分为四类:块级作用域、函数作用域、动态作用域、词法作用域(也称静态作用域)

块级作用域

在其它C类语言中,用大括号括起来的部分被称为作用域,但是在javascript并没有块级作用域,来看下面一个例子:

for(var i=0;i<3;i++){
    //
}
console.log(i);

它的结果为3,原因:执行完for循环后,此时的i的值为3,在后面仍有效

函数作用域

没有纯粹的函数的作用域

动态作用域

来看下面的例子:

function f(){
    alert(x);
}
function f1(){
    var x = 1;
    f();
}
function f2(){
    var x = 1;
    f();
}
f1();
f2();

如果说存在动态作用域,那么结果应该是分别为1、2,但是最终结果并不是我们想要的,它的结果为:x is not defined。所以javascript也没有动态作用域

词法作用域(也称静态作用域)

我们可以在函数最前面声明一个x=100

var x=100;
function f(){
    alert(x);
}
function f1(){
    var x = 1;
    f();
}
function f2(){
    var x = 1;
    f();
}
f1();
f2();

结果为分别弹出两次100。说明javascript的作用域为静态作用域 ,分析:

function f(){
    alert(x);
}
// f [[scope]]  == LE ==  window
//创建一个作用域对象f [[scope]],它等于创建它时候的词法环境LE(据前面的知识我们又可以知道此时的词法环境等于window)

function f1(){
    var x = 1;
    f();//真正执行的时候(一步一步往上找)LE  ->f.[[scope]]  ==  window
}

在词法解析阶段,就已经确定了相关的作用域。作用域还会形成一个相关的链条,我们称之为作用域链。来看下面的例子:

function f(){    //f.scope == window
    var x = 100;//f.LE == {x:100,g:函数}

    var g = function(){//g.scope = f.LE
        alert(x);
    }
    g();//在执行g的时候,先找g.scope,没有的话再找f.LE,还没有的话找f.scope……一直往上找window
}
f();

最终结果为:100
来看一个经典的例子:

//定义全局变量color,对于全局都适用,即在任何地方都可以使用全局变量color
var color = "red";

function changeColor(){
    //在changeColor()函数内部定义局部变量anotherColor,只在函数changeColor()里面有效
    var anotherColor = "blue";

    function swapColor(){
        //在swapColor()函数内部定义局部变量tempColor,只在函数swapColor()里面有效
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;

        //这里可以访问color、anotherColor和tempColor
        console.log(color);                //blue
        console.log(anotherColor);        //red
        console.log(tempColor);            //blue
    }

    swapColor();
    //这里只能访问color,不能访问anotherColor、tempColor
    console.log(color);                //blue
    console.log(anotherColor);        //anotherColor is not defined
    console.log(tempColor);            //tempColor is not defined
}

changeColor();
//这里只能访问color
console.log(color);                //blue
console.log(anotherColor);        //anotherColor is not defined
console.log(tempColor);            //tempColor is not defined

还需要注意的是:new Function的情况又不一样

var x= 123;
function f(){
    var x = 100;
    //g.[[scope]]  == window
    var g = new Function("","alert(x)");
    g();
}
f();
//结果为:123

小结:

以f1{ f2{ x}}为例,想得到x,首先会在函数里面的词法环境里面去找,还没找到去父级函数的词法环境里面去找……一直到window对象里面去找。
这时候,问题来了。。。。

问题1:到这里看来如果有多个函数都想要一个变量,每次都要写一个好麻烦啊,我们有什么方法可以偷懒没?

方法:将变量设置为全局变量

问题2:不是说要减少全局用量的使用么?因为在我们做大项目的时候难免要引入多个JS库,变量间的命名可能会有冲突,且出错后不易查找,这个时候我们该怎么办呢?

方法:将变量设置在一个打的function中,比如下面这样:

function(){
    var a = 1;
    var b = 2;
    function f(){
        alert(a);
    }
}

问题3:照你的这种方法我们在外面又访问不到了,怎么办?

方法:我们使用匿名函数的方法,示例如下:

(function(){
    var a = 1,
        b = 2;
    function f(){
        alert(a);
    }
    window.f = f;
})();
f();
//结果为:1
时间: 2024-10-26 04:18:06

js 面试的坑:变量提升的相关文章

js 面试的坑

判断页面滚动方向(上下) <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style> body{ height:1000px; } </style> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"

JS中作用域和变量提升(hoisting)的深入理解

作用域(Scoping) javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言.我对作用域的理解是只会对某个范围产生作用,而不会对外产生影响的封闭空间.在这样的一些空间里,外部不能访问内部变量,但内部可以访问外部变量. c语言的变量分为全局变量和局部变量,全局变量的作用范围是任何文件和函数访问(当然,对于非变量定义的其他c文件,需要使用extern关键字进行申明,使用static关键字也可以将作用范围限定在当前文件中),局部变量的作用范围就是从申明到最近的大括号涵盖的块级

原型模式故事链(4)--JS执行上下文、变量提升、函数声明

上一章:JS的数据类型 传送门:https://segmentfault.com/a/11... 好!话不多少,我们就开始吧.对变量提升和函数声明的理解,能让你更清楚容易的理解,为什么你的程序报错了~哈哈哈 我们前端的代码一般就三个部分组成html + css +js,一般呢我们的JS又会放在最后执行. 执行上下文:所谓的执行上下文,就是JS代码执行的环境. Javascript中代码的运行环境分为以下三种: 全局上下文 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境.

JS预解析与变量提升

预解析 JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的.JavaScript解析器执行JavaScript代码的时候,分为两个过程:预解析过程和代码执行过程 预解析过程: 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用. 先提升var,在提升function. JavaScript的执行过程 // 案例1 var a = 25; function abc() { alert(a)

JavaScript中的各种变量提升(Hoisting)

首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确.因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升(Hoisting). JS 存在变量提升(Hoisting),这个的设计其实是低劣的,它允许变量不声明就可以访问,或声明在后使用在前.新手对于此则很迷惑,甚至许多使用JS多年老手也比较迷惑.但在 ES6 加入 let/const 后,变量Hoisting 就不存在了. 一. 变量未声明,直接使用 f

javascript变量提升详解

js变量提升 对于大多数js开发者来说,变量提升可以说是一个非常常见的问题,但是可能很多人对其不是特别的了解.所以在此,我想来讲一讲. 先从一个简单的例子来入门: a = 2; var a; console.log(a); 你觉得以上的代码会输出什么?是输出undefined吗?如果是按照程序的自上而下执行的话,那么这一段代码确实是输出undefined.然而,javascript并不是严格的自上而下执行的语言. 这一段代码的输出结果是2,是不是感到很意外?为什么会这样呢?这个问题的关键就在于变

关于JS变量提升的一些坑

1 function log(str) { 2 // 本篇文章所有的打印都将调用此方法 3 console.log(str); 4 } 函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部 变量声明.命名.提升 在JS中, 变量有4种基本方式进入作用域: 语言内置: 所有的作用域里都有this和arguments:(需要注意的是arguments在全局作用域是不可见的) 形式参数: 函数的形式参数会作为函数体作用域的一部分: 函数声明: 像这种形式: function foo() {

js变量提升的一个小坑

好久没写博客了,原本想实训结束能对整个实训项目认真总结一下,没想到回到学校一点都不轻松,最近在制作网页版简历,遇到了一个小问题,现在不总结以后肯定忙得顾不上,所以长话短说,抓紧时间写下来. 对js语法比较熟的同学可能都知道:js是没有块级作用域的,有一个新手很容易出错的地方 for(var i = 0 ; i < 10 ; i ++){ setTimeout(function(){ console.log(i) },1000*i) } 这段代码会输出10个10,而不是期望的1,2,...,10,

js变量提升的坑

关于js变量提升 变量提升 在js函数内部是可以直接修改全局的变量的,个人感觉是不好的设计, 但是确实存在这个概念 原理: 先查看有没有函数变量bb 查看形参有没有bb 查看全局有没有bb 报错, 找不到bb变量 修改函数内部 var bb = 1; function foo(cc){ var bb = 2; // 这里的bb, 其实是函数的局部变量 console.log(cc); } foo(bb); // 1 console.log(bb); // 1 修改的是形参 var bb = 1;