javascript中的作用域(词法and动态)
js中作用域的问题可以说是老生常谈,个人认为js的作用域中存在着两种作用域,一种是词法作用域,一种是动态作用域。
词法作用域
词法作用域就是定义在词法阶段的作用域,也就是说由我们写代码时将变量写在哪里所决定的,当然在js中大部分是这种情况。
var a = 20;function foo () { console.log(a);}foo(); // 20function bar () { var a = 30; foo(); // 20}bar();
这个例子就是一个很好的印证,可以发现的是,无论foo在哪里调用,其a的值永远是全局作用域中的a的值,这就是词法作用域,定义函数时,作用域是在全局,那么foo上层作用域就是全局,改变其调用位置是不能改变其作用域链的,其作用域链是在定义时就决定好的。
利用词法作用域,我们可以引申出闭包的概念,将我们上面的代码改写:
var a = 20;function bar () { var a = 30; return function foo () { console.log(a); }}bar()(); // 30
foo函数在bar函数内定义,所以foo函数的作用域链上层对应的是bar的作用域,利用闭包将foo暴露出去,由于foo函数仍然保持着对bar作用域的引用,所以bar内部的作用域依然存在,没有被回收,这也是闭包能够产生私有变量等效果的原因。
改变作用域
对没错,词法作用域可以被改变,通过eval或者with就可以将作用域改变,但是这并不被提倡。
eval改变作用域:
var a = 20;function bar () { eval("var a = 30;"); console.log(a);}bar(); // 30
eval内的代码可以看做,本身就写在了那一行,但是在严格模式下,eval有着自己的作用域,所以严格模式下上面的代码会报出一个ReferenceError。
with改变作用域:
function foo (obj) { with (obj) { b = 2; a = 2; }}var obj = {a: 1};foo(obj);console.log(obj, b); // { a: 2 } 2
with可以形成一个新的作用域,其词法标识符就是这个对象的属性,所以可以看到的是a = 2,本质上改变的是obj的属性,而b = 2由于这个作用域下没有找到该变量,所以会沿着作用域链向上查找,由于在foo的作用域内也没有找到,一直到全局作用域都没有找到,所以会给全局添加一个属性,这就将b变为了全局变量,所以我们在全局作用域中可以访问到b。但是我们在with中用var定义变量时,会将该变量定义到其外层作用域中。
var obj = {a: 1};with (obj) { var b = 2; var a = 2;}console.log(obj, b, a); // { a: 2 } 2 undefined
所以我们可以看到的with块内的变量也有着提前声明,但是其提前声明的位置是其上层作用域中。这种行为实在是十分让人理解,将对象放入一个新的作用域,但是同时可以给其上层作用域定义变量。当然在严格模式中,完全不用担心,因为with在严格模式中被禁用。
动态作用域
动态作用域取决于其调用方式以及在哪里调用,这个听着有点像this啊,没错,在js中唯一的动态作用域就是this,当然也可以叫其延迟绑定。在谈起this之前,首先要知道的是,执行上下文是什么?
每一种代码的执行都依赖于自身的上下文,函数的每一次调用都会进入函数执行中的一个上下文,并且在函数每次调用时都会产生一个变量对象(虚拟出来的,真实代码中是访问不出来的,会被视作undefined),函数中的每一个变量都可以视为这个变量对象的一个属性,在进入上下文时,首先会对函数的形参进行操作,将形参添加到变量对象中,然后会对函数声明进行操作,假若变量对象中存在同名属性时,同名属性将被覆盖,最后对变量声明进行操作,假若变量对象中存在同名属性时,该变量则会被忽略声明,下面的代码就可以验证我们的这个过程:
function foo (fn) { function fn () {} var fn; console.log(fn); // [Function: fn]}foo();
我们的this和执行上下文没有关系,但是和我们的变量对象有着很大的关系。
在非严格模式下,this指向null或者undefined时会指向全局对象。
以这个准则来看我们《JavaScript语言精粹》中提到的函数的四种调用方式与this取值的关系:
1.方法调用模式
var obj = { a: 2, foo: function () { console.log(this.a); }};obj.foo();
这种情况下,this指向的就是该对象。
2.函数调用模式
var a = 2;function foo () { console.log(this.a);}foo(); // 浏览器环境中:2// 严格模式下var a = 2;function foo () { "use strict"; console.log(this.a); // TypeError}foo();
这种情况下this应该指向的那个我们上文所提到的那个虚拟出来的变量对象,由于其本来并不存在,所以this指向的undefined,在非严格模式下指向的是全局window,但是在严格模式下,禁止了这种隐式的转换。函数调用模式下,this其实可以理解为指向我们虚拟出来的那个变量对象。
3.构造器调用模式
也就是该函数被当做构造函数来调用,这是首先会在函数内部创建一个空对象,然后在将this指向这个空对象,最后将这个对象返回出去。
4.call与apply调用
这时this指向的是其第一个参数。
但是自动有了箭头函数后,箭头函数中的this并不是动态作用域,而是属于词法作用域,再其定义时就已经确定好了,相当于function () {}.bind(this)。
关键字:JavaScript
版权声明
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!