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
js 面试的坑:变量提升的更多相关文章
- JS 函数作用域及变量提升那些事!
虽然看了多次js函数作用域及变量提升的理论知识,但小编也是一知半解~ 这几天做了几道js小题,对这部分进行了从新的理解,还是有所收获的~ 主要参考书籍: <你不知道的JavaScript(上卷) ...
- js 面试的坑
JavaScript事件属性event.target <!DOCTYPE html> <html> <head> <meta charset="UT ...
- js笔记——js里var与变量提升
var是否可以省略 一般情况下,是可以省略var的,但有两点值得注意: 1.var a=1 与 a=1 ,这两条语句一般情况下作用是一样的.但是前者不能用delete删除.不过,绝大多数情况下,这种差 ...
- JS中作用域和变量提升(hoisting)的深入理解
作用域(Scoping) javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言.我对作用域的理解是只会对某个范围产生作用,而不会对外产生影响的封闭空间.在这样的一些空间里,外部 ...
- 原型模式故事链(4)--JS执行上下文、变量提升、函数声明
上一章:JS的数据类型 传送门:https://segmentfault.com/a/11... 好!话不多少,我们就开始吧.对变量提升和函数声明的理解,能让你更清楚容易的理解,为什么你的程序报错了~ ...
- JS预解析与变量提升
预解析 JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的.JavaScript解析器执行JavaScript代码的时候,分为两个过程:预解析过程和代码执行过程 预解析过程 ...
- JS中的 变量提升
首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确.因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升( ...
- JS _函数作用域及变量提升
虽然看了多次js函数作用域及变量提升的理论知识,但也是一知半解~ 这几天做了几道js小题,对这部分进行了从新的理解,还是有所收获的~ 主要参考书籍: <你不知道的JavaScript(上卷)&g ...
- JavaScript中的各种变量提升(Hoisting)
首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确.因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升( ...
随机推荐
- python 二(续)——面向对象编程进阶
1.类的成员 2.类成员修饰符 3.类的特殊成员 在python第二课——面向对象初级,文章中介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一 ...
- Python框架之Django学习笔记(十二)
Django站点管理 十一转眼结束,说好的充电没能顺利开展,反而悠闲的看了电视剧以及去影院看了新上映的<心花路放>.<亲爱的>以及<黄金时代>,说好的劳逸结合现在回 ...
- Selenium自动化测试框架的搭建 (转)
http://www.51testing.com/html/96/86796-851606.html
- jsp运行机制
一.JSP机制概述 可以把执行JSP页面的执行分成两个阶段,一个是转译阶段,一个是请求阶段. 转译阶段:JSP页面转换成Servlet类. 请求阶段:Servlet类执行,将响应结果发送至客户端. 1 ...
- mysql物理备份恢复 xtrabackup 初试
听闻xtrabackup开源且强大 2018-03-06 11:54:41 在官网下载安装了最新的2.4.9版本 网上文章都用的innobackupex,但是最新版已经抛弃了,自己看看手册<Pe ...
- [BZOJ1066][luogu_P2472][SCOI2007]蜥蜴
[BZOJ1066][luogu_P2472][SCOI2007]蜥蜴 试题描述 在一个 \(r\) 行 \(c\) 列的网格地图中有一些高度不同的石柱,一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥 ...
- 编程风格---代码中doxygen方式的注释写法
代码中doxygen方式的注释写法: 1. 模块定义(单独显示一页) /* * @defgroup 模块名 模块的说明文字 * @{ */ … 定义的内容 … /** @} */ // 模块结尾 2. ...
- git filter-branch应用
1.修改author和committer git filter-branch --commit-filter ' export GIT_AUTHOR_EMAIL=me@example.com; exp ...
- redis学习(七)jedis客户端
1.下载jedis的jar包 http://repo1.maven.org/maven2/redis/clients/jedis/2.8.1/ 2.启动redis后台 3.测试联通 package c ...
- Python之面向对象:属性
一.属性定义 1.类属性 类属性定义在类中且在函数体之外:类属性通常不作为实例属性使用:类变量紧接在类名后面定义 类属性的引用:类名.count eg:Employee.count 实例中可以引用类的 ...