JS-ES6-let和const、解构赋值、箭头函数、内置对象扩展

一、ES6简介

ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的 一项脚本语言的标准化规范 ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。
 

1、为什么使用 ES6 ?

1. 变量提升特性增加了程序运行时的不可预测性
2. 语法过于松散,实现相同的功能,不同的人可能会写出不同的代码

2、为什么需要块级作用域?

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

  • 内层变量可能会覆盖外层变量
  • 用来计数的循环变量泄露为全局变量(如下Let中的经典面试题)

(2)ES6 的块级作用域 

  • ES6 允许块级作用域的任意嵌套
  • 内层作用域可以定义外层作用域的同名变量
  • 使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了

(3)注意点

① 浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。但是一般不写函数声明语句,而是写函数表达式。

相关规则如下:

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

② ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

二、ES6新增语法

1、let

(1)只在所处于的块级有效

if (true) {
    let a = 10;
}
console.log(a) // a is not defined

注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性

(2)不存在变量提升

console.log(a); // a is not defined
let a = 20;

(3)暂时性死区

ES6中,在代码块内,使用let/const命令声明变量之前,该变量都是不可用的,在变量声明之前属于该变量的“死区”。(也就是变量不提升的效果)

暂时性死区的本质:只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

var tmp = 123;
if (true) {
    tmp = 'abc';    // ReferenceError
    let tmp;
}
经典面试题
 
使用var
var arr = [];
for (var i = 0; i < 2; i++) {
     arr[i] = function () {
         console.log(i);
     } 
}
arr[0]();    // 2
arr[1]();    // 2
此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。
详解:上面代码中,变量 ivar命令声明的,在全局范围内都有效,所以全局只有一个变量 i。每一次循环,变量 i的值都会发生改变,而循环内被赋给数组 a的函数内部的 console.log(i),里面的 i指向的就是全局的 i。也就是说,所有数组 a的成员里面的 i,指向的都是同一个 i,导致运行时输出的是最后一轮的 i的值,也就是2。
 
使用let
let arr = [];
for (let i = 0; i < 2; i++) {
     arr[i] = function () {
         console.log(i);
     } 
}
arr[0]();    // 0
arr[1]();    // 1
此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,
函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.
详解:上面代码中,变量 ilet声明的,当前的 i只在本轮循环有效,所以每一次循环的 i其实都是一个新的变量。你可能会问,如果每一轮循环的变量 i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i时,就在上一轮循环的基础上进行计算。
关于for循环作用域的重要补充:

设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。

(4)不允许重复声明

let不允许在相同作用域内,重复声明同一个变量。因此,不能在函数内部重新声明参数。

2、const

作用:声明常量,常量就是值( 内存地址 )不能变化的量。
关于这个内存地址:
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于 简单类型的数据(数值、字符串、布尔值), 值就保存在变量指向的那个内存地址,因此等同于常量。但对于 复合类型的数据(主要是对象和数组), 变量指向的内存地址,保存的只是一个指向实际数据的指针const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
(1)具有块级作用域
if (true) {
const a = 10;
}
console.log(a) // a is not defined

(2)声明常量时必须赋值(初始化)

const PI; // Missing initializer in const declaration

(3)常量赋值后,值不能修改

const PI = 3.14;
PI = 100; // Assignment to constant variable.

3、let、const、var 的区别

JS-ES6-let和const、解构赋值、箭头函数、内置对象扩展_第1张图片

var存在变量提升,变量可以在声明之前使用。let、const不存在变量提升,变量一定要声明后才能使用,否则会报错。

补充:ES6声明变量的6种方法

  • ES5:var、function
  • ES6:let、const、import、class

4、解构赋值

ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构,且均可用于嵌套结构。 (可不完全解构)

(1)数组解构

let [a, b, c] = [1, 2, 3];
如果解构不成功,变量的值为undefined。
let [foo] = [];
let [bar, foo] = [1];

注:等号的右边不是数组(或者不是可遍历的结构),那么将会报错。事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

(2)对象解构

数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序变量必须与属性同名,才能取到正确的值。

let person = { name: 'zhangsan', age: 20 };
let { name, age } = person;
console.log(name); // 'zhangsan' 
console.log(age); // 20

let {name: myName, age: myAge} = person; // myName myAge 属于别名
console.log(myName); // 'zhangsan' 
console.log(myAge); // 20

将现有对象的方法,赋值到某个变量:

// 例一
let { log, sin, cos } = Math;

// 例二
const { log } = console;
log('hello') // hello

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

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };    //foo是匹配模式,baz才是变量
baz // "aaa"
foo // error: foo is not defined

(3)默认值

数组和对象的解构都可以指定默认值。

默认值生效的条件是,对象的属性值严格等于undefined

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'

var {x = 3} = {};
x // 3

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

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

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

(4)注意点

  • 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
  • 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
  • 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
  • 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
  • 函数的参数也可以使用解构赋值,也可使用默认值。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

let {length : len} = 'hello';
len // 5

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错。

(5)用途(7个)

  • 交换变量的值
let x = 1;
let y = 2;

[x, y] = [y, x];
  • 从函数返回多个值

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();
  • 函数参数的定义
  • 提取 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]
  • 函数参数的默认值
  • 遍历 Map 结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

// 获取键名
for (let [key] of map) {
  // ...
}

// 获取键值
for (let [,value] of map) {
  // ...
}
  • 输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");

5、箭头函数

