文章目录
1. 重点提炼
- 作用域
- let 与 var、const的使用与区别
- 解构赋值的使用 => 数组、对象、字符串等
2. 作用域
对象 | 类型 |
---|---|
global/window | 全局作用域 |
function | 函数作用域(局部作用域) |
{} | 块状作用域 |
this | 动态作用域 |
如果一个 变量 或者其他表达式不在 “当前的作用域”,那么JavaScript机制会继续沿着作用域链上查找直到全局作用域(global或浏览器中的window)如果找不到将不可被使用。
2.1 全局作用域
变量在函数或者代码块 {}
外定义,即为全局作用域。不过,在函数或者代码块 {}
内未定义的变量也是拥有全局作用域的(不推荐)。
var course = "es"
// 此处可调用 course 变量
function myFunction() {
// 函数内可调用 course 变量
}
上述代码中变量 course
就是在函数外定义的,它是拥有全局作用域的。这个变量可以在任意地方被读取或者修改,当然如果变量在函数内没有声明(没有使用 var
关键字),该变量依然为全局变量。
// 此处可调用 course 变量
function myFunction() {
course = "es"
// 此处可调用 course 变量
}
以上实例中 course
在函数内,但是拥有全局作用域,它将作为 global
或者 window
的属性存在。
注意
在函数内部或代码块中没有定义的变量实际上是作为
window/global
的属性存在,而不是全局变量。换句话说没有使用var
定义的变量虽然拥有全局作用域,但是它是可以被delete
(只能删除属性)的,而全局变量不可以。
2.2 函数作用域
在函数内部定义的变量,就是局部作用域。函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!
function bar() {
var testValue = 'inner'
}
console.log(testValue) // 报错:ReferenceError: testValue is not defined
如果想读取函数内的变量,必须借助 return 或者闭包。
function bar(value) {
var testValue = 'inner'
return testValue + value
}
console.log(bar('fun')) // "innerfun"
这是借助 return 的方式,下面是闭包的方式:
function bar(value) {
var testValue = 'inner'
var rusult = testValue + value
function innser() {
return rusult
}
return innser()
}
console.log(bar('fun')) // "innerfun"
通俗的讲,return
是函数对外交流的出口,而 return
可以返回的是函数,根据作用域的规则,函数内部的子函数是可以获取函数作用域内的变量的。
说到这其实大家会想到嵌套函数的作用域问题,如果 inner 函数再嵌套函数呢?这就涉及到另一个概念:作用域链。
仔细观察上图,其实不难理解作用域链是什么,因为你可以按照原型链那样去理解。任何一个作用域链都是一个堆栈,首先先把全局作用域压入栈底,再按照函数的嵌套关系一次压入堆栈。在执行的时候就按照这个作用域链寻找变量。
2.3 块状作用域
块状作用域是很熟悉的概念,但是在JavaScript
中不被支持,就像上述知识一样,除了全局作用域就是函数作用域,一直没有自己的块状作用域。在 ES6
中已经改变了这个现象,块状作用域得到普及。关于什么是块,只要认识 {}
就可以了。
if (true) {
let a = 1
console.log(a)
}
在这个代码中, if 后 {}
就是“块”,这个里面的变量就是拥有这个块状作用域,按照规则, {}
之外是无法访问这个变量的。
2.4 动态作用域
在 JavaScript
中很多伙伴对 this
的指向时而清楚时而模糊,其实结合作用域会对 this
有一个清晰的理解。不妨先来看下这段代码:
window.a = 3
function test() {
console.log(this.a)
}
test.bind({
a: 2
})() // 2
test() // 3
在这里 bind
已经把作用域的范围进行了修改指向了 { a: 2 }
,而 this
指向的是当前作用域对象,是不是可以清楚的理解了呢?
接下来我们再思考另一个问题:作用域是在代码编写的时候就已经决定了呢,还是在代码执行的过程中才决定的?
var course = " es"
// 此处可调用 course 变量
function myFunction() {
// 函数内可调用 course 变量
}
在看看这段代码,写代码的时候就知道 course
就是全局作用域,函数内部的用 var
定义的变量就是函数作用域。这个也就是专业术语:词法作用域
。 通俗的讲变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域
。 相反,只能在执行阶段才能决定变量的作用域,那就是动态作用域
。
3. 新的声明方式:let
- 不属于顶层对象window
- 不允许重复声明
- 不存在变量提升
- 暂时性死区
- 块级作用域
3.1 代码演示
首先安装xiaodi
写的es
脚手架 =>
npm i xd-es-cli -g
xd-es-cli init
npm i
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.40
Branch: branch02commit description:a0.40(es脚手架搭建环境)
tag:a0.40
写var
和不写var
两者作用是不一样的,用var
声明一个变量,相当于在当前作用域内声明了一个变量。
不写var
,实际是对属性进行赋值 => 相当于在window
对象上定义了一个全局属性b
。
这就意味着,你不可以通过window. 变量名
的方式访问这些变量,而 var
声明的全局变量是 window
的属性,是可以通过 window. 变量名
的方式访问的。
es-demo\static\1-1.js
// var -> variable
var a = 5
console.log(a)
b = 6
console.log(b)
es-demo\src\index.html
<script src="static/1-1.js"></script>
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.41
Branch: branch02commit description:a0.41(var的特性)
tag:a0.41
delete
关键字作用是删除某个对象下的属性,不能删除变量。
delete
=> 看能否删除a
、b
,就能分清楚谁是对象?谁是属性了?
// var -> variable
var a = 5
console.log(a)
delete a
console.log(a)
b = 6
console.log(b)
delete b
console.log(b)
报错了 => b
没有定义。说明b
已经被删掉了,所以是获取不到b
的。但是删除了a
,仍然还可以输出a
,证明a
才是变量,b
是属性。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.42
Branch: branch02commit description:a0.42(delete验证是否为属性或为变量)
tag:a0.42
// var -> variable
var a = 5
console.log(a)
console.log(window.a)
b = 6
console.log(b)
console.log(window.b)
我们发现都可以输出window
下的a
、b
属性,这是神马情况?
变量不是只是一个变量吗?并不应该在window
下面啊? =>
js
设计初期的败笔导致的,它把顶层对象的属性和全局变量进行了挂钩,这是js
语言最大败笔设计之一。
如果定义了很多全局变量都会挂在window
下,这就污染了全局变量。 => let
解决这个问题
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.43
Branch: branch02commit description:a0.43(js的设计败笔之一——污染全局变量)
tag:a0.43
let a = 5
console.log(a)
console.log(window.a)
let
弥补了var
定义的全局变量与顶层对象挂钩的问题。
即 let定义的变量 => 不属于顶层对象window
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.44
Branch: branch02commit description:a0.44(let 弥补了var定义的全局变量与顶层对象挂钩的问题)
tag:a0.44
var
定义的变量是允许变量名相同的 => 后定义的变量会覆盖掉之前定义的变量
var a = 5
var a = 6
console.log(a)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.45
Branch: branch02commit description:a0.45(var 定义的变量是允许变量名相同的)
tag:a0.45
let
=> 不允许重复声明 => 会报错
let a = 5
let a = 6
console.log(a)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.46
Branch: branch02commit description:a0.46(let => 不允许重复声明 )
tag:a0.46
var
=> 变量提升
console.log(a)
var a = 5
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.47
Branch: branch02commit description:a0.47(var => 变量提升)
tag:a0.47
打印一个没有定义变量,实际应该报错的
console.log(a)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.48
Branch: branch02commit description:a0.48(打印一个没有定义变量,实际应该报错的)
tag:a0.48
var
=> 变量提升
以上代码相当于 =>
var a
console.log(a)
a = 5
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.49
Branch: branch02commit description:a0.49(var => 变量提升原理)
tag:a0.49
let
=> 不存在变量提升
console.log(a)
let a = 5
报错 => 没有声明的变量。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.50
Branch: branch02commit description:a0.50(let => 不存在变量提升)
tag:a0.50
var
=> 不存在暂时性死区
var a = 5
if (true) {
a = 6
var a
}
没报任何错
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.51
Branch: branch02commit description:a0.51(var => 不存在暂时性死区)
tag:a0.51
let
=> 暂时性死区
var a = 5
if (true) {
a = 6
let a
}
if
后的大括号实际是一个封闭作用域,在这个封闭作用域内凡是在声明之前使用这个变量,则会报错。
上面代码中,存在全局变量 a
,但是块级作用域内 let
又声明了一个局部变量 a
,导致后者绑定这个块级作用域,所以在let声明变量前,对 a
赋值会报错。
ES6 明确规定,如果区块中存在 let
和 const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用 let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.52
Branch: branch02commit description:a0.52(let => 暂时性死区)
tag:a0.52
但是有些暂时性死区是比较隐蔽的
function foo(a = b, b = 2){
console.log(a, b)
}
foo()
等号实际就是赋值语句,首先b
赋值给a
,此时b
没有值,代码是从左往右执行的。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.53
Branch: branch02commit description:a0.53(有些暂时性死区是比较隐蔽的)
tag:a0.53
function foo(a = 2, b = a){
console.log(a, b)
}
foo()
如果a
已经有值,赋给b就不报错了。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.54
Branch: branch02commit description:a0.54(如果a已经有值,覆盖b就不报错了)
tag:a0.54
var
=> 不存在块级作用域,在es5
中只有全局作用域和函数作用域
for (var i = 0; i < 3; i++) {
console.log('循环内:' + i)
}
console.log('循环外:' + i)
因为var
定义的变量并没有块级作用域,因此在循环外依然可以获取到i
的值。实际上这种规则对于变量非常不安全,本来就只是想在括号内使用,结果外部也可使用。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.55
Branch: branch02commit description:a0.55(var => 不存在块级作用域)
tag:a0.55
let
=> 存在块级作用域
for (let i = 0; i < 3; i++) {
console.log('循环内:' + i)
}
console.log('循环外:' + i)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.56
Branch: branch02commit description:a0.56(let => 存在块级作用域)
tag:a0.56
再看一个块级作用域例子。
if (false) {
var a = 5
}
console.log(a)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.57
Branch: branch02commit description:a0.57(var => 块级作用域例子)
tag:a0.57
if (false) {
let a = 5
}
console.log(a)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.58
Branch: branch02commit description:a0.58(let => 块级作用域例子)
tag:a0.58
if (true) var a =5;
不报错。
if (true) let a =5;
let
报错
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.59
Branch: branch02commit description:a0.59(let => if块级作用报错)
tag:a0.59
if (true) {
let a = 5
}
不报错了,因此es6
要求if
必须带大括号,否则报错。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.60
Branch: branch02commit description:a0.60(es6要求if必须带大括号)
tag:a0.60
3.2 笔试题
for (var i = 0; i < 3; i++) {
setTimeout(function(){
console.log(i)
})
}
三个3,因为异步原因
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.61
Branch: branch02commit description:a0.61(异步笔试题)
tag:a0.61
以上希望得到0、1、2
的输出 => 闭包
有一个外部函数,并且有一个内部函数,内部函数会调用外部函数的变量,这样就可以保证外部函数的变量不被释放。
for (var i = 0; i < 3; i++) {
(function(j){
setTimeout(function(){
console.log(j)
})
})(i)
}
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.62
Branch: branch02commit description:a0.62(异步笔试题 => 正常输出利用闭包)
tag:a0.62
以上也可不利用闭包,利用let
解决 =>
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i)
})
}
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.63
Branch: branch02commit description:a0.63(异步笔试题 => 正常输出利用let)
tag:a0.63
利用babel
转译看看 => 还是用闭包解决的
"use strict";
var _loop = function _loop(i) {
setTimeout(function () {
console.log(i);
});
};
for (var i = 0; i < 3; i++) {
_loop(i);
}
4. 新的声明方式const (常量)
- 不属于顶层对象window
- 不允许重复声明
- 不存在变量提升
- 暂时性死区
- 块级作用域
4.1 代码演示
es5定义常量方法
Object.defineProperty
=>
第一个参数是在哪个对象上添加属性
第二个参数是属性名称
第三个参数是对象,给其赋值 => writable
是否为可写属性
es-demo\static\1-2.js
Object.defineProperty(window, 'PI', {
value: 3.14,
writable: false
})
console.log(PI)
PI = 5
console.log(PI)
设置PI
不可写,因此再赋值为5也不会生效,但是不会报错。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.64
Branch: branch02commit description:a0.64(es5定义常量方法)
tag:a0.64
es6
定义常量 =>
const a = 5
a = 6
给常量赋值会报错。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.65
Branch: branch02commit description:a0.65(es6定义常量)
tag:a0.65
const a
a = 5
const
声明的时候同时赋值,不能先声明后赋值。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.66
Branch: branch02commit description:a0.66(const 声明的时候同时赋值,不能先声明后赋值。)
tag:a0.66
const
其他特性与let
是一样的。
如:块级作用域
if (true) {
const a = 5
}
console.log(a)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.67
Branch: branch02commit description:a0.67(const 块级作用域)
tag:a0.67
const
变量也不存在变量提升,存在暂时性死区
if(true){
console.log(a)
const a = 5
}
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.68
Branch: branch02commit description:a0.68(const 变量也不存在变量提升,存在暂时性死区)
tag:a0.68
前面定义的都是基本数据类型,现在定义一些复杂数据类型
。
const obj = {
name: 'xd',
age: 28
}
console.log(obj)
obj.school = 'QH'
console.log(obj)
之前学过const
是常量,是不可以被改变的,这里怎么发生变化了呢?
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.69
Branch: branch02commit description:a0.69(const 复杂类型数据)
tag:a0.69
=> js
变量存储
基本数据类型存储在栈内存
中,复杂(引用)数据类型(数组、对象)存储在堆内存
针对引用类型 => 在栈内存存的是堆内存地址。
const
=> 对于数组,也可以改变其值
const arr = [1, 2, 3]
arr.push(4)
console.log(arr)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.70
Branch: branch02commit description:a0.70(const => 对于数组,也可以改变其值)
tag:a0.70
实际是对应const
的引用类型变量,不能改变的是栈内存的堆内存地址,堆里的内容是不管的。
const
实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。
那如何让对象内容也为常量不变呢? => freeze
函数,注意freeze
里只能传对象
,不能传数组
const obj = {
name: 'xd',
age: 28
}
Object.freeze(obj)
console.log(obj)
obj.school = 'QH'
console.log(obj)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.71
Branch: branch02commit description:a0.71(const => 对于数组,也可以改变其值)
tag:a0.71
嵌套对象 => Object.freeze
浅层冻结,只会对最近一层的对象进行冻结,并不会对深层对象冻结。
const obj = {
name: 'xd',
age: 28,
skill: {
name: 'code',
year: 5
}
}
Object.freeze(obj)
console.log(obj)
obj.school = 'QH'
obj.skill.year = 10
console.log(obj)
嵌套对象的值可以被改变。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.72
Branch: branch02commit description:a0.72(Object.freeze 浅层冻结)
tag:a0.72
如果想把skill
中值也冻结,还需单独冻结skill
。 => 相当于根据嵌套进行递归冻结
。
const obj = {
name: 'xd',
age: 28,
skill: {
name: 'code',
year: 5
}
}
Object.freeze(obj)
Object.freeze(obj.skill)
console.log(obj)
obj.school = 'QH'
obj.skill.year = 10
console.log(obj)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.73
Branch: branch02commit description:a0.73(如果想把skill中值也冻结,还需单独冻结skill)
tag:a0.73
5. 解构赋值
- 按照一定模式,从数组和对象中提取值,对变量进行赋值
- 数组解构
- 对象解构
- 字符串解构
- 应用
5.1 代码演示
现在的语法为了兼容各个浏览器,将其写在src
目录下,利用webpack
进行打包编译。
es-demo\src\index.js
import './1-3'
原始的取数组值的方法 => 挨个索引取,可以用循环遍历即可
es-demo\src\1-3.js
let arr = [1, 2, 3]
let a = arr[0]
let b = arr[1]
let c = arr[2]
console.log(a, b, c)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.74
Branch: branch02commit description:a0.74(原始的取数组值)
tag:a0.74
解构取数组值的方法
等号左边形式要与右边匹配。
let arr = [1, 2, 3]
let [a, b, c] = [1, 2, 3]
console.log(a, b, c)
解构赋值重点是在赋值,赋值的元素是要拷贝出来赋值给变量,赋值的元素本身是不会被改变的。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.75
Branch: branch02commit description:a0.75(解构取数组值的方法 )
tag:a0.75
赋值元素可以是任意可遍历的对象
赋值的元素不仅是数组,它可以是任意可遍历的对象
let [a, b, c] = "abc" // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3])
左边的变量
被赋值的变量还可以是对象的属性,不局限于单纯的变量。
let user = {}
[user.firstName, user.secondName] = 'Kobe Bryant'.split(' ')
console.log(user.firstName, user.secondName) // Kobe Bryant
循环体
解构赋值在循环体中的应用,可以配合 entries 使用。
let user = {
name: 'John',
age: 30
}
// loop over keys-and-values
for (let [key, value] of Object.entries(user)) {
console.log(`${key}:${value}`) // name:John, then age:30
}
当然,对于 map 对象依然适用
let user = new Map()
user.set('name', 'John')
user.set('age', '30')
for (let [key, value] of user.entries()) {
console.log(`${key}:${value}`) // name:John, then age:30
}
可以跳过赋值元素
如果想忽略数组的某个元素对变量进行赋值,可以使用逗号来处理。
// second element is not needed
let [name, , title] = ['John', 'Jim', 'Sun', 'Moon']
console.log( title ) // Sun
rest 参数
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]
console.log(name1) // Julius
console.log(name2) // Caesar
// Note that type of `rest` is Array.
console.log(rest[0]) // Consul
console.log(rest[1]) // of the Roman Republic
console.log(rest.length) // 2
注意
我们可以使用 rest 来接受赋值数组的剩余元素,不过要确保这个 rest 参数是放在被赋值变量的最后一个位置上。
默认值
如果数组的内容少于变量的个数,并不会报错,没有分配到内容的变量会是 undefined。
let [firstName, surname] = []
console.log(firstName) // undefined
console.log(surname) // undefined
当然你也可以给变量赋予默认值,防止 undefined 的情况出现:
// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"]
console.log(name) // Julius (from array)
console.log(surname) // Anonymous (default used)
复杂一些的数组解构,只有等号左边结构匹配即可。实际解构赋值就是模式的匹配。
let [a, b, [c, d]] = [1, 2, [3, 4]]
console.log(a, b, c, d)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.76
Branch: branch02commit description:a0.76(复杂一些的数组解构,只有等号左边结构匹配即可。 )
tag:a0.76
5.2 笔试题1
let [a, b, [c]] = [1, 2, [3, 4]]
console.log(a, b, c)
c
对应的是3
,而4
并没有对应值,因为等号左边[c]
只有一项,因此只能与3
对应。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.77
Branch: branch02commit description:a0.77(解构赋值-笔试题1 )
tag:a0.77
5.3 笔试题2
let [a, b, c] = [1, 2, [3, 4]]
console.log(a, b, c)
c
没有中括号,因此根据模式匹配对应[3, 4]
。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.78
Branch: branch02commit description:a0.78(解构赋值-笔试题2 )
tag:a0.78
5.4 笔试题3
let [a, b, c, d] = [1, 2, [3, 4]]
console.log(a, b, c, d)
等号左边没有对应的模式匹配则为undefined
。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.79
Branch: branch02commit description:a0.79(解构赋值-笔试题3)
tag:a0.79
5.5 笔试题4
let [a, b, c, d = 5] = [1, 2, [3, 4]]
console.log(a, b, c, d)
等号左边的结构可以直接赋值。
解构赋值实际是惰性的,如果等号右边模式对应有值,则取它,否则就根据左边的默认值,没有默认值就是undefined
。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.80
Branch: branch02commit description:a0.80(解构赋值-笔试题4)
tag:a0.80
5.6 笔试题5
let [a, b, c, d = 5] = [1, 2, [3, 4], 6]
console.log(a, b, c, d)
解构 => 惰性取值,右边有值,则直接使用,会忽略默认值。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a.081
Branch: branch02commit description:a0.81(解构赋值-笔试题5)
tag:a0.81
对象的普通取值。
解构赋值除了可以应用在 Array,也可以应用在 Object。基本的语法如下:
let user = {
name: '6xd',
age: 28
}
let name = user.name
let age = user.age
console.log(name, age);
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.82
Branch: branch02commit description:a0.82(对象的普通取值)
tag:a0.82
对象结构赋值 =>
大致的意思是我们有一个 Object
想把里面的属性分别拿出来而无需通过调用属性的方式赋值给指定的变量。具体的做法是在赋值的左侧声明一个和 Object
结构等同的模板,然后把关心属性的 value
指定为新的变量即可。
let user = {
name: '6xd',
age: 28
}
let {age, name} = user;
console.log(name, age);
因为对象中的key
是唯一的,所以对象解构的,是通过key
对应的,而不是顺序。而数组解构则是通过顺序进行对应的。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.83
Branch: branch02commit description:a0.83(对象结构赋值)
tag:a0.83
比如对象解构完成后,需要赋值给别的变量,如何实现呢?
let user = {
name: '6xd',
age: 28
}
let {age: uage, name: uname} = user
console.log(uname, uage)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.84
Branch: branch02commit description:a0.84(对象结构赋值2)
tag:a0.84
let user = {
name: '6xd',
age: 28
}
let {age: uage, name: uname} = user
uname = 'xm'
uage = 99
console.log(uname, uage)
let {age, name} = user;
console.log(name, age)
对象解构赋值,相当于赋值给新变量,修改新变量的值不会改变原对象的属性值。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.85
Branch: branch02commit description:a0.85(对象解构赋值,相当于赋值给新变量)
tag:a0.85
默认值
当然,这个赋值的过程中也是可以指定默认值的,这样做:
let options = {
title: "Menu"
}
let {width = 100, height = 200, title} = options
console.log(title) // Menu
console.log(width) // 100
console.log(height) // 200
rest 运算符
如果我们想象操作数组一样,只关心指定的属性,其他可以暂存到一个变量下,这就要用到 rest
运算符了
let options = {
title: "Menu",
height: 200,
width: 100
}
let {title, ...rest} = options
// now title="Menu", rest={height: 200, width: 100}
console.log(rest.height) // 200
console.log(rest.width) // 100
嵌套对象
如果一个 Array
或者 Object
比较复杂,它嵌套了 Array
或者 Object
,那只要被赋值的结构和右侧赋值的元素一致就好了。
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true // something extra that we will not destruct
}
// destructuring assignment on multiple lines for clarity
let {
size: { // put size here
width,
height
},
items: [item1, item2], // assign items here
title = 'Menu' // not present in the object (default value is used)
} = options
console.log(title) // Menu
console.log(width) // 100
console.log(height) // 200
console.log(item1) // Cake
console.log(item2) // Donut
这个原理其实很简单,如果不理解可以看下图:
实际上,解构赋值在项目中用得非常多,如想从后端请求数据,赋值给前端渲染。
es5
=> 取字符串中单个字符
let str = 'Github'
for (let i = 0; i < str.length; i++) {
console.log(str[i])
}
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.86
Branch: branch02commit description:a0.86(es5 => 取字符串中单个字符)
tag:a0.86
字符串解构赋值 => 实际与数组的解构差不多
let str = 'Github'
let [a, b, c, d, e, f] = str
console.log(a, b, c, d, e, f)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.87
Branch: branch02commit description:a0.87(字符串解构赋值)
tag:a0.87
5.7 应用场景1 ——赋默认值
let [a, b, c = 8] = [4, 5, 6]
console.log(a, b, c)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.88
Branch: branch02commit description:a0.88(解构赋值应用场景1 ——数组解构赋默认值)
tag:a0.88
let {name, age = 18} = {
name: '6xiaodi',
age: 28
}
console.log(name, age)
let {name, age = 18} = {
name: "6xiaodi",
// age: 28
}
console.log(name, age)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.89
Branch: branch02commit description:a0.89(解构赋值应用场景1 ——对象解构赋默认值)
tag:a0.89
解构 => 遵循惰性赋值规则,等号右边有则忽视左边默认值。
function foo(){
console.log(123)
}
let [a = foo()] = [1]
function foo(){
console.log(123)
}
let [a = foo()] = []
右边是空数组,左边默认值是个方法,自动执行
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.90
Branch: branch02commit description:a0.90(解构赋值应用场景1 ——解构惰性赋值)
tag:a0.90
5.8 应用场景2——函数参数解构赋值
函数数组参数解构赋值 =>
function foo([a, b, c]) {
console.log(a, b, c)
}
let arr = [1, 2, 3]
foo(arr)
这样就省去了用数组参数接收实参,再解构取。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.91
Branch: branch02commit description:a0.91(解构赋值应用场景2——函数数组参数解构赋值)
tag:a0.91
函数对象参数解构赋值 => 解构同时还可提供默认值
function foo({name, age, school = 'beida'}) {
console.log(name, age, school)
}
let obj = {
name: 'lisi',
age: 34,
school: 'qinghua'
}
foo(obj)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.92
Branch: branch02commit description:a0.92(解构赋值应用场景2——函数对象参数解构赋值 => 解构同时还可提供默认值)
tag:a0.92
5.9 应用场景3——函数返回值解构赋值
函数返回值解构 =>
function foo() {
let obj = {
name: 'lisi',
age: 34,
school: 'qinghua'
}
return obj
}
let {name, age} = foo()
console.log(name, age)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.93
Branch: branch02commit description:a0.93(解构赋值应用场景3——函数返回值解构赋值)
tag:a0.93
5.10 应用场景4——提取JSON数据
一般前端请求接口,后端会返回JSON字符串。 => 前端一般会先转为JSON对象再决心解构。
let json = '{"a": "hello", "b": "world"}'
let {a, b}= JSON.parse(json)
console.log(a, b)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a0.94
Branch: branch02commit description:a0.94(解构赋值应用场景4——提取JSON数据)
tag:a0.94
(后续待补充)