「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
大家好,我是L同学,最近在学习ES6的内容,所以对ES6中的常用知识点进行了总结。本文主要总结了字面量增强、数组和对象的解构、var/let/const对比等这些知识点。
字面量的增强
字面量的增强包括属性简写、方法简写和计算属性名。当属性名和属性值一样时,可以进行属性简写。方法简写不要写成箭头函数,因为会存在this指向问题。
var name = 'hello'
var age = 18
var obj = {
// 1. 属性的简写
name,
age,
// 2.方法的简写
foo: function () {
console.log(this); // obj这个对象
},
bar() {
console.log(this); //obj
},
baz: () => {
console.log(this); // 箭头函数不绑定this,这里的this由上层作用域决定,在node中全局中this是{},浏览器中是window
},
// 3. 计算属性名
[name + 123]: 'hahahaha'
}
obj.foo()
obj.bar()
obj.baz()
// obj[name + 234] = 'llllll'
// console.log(obj);
数组和对象的解构
数组的解构
(1)可以对数组按顺序解构。在ES6之前,我们通过数组的索引来获取值,我们需要声明新的变量去接收数组的值。
var names = ['abc','cba','nba']
/*
var item1 = names[0]
var item2 = names[1]
var item3 = names[2]
*/
// 对数组的解构 []
var [item1, item2, item3] = names
console.log(item1, item2, item3); // abc cba nba
(2)解构数组中的任意后面元素。如果我只想解构出数组中的第3个元素呢?那么以下方法可以解决。
var [, ,itemz] = names
console.log(itemz); // nba
var [, itema] = names
console.log(itema); // cba
(3)解构出一个元素,后面的元素放到一个数组中。
// 解构出一个元素,后面的元素放到一个数组中
var [itemx, ...items] = names
console.log(itemx, items); // abc [ 'cba', 'nba' ]
(4)解构赋予默认值。
var [a, b, c, d = 'dddd'] = names
console.log(b, d); // cba dddd
对象的解构
在函数中对参数进行解构是很常用的,例如在vuex中的action对context进行解构,解构出commit。
var obj = {
name: 'hello',
age: 18,
height: 1.80
}
// 对象的解构
var {name, age, height} = obj
console.log(name, age, height); // hello 18 1.8
进行重命名。
// 重命名
var {name: newName} = obj
console.log(newName); // hello
对于获取到的对象,我们需要处理某个key,但是没法判别它的值是否存在时,可以对对象进行解构同时赋予默认值。
var {address = "上海市"} = obj
console.log(address); // 上海市
var {hobby:newHobby='学习'} = obj
console.log(newHobby); // 学习
在函数中对参数进行解构是比较常用的方法。
function foo(info) {
console.log(info.name, info.age); // hello 18
}
foo(obj)
function bar({name, height}) {
console.log(name, height); // hello 1.8
}
bar(obj)
在vuex中的action解构出commit
let和const
let
let声明的成员只会在所声明的块中生效。
if(true) {
// var foo = 'foo'
let foo = 'foo'
}
console.log(foo);
在全局获取块级作用域中的foo,会报ReferenceError: foo is not defined的错误。
我们来看下for双层循环中使用var和let声明同名计数器的区别。
for(var i = 0; i < 3; i++) {
for(var i = 0; i < 3; i++) {
console.log(i);
}
console.log('内层结束 i=', +i);
}
看到运行结果,这不是我们想象当中的打印9个数字。这是因为内层循环的i不是块级作用域的成员,而是全局成员。内层循环声明的i会覆盖掉之前外层循环所声明的i。内层循环执行完了之后,i=3,外层拿到的i仍是全局的i,所以外层的i为3,不满足循环条件,所以不会继续循环。
我们把内层循环声明变量的var改成let,来看下循环结果。
for(var i = 0; i < 3; i++) {
for(let i = 0; i < 3; i++) {
console.log(i);
}
console.log('内层结束 i=', +i);
}
我们可以看到外层循环了3次。这是因为内层循环的i是内部的块级作用域的成员,它不会影响外部。
let的应用场景
在循环绑定事件中,let能在事件处理函数中获取正确的索引。
如果我们使用var会怎么样呢?
var elements = [{}, {}, {}]
for(var i = 0; i < elements.length; i++) {
elements[i].onclick = function() {
console.log(i);
}
}
elements[1].onclick()
elements[2].onclick()
我们可以看到无论是第几个元素触发了点击事件,都会打印3。这是因为i是全局作用域的i,在循环完成过后,i已经被累加到了3。
内存图分析
那么可以应用闭包来解决这个问题,这个也是闭包的典型应用场景。
建立了闭包,其实闭包借助函数作用域去摆脱全局作用域所产生的影响。
var elements = [{}, {}, {}]
for(var i = 0; i < elements.length; i++) {
elements[i].onclick = (function(i) {
return function() {
console.log(i);
}
})(i)
}
elements[0].onclick()
elements[1].onclick()
或者这样
var elements = [{}, {}, {}]
for(var i = 0; i < elements.length; i++) {
(function(i) {
elements[i].onclick = function() {
console.log(i);
}
})(i)
}
elements[0].onclick()
elements[1].onclick()
内存地址图
我们还可以通过添加自定义属性来解决问题。
for(var i = 0; i < elements.length; i++) {
elements[i].myIndex = i
elements[i].onclick = function() {
console.log(`当前索引为${this.myIndex}`);
}
}
内存图
我们可以利用let来解决这个问题。在for循环体内形成了块级作用域,i只能在块级作用域内被访问。其实内部也是闭包的机制,onclick在执行的时候循环早就结束了,实际的i早就已经销毁了,就是因为闭包的机制我们才可以拿到原本执行循环的那个i所对应的值。
var elements = [{}, {}, {}]
for(let i = 0; i < elements.length; i++) {
elements[i].onclick = function() {
console.log(i);
}
}
elements[0].onclick()
elements[1].onclick()
我们来看以下代码,让人疑惑的是在循环体内使用跟计数器一样的变量会产生冲突吗?
for(let i = 0; i < 3; i++) {
let i = 'foo'
console.log(i);
}
我们先来看打印结果。
我们来解构上述代码。
我们可以知道这两个i是互不影响的,因为它们不在同一个作用域。
// 外部循环的块里面所产生的局部变量
let i = 0
if(i < 3) {
// if块级作用域内部的局部变量
let i = 'foo'
console.log(i);
}
i++
if(i < 3) {
let i = 'foo'
console.log(i);
}
i++
if(i < 3) {
let i = 'foo'
console.log(i);
}
i++
我们还可以通过事件委托的方式来解决循环添加事件的问题。
<body>
<button index="1">按钮1</button>
<button index="2">按钮2</button>
<button index="3">按钮3</button>
<script src="./06-add-event-loop.js">
</script>
</body>
document.body.onclick = function(e) {
var target = e.target
targetDom = target.tagName
// console.log(targetDom);
if(targetDom === 'BUTTON') {
var index = target.getAttribute('index')
console.log(`当前点击的是第${index}个`);
}
}
const
const 声明的是常量,保存的数据一旦被赋值,就不能被修改。
const name = 'abc'
name = 'cba'
以上代码,会报TypeError: Assignment to constant variable.的错误。 常量要求声明同时赋值。
const name
name = 'haha'
const声明的成员不能被修改,只是说我们不允许在声明了过后重新指向新的内存地址,并不是说不允许我们修改常量中的属性成员。常量只是要求内层指向不允许被修改,对于数据成员的修改是没有问题的。
注意:如果赋值的是引用类型(内存地址),那么可以通过引用找到对应的对象,修改对象中的内容。
const obj = {
name: 'hello'
}
obj = {}
以上代码,同样会报TypeError: Assignment to constant variable.的错误。因为obj保存的是引用地址,通过重新赋值空对象,修改了保存的引用地址。
const可以修改对象中的属性。
const obj = {
name: 'hello'
}
obj.name = 'world'
console.log(obj.name); // world
let和const不允许重复声明变量
let和const不允许重复声明变量,但是var允许重复声明变量。
var foo = 'abc'
var foo = 'bca'
let foo = 'abc'
let foo = 'cba'
let/const重复声明变量会报SyntaxError: Identifier 'foo' has already been declared这样的错误。
let和const没有作用域提升
作用域提升: 在声明变量的作用域中,如果这个变量能在声明之前被访问,那么我们可以称之为作用域提升。 var中是存在作用域提升的,但是let和const没有作用域提升。
console.log(foo); // undefined
var foo = 'foo'
let和const没有作用域提升,但是会在解析阶段被创建出来。
console.log(foo); // 报错 ReferenceError: Cannot access 'foo' before initialization
let foo = 'foo'
在ES6中新增了块级作用域,并且对let、const、function、class声明的类型是有效的。
{
let foo = 'foo'
function demo() {
console.log('demo');
}
class Person {}
}
我们可以看到在外层依然可以调用函数,是因为大部分浏览器为了兼容以前的代码,让function没有块级作用域。
// console.log(foo); // ReferenceError: foo is not defined
demo() // demo
let p = new Person() // ReferenceError: Person is not defined
if(true) {
var foo = 'foo'
let bar = 'bar'
}
console.log(foo); // foo
console.log(bar); // ReferenceError: bar is not defined
var color = 'red'
switch(color) {
case 'red':
var foo = 'foo'
let bar = 'bar'
}
console.log(foo); // foo
console.log(bar); // ReferenceError: bar is not defined
for(var i = 0; i < 5; i++) {
console.log(i); // 0 1 2 3 4
}
console.log(i); // 5
for(let i = 0; i < 5; i++) {
console.log(i); // 0 1 2 3 4
}
console.log(i); // ReferenceError: i is not defined
块级作用域的应用
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
使用var,无论点击哪个按钮都会打印出第3个按钮被点击.
let btns = document.getElementsByTagName('button')
for(var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
console.log('第' + i + '个按钮被点击');
}
}
console.log(i);
使用let会形成块级作用域,点击哪个按钮会打印哪个按钮被点击了。
for(let i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
console.log('第' + i + '个按钮被点击');
}
}
此时在外层访问i,是访问不到的。