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
    

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

    参考

  • 相关阅读:
    【大数据应用技术】作业一 |了解大数据的特点、来源与数据呈现方式
    结对项目——四则运算 “软件”之升级版
    作业四 个人项目-小学四则运算 “软件”
    作业三:读《构建之法》1-5章有感
    分布式版本控制系统Git的安装与使用
    用热情点燃软件工程II
    字符串、文件操作,英文词频统计预处理
    了解大数据的特点、来源与数据呈现方式
    作业五
    第四次作业
  • 原文地址:https://www.cnblogs.com/souldee/p/9372505.html
Copyright © 2011-2022 走看看