• es6变量声明和解构赋值


    /*声明:

    * 本文内容多为学习借鉴性内容,大部分非原创

    * 特别感谢阮一峰的 ECMAScript6 入门,推荐大家学习

    */

    一.es5变量声明的不足

    1.变量提升和函数声明提升

    es5的代码加载顺序是自上而下的但是 var和function(不包含函数表达式)的声明,(不包含赋值),会得到优先加载,加载时提升到作用域的top位置

    函数内的变量声明会提升到函数内部代码的顶部,优先运行;

    并且变量的值为undefined;

    (1)栗子1

    var time = new Date();
    	function tm(){
    		
    		if (false) {
    			var time = "aa";
    		};
    		console.log(time)
    	}
    	tm();
    // 在控制台输出的是 undefined ,这是因为var 声明的变量会先提升

    上述代码等价于一下内容

    var time = new Date();
    	function tm(){
    		              //var time;     //函数内局部环境下声明会覆盖全局作用域,同时if内的声明得到提升到function的top位置,但此时time仅仅是声明提升,并没有赋值,所以运行console时输出unidefined;
    		if (false) {
    			var time = "aa";
    		};
    		console.log(time)
    	}
    	tm();
    // 在控制台输出的是 undefined ,这是因为var 声明的变量会先提升
    

    (2)栗子2

    fn();
      console.log(a);
    var a = 0; console.log(a);

    function fn(){ var a = 1; }
    //控制台输出 undefined和0

     此处,函数声明先得到提升,然后var a=0;变量声明得到提升(但此时并没有变量赋值,函数体内的为局部变量),如下代码

    fn();
    var a; //此处a是由 var a=0;提升而来; console.log(a); var a = 0; console.log(a); function fn(){ var a = 1; } //控制台输出 undefined和0

    (3)栗子3

    fn()
      alert(a)
      var a = 0;
      alert(a);
      function fn(){
          a = 1;
      }
    //此处控制台输出的是 1 和 0;
    //因为 a=1;没有关键词直接赋值使用在非严格模式下默认挂到window环境下,优先执行了声明和赋值;
    //但是在严格模式下这种写法是错误的;

    (4)栗子4

    重复声明的变量

    在同一作用域下,后声明的变量会覆盖之前声明的变量

    不同作用域下局部变量会覆盖外层变量(外层为window则覆盖全局变量)

    此处,不举例;

    二.es6的let

    1.同一作用域下的let 声明的变量是不能被覆盖的,即不允许重复声明

    (1)栗子1

    {
    let a = 1; let a = 2; } //Identifier 'a' has already been declared

      

    2.es6新增的  let  命令所声明的变量只在let命令所在的代码块内有效 --- 块级作用域

    (1)栗子1

    {
      let a=1;
      var b = 22;
    }
    console.log(b)   //22;
    console.log(a)  //报错 Uncaught ReferenceError: a is not defined
    
    

    (2)栗子2,

        在for循环中十分适合使用let

    for(let i = 0; i<15;i++){
      console.log("inner"+i)  //inner 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
     }
    console.log("outer"+i) //Uncaught ReferenceError: i is not defined 这样变量i不会覆盖外层的变量

    (2)栗子3,

         闭包问题的解决

           <!-- 在页面添加三个按钮 -->
          <input type="button" value="1">
    <input type="button" value="2"> <input type="button" value="3"> <script> // 获取页面所有的input var aBtn = document.getElementsByTagName('input'); // 循环绑定点击事件,当然毫无疑问这里点击之后会弹出3,
    //因为我们知道js是单线程的,当基本逻辑语句执行完之后,才会执行点击事件
    //变量ivar命令声明的,在全局范围内都有效,所以全局只有一个变量i
    //每一次循环,变量i的值都会发生改变,而循环内被赋给数组aBtn的函数内部的alert(i),里面的i指向的就是全局的i
    也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是3。 //当你触发点击事件的时候,for循环都已经跑完了,所以i已经变成了3; for(var i = 0; i < aBtn.length; i++){ aBtn[i].onclick = function(){ alert(i); } } // 解决方法1:也是最简单的方法,就是将for循环的var变成let //这样当点击每个按钮的时候,就会依次弹出0,1,2; //let是ES6新增的一个变量声明方式,拥有块级作用域;
    //变量ilet声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6
    // JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算 for(let i = 0; i < aBtn.length; i++){ aBtn[i].onclick = function(){ alert(i); } } //解决方法2:利用闭包(说是闭包,貌似也不完全是,也就是函数作用域访问原则的特性 //函数内部可以访问外部的变量,外部却访问不了里边的; for(var i = 0; i < aBtn.length; i++){ (function(i){ aBtn[i].onclick = function(){ alert(i); } })(i); } </script>

    (4)栗子4,

            for的两个作用域

    for (let i = 0; i < 3; i++) {//就是设置循环变量的那部分是一个父作用域
      let i = 'abc';             //而循环体内部是一个单独的子作用域
    console.log(i); }
    // abc
    // abc
    // abc
    for (let i = 0; i < 3; i++) {
      console.log(i);
    }
    // 0
    // 1
    // 2
     

    3.不存在变量提升

    var   命令会发生"变量提升" 的现象,即变量可以在声明之前使用,值为undefined;

    而 let 要求更严格,let命令要求它所声明的变量一定要在声明后使用,否则报错; 

    (1)栗子1

    //使用var
    console.log(a); //输出undefined var a = 1; //使用let console.log(b);

    //此处会报错 Uncaught ReferenceError: b is not defined
    //这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
    let b = 2;

    ***

    注:以下很多问题可以通过let变量 "先声明后使用" 来进行理解

    ***

    4.暂时性死区

    * 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响 *

    (1)栗子1

    var tmp = 123;
    
    if (true) {
      tmp = 'abc';  //Uncaught ReferenceError:tmp is not defined
      let tmp;
    }

     上面代码存在全局变量tmp ,本来可以正常运行的;但是在块级作用域内let又声明了一个局部变量 tmp 导致 let 将tmp绑定在这个区域内,(并非整个作用域,从使用let到let所在作用域的顶部的区域)

        所以tmp在let声明前就调用了,对tmp的赋值运算就会报错

     ES6明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

    总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

     (2)栗子2

       暂时性死区的范围

    if (true) {
      // TDZ开始
      tmp = 'abc'; // ReferenceError
      console.log(tmp); // ReferenceError
    
      let tmp; // TDZ结束
      console.log(tmp); // undefined
    
      tmp = 123;
      console.log(tmp); // 123
    }
    

     虽然TDZ结束了但是在TDz开始时就已经报错,程序已经无法正常运行了

    (3)栗子3

       typeof 会报错

    在没有let之前,typeof运算符是百分之百安全的,永远不会报错

    “暂时性死区”也意味着typeof不再是一个百分之百安全的操作

    //暂时性死区导致报错
    typeof x; // ReferenceError
    let x;
    
    //没有暂时性死区 使用typeof检测一个未申明的变量也不会报错而是返回undefined
    typeof un_defin_ed    //undefined
    

    (4)栗子4  

       比较隐蔽的暂时性死区

    //使用var的情况
    var x=x;
    console.log(x);  //undefined
    
    
    //使用let的情况
    let x = x;          //ReferenceError : x is not defined
    console.log(x);
    

      //解构赋值

    function bar(x = y, y = 2) { 
      return [x, y];
    }
    
    bar(); // 报错
    上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。如果y的默认值是x,就不会报错,因为此时x已经声明了。

    5.块级作用域

    let实际上为 JavaScript 新增了块级作用域。

    外层作用域无法读取内层作用域的变量 , 这表示外层代码块不受内层代码块的影响。

    内层作用域可以定义外层作用域的同名变量 , 内层作用域可以使用外层作用域的变量。

    (1)栗子1

      块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式( IIFE )不再必要了

       

    // IIFE 写法
    (function () {
      var tmp = ...;
      ...
    }());
    
    // 块级作用域写法
    {
      let tmp = ...;
      ...
    }
    

    (2)栗子2

    函数能否在块级作用域内声明;

    ES5规定函数只能在顶层作用域和函数作用域中声明,不能再块级作用域中声明(不包括函数表达式);

    // 情况一
    if (true) {
      function f() {}
    }
    
    // 情况二
    try {
      function f() {}
    } catch(e) {
      // ...
    }
    

      

    上面两种函数声明,根据 ES5 的规定都是非法的。

    但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。

    function f() { console.log('I am outside!'); }
    
    (function () {
      if (false) {
        // 重复声明一次函数f
        function f() { console.log('I am inside!'); }
      }
    
      f();
    }());
    //上面代码在 ES5 中运行,会得到“I am inside!”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。
    
    // ES5 环境
    function f() { console.log('I am outside!'); }
    
    (function () {
      function f() { console.log('I am inside!'); }
      if (false) {
      }
      f();
    }());
    

      

    ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢?
    
    原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。
    
    允许在块级作用域内声明函数。
    函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
    同时,函数声明还会提升到所在的块级作用域的头部。
    注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。
    
    根据这三条规则,在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。
    // 浏览器的 ES6 环境
    function f() { console.log('I am outside!'); }
    
    (function () {
      if (false) {
        // 重复声明一次函数f
        function f() { console.log('I am inside!'); }
      }
    
      f();
    }());
    // Uncaught TypeError: f is not a function
    上面的代码在符合 ES6 的浏览器中,都会报错,因为实际运行的是下面的代码。
    
    // 浏览器的 ES6 环境
    function f() { console.log('I am outside!'); }
    (function () {
      var f = undefined;
      if (false) {
        function f() { console.log('I am inside!'); }
      }
    
      f();
    }());
    // Uncaught TypeError: f is not a function
    

    考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

    // 函数声明语句
    {
      let a = 'secret';
      function f() {
        return a;
      }
    }
    
    // 函数表达式
    {
      let a = 'secret';
      let f = function () {
        return a;
      };
    }

    还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。

    // 报错
    'use strict';
    if (true)
      function f() {}

    三.const 命令

    (一)基本特性

    const声明一个只读的常量。一旦声明,常量的值就不能改变。

    const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

    而且对于const来说,只声明不赋值,就会报错。

    const bntt;
     Uncaught SyntaxError: Missing initializer in const declaration
    

      

     const 声明变量和let有相似之处 :

         变量声明不提升,

         暂时性死区,块级作用域,

         不可重复声明.

     (二)本质

    const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。

    对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。

    但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,

    const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

    const foo = {};
    
    // 为 foo 添加一个属性,可以成功
    foo.prop = 123;
    foo.prop // 123
    
    // 将 foo 指向另一个对象,就会报错
    foo = {}; // TypeError: "foo" is read-only    Assignment to constant variable.

    上述代码中常量foo储存的是一个地址,这个地址指向一个队形.不可变的是这个地址,即不能把foo指向另一个地址,但是对象本身是可变的,所以可以为其添加新的属性;

    //这个栗子原理同上
     coonst a = [];
     a.push("aa") ;//可执行
     a.length = 0; //可执行
      a=["bb"] ;    //报错
    

    (三) 冻结对象

    Object.freeze() 方法可以冻结一个对象(浅冻结),

    冻结指的是不能向这个对象添加新的属性,

    不能修改其已有属性的值,

    不能删除已有属性,

    以及不能修改该对象已有属性的可枚举性、可配置性、可写性。

    也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。

    语法 Object.freeze(obj)

    //下面是一个将对象彻底冻结的函数。
    
    var constantize = (obj) => {
      Object.freeze(obj);
      Object.keys(obj).forEach( (key, i) => {
        if ( typeof obj[key] === 'object' ) {
          constantize( obj[key] );
        }
      });
    };

    四.顶层对象的属性

    1.顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

    2.

    顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。

    ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

    var a = 1;
    // 如果在 Node 的 REPL 环境,可以写成 global.a
    // 或者采用通用方法,写成 this.a
    window.a // 1
    
    let b = 1;
    window.b // undefined
    上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。
    

      

    3.

    ES5 的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。

    • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window
    • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self
    • Node 里面,顶层对象是global,但其他环境都不支持。

    同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

    • 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
    • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined
    • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全政策),那么evalnew Function这些方法都可能无法使用。
    //综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
    
    // 方法一
    (typeof window !== 'undefined'
       ? window
       : (typeof process === 'object' &&
          typeof require === 'function' &&
          typeof global === 'object')
         ? global
         : this);
    
    // 方法二
    var getGlobal = function () {
      if (typeof self !== 'undefined') { return self; }
      if (typeof window !== 'undefined') { return window; }
      if (typeof global !== 'undefined') { return global; }
      throw new Error('unable to locate global object');
    };
    
  • 相关阅读:
    数据库特性之原子性和一致性
    [linux] 输出重定向与后台运行
    shell编程其实真的很简单(一)
    Java8中的流操作-基本使用&性能测试
    Hadoop到底是干什么用的?
    为什么要有文件系统?文件系统都有哪些种类?
    MySQL insert value与values
    MySQL create语句
    fiddler抓包-简单易操作(二)
    jmeter文件目录说明(一)
  • 原文地址:https://www.cnblogs.com/lizhiwei8/p/7716200.html
Copyright © 2020-2023  润新知