let/const基本使用

  • let关键字:
    • 从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量;
  • const关键字:
    • const关键字是constant的单词的缩写,表示常量、衡量的意思
    • 它表示保存的数据一旦被赋值,就不能被修改;
    • 但是如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容;
  • 注意:
    • 另外let、const不允许重复声明变量;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ES6开始
// 1.let
let message2 = "你好, 世界"
message2 = "你好, why"
message2 = 123
console.log(message2)

// 2.const
// const message3 = "nihao, shijie"
// message3 = "nihao, why" //报错

// 赋值引用类型
const info = {
name: "why",
age: 18
}
// info = {} // 报错
info.name = "kobe"
console.log(info)

let/const的块级作用域

  • 在ES6中新增了块级作用域,并且通过let、const、function、class声明的标识符是具备块级作用域的限制的:image.png
  • 但是我们会发现函数拥有块级作用域,但是外面依然是可以访问的:
    • 这是因为引擎会对函数的声明进行特殊的处理,允许像var那样进行提升;

块级作用域的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 监听按钮的点击
const btnEls = document.querySelectorAll("button")
// [btn1, btn2, btn3, btn4]
// for (var i = 0; i < btnEls.length; i++) {
// var btnEl = btnEls[i];
// // btnEl.index = i
// (function(m) {
// btnEl.onclick = function() {
// debugger
// console.log(`点击了${m}按钮`)
// }
// })(i)
// }


for (let i = 0; i < btnEls.length; i++) {
const btnEl = btnEls[i];
btnEl.onclick = function() {
console.log(`点击了${i}按钮`)
}
}
  • 对于let、const:
    • 对于let和const来说,是目前开发中推荐使用的;
    • 我们会优先推荐使用const,这样可以保证数据的安全性不会被随意的篡改;
    • 只有当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用let;
    • 这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范;

字符串模板基本使用

  • 在ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的

  • ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接:

    • 首先,我们会使用** `` 符号来编写字符串,称之为模板字符串**;
      • 其次,在模板字符串中,我们可以通过 **${expression} **来嵌入动态的内容;
  • 模板字符串还有另外一种用法:标签模板字符串

  • 如果我们使用标签模板字符串,并且在调用的时候插入其他的变量:

    • 模板字符串被拆分了
    • 第一个元素是数组,是被模块字符串拆分的字符串组合;
    • 后面的元素是一个个模块字符串传入的内容;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const name = "why"
const age = 18

// 1.基本用法
const info = `my name is ${name}, age is ${age}`
console.log(info)


// 2.标签模板字符串的用法
function foo(...args) {
console.log("参数:", args)
}

// foo("why", 18, 1.88)
foo`my name is ${name}, age is ${age}, height is ${1.88}`

image.png

函数默认参数用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 注意: 默认参数是不会对null进行处理的
function foo (arg1 = "我是默认值", arg2 = "我也是默认值") {
// 1.两种写法不严谨
// 默认值写法一:
// arg1 = arg1 ? arg1: "我是默认值"

// 默认值写法二:
// arg1 = arg1 || "我是默认值"

// 2.严谨的写法
// 三元运算符
// arg1 = (arg1 === undefined || arg1 === null) ? "我是默认值": arg1

// ES6之后新增语法: ??
arg1 = arg1 ?? "我是默认值" // 如果没有这句代码,args传入null,还是会打印null,有之后打印“我是默认值”

// 3.简便的写法: 默认参数
console.log(arg1)
}

foo(123, 321) //123
foo()//我是默认值
foo(0)//0
foo("")//
foo(false)//false
foo(null)//我是默认值
foo(undefined)//我是默认值

image.png

函数默认参数注意

1
2
3
4
5
6
7
8
9
10
// 1.注意一: 有默认参数的形参尽量写到后面
// 2.有默认参数的形参, 是不会计算在length之内(并且后面所有的参数都不会计算在length之内)
// 3.剩余参数也是放到后面(默认参数放到剩余参数的前面)
function foo(age, name = "why", ...args) {
console.log(name, age, args)
}

foo(18, "abc", "cba", "nba")//

console.log(foo.length)

image.png

函数默认参数解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 2.函数的默认值是一个对象
// function foo(obj = { name: "why", age: 18 }) {
// console.log(obj.name, obj.age)
// }

function foo ({ name, age } = { name: "why", age: 18 }) {
console.log(name, age)
}

function foo ({ name = "why", age = 18 } = {}) {
console.log(name, age)
}

foo()

函数的剩余参数

  • ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:
    • 如果最后一个参数是 … 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
  • 那么剩余参数和arguments有什么区别呢?
    • 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参;
    • arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作;
    • arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构,而rest参数是ES6中提供并且希望以此 来替代arguments的;
  • 注意:剩余参数必须放到最后一个位置,否则会报错。

