ES6深入认识01

ES6

Posted by EWL on December 23, 2018

ES6的深入认识(1)

let&const

首先列出我对于let和cons起初粗浅的认识

  • let ≈ var,const只是定义一个不可变常量
  • 二者均不支持提升

学习总结后的一些认识: (1) var在全局创建的变量,全局各处均可以访问到,let则不是,如果在代码块中创建,则只能在代码块中访问。

验证: 第一点认识起初不是很明朗,于是进行了如下的验证:

var arr01 = [],
        arr02 = [];

    for(let i = 0; i < 5; i++) {
      arr01.push(function() {
        console.log('i in arr01', i);
      });
    }

    for(var i = 0; i < 5; i++) {
      arr02.push(function() {
        console.log('i in arr02', i);
      });
    }

    arr01.forEach(item => item());
    arr02.forEach(item => item());

结果 image.png

结论:很明显,var创建的i一轮又一轮被重新赋值,到最后我们执行打印函数的时候,全局的i只等于最后循环结束时被赋予的值了,前面的值均已经被覆盖掉,let创建的i则健全的保留了每一次创建时被赋予的新值。

(2) var创建的变量,在循环中始终保持只有一个变量被循环覆盖式赋值,而let在循环中每一次都会创建一个新的变量,赋予新值(js的内部引擎会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。)

(3)暂时性死区 在代码块中,如果我们在没有用let或者const去声明一个变量之前就使用了该变量,那么就会报引用错误。换一句话说,也就是在进入当前作用域时,需要使用的变量就已经存在,但是不可获取,只有在遇到声明该变量的这一行代码出现之后,才可以正常的使用。

(4)块级作用域存在的意义 内层变量可能会覆盖外层变量 先解读一段代码

var tmp = new Date();
    function f() {
      console.log(tmp);
      if (false) {
        var tmp = 'hello world';
      }
    }

    f(); // undefined

起初并不明白为什么会返回Undefined,我最开始认为应当返回new Date(),但是后来发现var是存在变量提升的,所以上述代码应当解读为:

var tmp = new Date();
    function f() {
      var tmp = undefined;
      console.log(tmp);
      if (false) {
        tmp = 'hello world';
      }
    }
    f(); // undefined

用来计数的循环变量泄露为全局变量 同样先读一段代码

    var s = 'hello';
    for (var i = 0; i < s.length; i++) {
      console.log(s[i]);
    }
    console.log(i); // 5

在假设不考虑性能的前提下,如果我有两个以上的循环,

for() {
    //...
}
for() {
    //...
}
for() {
    //...
}
//...

那么我一个页面要设置多个计数的循环变量。但是如果换做是let,那么情况就发生变化了。

var s = 'hello';

    for (let i = 0; i < s.length; i++) {
      console.log(s[i]);
    }

    for(let i = 5; i < 10; i++) {
      console.log('第二个循环', i);
    }

    for(let i = 10; i < 15; i++) {
      console.log('第三个循环', i);
    }
    console.log(i); // 5

结果 image.png

我不用再考虑计数变量会泄露到全局了。

(5)ES6的块级作用域

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

起初看这一段代码的时候,会有一些疑问,前面的学习告诉我let是不能重复对一个变量进行声明的,这一段代码必然会报错,但是运行之后没有报错甚至打印了5,这就说明函数中不仅有函数自己的局部作用域,还有if代码块中的块级作用域。 此时,就不得不联想到es5中常年为了躲避变量泄露二使用到的IIFE(Immediately-invoked-function-expression),哇,很方便了。

// before
(function(){
var temp = 'hello';
})();

// after
{
    let temp = ...;
    ...
}

(6)块级作用域与函数声明 在学习过程中,碰到这么一段代码,说在es5环境下运行和es6环境下运行是不一样的,前者环境下会执行内部函数,后者则会视执行环境的表现而定。

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

果然,在Chrome中执行结果如下: image.png

这么一看,与理论上es6的块级作用域表现不符,我们期待的是会打印出外层函数的outside,但是这里的表现说明,我们的函数没有做到es5的提升,即

function f() { console.log('I am outside!'); }

(function () {
  // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }
  if (false) {
    

  f();
}());

那具体是怎么回事呢,在查阅资料之后发现,es6的附录中规定了浏览器的表现可以不遵守理论上的规定,它可以有自己的表现方式,如下:

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。

那么上述代码的解读就变成了:

function f() { console.log('I am outside!'); }

    (function () {
      var f = undefined;
      if (false) {
        // 重复声明一次函数f
        function f() { console.log('I am inside!'); }
      }

      f();
    }());

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

(7)const保证不变的并非是值 以前的学习只会粗浅的认为const会保证一个值不变,但是直到我遇到const设置对象为常量。

const foo = {};
foo.name = 'foo';
console.log('foo', foo);
foo.name = 'boo';
console.log('foo', foo);

foo = {};

结果 image.png 原来const只能保证设置的值指向的内存指针是不变的,而这个内存地址缩储存的值为多少const并不关心,所以上述代码中name属性如何变又或是foo的对象属性增或减与const无关,因为foo所在的内存地址始终没有变过。

结论:使用const去声明一个不变的对象时需要格外的谨慎

那么,我们真的想要一个始终都不会变的对象,又该怎么办呢?Object有一个方法叫做freeze

const foo = Object.freeze({});
    foo.name = 'foo';
    console.log('foo', foo);
    foo.name = 'boo';
    console.log('foo', foo);

    foo = {};

结果 image.png

严格模式下: image.png

可是,这就结束了吗?我们以前复制对象的时候回去考虑深浅拷贝的问题,那么这里修改的时候,会不会也考虑到层级深浅的问题呢?于是就出现了以下的代码(函数声明或是表达式都可以)

var freezeComplete = (obj) => {
      Object.freeze(obj);
      Object.keys(obj).forEach( (key, i) => {
        if ( typeof obj[key] === 'object' ) {
          freezeComplete( obj[key] );
        }
      });
      return obj;
    };
    const foo = freezeComplete({
      person: {},
      otherKeys: 'otherKeys'
    });

    foo.person.name = 'foo';
    foo.person.height = '1.8';
    console.log('foo', foo);

image.png