ES6中新增的定义函数的方式。
() => {} //():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
const fn = () => {}//代表把一个函数赋值给fn
函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号。
function sum(num1, num2) {
     return num1 + num2; 
}
//ES6写法
const sum = (num1, num2) => num1 + num2;

如果形参只有一个,可以省略小括号

function fn (v) {
     return v; 
}
//ES6写法
const fn = v => v;
箭头函数不绑定this关键字,箭头函数中的this,指向的是 函数定义位置的上下文this。
const obj = { name: '张三'} 
 function fn () { 
     console.log(this);//this 指向 是obj对象
     return () => { 
         console.log(this);
//this 指向 的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象
     } 
 } 
 const resFn = fn.call(obj); 
 resFn();

小结:

箭头函数中不绑定this,箭头函数中的this指向是它所定义的位置,可以简单理解成,定义箭头函数中的作用域的this指向谁,它就指向谁

- 箭头函数的优点在于解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题

面试题:

var age = 100;

var obj = {
	age: 20,
	say: () => {
		alert(this.age)
	}
}

obj.say();
//箭头函数this指向的是被声明的作用域里面,而对象没有作用域的,所以箭头函数虽然在对象中被定义,但是this指向的是全局作用域

6、剩余参数

剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
function sum (first, ...args) {
     console.log(first); // 10
     console.log(args); // [20, 30] 
 }
 sum(10, 20, 30)
剩余参数和解构配合使用
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students; 
console.log(s1);  // 'wangwu' 
console.log(s2);  // ['zhangsan', 'lisi']

三、ES6内置对象扩展

1、Array 的扩展方法

(1)扩展运算符(展开语法)

扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3
console.log(1, 2, 3)
扩展运算符可以应用于 合并数组
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);
将类数组或可遍历对象转换为真正的数组
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];

(2)构造函数方法:Array.from()

将类数组或可遍历对象转换为真正的数组
let arrayLike = {
 '0': 'a',
 '1': 'b',
 '2': 'c',
 length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
let arrayLike = {
 "0": 1,
 "1": 2,
 "length": 2 }
let newAry = Array.from(aryLike, item => item *2)

(3)实例方法:find()

用于找出第一个符合条件的数组成员,如果没有找到返回undefined
let ary = [{
     id: 1,
     name: '张三'
 }, { 
     id: 2,
     name: '李四'
 }]; 
 let target = ary.find((item, index) => item.id == 2);
//找数组里面符合条件的值,当数组中元素id等于2的查找出来,注意,只会匹配第一个
(4) 实例方法:findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2

(5)实例方法:includes()

表示某个数组是否包含给定的值,返回布尔值。
[1, 2, 3].includes(2) // true 
[1, 2, 3].includes(4) // false

2、String 的扩展方法

(1)模板字符串

ES6新增的创建字符串的方式,使用反引号定义。
let name = `zhangsan`;
模板字符串中可以 解析变量
let name = '张三';
let sayHello = `hello,my name is ${name}`; // hello, my name is zhangsan
模板字符串中可以 换行
let result = {
     name: 'zhangsan',
     age: 20,
     sex: '男' }
let html = ` 
${result.name} ${result.age} ${result.sex}
`;
在模板字符串中可以 调用函数
const sayHello = function () {
     return '哈哈哈哈 追不到我吧 我就是这么强大';
};
let greet = `${sayHello()} 哈哈哈哈`;
console.log(greet); // 哈哈哈哈 追不到我吧 我就是这么强大 哈哈哈哈

(2)实例方法:startsWith() 和 endsWith()

startsWith() :表示参数字符串是否在原字符串的头部,返回布尔值
endsWith() :表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true 
str.endsWith('!') // true

(3)实例方法:repeat()

repeat方法表示将原字符串重复n次,返回一个新字符串。
'x'.repeat(3) // "xxx" 
'hello'.repeat(2) // "hellohello"

3、Set 数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set 结构的键名就是键值。
 
Set本身是一个构造函数,用来生成 Set 数据结构。
const s = new Set();
Set函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);

(1)实例方法(4个操作方法)

  1. add(value):添加某个值,返回 Set 结构本身
  2. delete(value):删除某个值,返回一个布尔值,表示删除是否成功
  3. has(value):返回一个布尔值,表示该值是否为 Set 的成员
  4. clear():清除所有成员,没有返回值
const s = new Set();
s.add(1).add(2).add(3); // 向 set 结构中添加值
s.delete(2) // 删除 set 结构中的2值 
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值
s.clear() // 清除 set 结构中的所有值

注意:删除的是元素的值,不是代表的索引

(2)遍历(4个实例方法)

  1. Set.prototype.keys():返回键名的遍历器
  2. Set.prototype.values():返回键值的遍历器
  3. Set.prototype.entries():返回键值对的遍历器
  4. Set.prototype.forEach():使用回调函数遍历每个成员

Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。

Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。 forEach方法的参数就是一个处理函数。
 
s.forEach(value => console.log(value))

(3)去重

1. 去除数组重复成员

// 去除数组的重复成员
[...new Set(array)]

2. 去除字符串里面的重复字符

[...new Set('ababbc')].join('')
// "abc"

3. Array.from方法可以将 Set 结构转为数组

function dedupe(array) {
  return Array.from(new Set(array));
}

dedupe([1, 1, 2, 3]) // [1, 2, 3]

(4)并集、交集、差集

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

(5)遍历操作中改变原来的 Set 结构

// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6

// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6

4、Map 数据结构

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(js)