zoukankan      html  css  js  c++  java
  • ES6学习之路1

    ECMAScript 6.0(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

    好吧,上面是我在<ECMAScript6入门>一书中摘出来的一句话,产生深入细致的学习ECMAScript的心思,还是因为想要学习node.js,因为node.js对ECMAScript6的支持度很高,所以,走起.

    一  let命令

    ES6新增了let命令,用来声明变量,类似于var,但是它有以下的特性和特点:

    1.let允许创建块级作用域

    2.它所声明的变量,只在let命令所在的代码块内有效.

    3.let不像var那样存在"变量提升"(也就是变量声明提前),所以,变量一定要在声明后使用,否则会报错.

    4.let存在"暂时性死区域"

    5.let不允许在相同作用域内,声明同一个变量.

     let允许创建块级作用域.首先,我们知道ES5只有全局作用域和函数作用域,没有块级作用域,这会带来很多不便,其中新手最常见也最容易犯的一个错误就是:

    var arr = [];
    for (var i = 0; i < 10; i++) {
        arr[i] = function () {
            console.log(i);
        }
    }
    arr[2]();
    //输出的结果为10

    这是因为i在var声明下,是作用在全局作用域中的一个全局变量,每次循环,新的i值就会覆盖旧的i值,最后只能输出为10

    {
        var a = 10;
        let b = 20;
    }
    console.log(a);  //输出10
    console.log(b);  //ReferenceError

    上面的代码块中,分别用var和let声明变量a,b,然后在代码块之外打印,会发现,变量a可以顺利打印出来,而变量b会报错,这是因为let声明的变量只能let命令所在的代码块内有效.

    即let和const声明只在最靠近的一个块中(花括号内)有效

    而如果我们用刚才的for循环来举例,如下

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

    i只在for循环中有效,原因上面有解释.

    也由此,我们可以将let用在for循环中,来解决第一个实例的问题.

    var arr = []
    for (let i = 0; i < 10; i++) {
        arr[i] = function () {
            console.log(i);
        }
    }
    arr[6](); //结果为6

    使用let来声明i,i仅在块级作用域中有效,最后输出为6.

    这里,同时要提一句,for循环的一个特别之处,就是for循环语句部分就是一个父作用域,而循环体内部是一个单独的子作用域.

    for (let i = 0; i < 5; i++) {
        let i = "fangbin";
        console.log(i);
    }
    //输出了5次"fangbin"

    这表明,函数内部的变量i和函数外部的变量i是分离的.

    其实闭包也可以解决上面的问题,代码如下:

    <div class="fangbin">哈哈哈哈</div>
    <div class="fangbin">哈哈哈哈</div>
    <div class="fangbin">哈哈哈哈</div>
    <div class="fangbin">哈哈哈哈</div>
    <div class="fangbin">哈哈哈哈</div>
    <div class="fangbin">哈哈哈哈</div>
    var fangbins = document.querySelectorAll(".fangbin");
    function fangClick (i) {
        var cli = function (e) {
            console.log(i)
        }
        return cli;
    }
    for (var i = 0; i < fangbins.length; i++) {
        fangbins[i].onclick = fangClick(i);
    }

    这里,闭包不是重点,所以不过多赘述.

    接下来再说说,let不存在变量提升的问题,代码如下

    console.log(a);  //打印出undefined
    console.log(b);  //ReferenceError
    var a = 10;
    let b = 20;

    变量a是用var命令声明,会发生变量声明提前,即脚本开始运行时,变量a已经存在了,只是没有值,所以打印出来undenfined,而变量b是let声明,不会发生变量声明提前,这表示在声明它之前,b是不存在的,如果使用了变量b,则会报错.

    所以,如果要使用let声明的变量,则需要在其之后,否则会报错.

    再来谈谈暂时性死区。

    只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。以下代码很有代表性:

    var tmp = 123;
    if (true) {
      tmp = 'abc'; // ReferenceError
      let tmp;
    }

    上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

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

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

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

    上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。

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

    typeof x; // ReferenceError
    let x;

    上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError。

    作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。

    typeof undeclared_variable // "undefined"

    上面代码中,undeclared_variable是一个不存在的变量名,结果返回“undefined”。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。

    有些“死区”比较隐蔽,不太容易发现。

    function bar(x = y, y = 2) {
        return [x, y];
    }
    
    bar(); // 报错

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

    另外,下面的代码也会报错,与var的行为不同。

    // 不报错
    var x = x;
    
    // 报错
    let x = x;
    // ReferenceError: x is not defined

    上面代码报错,也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。

    ES6规定暂时性死区和letconst语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

    总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

     最后说说不允许重复声明。

    let不允许在相同作用域内,重复声明同一个变量。

    以下两端代码很有代表性:

    // 报错
    function () {
        let a = 10;
        var a = 1;
    }
    
    // 报错
    function () {
        let a = 10;
        let a = 1;
    }

    因此,不能在函数内部重新声明参数。

    function func(arg) {
        let arg; // 报错
    }
    
    function func(arg) {
        {
            let arg; // 不报错
        }
    }

    二、ES6的块级作用域

    讲到块级作用域,先要说说为什么需要块级作用域,

    ES5只有全局作用域和函数作用域,没有块级作用域,这会带来很多不便,其一,内层变量可能会覆盖外层变量,其二,用来计数的循环变量会泄露为全局变量.

    首先,我们先看一个有意思的代码:

    var time = new Date();
    var fang = function () {
        console.log(time);
        if (false) {
            var time = "哈哈哈哈";
        }
    }
    fang();  //undefined;

    这里打印出来的结果为undefined,相比有人会有点奇怪,现在来仔细的看一遍代码,会发现,前面对变量time进行了声明,而在函数中,通过判断语句来对变量time重新进行了声明,但是,在ES5中,只有全局作用域和函数作用域,所以,变量time声明提前,这时候,内层变量覆盖了外层变量,就会打印出来undefined.

    第二种情况就是用来计数的循环变量会泄露为全局变量.

    var fang = "fangbin";
    for (var i = 0; i < fang.length; i++) {
        console.log(fang[i]);
    }
    console.log(i);  //7

    这里的i只是用来控制循环,但是当循环结束,i并没有消失,而是泄露成了全局变量.

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

    function f1() {
        let n = 5;
        if (true) {
            let n = 10;
        }
        console.log(n); // 5
    }

    上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。

    同时,外层作用域无法调用内层作用域的变量.而且,ES6允许块级作用域任意嵌套.

    {{{{{
        {let fang = "fangbinzhenshuai"}
        console.log(fang);  //ReferenceError
    }}}}}

    而内层作用域可以定义外层作用域同名变量.

    {{{{{
        let fang = "hahhah";
        {let fang = "fangbinzhenshuai"}
    }}}}}

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

    再来说说,函数声明和块级作用域的关系,

    ES5中规定,函数只能在顶层作用域和函数作用域中声明,不能再块级作用域中声明,但是大多数浏览器都支持其在块级作用域中的声明,不过,"严格模式"下还是会报错.

    ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。

    ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

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

    下面几行代码很有代表性:

    // ES6的浏览器环境
    function f() { console.log('I am outside!'); }
    (function () {
        if (false) {
            function f() { console.log('I am inside!'); }
          }
    
          f();
    }());
    // Uncaught TypeError: f is not a function

    上面的代码报错,是因为实际运行的是下面的代码。

    // 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的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。

    三、const命令

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

    const fangbin = "真帅";
    console.log(fangbin);  //真帅
    fangbin = "真丑";
    //Uncaught TypeError: Assignment to constant variable

    const声明的变量(姑且如此说)不能改变值,这也意味着const声明的常量必须马上赋值,对于const来说,如果只声明不赋值,就会报错.

    同时,const的作用域和let命令相同,只在声明的块级作用域内有效.

    const声明的变量也不会声明提升,同样存在暂时性死区,所以,只能在声明之后,再使用.

    const声明的常量和let命令一样,不能再同一个作用域内声明重复的常量.(有待更多验证)

    对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。

    const foo = {};
    foo.prop = 123;
    console.log(foo.prop);  // 123
    console.log(foo);
    foo = {}; // Uncaught TypeError: Assignment to constant variable

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

    下面是另外一个例子:

    const a = [];
    a.push('Hello'); // 可执行
    a.length = 0;    // 可执行
    a = ['Dave'];    // 报错

    上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

    待续......

  • 相关阅读:
    相机
    播放音乐
    录音
    NSURLConnection下载
    UITableView
    UIPageControl
    UIScrollView
    ajax禁止浏览器缓存
    java替换word2003
    退出登录
  • 原文地址:https://www.cnblogs.com/fbzs/p/6344186.html
Copyright © 2011-2022 走看看