Js的作用域和闭包

作用域的嵌套将形成作用域链,函数的嵌套将形成闭包。闭包与作用域链是JavaScript区别于其他语言的重要特征之一。

作用域

ES5中,作用域分为:全局作用域和函数作用域。

全局作用域

最外层函数定义的变量拥有全局作用域,任何内部函数都可以访问。

var out = "out";
function fn(){
  console.log(out);
}
fn();

局部作用域

局部作用域一般只在固定的代码片段内可以访问,而对于函数外部无法进行访问。

function fn(){
  var inner = "inner";
}
fn();
console.log(inner);  //ReferenceError: inner is not defined

需要注意的是,函数内部声明变量时,必须使用var声明,否则,相当于声明了一个全局变量。

function fn(){
  inner = "inner";
}
fn();
console.log(inner); //inner
var scope = "aaa"
function fn(){
  console.log(scope); //undefined
  var scope = "bbb";
  console.log(scope); //bbb
}
fn();

第一次输出的是“undefined”,而不是全局变量“aaa”。只要函数内定义了一个局部变量,函数在解析时都会将这个变量“提前声明:

var scope = "aaa"
function fn(){
  var scope; //提前声明了局部变量
  console.log(scope); //undefined
  scope = "bbb";
  console.log(scope); //bbb
}
fn();

然而,也不能因此草率地将局部作用域定义为:用var声明的变量作用范围起止于花括号之间。
在c#或java中,花括号内中的每一段代码都具有各自的作用域,而变量在声明它们的代码之外是不可见的:

for(int i=0 ; i<10 ; i++){
  //i的作用范围只在for循环中
}
Console.Write(i); //error

而对于ES5之前的javascript来说,并没有所谓的块级作用域,javascript的作用域是相对函数而言的,可以成为函数作用域:

for(var i=0 ; i<10 ; i++){
  
}
console.log(i); //10

作用域链

在JavaScript中访问一个变量时,将从本地变量和参数开始,逐级向上遍历作用域直到全局作用域。

var scope = 0,zero = "scope-0";
(function(){
  var scope = 1 , one = "scope-1";
  (function(){
    var scope = 2 , two = "scope-2";
    (function(){
       var scope = 3 , three = "scope-3";
       console.log([three,two,one,zero].join(" "));
       //scope-3 scope-2 scope-1 scope-0
       console.log(scope); //3
    })();
    console.log(typeof three); //undefined
    console.log(scope); //2
  })();
  console.log(typeof two); //undefined
  console.log(scope); //1
})();
console.log(typeof one); //undefined
console.log(scope); //0

在最里层的函数中,各个变量都能被逐级遍历并输出。而倒数第二层函数中,变量three无法遍历找到,所以输出了undefined。

闭包

在一个函数中,定义另一个函数,成为函数嵌套。函数的嵌套将形成一个闭包。

闭包与作用域链相辅相成,函数的嵌套在产生了链式关系的多个作用域的同时,也形成了一个闭包。

怎样理解闭包?

  • 外部函数不能访问内嵌函数
  • 外部函数也不能访问内嵌函数的参数和变量
  • 而内嵌函数可以访问外部函数的参数和变量
  • 换一个说法:内嵌函数包含了外部函数的作用域

再回头看一下作用域链的例子,这次从闭包的角度来理解:

var scope = 0,zero = "scope-0";(function(){  var scope = 1 , one = "scope-1";  (function(){    var scope = 2 , two = "scope-2";    (function(){       var scope = 3 , three = "scope-3";       console.log([three,two,one,zero].join(" "));       //scope-3 scope-2 scope-1 scope-0       console.log(scope); //3    })();    console.log(typeof three); //undefined    console.log(scope); //2  })();  console.log(typeof two); //undefined  console.log(scope); //1})();console.log(typeof one); //undefinedconsole.log(scope); //0

最里层的函数能访问到其内部和外部定义的所有变量。而倒数第二层的函数无法访问到最里层的变量。同时,最里层的scope = 3 这个赋值操作并没有对其外部的同名变量产生影响。
再换个角度来理解闭包:

  • 每次外部函数的调用,内嵌函数都会被创建一次
  • 在它被创建时,外部函数的作用域(包括任何本地变量、参数等上下文),会成为每个内嵌函数对象的内部状态的一部分,即使在外部函数执行完并退出后。
var list=[];for(var i=0 ; i<2 ; i++){  list.push(function(){    console.log(i);  });}list.forEach(function(a){  a(); // 2 2})

得到两次“2”,而不是预期的“1”和“2”,这是因为在list中的两个函数访问的变量i都是其上一层作用域的同一个变量。
改动一下代码,通过闭包来解决这个问题:

var list=[];for(var i=0 ; i<2 ; i++){  list.push((function(j){    return function(){      console.log(j);    }  })(i))}list.forEach(function(a){  a(); // 0 1});

外层的“立即执行函数”接受了一个参数变量i,在其函数内以参数j的形式存在,它与被返回的内层函数中的名称j指向同一个引用。外层函数执行并退出后,参数j(此时它的值为i的当前值)成为了其内层函数的状态的一部分被保存了下来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值