zoukankan      html  css  js  c++  java
  • 作用域与闭包

    作用域

    变量存在并产生作用的环境上下文。在ES5规范中只有两种作用域

    • 全局作用域: 绑定window对象,因此在全局环境创建的变量和函数都是作为windows的属性和方法存在。
    • 函数作用域:初始绑定argument对象,每一个函数都有自己的作用域。

    作用域链

    函数可以嵌套,而每个函数都有自己的作用域,当进入一个函数的时候,函数环境就会被压入一个环境栈当中,当函数执行完毕之后又会出栈,将控制权转交给外面一层的函数。

    作用域的特性

    • 作用域链越内部的函数可以调用外部函数的变量,而外部函数不能调用内部变量。查找一个变量的时候会沿着作用域链一级一级的查找。
    // 内部调用外部
    var a = 3;
    function add() {
    	a += 1;
    }
    add()
    console.log(a); // 4
    
    // 外部调用内部
    function add() {
    	var b = 3;
    }
    b = b + 1;
    console.log(b); // b is not defined
    
    • 内部作用域会覆盖外部作用域的同名变量
    var a = 10;
    function fn() {
    	var a = 20;
    	console.log(a); 
    }
    fn() // 20
    
    • 是声明所在的作用域而不是执行所在的作用域。
    var a = 10;
    
    function fn1() {
    	console.log(a);
    }
    
    function fn2() {
    	var a = 20;
    	fn1(); 
    }
    
    fn2(); // 10
    

    上面代码最终输出了全局对象的a而不是fn2当中的a。

    • 变量提升,在作用域当中会发生变量提升的现象,会将变量提升到作用域的头部。
    console.log(a); // undefined
    var a = 10;
    

    上面的代码等价于

    var a;
    console.log(a); // undefined
    a = 10;
    

    函数也会被提升,并且作为JS的一等公民,函数优先被提升

    fn() // 2	
    
    var fn = function () {
      console.log('1');
    }
    
    function fn() {
      console.log('2');
    }
    

    如上代码所示,会输出2,这证明函数会被先提升到头部,这里如果将函数声明都删除只留下声明和赋值fn的语句则会报错not a function,原因在于函数声明没了就会将变量提升,而变量提升上去的话还没有赋值为函数。

    块级作用域

    块级作用域使得代码更加的优雅、简洁、可维护,例如for循环,从逻辑上来讲在通常使用for循环时候我们更希望的是它里面是拥有自己的作用域。其中的变量只在for循环当中起作用,但是在for循环当中没有块级作用域,因此绑定的是外部作用域。下面例子当中,可以看出for循环绑定的是全局作用域,因此i成为了window的属性。

    for (var i = 0; i < 3; i++) {
    	console.log(i); // 0 1 2
    }
    console.log(window.i); // 3
    

    在ES5的规范当中,有两种方法可以创建块级作用域。

    • with
      with语句会将一个指定的对象添加到作用域链当中,相当于给with代码块当中的前面都加上了指定的对象,为这些变量制定了作用域。在with中用var定义的变量会绑定到外部作用域。
    var a = { count: 1 };
    
    function fn(obj) {
    	with(obj) {
    		var b;
    		count++ ;
    		b = count;
    	}
    	console.log(b); // 2
    }
    fn(a);
    
    console.log(a.count); // 2
    

    如上代码所示,将a对象传入with,在其中改变count,最后输出a.count已经变成了2,说明with当中的count是对象a的,而在with声明的b,被绑定到外部作用域,所以在外面输出为2了。且在with当中声明同对象当中的同名变量var会被忽视。

    • try/catch
      try/catch的catch分句当中也会创建一个块级作用域。

    ES6新增了两个声明方式可以用于创建块级作用域

    • let
      let声明的变量只在所处的代码块内有效,使用大括号包裹并用let声明的变量,不会成为window的属性。
    {
    	let a = 10;
    	var b = 10;
    }
    console.log(a); // 报错
    console.log(b); // 10
    

    let还有不存在变量提升+未声明前不可使用的特性

    • const
      使用const也会创建块级作用域,表示声明一个固定的常量,该常量不可修改,但是这对于引用类型又不是完全限制的,声明一个引用类型,变量存放的是地址而非内容本身,因此可以修改该变量对应的对象的值。
    const obj = {
    	a: 1
    }
    obj.a = 2;
    console.log(obj.a); // 2
    

    突破作用域的特权函数——闭包

    JS高程3当中对于闭包的定义是有权访问另一个函数作用域中的变量的函数,当然这里的前提是这个闭包函数不被嵌套在该函数作用域当中,否则就不是特权了,而只是沿着作用域的查找。一个简单的闭包如下:

    function fn() {
    	var a = 10;
    	function getA() {
    		console.log(a);
    	}
    	return getA;
    }
    
    var f = fn();
    f(); // 10
    

    闭包的作用

    • 让变量始终存放在内存当中
    function counter() {
    	var count = 0;
    	function add() {
    		count++;
    		console.log(count);
    	}
    
    	return add;
    }
    
    var btn = counter();
    btn(); // 1
    btn(); // 2
    
    • 封装对象的私有属性和私有方法
      由于函数作用域的原因,外部作用域无法读取函数当中的变量,但是结合闭包之后,我们就可以将想要暴露的变量或者方法暴露出来了。
    function Counter() {
    	var count = 0;
    	function add() {
    		count++;
    		console.log(count);
    	}
    	function setNumber(num) {
    		counter = num;
    	}
    	function getCount() {
    		console.log(count);
    	}
    
    	return {
    		add: add,
    		get: getCount
    	};
    }
    
    var btn = Counter();
    btn.get(); // 0
    btn.add(); // 1
    btn.setNumber(5); // btn.setNumber is not a function
    

    以上代码当中,在函数当中定义了一个变量,三个方法,但是只返回了两个方法,当调用未返回的方法的时候发生了错误。

    参考

  • 相关阅读:
    BZOJ 2212/BZOJ 3702
    BZOJ 4761 Cow Navigation
    BZOJ 3209 花神的数论题
    BZOJ 4760 Hoof, Paper, Scissors
    BZOJ 3620 似乎在梦中见过的样子
    BZOJ 3940 Censoring
    BZOJ 3942 Censoring
    BZOJ 3571 画框
    BZOJ 1937 最小生成树
    BZOJ 1058 报表统计
  • 原文地址:https://www.cnblogs.com/souldee/p/9372505.html
Copyright © 2011-2022 走看看