ES6常用知识点总结(一)

385 阅读3分钟

「这是我参与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

image.png

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);
}

image.png 看到运行结果,这不是我们想象当中的打印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是内部的块级作用域的成员,它不会影响外部。

image.png

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。

image.png 内存图分析 image.png 那么可以应用闭包来解决这个问题,这个也是闭包的典型应用场景。

建立了闭包,其实闭包借助函数作用域去摆脱全局作用域所产生的影响。

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()

image.png 内存地址图

image.png 我们还可以通过添加自定义属性来解决问题。

for(var i = 0; i < elements.length; i++) {
  elements[i].myIndex = i
  elements[i].onclick = function() {
    console.log(`当前索引为${this.myIndex}`);
  }
}

内存图

image.png 我们可以利用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()

image.png 我们来看以下代码,让人疑惑的是在循环体内使用跟计数器一样的变量会产生冲突吗?

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

我们先来看打印结果。

image.png 我们来解构上述代码。

我们可以知道这两个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'

image.png 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);

image.png

使用let会形成块级作用域,点击哪个按钮会打印哪个按钮被点击了。

for(let i = 0; i < btns.length; i++) {
  btns[i].onclick = function() {
    console.log('第' + i + '个按钮被点击');
  }
}

image.png 此时在外层访问i,是访问不到的。