ES6--变量的声明及解构赋值

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://ligang.blog.csdn.net/article/details/54986979

​ ES6的目标是使得JavaScript语言可以用来编写大型的复杂的应用程序,成为企业级开发语言;该标准已于2015年6月17日正式发布。但是真正的普及我认为还得需要一段时间,然而这并不是理由让我们不去了解ES6。更重要的一点是,Google公司的V8引擎已经部署了ES6部分特性,对于NodeJS的开发者来说应该熟练掌握。

​ 在浏览器没有全面支持的情况下,Google公司推出了Traceur转码器(https://github.com/google/traceur-compiler),可将ES6编译为ES5代码;以及现在市面比较流行的Babel转码器(https://babeljs.io/),其自带一个babel-node命令,提供支持ES6的REPL环境。

$ npm install --global babel
$ babel-node
$ babel-node es6.js

​ ES6扩充了块级作用域,对字符串、数值、数组、对象、函数等都做了不同程度的扩展;引进了变量解构赋值、Set和Map数据结构、Iterator和for…of循环、Generator函数、Promise对象、Class和Module使得其更加灵活。 ​ 在这里,展望一下ES7,在ES7草案中最让人眼前一亮的当属Object.observe()和async函数,这意味在不久的将来,我们将不依赖任何框架去实现双向数据绑定和简单易用的异步编程。

一、let、const和块级作用域

​ 了解JavaScript的人提及到它,大家都会想到其不存在块级作用域,而是函数级作用域。从而导致了诸如“内层变量可能会覆盖外层变量”、“用来计数的循环变量泄露为全局变量”、“循环绑定事件”等问题。在ES6中,引入了let和const使块级作用域变成现实,立即执行匿名函数(IIFE)变得可替代。

let命令

let与var的异同点比较

内容点

let

var

定义变量

YES

YES

可被释放

YES

YES

可被提升

YES

重复定义检查

YES

可被用于块级作用域

YES

(1)let不会发生“变量提升”现象; (2)不允许在相同作用域重复声明一个变量; (3)let的作用域是块,而var的作用域是函数。函数本身的作用域在其所在的块级作用域之内; 示例:let块级作用域

{
  let a = 1;
  var b = 1;
}
a;  // Uncaught ReferenceError: a is not defined(…)
b;  // 1

示例:let块级作用域

for(var i = 0; i < 10; i++){}
i;  // 10
for(let j = 0; j < 10; j++){}
j;  // Uncaught ReferenceError: j is not defined(…)

示例:let不会发生“变量提升”现象

function test(){
  console.log(a);
  console.log(b);
  var a = 1;
  let b = 1;
}
test(); // a:undefined;b:Uncaught ReferenceError: b is not defined(…)

这意味着typeof不再是一个百分百安全的操作!!!

示例:typeof安全性

typeof a; // undefined
typeof b; // b:Uncaught ReferenceError: b is not defined(…)
let b;

总之,在代码块内使用let命令之前,该变量是不可用的!在语法上被称为“暂时性死区”。ES6规定暂时性死区和不存在变量提升,主要是为了减少运行时错误,防止在变量前就使用这个变量,从而导致意料之外的行为。

示例:不允许重复声明

function test(){
  let a = 10;
  var a = 1;
  return a;
}
test(); // Uncaught SyntaxError: Identifier 'a' has already been declared
function f(){
  let a = 1;
  {
    // var a = 2; // 会报错
    // let a = 2; // 不会报错
   }
}

示例:函数本身的作用域在其所在的块级作用域之内

function f() {
    console.log('out');
}
(function() {
  if(false){
      function f() {
          console.log('in');
      }
  }
  f();
}());   //ES5中:in;ES6中:out!

示例:循环绑定事件

<button>A</button>
<button>B</button>
<button>C</button>
<button>D</button>
<div id="output"></div>
var buttons = document.querySelectorAll("button"),
    output = document.querySelector("#output");

方式一:(常规方式)buttons[i].innerHTML 报错

for(var i = 0, len = buttons.length; i < len; i++){
  buttons[i].addEventListener("click", function(event){
    output.innerHTML = buttons[i].innerHTML;
  });
}

方式二:forEach封装函数作用域

var buttonsAry = Array.prototype.slice.apply(buttons);
buttonsAry.forEach(function(currentBtn, index, ary){
  currentBtn.addEventListener("click", function(event){
    output.innerHTML = currentBtn.innerHTML;
  });
});

方式三:(let会计作用域)正常展示(为循环体的每一次执行都产生一个作用域)

for(let i = 0, len = buttons.length; i < len; i++){
  buttons[i].addEventListener("click", function(event){
    output.innerHTML = buttons[i].innerHTML;
  });
}

方式四:for…of迭代

for(let btn of buttons){
  btn.addEventListener("click", function(event){
    output.innerHTML = btn.innerHTML;
  });
}

const命令

​ const用来声明常量。一旦声明,其值就不能改变。这意味着,const一旦声明常量,就必须立即初始化。const同let命令同样只在当前块级作用域中有效,存在“暂时性死区”。 ​ ECMAScript在对变量的引用进行读取时,会从该变量对应的内存地址所指向的内存空间中读取内容,而当用户改变变量的值时,引擎会重新从内存中分配一个新的内存空间以存储新的值,并将新的内容地址与变量进行绑定。const的原理便是在变量名与内存地址之间建立不可变的绑定,当后面的程序尝试申请的内存空间时,引擎便会抛出错误

示例:常量

const PI = 3.14;
PI = 3.1415;  // caught TypeError: Assignment to constant variable.(…)

对于复合型的变量,其指向数据的存储地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变。

示例:跨模块常量

/* constants.js 模块 */
export const PI = 3.14;
export const AUTHOR = "LIGANG";

/* a.js 模块 */
import * as constants from "./constants";
console.log(constants.PI, constants.AUTHOR);

/* b.js 模块 */
import {PI, AUTHOR} from "./constants";
console.log(PI, AUTHOR);

变量声明

ES5中声明变量只有两种方法:varfunction; ES6中声明变量的方式:varfunctionletconstimportclass命令

全局对象的属性

​ 全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。在JavaScript语言中,所有全局变量都是全局对象的属性。ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。

var a = 1;
let b = 1;
console.log(window.a);  // 1
console.log(window.b);  // undefined

更佳体验

​ 在ES6中,let的设计初衷便是为了代替var。这意味着,TC-39希望在ES6之后,开发者尽可能甚至彻底不再使用var来定义变量。从工程化角度,我们应在ES6中遵循以下三条原则: (1)使用const来定义值的存储容器(常量); (2)只用在值容器明确地被确定将会被改变时才使用let来定义(变量); (3)不再使用var。

二、变量的解构赋值

​ ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。只要某种数据结构具有Iterator接口,都可以进行解构。解构的规则是,只要等号右边的值不是对象,就先将其转化为对象。

数组的解构赋值

Syntax: [arg1, arg2] = [value1, value2] Syntax: [arg1, , arg3] = [value1, value2, value3] Syntax: [arg1, arg2, ...resetArgs] = [value1, value2, value3, value4]

示例:数组解构

var [a, b, c] = [1, 2, 3];
let [foo, [[bar], baz]] = [1 [[2], 3]];

注意:下述几种情况会报错,因为其转为对象不具备Iterator接口。

let [foo] = 1/false/NaN/undefined/null/{};

对象的解构赋值

Syntax: {arg1, arg2} = {arg1: value1, arg2: value2}

示例:对象解构

var {foo, bar} = {foo: "aaa", bar: "bbb"};

对象的解构赋值的内部机制,是先找到同名的内部属性,然后再赋值给对象的变量。真正被赋值的是后者,而不是前者。

var {foo: baz} = {foo: "aaa"}; 
baz;  // "aaa"
foo;  // error: foo is not defined

字符串的解构赋值

示例:字符串解构

var [a, b, c, d, e] = 'hello';
a;  // 'h'
e;  // 'o'

函数参数的解构赋值

function add([x, y]){
    return x + y;
}
add([1, 2]);

默认值

var {x, y = 5} = {x: 1};
x;  // 1
y;  // 5

注意: ES6内部使用严格相等“===”判断一个位置是否有值。所以,不严格等于undefined,默认值是不会生效的。

var {x = 3} = {x: undefined};
x;  // 3
var {x = 3} = {x: null};
x;  // null

用途

(1)交换变量的值:

[x, y] = [y, x]

(2)从函数中返回多个值:

function test(){
    return ["ligang", 25];
}
var [name, age] = test();

(3)函数参数的定义:

function f({x, y, z}) {}
f([1, 2, 3]); // 有序
f({x:1, y:2, z:3}); // 无序

(4)函数参数的默认值:避免了使用“||”操作

function test({a = 1, b = 2}){}

(5)提取JSON数据

var jsonData = {
    name: 'ligang',
    age: 25,
    data: [100, 99]
};
let {name, age, data} = jsonData;

(6)遍历Map数据结构:

for(let [key, value] of map){}

(7)输入模块的指定方法: 加载模块时,往往需要指定输入哪些方法,解构赋值使得输入语句非常清晰。

const {testMethod1, testMethod2} = require("constants");

(8)深层匹配:

有时我们需要获取某深层对象中属性,ES6之前我们只能一层层迭代获取,在ES6中可以通过模式匹配进行获取。

示例:Object in Object

var obj = {
  a: 1,
  b: {
    c: 3
  }
};
// 后去属性a和c的值
let {a, b:{c}} = obj; 
console.log(a); // 1
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(c); // 3

// 可以给c指定别名
let {a, b:{c: t}} = obj;
console.log(a); // 1
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(c); // undefined
console.log(t); // 3

示例:Array in Object

let {a, b: [x, y]} = {a: 1, b: [2, 3]};
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

示例:Object in Array

let [a, {b, c}] = [1, {b: 2, c: 3}];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

示例:Array in Array

let [a, [b, c]] = [1, [2, 3]];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

注意

  • 解构不成功,变量的值为undefined;
  • 解构只能用于数组或对象,原始类型可以转为相应的对象,但是对undefined或null进行解构,就会报错;
var [foo] = undefined;  // TypeError
var [foo] = null;       // TypeError
  • 只要有可能导致解构的歧义,就不得使用圆括号。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang开发

JavaScrip概述

JavaScript 是一种运行在 客户端 的 解释性语言 网页三部分 HTML:控制网页的 结构 CSS:控制网页的 样式 JavaScript:控...

5730
来自专栏nodejs全栈开发

let const 与var的区别

第一点,var在javascript中是支持预解析的,而let不支持预解析,代码如图:

82920
来自专栏nodejs全栈开发

一道火车票排序的面试题

面试题是这样描述的,一个人从北京出发,坐火车去旅行,途经N个城市,目的地是上海,拿到这些火车票如何对其进行排序。

10520
来自专栏前端达人

「Web Animation API 专题」用原生JS制作一个图片随机移动的动画

当我们谈及网页动画时,自然联想到的是 CSS3 动画、JS 动画、SVG 动画 等技术以及 jQuery.animate() 等动画封装库,根据实际动画内容设计...

23020
来自专栏call_me_R

[译] 我见过最好最详细的 JavaScript 关系的解释

我无意在Reddit上找到了这个JavaScript meme,它是我见过最好的抽象。

7710
来自专栏前端技术地图

React组件设计实践总结04 - 组件的思维

在 React 的世界里”一切都是组件“, 组件可以映射作函数式编程中的函数,React 的组件和函数一样的灵活的特性不仅仅可以用于绘制 UI,还可以用于封装业...

19220
来自专栏前端技术地图

Javascript竟然没有标准库?

最近在SegmentFault热心解题,一个问题比较让我比较印象深刻:一个初学者试图在浏览器中导入Node.js的net模块。结果就是在控制台打印后是一个空对象...

19130
来自专栏JAVA人生/面试技巧

typeof运算对于null会返回“Object"

您也许会问,为什么 typeof 运算符对于 null 值会返回 "Object"。这实际上是 JavaScript 最初实现中的一个错误,然后被 ECMASc...

11940
来自专栏跨平台全栈俱乐部

如何从JavaScript跨越到TypeScript [基础进阶知识点]

一致,都会导致报错。 建议使用npm 全局安装typeScript 然后使用 tsc *.ts 进行编译TS文件

11620
来自专栏web全栈工程师的取经之路

谈谈ES6前后的异步编程

Javascript 语言的执行环境是“单线程”的,如果没有异步编程,根本没法用,非卡死不可。

8520

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励