简述es6(1)
何谓es6:
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
新增特性:
let 、const命令
ES6 新增的let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效。适用例子:for
循环的计数器 计数器i
只在for
循环体内有效,在循环体外引用就会报错。另外,for
循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
- 不存在变量提升:
var
命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。 - 暂时性死区:只要块级作用域内存在
let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。在let
命令声明变量tmp
之前,都属于变量tmp
的“死区”。 - 不允许重复声明:
let
不允许在相同作用域内,重复声明同一个变量。不能在函数内部重新声明参数。
块级作用域:是对ES5 只有全局作用域和函数作用域带来的不合理结果的改善
- 第一种场景,内层变量可能会覆盖外层变量。
- 用来计数的循环变量泄露为全局变量。
ES6 允许块级作用域的任意嵌套。内层作用域可以定义外层作用域的同名变量。ES6 规定,块级作用域之中,函数声明语句的行为类似于let
,在块级作用域之外不可引用。以下是三个规则
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。
const 命令:const
声明一个只读的常量。一旦声明,常量的值就不能改变。const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。如果真的想将对象冻结,应该使用Object.freeze
方法。
ES5 只有两种声明变量的方法:var
命令和function
命令。ES6 除了添加let
和const
命令,后面章节还会提到,另外两种声明变量的方法:import
命令和class
命令。所以,ES6 一共有 6 种声明变量的方法。
顶层对象的属性: 顶层对象,在浏览器环境指的是window
对象,在 Node 指的是global
对象。ES5 之中,顶层对象的属性与全局变量是等价的。
为了保持兼容性,var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。返回undefined
。
globalThis 对象:顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。顶层对象在各种实现里面是不统一的。
- 浏览器里面,顶层对象是
window
,但 Node 和 Web Worker 没有window
。 - 浏览器和 Web Worker 里面,
self
也指向顶层对象,但是 Node 没有self
。 - Node 里面,顶层对象是
global
,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this
关键字,但是有局限性。ES2020 在语言标准的层面,引入globalThis
作为顶层对象。也就是说,任何环境下,globalThis
都是存在的,都可以从它拿到顶层对象,指向全局环境下的this
。
变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。以前,为变量赋值,只能直接指定值。
let a=1; let b=2; let c=3;
ES6 允许写成下面这样。可以从数组中提取值,按照对应位置,对变量赋值。“模式匹配”
let [a, b, c] = [1, 2, 3];
数组的解构赋值:只要等号两边的模式相同,左边的变量就会被赋予对应的值。另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。如果等号的右边不是数组,那么将会报错。
只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
解构赋值允许指定默认值。默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
数组的解构赋值:对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。可以很方便地将现有对象的方法,赋值到某个变量。
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。与数组一样,解构也可以用于嵌套结构的对象。
对象的解构也可以指定默认值。对象的属性值严格等于undefined
。**解构赋值允许等号左边的模式之中,不放置任何变量名。 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
字符串的解构赋值:字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值。
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o" let {length : len} = 'hello'; len // 5
竖直和布尔值的解构赋值:解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
函数参数的解构赋值:
function add([x, y]){ return x + y; } add([1, 2]); // 3
上面代码中,函数add
的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x
和y
。对于函数内部的代码来说,它们能感受到的参数就是x
和y
。函数参数的解构也可以使用默认值。
圆括号问题:解构赋值虽然很方便解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号。以下三种解构赋值不得使用圆括号。
(1)变量声明语句
// 全部报错 let [(a)] = [1]; let {x: (c)} = {}; let ({x: c}) = {}; let {(x: c)} = {}; let {(x): c} = {}; let { o: ({ p: p }) } = { o: { p: 2 } };
(2)函数参数 也属于变量声明,因此不能带有圆括号。
// 报错 function f([(z)]) { return z; } // 报错 function f([z,(x)]) { return x; }
(3)赋值语句的模式
// 全部报错 ({ p: a }) = { p: 42 }; ([a]) = [5];
用途:
(1)交换变量的值
let x = 1; let y = 2; [x, y] = [y, x];
(2)从函数返回多个值 函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。
// 返回一个数组 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 返回一个对象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
(3)函数参数的定义 解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值 function f([x, y, z]) { ... } f([1, 2, 3]); // 参数是一组无次序的值 function f({x, y, z}) { ... } f({z: 3, y: 2, x: 1});
(4)提取 JSON 数据 解构赋值对提取 JSON 对象中的数据,尤其有用。
let jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; console.log(id, status, number); // 42, "OK", [867, 5309]
(5)函数参数的默认值 指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';
这样的语句。
jQuery.ajax = function (url, { async = true, beforeSend = function () {}, cache = true, complete = function () {}, crossDomain = false, global = true, // ... more config } = {}) { // ... do stuff };
(6)遍历 Map 结构 任何部署了 Iterator 接口的对象,都可以用for...of
循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value); }
// 获取键名 for (let [key] of map) { // ... } // 获取键值 for (let [,value] of map) { // ... }
(7)输入模块的指定方法 加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");