函数箭头函数的补充

  • 在前面我们已经学习了箭头函数的用法,这里进行一些补充:
    • 箭头函数是没有显式原型prototype的,所以不能作为构造函数,使用new来创建对象;
    • 箭头函数也不绑定this、arguments、super参数;
1
2
3
4
5
6
7
8
9
10
11
12
// 1.function定义的函数是有两个原型的:
function foo () { }
console.log(foo.prototype) // new foo() -> f.__proto__ = foo.prototype
console.log(foo.__proto__) // -> Function.prototype

// 2.箭头函数是没有显式原型
// 在ES6之后, 定义一个类要使用class定义
var bar = () => { }
console.log(bar.__proto__ === Function.prototype) // true
// 没有显式原型
console.log(bar.prototype) //undefined
// var b = new bar() // 没有显示原型,所以用new bar()会报错

展开语法

  • 展开语法
    • 可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开;
    • 还可以在构造字面量对象时, 将对象表达式按key-value的方式展开;
  • 展开语法的场景:
    • 在函数调用时使用;
    • 在数组构造时使用;
    • 在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性;
  • 注意:展开运算符其实是一种浅拷贝;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1.基本演练
// ES6
const names = ["abc", "cba", "nba", "mba"]
const str = "Hello"

const newNames = [...names, "aaa", "bbb"]
console.log(newNames)

function foo (name1, name2, ...args) {
console.log(name1, name2, args)
}

foo(...names)
foo(...str)

// ES9(ES2018)
const obj = {
name: "why",
age: 18
}
// 不可以这样来使用
// foo(...obj) // 在函数的调用时, 用展开运算符, 将对应的展开数据, 进行迭代
// 可迭代对象: 数组/string/arguments

const info = {
...obj,
height: 1.88,
address: "广州市"
}
console.log(info)

image.png

引用赋值-浅/深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const obj = {
name: "why",
age: 18,
height: 1.88,
friend: {
name: "curry"
}
}

// 1.引用赋值
// const info1 = obj


// 2.浅拷贝
const info2 = {
...obj
}
info2.name = "kobe"
console.log(obj.name)//why
console.log(info2.name)//kobe
info2.friend.name = "james"
console.log(obj.friend.name)//james
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const obj = {
name: "why",
age: 18,
height: 1.88,
friend: {
name: "curry"
}
}

// 3.深拷贝
// 方式一: 第三方库
// 方式二: 自己实现
// function deepCopy(obj) {}
// 方式三: 利用先有的js机制, 实现深拷贝JSON
const info3 = JSON.parse(JSON.stringify(obj))
console.log(info3.friend.name)//curry
info3.friend.name = "james"
console.log("info3.friend.name:", info3.friend.name)//james
console.log(obj.friend.name)//curry

Symbol的基本使用

  • Symbol是什么呢?Symbol是ES6中新增的一个基本数据类型,翻译为符号。
  • 那么为什么需要Symbol呢?
    • 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
    • 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易 造成冲突,从而覆盖掉它内部的某个属性;
    • 比如我们前面在讲apply、call、bind实现时,我们有给其中添加一个fn属性,那么如果它内部原来已经有了fn属性了呢?
    • 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;
  • Symbol就是为了解决上面的问题,用来生成一个独一无二的值。
    • Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
    • 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;
  • Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;
  • 我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;
1
2
3
4
5
6
7
8
9
const s1 = Symbol()
// const info = { name: "why" }
const obj = {
[s1]: "aaa"
}

const s2 = Symbol()
obj[s2] = "bbb"
console.log(obj)

image.png

  • 前面我们讲Symbol的目的是为了创建一个独一无二的值,那么如果我们现在就是想创建相同的Symbol应该怎么来做呢?
    • 我们可以使用Symbol.for方法来做到这一点;
    • 并且我们可以通过Symbol.keyFor方法来获取对应的key;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const s1 = Symbol() // aaa
const s2 = Symbol() // bbb

// 1.加入对象中
const obj = {
name: "why",
age: 18,
[s1]: "aaa",
[s2]: "bbb"
}

// 2.获取symbol对应的key
console.log(Object.keys(obj))
console.log(Object.getOwnPropertySymbols(obj))
const symbolKeys = Object.getOwnPropertySymbols(obj)
for (const key of symbolKeys) {
console.log(obj[key])
}

// 3.description
// 3.1.Symbol函数直接生成的值, 都是独一无二
const s3 = Symbol("ccc")
console.log(s3.description)
const s4 = Symbol(s3.description)
console.log(s3 === s4)

// 3.2. 如果相同的key, 通过Symbol.for可以生成相同的Symbol值
const s5 = Symbol.for("ddd")
const s6 = Symbol.for("ddd")
console.log(s5 === s6)

// 获取传入的key
console.log(Symbol.keyFor(s5))

image.png