We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
上一篇文章简单谈了下"预解析"的概念,文章末尾抛出了一个引子:
var x = 1; function foo() { if(!x) { var x = 2; } console.log(x); // 2 } foo();
按照我们的惯性思维,它的代码执行思路应该是这样的:
if
console.log(x)
foo()
可惜,事与愿违,这段代码最后的返回结果为2.是不是很惊讶,之所以这样,就不得不要说JS的变量作用域了。
1. 什么是变量作用域? 顾名思义,作用域就是变量的作用范围和变量的生命周期,生命周期即:变量是什么时候产生的,又是什么时候消亡的;作用范围即:在哪里我们可以访问到它 说的简单粗暴点,变量作用域就是变量起作用的范围,变量在哪里生,变量在哪里死. 2. 全局作用域&局部作用域 不仅如此,作用域还可以分为全局作用域和局部作用域. 所谓全局作用域就是指我们所定义的函数、变量在脚本的任何位置都可以访问,我们把在全局作用域内定义的变量叫作全局变量: 需要注意的是,如果一个变量没有用var声明,那么这个变量就是全局变量 与之相对应的就是局部作用域,而局部作用域也可以叫作函数作用域.我们把在局部作用域内定义的变量叫作局部变量,局部变量只能在定义它的函数体内才能访问得到: 需要注意的是,函数的参数其实也是一个局部变量
作用范围
生命周期
var
var a = 1; // 全局变量a function foo() { var a = 2; // 局部变量a,定义在了foo函数里,只能在该函数里访问 b = 1; // 全局变量,尽管定义在foo函数里,但是未使用var关键字声明 console.log(a); console.log(b); }; foo(); console.log(a);
上面代码分别返回2,1,1对应着局部变量a与全局变量b与全局变量a
a
b
3. 变量对象 先不用关心变量对象是什么,还是看代码: 定义了全局变量a和foo函数
var a = 1; function foo(){alert(1)}; console.log(a); // 1 console.log(foo()); // 1
除了以上访问方式,还可以:
console.log(window.a); // 1 console.log(window.foo()); // 1
他们完全等价:
a === window.a // true foo === window.foo // true
这会儿,就可以引申出另一个概念执行环境了(其实在某种程度上执行环境等同于作用域),上面例子说明了在全局作用域下定义的变量和函数,其实都是window对象上的属性和方法.而window对象在浏览器中又处于全局全局环境,也就是最外层的环境,这个环境只有在关闭浏览器才会销毁.事实上,每个函数特都会有自己的执行环境,虽然这些环境我们看不见摸不着,但它确确实实存在,而每个执行环境又有自己对应的变量对象,也就是该环境中的变量和函数. 这段解释很拗口,还是来看代码:
执行环境
window
变量对象
var person = 'xiaoA'; function foo() { var person = 'xiaoB'; var age = 18; function bar() { var person = 'xiaoC'; var sex = 'female'; console.log(person); // xiaoC console.log(sex); // female console.log(age); // 18 } return bar(); }
首先,上面例子包含三个环境:他们分别是:
person
age
bar()
sex
foo
null
至此,我们回到文章开头的那段代码来分析下:
实际的执行过程是这样的:
预解析(在代码执行前JS引擎在后台就已经处理完了), 1. 初始化全局环境: var x; function foo(){}; x=10; 2. 执行foo(),初始化foo()的局部环境: function foo() { var x; //JS中只有函数作用域,不存在块级作用域,所以这里面的x其实是个局部变量 if(!x) { // !x ==>!undefined ==> true var x = 2; // x = 2 } console.log(x); // 2 } 3. 执行完毕,打印2
趁热打铁,把上面的代码再稍稍修改下,变成这样:
var x = 1; function fn() { if(x) { // undefined ==> false var x = 2; // 条件不成立,所以根本就不会执行到if语句的代码块里,也就是说这段代码不会执行 } console.log(x); } fn(); // undefined
这里简单说下这段代码的执行过程,预解析完成后,执行fn函数,由于JS没有块级作用域,所以说在fn函数内部,JS解析器在进入函数的时候,就把变量x的声明提升到函数顶部了(仿佛没有if存在一样),理所应当的此时x为undefined,然后才是执行if语句,第一步判断condition,此时x为undefined,condition为false,所以根本就不会执行if语句块里的代码,所以最后打印出了undefined
通过上面例子的返回值,知道为什么局部作用域也叫函数作用域了吧,因为JS根本没有块级作用域:
// JS没有块级作用域,在JS里,函数里的{}是叫作函数体,而if,else,while这些{}才叫作block(代码块) var a = true; if(a) { var b = 1; } // 如果有块级作用域这里b会报错,实际上 console.log(b); // 1 function foo() { var d = 1; } console.log(d); // 报错,未定义
下面来几个例子,加深下预解析和作用域的理解:
var x = 1; function foo() { console.log(x); x = 2; // 没有用var声明,即x为外部那个全局x } foo(); // 1 console.log(x); // 2
在上面例子的基础上稍微修改下:
var x = 1; function foo(x) { console.log(x); // 函数的参数x,尽管foo()调用的时候没有传参,但是之前我们说过函数的参数也是局部变量,在函数体里也就是相当于var声明的,需要预解析(初始化为undefined)所以此条其实是undefined x = 2; // 修改局部x为2 } foo(); // undefined console.log(x); // 1
返回结果说明了不仅函数的参数是局部变量,并且函数作用域内部的修改不会反映到全局上
继续在它们的基础上变下型:
var x = 1; function foo(x) { console.log(x); // 全局x,foo调用的时候传了进来 x = 2; // 执行到这句时,这个x其实是参数x,为局部变量 } foo(x); // 1 console.log(x); // 1
调用函数传入了参数(全局上有)与函数内部参数(函数内部也有一个同名的)同名时,会先执行全局的(在函数内部预解析之前就已经有值了),此时预解析(初始化)函数内部的x,然后修改x的值
继续变型:
var x = 1; function foo() { console.log(x); var x = 2; } foo(); // undefined console.log(x); // 1
foo()的局部环境中,根据作用域链的机制,虽然有同名的x变量,但它始终会找离本环境最近的x,由于x在函数内部是var声明的,所以后台做了预解析,把变量提升到了该环境顶部.
x
The text was updated successfully, but these errors were encountered:
No branches or pull requests
上一篇文章简单谈了下"预解析"的概念,文章末尾抛出了一个引子:
按照我们的惯性思维,它的代码执行思路应该是这样的:
a. 判断x的布尔值,如果为真,将变量x赋值为2,由于我们定义了全局变量,所以
if
里的条件为假,语句里的代码不执行b. 执行
console.log(x)
foo()
,输出全局变量1可惜,事与愿违,这段代码最后的返回结果为2.是不是很惊讶,之所以这样,就不得不要说JS的变量作用域了。
作用域
1. 什么是变量作用域?
顾名思义,作用域就是变量的
作用范围
和变量的生命周期
,生命周期
即:变量是什么时候产生的,又是什么时候消亡的;作用范围
即:在哪里我们可以访问到它说的简单粗暴点,变量作用域就是变量起作用的范围,变量在哪里生,变量在哪里死.
2. 全局作用域&局部作用域
不仅如此,作用域还可以分为全局作用域和局部作用域.
所谓全局作用域就是指我们所定义的函数、变量在脚本的任何位置都可以访问,我们把在全局作用域内定义的变量叫作全局变量:
需要注意的是,如果一个变量没有用
var
声明,那么这个变量就是全局变量与之相对应的就是局部作用域,而局部作用域也可以叫作函数作用域.我们把在局部作用域内定义的变量叫作局部变量,局部变量只能在定义它的函数体内才能访问得到:
需要注意的是,函数的参数其实也是一个局部变量
上面代码分别返回2,1,1对应着局部变量
a
与全局变量b
与全局变量a
3. 变量对象
先不用关心变量对象是什么,还是看代码:
定义了全局变量a和foo函数
除了以上访问方式,还可以:
他们完全等价:
这会儿,就可以引申出另一个概念
执行环境
了(其实在某种程度上执行环境等同于作用域),上面例子说明了在全局作用域下定义的变量和函数,其实都是window
对象上的属性和方法.而window
对象在浏览器中又处于全局全局环境,也就是最外层的环境,这个环境只有在关闭浏览器才会销毁.事实上,每个函数特都会有自己的执行环境,虽然这些环境我们看不见摸不着,但它确确实实存在,而每个执行环境又有自己对应的变量对象
,也就是该环境中的变量和函数.这段解释很拗口,还是来看代码:
首先,上面例子包含三个环境:他们分别是:
window
:此环境又保存着变量
person
和foo()
函数foo()
的局部环境此环境又保存着变量
person
(局部变量并非全局下的person)、变量age
和bar()
函数.如果在该环境没有定义person
,就会在它的父环境找,如果有,就返回父环境的.bar()
的局部环境此环境又保存着变量
person
和变量sex
.如果在该环境没有定义person
,就会在它的父环境(foo
的)找,如果foo
也没定义,就继续往上到全局环境里找,此时全局已经是最外层了(假如此层不是最外层环境,如果上面还有很多嵌套的环境,就以此类推,按照这条链逐步往外追溯,直到找到为止),再往外就是null
了,全局如果找到,就返回.下面图片形象的展示了这个代码的作用域链:
至此,我们回到文章开头的那段代码来分析下:
实际的执行过程是这样的:
趁热打铁,把上面的代码再稍稍修改下,变成这样:
这里简单说下这段代码的执行过程,预解析完成后,执行fn函数,由于JS没有块级作用域,所以说在fn函数内部,JS解析器在进入函数的时候,就把变量x的声明提升到函数顶部了(仿佛没有if存在一样),理所应当的此时x为undefined,然后才是执行if语句,第一步判断condition,此时x为undefined,condition为false,所以根本就不会执行if语句块里的代码,所以最后打印出了undefined
通过上面例子的返回值,知道为什么局部作用域也叫函数作用域了吧,因为JS根本没有块级作用域:
下面来几个例子,加深下预解析和作用域的理解:
在上面例子的基础上稍微修改下:
返回结果说明了不仅函数的参数是局部变量,并且函数作用域内部的修改不会反映到全局上
继续在它们的基础上变下型:
调用函数传入了参数(全局上有)与函数内部参数(函数内部也有一个同名的)同名时,会先执行全局的(在函数内部预解析之前就已经有值了),此时预解析(初始化)函数内部的x,然后修改x的值
继续变型:
foo()
的局部环境中,根据作用域链的机制,虽然有同名的x
变量,但它始终会找离本环境最近的x
,由于x
在函数内部是var
声明的,所以后台做了预解析,把变量提升到了该环境顶部.The text was updated successfully, but these errors were encountered: