let 与 const
var 和 let 区别
var 和 let 都是用来声明变量,但是两者有很大的区别
1、let 变量不能被重复声明
// 使用 let
let star;
let star;
报出:
let star;
^
SyntaxError: Identifier 'star' has already been declared // 语法错误
// 使用 var
var star;
var star;
// 将不会报出语法错误
2、let 拥有块级作用域,也称’暂时性死区’,不影响作用域链的效果
// 使用 let
{
let star = 'hello world';
}
console.log(star);
报出:
console.log(star)
^
ReferenceError: star is not defined // 引用错误
// 使用 var
{
var star = 'hello world';
}
console.log(star); // 正常输出 'hello world'
3、let 不存在变量提升,var 存在变量提升
// 使用 let
console.log(cat);
let cat = 'xiaobai';
报出:
console.log(cat)
^
ReferenceError: Cannot access 'cat' before initialization // 引用错误
// 使用 var
console.log(cat); // cat 变量被提升,输出 undefined
var cat = 'xiaobai';
举个经典例子:
样式代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.item {
float: left;
margin: 30px;
width: 100px;
height: 100px;
border: 1px solid #ff6600;
}
</style>
</head>
<body>
<div>
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<script src="./let.js"></script>
</body>
</html>
js代码:
let items = document.querySelectorAll('.item');
for (var i = 0; i < items.length; i++) {
items[i].onclick = function () {
items[i].style.background = 'blue';
}
}
效果如上图所示,当我们点击第一,第二,或者第三个块时,类型错误,就是没有找到 item[i] 这个变量,这是由于使用 var 定义的变量。
在for循环中这个块中使用var声明的变量,默认就是全局变量,在 window 上,可以直接访问到。而在for循环这个块中访问到的是全局的变量 i , i++ 在执行完之后,此时在 window.i = 3
这时就用到了,let 声明变量,就会在for循环这个块中,产生一个块级作用域,与全局作用域无关,items[i] 就是我们想要的变量。
const 特性
const 的特性 基本和 let 一致,比如,变量不能被重复声明,拥有块级作用域,不存在变量提升。
但是 const 也有自己的特性:
1、const 声明的变量为常量,潜规则写法是,变量名都要大写,比如 const STAR = 'white'
;
2、const 声明变量,必须被赋值;
3、const 声明的变量为常量,因此不能被重新赋值,但是对象和数组除外,因为数组和对象里面的值被改变时,引用的内存地址没有发生变化,所以不会出现错误;
解构赋值
ES6 允许按照一定模式从数组和对象中提取值,对变量进行解构赋值。
解构赋值最大程度上解决了,ES5 的利用属性取值,比如 obj.a
对象解构赋值的几种方式:
1、完全解构
const CAT = {
a: 10,
b: 20,
c: 'nihao'
}
const {a, b, c} = CAT
2、不完全解构
const CAT = {
a: 10,
b: 20,
c: 'nihao'
}
const {a, b} = CAT // 只取出 a, b
3、剩余运算符
const CAT = {
a: 10,
b: 20,
c: 'nihao'
}
const {a, ...res} = CAT
console.log(res) // 输出 {a: 20, c: 'nihao'}
4、默认值
const {a, b = 20} = {a: 10}
数组解构:
1、数组完全解构
const NUM = [1, 2, 3];
const [a, b, c] = NUM;
2、嵌套数组解构
const NUM = [1, [2], 3];
const [a, [b], c] = NUM; // NUM 数组里面有一个嵌套的数组,此时必须用 [b] 来解构 [2]
模板字符串
ES6 引入新的声明字符串的方式 `` , 对比 ‘’, “” 的有点:
1、内容可直接换行,不用再使用 ‘+’ 拼接
2、可以直接拼接变量
let white = '小白';
let cat = `<ul>
<li>你好,${white}</li>
</ul>`;
console.log(cat);
直接输出:
<ul>
<li>你好,${white}</li>
</ul>
rest 参数
rest 参数,用于获取函数的实参,代替 arguments
当我们使用 arguments 时:
function date() {
console.log(arguments);
}
date(1, 2, 3, 4);
打印台输出:
可以看到 arguments 原型中 constructor 属性为 Object,这就说明了 arguments 并不是数组,因此不能使用数组的方法,因此在处理多个参数时,并不能使用更加方便的数组方法进行处理。
接下来,我们再看一下 rest 获取参数的类型:
function date(...args) {
console.log(args);
}
date(1, 2, 3, 4);
打印台输出可以清楚的看到,rest 获取到的参数时数组类型,可以使用数组的方法进行操作它。
rest 参数必须放到参数的最后面:
function data(a, b, ...args) {
console.log(a); // 1
console.log(b); // 2
console.log(args); // [3, 4]
}
data(1, 2, 3, 4);
扩展运算符
...
扩展运算符可以将 数组 转化为 逗号 分隔的参数序列。
const arr = [1, 2, 3]
function foo() {
console.log(arguments) // 1, 2, 3
}
foo(...arr)
应用:
const arr1 = [1, 2, 3]
arr2 = [4, 5, 6]
const newArr = [...arr1, ...arr2]
console.log(newArr) // [1, 2, 3, 4, 5, 6]
数组的克隆
const arr = [1, 2, 3]
const newArr = [...arr]
console.log(newArr) // [1, 2, 3]
将伪数组或类数组转换成真正的数组
function foo() {
const args = [...arguments]
console.log(args) // [1, 2, 3]
}
foo(1, 2, 3)
Symbol 新增数据类型
ES6 新引入的基础数据类型,表示独一无二的值,是一种类似于字符串的类型。
Symbol 特点:
值是唯一的,用来解决命名冲突
不能与其他数据类型进行运算
定义的对象属性,不能使用 for···in 遍历,但是可以用Reflect.ownKeys 来获取对象的所有键名
Symbol 的创建:
const s1 = Symbol('1')
// 或者:
const s2 = Symbol.for('1')
// 给对象添加 Symbol 类型的属性,保护对象属性的安全性
const obj = {
name: 'xiaobai',
[Symbol('say')]: function() {
console.log('说话')
}
}
obj.[Symbol('say')]()
// 或者
const obj1 = {
up: '',
dowm: ''
}
const methods = {
up: Symbol(),
dowm: Symbol()
}
obj[methods.up] = function() {
}
obj[methods.down] = function() {
}
// 这样创建的 up 和 dowm 方法是唯一的,不会和obj1中的up,dowm 属性发生冲突,保护了属性的安全。
迭代器(Iterator)
迭代器是一种接口,为不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以进行遍历。
ES6 新的遍历方式,for···of 循环,Iterator 接口主要供 for···of 使用。
可使用 for···of 循环的数据:
Array,arguments,set,map,String,NodeList
const arr = ['red', 'black', 'blue']
for(let i of arr) {
console.log(i) // 'red' 'black' 'blue'
}
for(let i in arr) {
console.log(i) // 1, 2, 3
}
如何使用 for...of
遍历自定义的数组呢,比如对象中不包含 迭代器(Iterator),但是可以自定义一个迭代器。
// 直接使用 for...of 遍历对象
const obj = {
arr: ['red', 'blue', 'green']
}
for(let i of obj) {
console.log(i) // Uncaught TypeError: obj is not iterable
}
// 给对象添加一个迭代器
const obj = {
color: ['red', 'blue', 'green'],
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.color.length) {
const res = { value: this.color[index], done: false }
index++
return res
}else {
return { value: undefined, done: true }
}
}
}
}
}
for (let i of obj) {
console.log(i) // 'red' 'blue', 'green'
}
生成器(generator)
生成函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全相同。
使用
function * gen() {
console.log('red')
yield '1'
console.log('blue')
yield '2'
console.log('green')
yield '3'
}
const iterator = gen()
iterator.next() // 'red'
iterator.next() // 'blue'
iterator.next() // 'green'
yield
主要用作代码隔断,生成器(generator)在执行的时候必须使用生成器(iterator)的next()
迭代。一个 next() 只能迭代一段 yield 代码。
解决异步回调
function one() {
setTimeout(() => {
console.log(1)
iterator.next()
}, 1000);
}
function two() {
setTimeout(() => {
console.log(2)
iterator.next()
}, 2000);
}
function three() {
setTimeout(() => {
console.log(3)
}, 3000);
}
function * gen() {
yield one()
yield two()
yield three()
}
const iterator = gen()
iterator.next()
Promise
Promise 是 ES6 引入的异步编程解决方案。语法上 Promise 是一个构造函数,用来封装异步操作,并可以获取成功或失败的结果。
基本使用:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
const data = 'hello world'
// 成功返回的结果
resolve(data)
// 失败返回的结果
// reject(data)
}, 1000)
})
p.then(res => {
console.log(res) // 'hello world'
}, err => {
console.log(err)
})
Promise 封装读取文件
const fs = require('fs')
const p = new Promise((resolve, reject) => {
fs.readFile('./文件.md', (err, data) => {
if(err) reject(err)
resolve(data)
})
})
p.then(res => {
console.log(res.toString())
}, err => {
console.log(err)
})
Promise封装 Ajax请求
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://www.baidu.com')
xhr.send()
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
}else {
reject(xhr.status)
}
}
}
})
p.then(res => {
console.log(res)
}, err => {
console.error(err)
})
Set 集合
ES6 新的数据结构 Set(集合),虽然类似于数组,但是成员的每一个值都是唯一的。集合实现了迭代器(iterator)接口,所以可以使用扩展运算符(...)
和for...of
进行遍历。
集合的属性和方法:
- size:返回集合的元素个数
- add:增加一个新元素,返回当前集合
- delete:删除元素,返回一个Boolean值
- has:检测集合中是否包含某个元素,返回Boolean值
- clear:清空集合
const set = new Set(['red', 'blue', 'green'])
const res1 = set.has('red')
console.log(res1) // true
const r2 = set.size
console.log(r2) // 3
set.add('white')
console.log(set) // Set(4) {"red", "blue", "green", "white"}
set.delete('red')
console.log(set) // Set(3) {"blue", "green", "white"}
set.clear()
console.log(set) // Set(0) {}
Set集合的应用:
数组去重
const arr1 = [1, 2, 3, 3, 4, 2, 5]
const res1 = [...new Set(arr1)]
console.log(res1) // [1, 2, 3, 4, 5]
两个数组取交集
const arr1 = [1, 2, 3, 3, 4, 2, 5]
const arr2 = [4, 5, 6, 7, 5, 6]
const res2 = [...new Set(arr1)].filter(item => {
const s = new Set(arr2)
if(s.has(item)) {
return true
}else {
return false
}
})
console.log(res2) // [4, 5]
// 或者
const res3 = [...new Set(arr1)].filter(item => new Set(arr2).has(item))
console.log(res3) // [4, 5]
两个数组并集(数组合并)
const arr1 = [1, 2, 3, 3, 4, 2, 5]
const arr2 = [4, 5, 6, 7, 5, 6]
const res4 = [...new Set(arr1), ...new Set(arr2)]
console.log(res4) // [1, 2, 3, 4, 5, 4, 5, 6, 7]
数组差集
const arr1 = [1, 2, 3, 3, 4, 2, 5]
const arr2 = [4, 5, 6, 7, 5, 6]
const res5 = [...new Set(arr1)].filter(item => !new Set(arr2).has(item))
console.log(res5) // [1, 2, 3]
Map
ES6 新的数据结构。类似于对象,键值对的集合。但是‘键’的范围不限于字符串。各种类型的值(包括对象)都可以当做键。Map 也实现了 iterator 接口,可以使用扩展运算符(...)
和for...of
遍历。
Map 的属性和结构
- size:返回Map元素的个数
- set:增加一个新元素,返回当前 Map
- get:返回键名对象的键值
- has:检测 Map 中是否含某个元素,返回Boolean值
- clear:清空集合
使用
const map = new Map()
map.set('name', 'zhangsan')
map.set('age', 18)
map.set('say', function() {
console.log('hello')
})
console.log(map) // {"name" => "zhangsan", "age" => 18, "say" => ƒ}
console.log(map.size) // 3
const res1 = map.get('name')
console.log(res1) // '张三'
const res2 = map.has('age')
console.log(res2) // true
const res3 = map.delete('say')
console.log(res3) // true
对象(Object)方法的扩展
Object.is
判断两个值是否相等,相当于全等于(===),但是不能判断 NaN 和 (-0, 0)
console.log(Object.is(123, 123)) // true
console.log(Object.is('red', 'red')) // true
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(-0, 0)) // false
Object.assign
对象合并,后一个对象中的属性值会覆盖前一个对象上的属性值,如果前一个对象中不存在后一个对象中的某个属性,就会在对象中添加这个属性。
const data1 = {
name: 'zhangsan',
age: 15,
color: 'red'
}
const data2 = {
name: 'zhangsan',
age: 15,
color: 'blue',
height: 60
}
const data = Object.assign(data1, data2)
console.log(data) // {name: "zhangsan", age: 15, color: "blue", height: 60}
Object.getPrototypeOf 和 Object.setPrototypeOf
const obj = {
name: 'xiaobai'
}
const obj1 = {
arr: [1, 2, 3]
}
Object.setPrototypeOf(obj, obj1)
console.log(obj) // obj.__proto__.obj1 = arr
console.log(Object.getPrototypeOf(obj, obj1)) // {arr: [1, 2, 3]}
Object.values
Object.values 返回的是一个对象所有可枚举的属性值的数组
const stu = {
name: 'zhangsan',
age: 17,
height: '60kg'
}
console.log(Object.keys(stu)) // ["name", "age", "height"]
console.log(Object.values(stu)) // ["zhangsan", 17, "60kg"]
Object.entries
Object.entries 返回一个给定对象自身可遍历的属性 [key, value] 的数组,还可将对象转化成一个二维数组
const stu = {
name: 'zhangsan',
age: 17,
height: '60kg'
}
console.log(Object.entries(stu)) // [Array(2), Array(2), Array(2)]
// ["name", "zhangsan"]
Object.getOwnPrototyDescriptors
返回指定对象所有自身属性的描述对象
const stu = {
name: 'zhangsan',
age: 17,
height: '60kg'
}
console.log(Object.getOwnPropertyDescriptors(stu))
// {name: {…}, age: {…}, height: {…}}
// name: {
// configurable: true
// enumerable: true
// value: "zhangsan"
// writable: true
// }
Object.fromEntries
创建对象,接收的是一个二维数组,数组里面传键值对形式数组,可以接收 Map 数据格式
const stu1 = Object.fromEntries([
['name', 'zhangsan'],
['age', 18]
])
console.log(stu1) // {name: "zhangsan", age: 18}
const map = new Map()
map.set('name', 'lisi')
map.set('age', 17)
const stu2 = Object.fromEntries(map)
console.log(stu2) // {name: "lisi", age: 17}
数组(Array)方法扩展
includes
includes 方法用来检测数组中是否包含某个元素,返回 Boolean 值
const arr = ['red', 'blue', 'green', 'white']
console.log(arr.includes('red')) // true
console.log(arr.includes('black')) // false
flat 和 flatMap
flat 是将多维数组转换成低维数组
const arr1 = [1, 2, 3, [4, 5, 6, [7, 8]]]
console.log(arr1.flat()) // [1, 2, 3, 4, 5, 6, [7, 8]]
// 传参表示深度
console.log(arr1.flat(2)) // // [1, 2, 3, 4, 5, 6, 7, 8]
flatMap 遍历数组之后,如果返回结果是多维数组,转换成低维数组
const arr2 = [1, 2, 3, 4]
const res = arr2.flatMap(item => [item * 10])
console.log(res) // [10, 20, 30, 40]
async 与 await
async 和 await 两种语法结合,可以让异步代码看起来像同步代码。
async
- async 函数返回值为 promise 对象
- promise 对象结果由 async 函数执行的返回值决定
await
- await 函数必须写在 async 函数中
- await 右侧的表达式一般为 promise 对象
- await 返回的是 promise 的成功值
- await 的 promise 失败了,就会抛出异常,需要通过 try…catch 捕获处理
使用
const fs = require('fs')
const p1 = new Promise((resolve, reject) => {
fs.readFile('./let.js', (err, data) => {
if(err) reject(err)
resolve(data)
})
})
const p2 = new Promise((resolve, reject) => {
fs.readFile('./map.js', (err, data) => {
if(err) reject(err)
resolve(data)
})
})
async function foo() {
const res1 = await p1
const res2 = await p2
console.log(res1.toString())
console.log(res2.toString())
}
foo()
发送 Ajax 请求
function Ajax(type='GET', url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(type, url)
xhr.send()
xhr.onreadystatechange = function () {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
}else {
reject(xhr.status)
}
}
}
})
}
async function foo() {
const res1 = await Ajax('GET', 'https://www.baidu.com')
const res2 = await Ajax('GET', 'https://www.sina.com')
console.log(res1)
console.log(res2)
}
foo()
可选链操作符
?.
当对象层数比较深,使用可选链操作符,可以免去层级判断。
如果不使用可选链操作符,一般使用 &&
来连接判断
function foo(config) {
const db = config && config.db && config.db.host
console.log(db)
}
foo({
db: {
host: '127.0.0.1',
post: 3306
}
})
使用可选链操作符
function foo(config) {
const db = config ?. db ?. host
console.log(db)
}
foo({
db: {
host: '127.0.0.1',
post: 3306
}
})
xhr = new XMLHttpRequest()
xhr.open(type, url)
xhr.send()
xhr.onreadystatechange = function () {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
}else {
reject(xhr.status)
}
}
}
})
}
async function foo() {
const res1 = await Ajax(‘GET’, ‘https://www.baidu.com’)
const res2 = await Ajax(‘GET’, ‘https://www.sina.com’)
console.log(res1)
console.log(res2)
}
foo()
# 可选链操作符
`?.` 当对象层数比较深,使用可选链操作符,可以免去层级判断。
如果不使用可选链操作符,一般使用 `&&` 来连接判断
```js
function foo(config) {
const db = config && config.db && config.db.host
console.log(db)
}
foo({
db: {
host: '127.0.0.1',
post: 3306
}
})
使用可选链操作符
function foo(config) {
const db = config ?. db ?. host
console.log(db)
}
foo({
db: {
host: '127.0.0.1',
post: 3306
}
})