作用域链

函数的作用域:

  • 作用域指的是变量存在的范围。在 ES5 的规范中,Javascript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。ES6 又新增了块级作用域。
  • 函数外部声明的变量就是全局变量,它可以在函数内部读取。
  • 在函数内部定义的变量,外部无法读取,称为“局部变量”。函数内部定义的变量,会在该作用域内覆盖同名全局变量。
  • 对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
function fn() {
var a = 1 ;
if (a > 2){
var b = 3;
}
console.log(b);
}
fn();

console.log(a);
输出:undefined
      ReferenceError: a is not defined 
  • JavaScript在ES6之前{}并没有带来块作用域,JavaScript的作用域是靠函数形成的,也就是说一个函数内定义的变量函数函数外不可以访问.
function f1() {
  var n = 999;
  function f2() {
  console.log(n); // 999
  }
}

上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的”链式作用域”结构,子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

  • 函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

var:

在声明变量的时候需要var,这样声明变量作用域才符合我们上面提到的规则,那么不写var会怎样呢

function fn() {
a = 1;
}
fn();

console.log(a);
输出:1
  • 可以看到不写var会声明一个全局的变量,这是我们在编程中应该要避免的,即使真的需要全局变量也应该在最外层作用域使用var声明。
    1. 每当执行一个新的函数,就会来到一个新的作用域下;
    2. 当使用一个变量或者给一个变量赋值的时候,会在当前作用域去寻找,如若找不到再往自己的上层作用域去寻找直至全局作用域,复杂的情况还需要看函数的声明前置。
    3. 上层作用域是指该函数声明的那一层作用域。

什么是闭包:

  • 在调用一个函数然后再返回一个函数就会生成闭包,因为一般情况下一个函数在执行完成之后,函数内的一些临时变量都会被内存释放,可是有些情况得不到释放,例如函数内部有些数据是有用的,仍需被使用则不会被释放。
  • 根据JavaScript的垃圾清理机制,不需要被使用的内容会被内存自动释放,而仍然被使用的内容不会被释放,全局作用域下的内容不会被释放。所以上述函数会存在延时的变量, 可以被保存,这就生成了闭包。
  • 而闭包就是为了保存一些临时的状态和变量。原理就是通过一些方法让函数内的临时变量保存下来。闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7
  • 上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。

  • 为什么会这样呢?原因就在于inc始终在内存中,而inc的存在依赖于createIncrementor,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

  • 闭包的另一个用处,是封装对象的私有属性和私有方法。

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25
  • 上面代码中,函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量。

  • 注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

你可能感兴趣的:(作用域链)