# 1、变量声明 -- let 、 const

# 1、变量声明--let、coust

  • var和let用法类似,let相对于var函数作用域引入块级作用域

    {
      var a = 10;
      let b = 20;
    }
    
    a // 10
    b // b is not defined.
    
  • 改进var变量提升带来的问题,引入暂时性死区概念,绑定所在区域,不会受外界影响

    // var 
    console.log(foo); // 输出undifined
    var foo = 2;
    
    // let 
    console.log(bar); // 报错 ReferenceError
    let bar = 2;
    
    
    var tmp = 123;
    if(true){
        console.log(tmp) // ReferenceError
        typeof tmp;      // ReferenceError
        tmp = 'abc';     // ReferenceError
        let tmp;         // 绑定此区域,先于声明之前的使用和赋值会报错
    }
    
  • 不允许重复声明变量,不能在函数内部重新声明参数

    // 报错
    function func(){
    let a = 10;
    var a = 10;
    }
    
    // 报错
    function func(arg) {
      let arg;
    }
    func()
    
    // 不报错
    function func(arg) {
      {
       let arg;
      }
    }
    func()
    
  • ES6的块级作用域, 是得变量不存在提升

    ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

    第一种场景,内层变量可能会覆盖外层变量。

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

    第二种场景,用来计数的循环变量泄露为全局变量。

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

    上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

  • 块级作用域代替了以前常用的匿名立即执行函数表达式(匿名 IIFE )

     // IIFE写法
    (function(){
       Var tmp = ...;
    })();
    
    // 块级作用域写法
    {
      let tmp = ...;
    }
    
  • const 命令

const声明一个只读的常量。一旦声明,常量的值就不能改变。所以只声明不赋值会报错。

const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.

const和let一样都不存在变量提升,都不允许重复声明,都不可以在声明前使用。

const 的本质: const 实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址保存的数据不得改动,因为简单的数据类型,值就保存在指向的那个内存地址,所以相当于常量,但是对于复合类型的数据(对象和数组),变量指向的内存中地址,保存的只是指向实际数据的指针,const只能保证这个指针式固定的,但是它指向的数据结构是不是可变的,就完全不能控制了,因此,将一个对象声明为敞亮必须小心了。

const foo = {};
// foo添加属性
foo.prop = 123;
foo.prop // 123
// 将foo指向另一个对象,就会报错
foo = {}; // TypeError: 'foo' is read-only

const arr = [];
arr.push('Hello');
a.length = 0;
a = ['Dave']  // 报错,可以操作不能再赋值

如果想冻结对象本身,需要Object.freeze方法

const foo = Object.freeze({});
// 常规模式下,下面一行代码不起作用,
// 严格模式下,会报错  'use strict'
foo.prop = 123;

对象和对象的属性都冻结的函数,递归

var cosntantize = (obj)=>{
   Object.freeze(onj);
   Object.keys(obj).forEach((key,i)=>{
      if(typeof(obj[key]==='object'){
         cosntantize(obj[key]);
      })
   })
}

# 2、顶层对象的属性

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

window.a = 1;
a //1
a = 2
window.a // 2

顶层对象的属性与全局变量挂钩,被认为是JacaScript语言设计的最大败笔,有几大弊端

1、没法在编译时就报出变量未声明的错误,只有在运行时才知道(全局变量可能是又顶层对象的属性创造,而属性创造是动态的)

2、程序会因为手写失误,不自觉创造一个全局变量

3、顶层对象的属性到处可以读写,不利于模块化编程

4、window是一个有实体概念的对象指的就是浏览器窗口对象,但是说顶层对象是有实体的对象是不合适的

ES6为了改变这一点,但是又兼顾兼容性,规定var和function声明的全局变量,依然是顶层对象的属性,另一方面规定let、const 、class命令声明的全局变量,不属于顶层对象的属性,从ES6开始,全局变量将逐渐与顶层对象属性脱钩。

var a = 1;
// 如果Node的REPL环境,可以写成global.a
// 或者this.a
window.a // 1
this.a // 1

let b =1;
window.b // undifined
this.b // undifined

# 3、globalThis对象

Javascript语言存在一个顶层对象,它提供全局环境(即全局作用域), 所有代码都是在这个谎精中运行,但是顶层对象在各种实现里面是不统一的

比如

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

为了同样的代码都能取到同样的顶层度喜庆,现在一般用this变量,但是有局限。

  • 全局环境中,this会返回顶层对象,但是Node模块和ES6模块中,this返回的是当前的模块。

  • 函数里面的this,如果函数不是做为对象的方法运行,而是单纯做为函数运行,this会执行顶层对象,但是,严格模式下,会返回undifined。

  • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么evalnew Function这些方法都可能无法使用。

    综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

    // 方法一
    (typeof window!=='undifined'
     ? 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');
    };
    

ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this

垫片库global-this模拟了这个提案,可以在所有环境拿到globalThis

-- 2020 - 2 - 23 10:58pm