/*声明:

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

* 特别感谢阮一峰的 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');
};

es6变量声明和解构赋值的更多相关文章

  1. ES6 变量的解构赋值

    数组的解构赋值     var [a,b,c] = [1,2,3];    左边是变量,右边是值,根据数据结构一一对应 只要等号两边的模式相同,左边的变量就会被赋予右边对应的值,必须模式相同 如果等号 ...

  2. es6变量的解构赋值学习笔记

    1. 解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象.由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错. let { prop: x } = undefin ...

  3. ES6 - 变量的解构赋值学习笔记

    变量的解析赋值 数组的解析赋值 es6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这就称为解构. var a = 1; var b = 2; var c = 3; //在es6中允许写成 ...

  4. ES6变量的解构赋值

    变量的解构赋值 1.数组的解构赋值 2.对象的解构赋值 3.字符串的解构赋值 4.数值和布尔值的解构赋值 5.函数参数的解构赋值 6.圆括号问题 7.用途 1.数组的解构赋值 ES6 允许写成下面这样 ...

  5. es6—变量的解构赋值

    数组的解构赋值 ]]]]]]] = []}} = {}} = {}})]: first]: last} = arr} = {}) {}))}))}) {}))}))].]]]])})] }}} = { ...

  6. ES6变量的解构赋值(一)数组的解构赋值

    let[a,...arr]=[1,2,3,4];//a==>1 arr==>[2,3,4] let [x, y, ...z] = ['a'];//a==>'a' y==>und ...

  7. ES6变量的解构赋值(二)对象的解构赋值

    前面我们知道,数组的结构赋值需要按顺序进行赋值, let [a,,c] = [,,] console.log(a); console.log(c);//3 let [a,b] = [1];consol ...

  8. ES6中的变量的解构赋值, 解放我们的双手,实现变量的批量赋值

    ES6--变量的解构赋值 引言 正文 一.数组的解构赋值 解构失败 不完全解构 默认值 二.对象的解构赋值 三.字符串的解构赋值 结束语 引言 变量的解构赋值, 听起来很复杂, 简单点说可以理解成批量 ...

  9. ES6 学习笔记(一)let,const和解构赋值

    let和const let和const是es6新增的两个变量声明关键字,与var的不同点在于: (1)let和const都是块级作用域,在{}内有效,这点在for循环中非常有用,只在循环体内有效.va ...

随机推荐

  1. Java泛型解析

    1. 概述    在引入范型之前,Java类型分为原始类型.复杂类型,其中复杂类型分为数组和类.引入范型后,一个复杂类型就可以在细分成更多的类型. 例如原先的类型List,现在在细分成List< ...

  2. vue不是内部或外部命令,配置一个Path系统变量就可以解决

    作为一个vue小白,最近为vue安装真是操碎了心.无论怎么查找网上的教程,还是解决不了"vue不是内部或外部的命令"诸如此类的问题.好在功夫不负有心人,终于在多次的试验下,成功解决 ...

  3. 如何用vue实现树形菜单?

    在公司培训了2周,布置的作业是从树形,grid分页以及echarts中选一个.由于都不是很熟,就挑了第一个.本来想在网上找找参考,然后模仿着做一个,但是网上的代码多少参差不齐,写到一半没了,所以只要自 ...

  4. 从(0,0)到(m,n),每次走一步,只能向上或者向右走,有多少种路径走到(m,n)

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  5. HierarchyID 数据类型用法

    树形层次结构(Hierarchy)经常出现在有结构的数据中,T-SQL新增数据类型HierarchyID, 其长度可变,用于存储层次结构中的路径.HierarchyID表示的层次结构是树形的,由应用程 ...

  6. 阿里java开发手册中命名规约解读之DO/BO/DTO/VO/AO

    前言 在阅读<阿里巴巴Java开发手册>时,看到命名规则中有这样一条 虽然知道这些是根据Java对象的角色所分配名称的后缀,但是没有弄清楚分别是什么意思,日常开发中也没有使用到. 网上查找 ...

  7. kindeditor使用

    下载地址http://kindeditor.net/down.php @官方文档 使用步骤: 引入js <script charset="utf-8" src="r ...

  8. linkin大话java

    青春不灭,理想犹存,linkin--勿忘初心! 不知不觉已经和java并肩作战将近了2年,在这2年之中模模糊糊研究了java2ee领域的好多东西,但是都没有做系统的整理.以前写的笔记也是零零散散不成一 ...

  9. RESTClient

    RESTClient是Mozilla Firefox一个用于测试http请求插件. 1.打开火狐扩展搜索RESTClient进行安装并重启浏览器. 2.重启后可以在Mozilla Firefox地址栏 ...

  10. 编译原理:基于状态转换图识别for语句

    int state =0;while(state<9){ switch state{ case 0: if(ch=='f'){ state=1;getchar(ch); } case 1: if ...