变量起做用的范围,js中能建立做用域的只能是函数javascript
{ let a = 1; var b = 2; } console.log(a); // a is not defined console.log(b); // 2
var的做用域就是所在的函数体html
let的做用域就是所在的代码块前端
当代码写好的时候,可以根据代码的结构肯定变量的做用域,这种状况下的做用域就是词法做用域。js就是此法做用域,不是动态做用域。java
在某个函数中使用var声明变量,那个变量就将被视做一个局部变量,只存在于函数中.es6
函数在调用结束时,该函数<wiz_tmp_highlight_tag class="cm-searching" style="margin-top: 0px; background: yellow;">做用域</wiz_tmp_highlight_tag>会被销毁,里面的全部局部变量也会被销毁。web
console.log(a); // a is not defined function test() { a = 1; } test();
(根据javascript高级程序设计第四章)解析上面的代码面试
综上,上面的代码能够改写成下面这样数组
function test() { a = 1; } test(); console.log(a); // 1
闭包是指有权访问另外一个函数做用域中的变量的函数浏览器
首先区分一点就是函数内部定义的函数,其做用域链会包括外部函数。请看下面两个例子对比闭包
// 案例一 function foo(){ var num = '123'; function bar(){ console.log(num); // '123' } bar(); } foo(); 案例二 function bar(){ console.log(num);// num is not defined } function foo(){ bar(); } foo();
<script> var arr = []; for(var i = 0; i<10; i++) { arr.push(function(){ console.log(i); }) } arr[0](); arr[1](); </script>
上述答案是输出10
分析: 数组中每一个函数若是执行时其做用域链都会保存全局执行环境对应的变量对象,因此函数执行时函数内部的
i
变量会沿着做用域链找到全局执行环境中的变量,此时全局执行环境中的变量i
为10,因此都会输出10.
若是想输出1,2,3...,
第一种方法能够把for循环中的var变为let,变量i
是let
声明的,当前的i
只在本轮循环有效,因此每一次循环的i
其实都是一个新的变量,因此最后输出的是6
。你可能会问,若是每一轮循环的变量i
都是从新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是由于 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i
时,就在上一轮循环的基础上进行计算。摘自《ECMAScript 6 入门》
第二种方法就象下面案例一下再写一个for循环;
第三个方法就如同案例三在函数内部再定义一个函数,并当即执行外部函数,使函数的活动对象中变量i
的值每次都不一样,从而保证内部函数在执行时其做用域链会包括外部函数的变量对象。
(通常来讲函数执行完毕该函数的做用域和活动变量会被销毁,可是由于函数里面定义的函数它的做用域链始终会包括外部函数的活动对象,因此外面的函数即便当即执行了,可是活动对象还在内存中,没有被销毁)
下面的案例再次巩固知识点,第二个案例再执行时,全局执行环境的变量对象i
又从新被动态赋值,for循环中函数当即执行,由于函数的参数是按值传递的,因此每一个函数获得的是不一样的i
值。
// 案例一 var arr = [ { name: '张三1'}, { name: '张三2' }, { name: '张三3' }, { name: '张三4' } ]; for ( var i = 0; i < arr.length; i++) { arr[ i ].sayHello = function () { console.log(i); }; } arr[0].sayHello(); arr[1].sayHello();
// 案例二 var arr = [ { name: '张三1'}, { name: '张三2' }, { name: '张三3' }, { name: '张三4' } ]; for ( var i = 0; i < arr.length; i++) { arr[ i ].sayHello = function () { console.log(i); }; } for ( var i = 0; i < arr.length; i++ ) { arr[ i ].sayHello(); }
// 案例三 var arr = [ { name: '张三1'}, { name: '张三2' }, { name: '张三3' }, { name: '张三4' } ]; for ( var i = 0; i < arr.length; i++) { arr[ i ].sayHello = (function (i) { return function(){ console.log(i); } })(i); }
分为预解析阶段和执行阶段
在预解析阶段,会将全部的变量声明(只提高声明不提高赋值)以及函数声明(指整个函数),提高到其所在的做用域的最顶上,通常会先提高函数声明再提高变量声明。
注意区分函数声明和函数表达式声明的区别
在变量提高条件下函数表达式和通常变量的声明的规则是同样的。下面的条件式声明章节还会用案例做对比
// 函数声明 fn(); function fn() { console.log('hello world'); }
// 函数表达式 fn(); var fn = function() { console.log('nihao'); }
如下是变量提高中的特别状况
在变量提高状况下,变量通常被分红两种,即通常变量和函数名变量
console.log(typeof f); // function var f; console.log(typeof f); // undefined function f(){}; console.log(typeof f); // undefined
console.log(typeof a); // function function a() { } console.log(typeof a); // function var a = ''; console.log(typeof a); // string
<font color="red">只提高函数对应变量,其余变量直接不提高,同时将变量的声明var去掉(在通常定义过程当中不推荐使用同名变量)</font>
都提高,可是后面的会覆盖前面的
func(); // second func function func(){ console.log("first func"); } function func(){ console.log("second func"); }
// 通常的变量同名对于变量的提高没有影响,由于提高的只是变量的声明,不会提高变量的赋值 console.log(typeof a); // undefined var a = 'abc'; console.log(a); // 'abc' var a = 1; console.log(a); // 1
下面有两个小栗子
var a = 1; var a = 2; console.log(a); // 2
var a = 1; var a; console.log(a); //1
刚刚去查了资料,参考《JavaScript高级程序设计》第7.3章节,原话以下
JavaScript历来不会告诉你是否屡次声明了同一个变量;遇到这种状况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。
段是指<script></script>
标签,代码执行时不分段的
<script> var num = 10; func(); // 第二个func console.log(str); // 报错 function func(){ console.log("第一个func"); } function func(){ console.log("第二个func"); } </script> <script> var str = 'abc'; console.log(num); // 10 func(); // 第三个func function func(){ console.log("第三个func"); } </script>
ES5 规定,函数只能在顶层做用域和函数做用域之中声明,不能在块级做用域声明。若是确实须要,也应该写成函数表达式,而不是函数声明语句。
test(); //报错 if(true){ function test(){ console.log("我是在if语句中声明的函数"); } }
// 各个浏览器执行结果不一样,不建议这么写 if(flag){ functiont test(){ console.log("flag为true时执行"); } }else{ function test(){ console.log("flag为false时执行"); } }
上面两种状况在ES5中都是非法的,能够将上面的demo改写成下面这样
// 下面会根据flag状态决定执行哪段代码 if(flag){ test = functiont(){ console.log("flag为true时执行"); } }else{ test = function(){ console.log("flag为false时执行"); } }
条件式变量声明能够被提高。
console.log( num ); // undefined if ( false ) { var num = 123; } console.log( num ); // undefined
块级做用域就是包含在{}
里面的
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
ES6 引入了块级做用域,明确容许在块级做用域之中声明函数。ES6 规定,块级做用域之中,函数声明语句的行为相似于let
,在块级做用域以外不可引用。
原来,若是改变了块级做用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻所以产生的不兼容问题,ES6在附录B里面规定,浏览器的实现能够不遵照上面的规定,有本身的行为方式。
var
,即会提高到全局做用域或函数做用域的头部。下面的例子可以很好的区分解释ES5和ES6两个环境下处理块级做用域中函数声明的区别
function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }());
// ES5 环境 function f() { console.log('I am outside!'); } (function () { function f() { console.log('I am inside!'); } if (false) { } f(); // 输出I am inside! }());
// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
只要块级做用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,再也不受外部的影响。
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面代码中,存在全局变量tmp,可是块级做用域内let又声明了一个局部变量tmp,致使后者绑定这个块级做用域,因此在let声明变量前,对tmp赋值会报错。
或者
{ var a = 1; let a = 1; } // 报错 Uncaught SyntaxError: Identifier 'a' has already been declared
“暂时性死区”是指在使用let
命令声明变量以前,该变量都是不可用的。
if (true) { let tmp; tmp = 'abc'; // abc }
const foo; // SyntaxError: Missing initializer in const declaration
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
ES5 只有两种声明变量的方法:
var
命令和function
命令。ES6 除了添加let
和const
命令,还有import
命令和class
命令。因此,ES6 一共有6种声明变量的方法。
window
,但 Node 和 Web Worker 没有window
。浏览器和 Web Worker 里面,self
也指向顶层对象,可是Node没有self
。
Node 里面,顶层对象是global
,但其余环境都不支持。(详见http://es6.ruanyifeng.com/#do...)
var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另外一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。let test = 'out'; function f(){ test = 'in'; console.log(window.test);// undefined } f(); console.log(test);// in
上下两个demo的区别就是test
有没有用let声明,当使用let声明时,浏览器会认为当前的环境是ES6环境,因此声明的变量不会复制给window;相反若是没用let声明test
,浏览器就会默认当前环境是ES5。
test = 'out'; function f(){ test = 'in'; onsole.log(window.test); // in } f(); console.log(test);// in
var num = 123; function f1() { console.log( num ); } function f2() { num = 456; f1(); } f2();
上述执行结果为456