全局中的解析和执行过程

预处理:创建一个词法环境(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
 

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

  1. JS 函数作用域及变量提升那些事!

    虽然看了多次js函数作用域及变量提升的理论知识,但小编也是一知半解~ 这几天做了几道js小题,对这部分进行了从新的理解,还是有所收获的~ 主要参考书籍: <你不知道的JavaScript(上卷) ...

  2. js 面试的坑

    JavaScript事件属性event.target <!DOCTYPE html> <html> <head> <meta charset="UT ...

  3. js笔记——js里var与变量提升

    var是否可以省略 一般情况下,是可以省略var的,但有两点值得注意: 1.var a=1 与 a=1 ,这两条语句一般情况下作用是一样的.但是前者不能用delete删除.不过,绝大多数情况下,这种差 ...

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

    作用域(Scoping) javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言.我对作用域的理解是只会对某个范围产生作用,而不会对外产生影响的封闭空间.在这样的一些空间里,外部 ...

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

    上一章:JS的数据类型 传送门:https://segmentfault.com/a/11... 好!话不多少,我们就开始吧.对变量提升和函数声明的理解,能让你更清楚容易的理解,为什么你的程序报错了~ ...

  6. JS预解析与变量提升

    预解析 JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的.JavaScript解析器执行JavaScript代码的时候,分为两个过程:预解析过程和代码执行过程 预解析过程 ...

  7. JS中的 变量提升

    首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确.因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升( ...

  8. JS _函数作用域及变量提升

    虽然看了多次js函数作用域及变量提升的理论知识,但也是一知半解~ 这几天做了几道js小题,对这部分进行了从新的理解,还是有所收获的~ 主要参考书籍: <你不知道的JavaScript(上卷)&g ...

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

    首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确.因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升( ...

随机推荐

  1. cf965d Single-use Stones

    ref #include <iostream> #include <cstdio> using namespace std; int a[100005], n, l, ans= ...

  2. 设计模式之第3章-模板方法模式(Java实现)

    设计模式之第3章-模板方法模式(Java实现) "那个,上次由于我老婆要给我做饭,所以就没有说完就走掉了...这个那个".这次和以前一样,先来开场福利(工厂方法模式已被作者踹下场) ...

  3. 设计模式之第1章-工厂方法模式(Java实现)

    设计模式之第1章-工厂方法模式(Java实现) “我先来”,“不,老公,我先!”.远远的就听到几个人,哦不,是工厂方法模式和抽象工厂模式俩小夫妻在争吵,尼妹,又不是吃东西,谁先来不都一样(吃货的世界~ ...

  4. 【Combinations】cpp

    题目: Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. For ex ...

  5. IOS开发学习笔记010-面向对象的三大特性

    面向对象的三大特性 1.封装 2.继承 3.多态 一.封装 将类内部的属性保护起来,在外部不能直接访问,那么如果需要访问怎么办呢? OC提供了set方法来对成员变量进行访问 set方法 1.作用:提供 ...

  6. 使用 Bullet,BulletManager 在 XNA 中创建子弹攻击目标(十五)

    平方已经开发了一些 Windows Phone 上的一些游戏,算不上什么技术大牛.在这里分享一下经验,仅为了和各位朋友交流经验.平方会逐步将自己编写的类上传到托管项目中,没有什么好名字,就叫 WPXN ...

  7. Aptana Studion出现 duplicate location重复定位报错

    1.下载SVN的时候出现报错 duplicate location 2.点击“available software sites”查看已可用的软件网站 3.在这里可以查看到SVN,勾选SVN复选框,点击 ...

  8. HDU5862 Counting Intersections

    Given some segments which are paralleled to the coordinate axis. You need to count the number of the ...

  9. 深入学习之mysql(二)表的操作

    1.表:是数据库中的存储数据的基本单位,一个表包含若干个字段和值 2.创建表: CREATE TABLE 表名称 ( 字段名1 数据库类型1 [约束条件1], 字段名2 数据库类型2 [约束条件2], ...

  10. java流(二)

    目录 1 ObjectOutputStream/ObjectInputStream的使用 2 序列化 3 具体序列化的过程 4 Externalizable的简易介绍 实现序列化的Person类 /* ...