一、let const var区别
var
在ES5中,顶层对象的属性和全局变量是等价的,用var生命的变量既是全局变量,也是顶层变量,在浏览器中顶层对象就是window对象,在node环境中就是global对象
var a = 10
console.log(window.a) //10
使用var声明的变量存在变量提升的情况
console.log(a) //undefined
var a = 20
在编译阶段,编译器会将其转变成以下执行
var a
console.log(a)
a = 20
使用var可以对一个变量多次声明,后面的声明会覆盖前面的变量声明
var a = 20
var a = 30
console.log(a) // 30
在函数中使用var声明的变量是局部的,如果在函数中不使用var是声明变量,该变量就是全局的
var a=20
function change(){
var a=30
}
change()
console.log(a) //20
var a=20
function change(){
a = 30
}
change()
console.log(a) //30
let
let 是ES6新增的指令,用来声明变量的,所声明的变量只有在let命令所在的代码块内有效
{
let a=20
}
console.log(a) // a is not defined
let 声明的变量不存在变量提升
console.log(a)
let a = 20 //Cannot access 'a' before initialization
只要块级作用域内存在let命令,这个区域就不再受外部的影响
var a = 20
if(true){
a = 30 //Cannot access 'a' before initialization
let a
}
使用let声明变量前,该变量都不可用,这就是经常说的''暂时性死区''
let不允许在相同的作用域内重复声明
let a = 20
let a = 30
//Identifier 'a' has already been declared
因此,我们不能在函数内部重复声明变量
function func(arg){
let arg
}
func()
const
const 声明一个只读的常量,一旦声明了,就不能改变其值
const a = 1
a = 3
//Assignment to constant variable.
这意味着const 一旦声明了变量,就必须初始化,不能留到以后赋值
const a
// Missing initializer in const declaration
如果用var或者let声明的变量,再用const声明会报错
var a = 20
let b = 30
const a = 30
const b = 80
// Identifier 'a' has already been declared
const实际上不是声明的变量的值不能改变,而是变量指向的内存地址所保存的那个那个数据不能改变,对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等于常量 对于复杂类型的数据,变量指向的是内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构是固定的
const foo = {}
foo.a = '成功'
foo = {} // foo指向另一个内存地址就会报错
//Assignment to constant variable.
三者区别
var const let 三者可以可以围绕下面几点展开
- 变量提升
- 暂时性死区
- 块级作用域
- 重复声明
- 修改声明的变量
- 使用
变量提升
var存在变量提升,const和let是不存在变量提升的
// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access 'b' before initialization
let b = 10
// const
console.log(c) // Cannot access 'c' before initialization
const c = 10
暂时性死区
var 不存在暂时性死区;let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用改变量
// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access 'b' before initialization
let b = 10
// const
console.log(c) // Cannot access 'c' before initialization
const c = 10
块级作用域
var不存在块级作用域,let和const存在块级作用域
// var
{
var a = 20
}
console.log(a) // 20
// let
{
let b = 20
}
console.log(b) // Uncaught ReferenceError: b is not defined
// const
{
const c = 20
}
console.log(c) // Uncaught ReferenceError: c is not defined
重复声明
var允许重复声明,let和const在同一个作用域中不允许重复声明
// var
var a = 10
var a = 20 // 20
// let
let b = 10
let b = 20 // Identifier 'b' has already been declared
// const
const c = 10
const c = 20 // Identifier 'c' has already been declared
修改声明的变量
var和let可以修改 const只能声明常量,值不能改变
// var
var a = 10
a = 20
console.log(a) // 20
//let
let b = 10
b = 20
console.log(b) // 20
// const
const c = 10
c = 20
console.log(c) // Uncaught TypeError: Assignment to constant variable
总结:我们在使用的时候,能用const尽量使用const,其他情况下尽量使用let,避免使用var
二、Set和Map数据结构
set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构
那什么叫集合,什么叫字典呢?
- 集合
是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合
- 字典 是一些元素的集合,每个元素有一个称作key的域,不同元素的key各不相同
相同点:集合和字典都可以存储不重复的值
不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储元素
Set
Set是es6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合
Set本身是一个构造函数,用来生成Set数据结构
const s = new Set()
增删改查
Set的实例关于增删改查的方法
- add()
- delete()
- has()
- clear()
add()
添加某个值,返回Set结构本身,当添加实例中已经存在的元素,set不会进行添加处理
s.add(1).add(2).add(2) // 2只被添加了一次
delete()
删除某个值,返回一个布尔值,表示删除成功
s.delete(1)
has()
返回一个布尔值,判断该值是否为Set的成员
s.has(2)
clear()
清除所有成员,没有返回值
s.clear()
遍历
Set实例遍历的方法有如下:
- keys(): 返回键名的遍历器
- values: 返回键值的遍历器
- entries: 返回键值对的遍历器
- forEach(): 使用回调函数遍历每个成员
Set的遍历顺序就是插入顺序keys方法、values方法、entries方法返回的都是遍历器对象
let set = new Set(['red','green','blue'])
for(let item of set.keys()){
console.log(item)
}
// red
// green
// blue
for(let item of set.values()){
console.log(item)
}
// red
// green
// blue
for(let item of set.entries()){
console.log(item)
}
// ['red', 'red']
// ['green', 'green']
// ['blue', 'blue']
forEach()用于对每个成员执行某种操作,没有返回值,键值、键名都相等,同样的forEach方法有第二个参数,用于绑定处理函数的this
let set = new Set([1,4,9])
set.forEach((value, key) => console.log(key + ':' + value))
// 1:1
// 4:4
// 9:9
扩展运算符和set结构相结合实现数组或字符串去重
// 数组
let arr = [3,5,2,2,5,5]
let unique = [...new Set(arr)]
// [3,5,2]
// 字符串
let str = '352255'
let unique = [...new Set(str)].join('')
// '352'
实现并集、交集、和差集
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,}
//差集
let intersect = new Set([...a].filter(x => !b.has(x)))
// Set {1}
Map
Map类型是键值对的有序数列表,而键和值都可以是任意类型
Map本身是一个构造函数,用来生成Map的数据结构
增删改查
Map结构的实例针对增删改查有以下属性和操作方法
- size属性
- set()
- get()
- has()
- delete()
- clear()
size
size 属性返回Map结构的成员总数
const map = new Map()
map.set('foo', true)
map.set('bar', false)
map.size //2
set()
设置键名key对应的键值为value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就生成该键,同时返回的是当前Map对象,可采用链式写法
const m = new Map()
m.set('string', '6')
m.set(262, 'standard')
m.set(undefined, 'nah')
m.set(1, 'a').set(2, 'b').set(3, 'c')
get()
get方法读取key对应的键值,如果找不到key,返回undefined
const m = new Map()
const hello = function(){console.log('hello')}
m.set(hello, 'Hello ES6')
m.get(hello) // Hello ES6
has()
has方法返回一个布尔值,表示某个键是否在当前Map对象之中
const m = new Map()
m.set('string', '6')
m.set(262, 'standard')
m.set(undefined, 'nah')
m.set(1, 'a').set(2, 'b').set(3, 'c')
m.has('string') // true
m.has('ss') // false
m.has('undefined') // false
delete()
delete方法删除某个键,返回true,如果删除失败,返回false
const m = new Map()
m.set(undefined, 'nah')
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
clear()
clear方法清除所有成员,没有返回值
let map = new Map()
map.set('foo', true)
map.set('bar', false)
map.size() // 2
map.clear()
map.size() // 0
遍历
Map结构原生提供三个遍历器生成函数和一个遍历方法
- keys(): 返回键名的遍历器
- values: 返回键值的遍历器
- entries(): 返回所有成员的遍历器
- forEach(): 遍历Map的所有成员
遍历的顺序就是插入的顺序
const map = new Map([['F', 'no'], ['T', 'yes']])
for(let key of map.keys()){
console.log(key)
}
// "F"
// "T"
for(let key of map.values()){
console.log(key)
}
// "no"
// "yes"
for(let key of map.entries()){
console.log(key)
}
// "F" "no"
// "T" "yes"
或者
for(let [key, value] of map.entries()){
console.log(key, value)
}
// "F" "no"
// "T" "yes"
map.forEach(function(value, key, map){
console.log(value)
console.log(key)
})
WeakSet 和 WeakMap
WeakSet
创建WeakSet实例
const ws = new WeakSet()
WeakSet可以接受一个具有Iterable接口的对象作为参数
const a = [[1,2], [3,4]]
const ws = new WeakSet(a)
// WeakSet {[1,2], [3,4]}
在API中WeakSet与Set有两个区别
- 没有遍历操作的API
- 没有size属性
WeakSet成员只能是引用类型,而不能是其他类型的值
let ws = new WeakSet()
// 成员不是引用类型
let ws = new WeakSet([2,3])
console.log(ws) // 报错Invalid value used in weak set
// 成员为引用类型
let obj1 = {name: 1}
let obj2 = {name: 1 }
let ws = new WeakSet([obj1, obj2])
consle.log(ws) // WeakSet {{…}, {…}}
WeakSet里面的引用只要在外部消失,它在WeakSet里面的引用就会自动消失
WeakMap
WeakMap结构和Map结构类似,也是用于生成键值对的集合
在API中WeakMap与Map有两个区别
- 没有遍历操作的API
- 没有clear清空的方法
// WeakMap可以使用set方法添加成员
const wm1 = new WeakMap()
const key = {foo: 1}
wm1.set(key, 2)
wm1.get(key) // 2
// WeakMap 也可以接受一个数组
// 作为构造函数的参数
const k1 = [1,2,3]
const k1 = [4,5,6]
const wm1 = new WeakMap([[k1, 1], [k2, 2]])
wm1.get(k2) //2
WeakMap 只接受对象作为键名(null除外),不接受其他类型的值作为键名
const map = new WeakMap()
map.set(1,2) // Invalid value used as weak map key
map.set(Symbol(), 2)// Invalid value used as weak map key
map.set(null, 2)// Invalid value used as weak map key
WeakMap的键名所指向的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不用手动删除引用
举个场景的例子:
在网页的DOM元素上添加数据,就可以使用WeakMap结构,当该DOM元素被清除,其所对应的WeakMap记录就会自动被移除
const wm = new WeakMap()
const element = document.getElementById('example')
wm.set(element, 'some information')
wm.get(element) // 'some information'
注意:WeakMap弱引用的只是键名,而不是键值。键值依然是正常的引用,下面的代码中,键值obj会在WeakMap产生新的引用,当你修改obj不会影响到内部
const wm = new WeakMap()
let key = {}
let obj = {foo: 1}
wm.set(key, obj)
obj = null;
console.log(wm.get(key)) // Object {foo: 1}