做用域,对于JavaScript语言来讲无处不在,变量做用域,函数做用域(运行时上下文和定义时上下文),做用域污染等等都跟做用域息息相关,掌握JavaScript做用于规则,能够很好地避免一些极端的状况。前端
在JavaScript语言下定义一个全局对象或者变量是最容易不过的了,这种全局变量最容易声明而且使用,由于它能被整个程序所访问到,可是经验丰富的程序员应该尽可能避免使用全局变量,它有以下的缺点:程序员
可是,也不是说必定禁止使用全局变量,在一些状况下全局变量的使用是不可避免的,例如,它是各个独立组件之间进行交互的惟一途径。web
尽量的使用局部变量,少用全局变量。 面试
局部变量:IIFE,let,cosnt等。编程
this.foo; // undefined window.foo; // undefined var foo = 'luffy';//声明一个全局变量,会自动绑定到window对象下 this.foo; // 'luffy' window.foo; // 'luffy'
可使用全局对象来判断程序是否在浏览器环境下能够运行数组
是否支持地理位置浏览器
if(navigator.geolocation){ //user geolocation }
是否支持HTML5闭包
if (window.applicationCache) { alert("你的浏览器支持HTML5"); } else { alert("你的浏览器不支持HTML5"); }
等等不少种新特性的检测。(须要时请问BD)app
在JavaScript中,若是不使用var关键字进行变量声明,该变量会被隐式转换成全局变量,所以会形成没必要要的全局空间污染。模块化
function swap(a,b) { temp = a; // temp变成了global a = b; b = temp; }
这段程序没有使用var来声明temp变量,最终致使之外的建立了一个从全局的变量temp,虽然代码执行起来也没有错误。正确的实现就是在函数体内部使用var temp;
将temp声明成局部变量。
在ES6新特性中,引入了块级做用域这个概念,所以还可使用let,const来声明局部变量。
with语句做用是让代码运行在特定对象做用域内。with语句是JavaScript最使人诟病的特性,不可靠且效率低下。
总而言之一句话,避免使用with语句便可
努力掌握JavaScript闭包这一律念,对于前端程序员来讲会有很是大的帮助。理解闭包不用死背概念,理解三个基本事实就能够。
function outerFoo() { var outVar = '外部变量'; return function() { console.log('我在函数体内部,访问到了:' + outVar); } } var foo = outerFoo(); foo(); //我在函数体内部,访问到了:外部变量
上面这个例子,函数内部返回一个函数,对于foo来讲,能够访问到自身外部定义的非全局变量(outVar是outerFoo函数体内的局部变量)。
仍然是上面那个例子,外部函数已经结束执行过程,返回值赋值给了foo变量(结果是一个函数),如今foo是window做用域下的一个函数,如图所示:
能够看到,window下的foo函数正确的访问到了outFoo函数体内部的局部变量。
缘由:JavaScript的函数值包含了比调用它们的时候执行的那部分代码还要多的信息,也就是说它们还在内部存储了它们可能会引用的定义在其封闭做用域内的变量,这种在其所涵盖的做用域内部跟踪变量的函数叫作闭包。
上述的foo函数就是一个闭包,其代码自己引用了外部变量outVar,每一次foo函数被调用,都会引用到这个outVar变量。
对于闭包,有一个很高的评价:闭包是JavaScript最优雅最有表现力的特性之一
function box() { var val = undefined; //给变量赋值undefined比不给变量赋值要优秀 return { set: function(newVal) { val = newVal; }, get: function() { return val; }, type: function() { return typeof val } } } var b = box(); b.type();//'undefined' b.set(17.2); b.get();//17.2 b.type();//'number'
上述例子产生了一个包含三个闭包的对象,这三个闭包分别对应函数返回对象的set、get、type属性。它们共享外部变量val,而且set闭包还能够更新val的值。
实际上,闭包存储的是外部变量的引用,而不是它们真实值的副本.因此闭包是能够更新改变外部变量的值的。
在ES6,JavaScript开始引入块级做用域,使用块级做用域声明的变量只有在包含他们的封闭语句或代码块{}内部可使用。可是,在ES6以前,是不存在块级做用域的。var声明的变量会被绑定到离它声明最近的那个做用域上,若是找不到就绑定到最外层window上。
function isWinner(player, others) { var higest = 0; for(var i = 0, n = others.length; i < n; i++){ var player = others[i]; //此处重复声明了一个player变量 if(player.score > higest) { higest = player.score; } } return player.score > higest; }
上述函数目的是判断player是不是最高分,可是,在函数内部重复声明了player变量,所以,每一次循环都修改了函数体的传入值player变量自己,最终结果确定不是预期的。
在ES5版本,JavaScript的变量做用域概念存在两种,函数级做用域和全局做用域。ES6增长了块级做用域,使用let、const能够声明块级做用域变量。
当即执行的函数表达式(IIFE),是前端面试过程基本都会问到的问题。下面使用一个老生常谈的面试题来说解它:
window.onload = function() { var result = []; for (var i = 0; i < 5; i++) { result[i] = function() { console.log(i); } } console.log(result[1]());// 5 }
你们应该都知道,最后的执行结果应该是5,而且,result数组内部存的值都是5。
形成结果的缘由能够用上面闭包的基本事实来解释,内部函数保存的变量实际上是外部变量的引用,也就是说,result的每个元素内部所引用的i值会随着for循环变化而变化,当咱们调用
result[1]();
这条语句的时候,i值已经变成了5,因此输出是5。
解决办法,就是使用当即执行函数来建立一个局部做用域来解决。
window.onload = function() { var result = []; for (var i = 0; i < 5; i++) { (function(j) { result[j] = function() { console.log(j); } })(i) } console.log(result[1]());// 1 }
上面使用当即执行函数获得了正确的结果,缘由就是它建立了一段块级做用域,当即函数内部将外部变量i的值当作参数穿入内部也就是参数j,以后内部使用的一直都是j这个局部变量,因此就获得了正确的运行结果。
上面IIFE是经过建立一个块级做用域的方式解决的这个问题,其实在ES6中,很是简便的就能够解决,那就是使用let关键字定义i值,由于let定义的变量就是块级做用域变量。
for(let i = 0; i < 5l i++)
便可获得预期答案。在使用IIFE的时候要注意几件事:首先,代码块不能包含任何跳出块的break语句和continue语句,由于在函数外部使用break和continue语句是不合法的。其次,若是代码块引用了this或特别的arguments变量,IIFE会改变它们的语义。
这条规则对于如今的环境和正确使用JavaScript语法规则编程的程序员来讲不太使用,简单来讲就是下面这种状况。
//推荐使用定义函数的方式 //第一种:函数声明 function double(x) { return x*2; } //第二种:函数表达式 var foo = function(){ return x*2; } //不合理的定义函数的方式 var f = function three(x) { return x*3; }
千万不要使用两种混搭的这种形式,虽然在目前的众多浏览器中都是合法的,可是在低版本终会存在问题,而且,变量three的做用域只是在自身函数体内部,在其余地方都是不能被引用的,而变量f能够被外部引用 。
function foo() { return 'global'; } function test(x) { function foo() { return 'local'; } var result = []; if (x) { result.push(foo()); } result.push(foo()); return result; } test(true); // ['local','local'] test(false); // ['local']
这种嵌套函数声明的方式也容易出现问题,全局做用域下声明了函数foo,在test函数体内部又声明了一个foo函数,最后输出的结果是函数体内部foo的执行结果。对于上面这种状况,应该在test函数体内部使用函数表达式的形式声明一个新变量。
function foo() { return 'global'; } function test(x) { var g = foo; var result = []; if (x) { g = function() { return 'local'; } result.push(g()); } result.push(g()); return result; } test(true); // ['local','local'] test(false); // ['global']
关于eval函数理解不是很深,没太理解这两节的内容,等有时间从头翻翻红宝书回来再看看。