let const
作用域问题
- 内层变量可能会覆盖外层变量
var tmp = new Date()
function f() {
console.log(tmp) // undefined
if(false) {
var tmp = 'iu' // 变量声明提升
}
}
f()
- 用于计数的循环变量
i泄露为全局变量
var str = 'hello'
for(var i = 0;i < str.length;i ++) {
console.log(str[i])
}
console.log(i) // 5
a.经典错误
var a = []
for(var i = 0;i < 3;i ++) {
a[i] = function() {
console.log(i)
}
}
a[0]() // 3
传统闭包解决
var a = []
for(var i = 0;i < 3;i ++) {
(function(j) { // 利用了函数作用域的特点
a[j] = function() {
console.log(j)
}
}(i))
}
let解决
let a = []
for(let i = 0;i < 3;i ++) {
a[i] = function() {
console.log(i)
}
}
b.经典错误
<button>1</button>
<button>2</button>
<button>3</button>
<script>
var btns = document.getElementsByTagName('button')
for(var i = 0;i < btns.length;i ++) {
// btns[i].onclick = function() {
// console.log(i)
// }
btns[i].addEventListener('click', function() {
console.log(i) // 不管点击哪个按钮,始终打印3
})
}
</script>
传统闭包解决
<button>1</button>
<button>2</button>
<button>3</button>
<script>
var btns = document.getElementsByTagName('button')
for(var i = 0;i < btns.length;i ++) {
// (function(j) {
// btns[j].onclick = function() {
// console.log(j)
// }
// })(i)
(function(j) {
btns[j].addEventListener('click', function() {
console.log(j)
})
}(i))
}
</script>
let解决
<button>1</button>
<button>2</button>
<button>3</button>
<script>
let btns = document.getElementsByTagName('button')
for(let i = 0;i < btns.length;i ++) {
// btns[i].onclick = function() {
// console.log(i + 1)
// }
btns[i].addEventListener('click', function() {
console.log(i + 1)
})
}
</script>
const
- 声明一个只读(仅是内存地址不能修改)的常量
- 声明和赋值同时进行
- 同一作用域中不可重复声明同一变量
const obj = {
name: 'sxw',
age: 18
}
obj = {} // 报错,修改了内存地址
obj.name = 'iu' // 成功
块级作用域的特点
不存在变量声明提升
TDZ暂时性死区
如果区块中存在let或const命令,此区块对这些命令所声明的变量从一开始就会形成封闭的块级作用域,凡在声明之前使用变量,立即报错。
if(true) {
// TDZ开始
tmp = 'a' // 报错
console.log(tmp) // 报错
// TDZ结束,在这行代码之前,都属于暂时性死区
let tmp
console.log(tmp) // undefined
tmp = 1
console.log(tmp) // 1
}
typeof x // 报错
let x
不允许重复声明同一变量
声明变量的方式总结:
ES5提供了var、functionES6提供了let、const、import、class
解构赋值
数组的解构赋值
只要数据结构具备Iterator接口,都可以采用数组形式的解构赋值。
let [a,b] = [1,2]
let [,b] = [1,2]
let x = 1;
let y = 2;
[x,y] = [y,x] // 互换两个变量的值
默认值形式
当且仅当数组成员严格等于undefined时,生效。
let [x=1] = [undefined]
x // 1
对象的解构赋值
function fn(value) {
console.log(value) // {name: 'iu',age: 18}
console.log(name,age) // iu 18
}
let obj = {
name: 'iu',
age: 18
}
fn({name,age}=obj)
// 对于对象的属性没有次序之分
let {bar,foo} = {foo: 'a',bar: 'b'}
foo // 'a'
bar // 'b'
默认值形式
var {x=3} = {}
x // 3
var {x: y=3} = {x: 5}
y // 5
字符串的解构赋值
字符串被转成了类似数组的对象,可用于解构赋值。
const [a,b,c] = 'sxw'
a // 's'
b // 'x'
c // 'w'
let {length: len} = 'hello'
len // 5
数值和布尔值的解构赋值
- 当等号右边不是对象或数组时,会先转成对象形式
undefined和null无法转为对象,对其进行解构赋值会报错
函数参数问题
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', '') // Hello World,出问题了,我们明明给y赋值为空字符串了,但是空字符串转为布尔值是false,所以此赋值并没有起到作用
// 优化
function log(x, y) {
// 先判断参数y是否被赋值,如果没有,再走默认值
if(typeof y === 'undefined') {
y = 'world'
}
console.log(x, y);
}
函数参数默认值
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // 模式不匹配
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
函数参数设定默认值的两种方式:
// 1.函数参数的默认值是空对象,但是设置了对象解构赋值的默认值
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 2.函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
// x和y都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x有值,y无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x和y都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 报错
f(undefined, 1) // [1, 1]
function fn({start,end}) {
console.log(start,end)
}
fn({start: 1,end: 2})
// {start,end} = {start: 1,end: 2}
// 函数参数解构赋值给默认值
function fn({start=1,end=2}) {
console.log(start,end)
}
fn({}) // 实参严格等于undefined,走默认值
// {start=1,end=2} = {}
function fn({start=1,end=2}) {
console.log(start,end)
}
fn() // 实参什么都不传 => {start=1,end=2} = undefined,导致了左右模式不匹配,报错
// {start=1,end=2} = undefined
// 参数的默认赋值
function fn({start=1,end=2} = {}) {
console.log(start,end)
}
fn() // 实参什么都不传,为undefined,即 undefined = {} ,走空对象,空对象的话又走解构赋值的默认值
// {start=1,end=2} = {},因为实参什么都不传,默认为undefined,走默认值 {},空对象再走解构赋值默认值
剩余运算符
把散列的元素变成一个集合(数组或对象),在函数或解构中使用。
function add() {
console.log(arguments) // 类数组,由元素1和2组成
let arr = Array.prototype.slice.call(arguments) // 将类数组转为数组
console.log(arr) // 数组[1,2]
}
add(1,2)
function add(...arr) {
// ...arr的作用是将散列的元素拼接成一个数组
console.log(arr) // 数组[1,2]
}
add(1,2)
let [a, ...b] = [1,2,3]
a // 1
b // [2,3]
扩展运算符
function fn(a,b,c) {
console.log(a,b,c) // 1,2,3
}
fn(...[1,2,3]) // 将数组变成散元素
- 从函数中返回多个值
// 返回数组
function example() {
return [1,2,3]
}
let [a,b,c] = example()
// 返回对象
function example() {
return {
foo: 1,
bar: 2
}
}
let {foo,bar} = example()
- 函数参数的定义
// 参数是一组有次序的值
function f([x,y,z]) {
...
}
f([1,2,3])
- 提取
json数据
let jsonData = {
id: 3,
status: 'ok',
data: [1,2,3]
}
let {id,status,data: arr} = jsonData
- 函数参数的默认值
var foo = config.foo || 'default foo' // 避免这种形式的写法
- 遍历
map结构
const map = new Map()
map.set('name','sxw')
map.set('age','18')
for(let [key,value] of map) {
console.log(key + 'is' + value)
}
// name is sxw
// age is 18
// 只获取键名
for(let [key] of map) {
// ...
}
// 只获取键值
for(let [,value] of map) {
// ...
}
- 输入模块的指定方法
// 加载模块时,需要指定输入哪些方法
const {SourceMapConsumer, SourceNode} = require('source=map')