对象
字面量¶
字面量就是指变量的常数值, 字面上看到的值.
对象字面量¶
对象字面量是被花括号包含, 含有键值对的以逗号分隔的列表.
属性¶
访问/新增/删除¶
访问¶
访问JS对象的属性有三种方式:
[obj].[prop]
[obj].[[prop]]
[obj].[[expression]]
新增¶
同访问.
删除¶
可以使用delete [obj].[prop]
删除一个对象的属性或方法.
注意
delete
关键字不会删除继承得到的属性, 但是如果你把__prototype__
中的某一个属性删除了, 那么所有继承__prototype__
的对象中的这个属性都会被删除.
方括号¶
方括号, 适用于访问, 修改或修改包含特殊字符的属性.
例子
定义:
执行:
方括号同样提供了一种可以通过任意表达式获取属性名的方式.
计算属性¶
当创建一个对象的时候, 可以在对象字面量的地方使用方括号, 所表示的属性叫做计算属性. 计算属性名从对象所在的环境中获取.
例子
属性条目缩写¶
在实际开发中, 常用已存在的变量当作属性名, 我们可以采取属性条目缩写的方法, 使属性条目变得更短.
属性名称限制¶
对象的属性名称可以是JS的保留字, 如for
, let
, return
等.
属性名没有任何限制, 可以是任何字符串或者标识符. 其他类型会被自动转换为字符串.
例子
定义:
执行:
属性存在性测试¶
可以利用in
操作符判断属性是否存在与对象中.
Tip
相比于其他语言, JS的对象能够被访问任何属性, 即使属性不存在也不会报错, 读取不存在的属性会得到undefined
.
例子
定义:
执行:
注意
in
的左侧必须是属性名, 通常是一个带引号的字符串. 如果忽略引号, 就意味着左边是一个变量, 它应该要包含要跑那段的实际属性名. 否则会报错.
标识符类型¶
根据规范, 只有两种原始类型可以用作键:字符串类型和标识符类型, symbol类型.
可以使用Symbol("[description]")
来创建一个唯一的标识符. 即使它们的描述相同.
例子
定义:
执行:
Tip
-
如果使用除此之外的类型, 会被自动转换为字符串.
例子
obj[1]
会自动变为obj["1"]
;obj[true]
会自动变为obj["true"]
. -
标识符不会被自动转换为字符串
JS中的大多数值都支持字符串的隐式转换, 但是标识符比较特殊, 不会被自动转换.
-
如果真的想显示一个标识符的描述, 可以访问标识符的
description
属性. -
如果要在对象字面量里面使用标识符, 需要使用方括号将其括起来.
我们需要
id
的值作为键, 而不是字符串'id'
. -
标识符属性不参与
for ... in ...
循环 -
Object.assign
复制属性会同时复制字符串和标识符属性.
注意
标识符键不是字符串, 而是标识符类型. 因此无法使用点.
运算符来访问这些属性, 点运算符只能用于访问字符串键.
"隐藏"属性¶
标识符允许我们创建对象的"隐藏"属性. 这些属性不会被其他模块重写.
例子
目录结构:
a.js
文件:
let user = {
name: "wenzexu"
}
// 添加一个隐藏属性
let id = Symbol("id_ajs")
user[id] = "ID from a.js"
// 打印所有属性
console.log(user.name)
console.log(user[id])
// 导出对象
module.exports = user
b.js
文件:
let user = require("./a.js")
// 尝试打印一个公开属性
console.log(user.name)
// 尝试打印一个隐藏属性
console.log(user[id])
执行b.js
文件:
$ node b.js
wenzexu
ID from a.js
wenzexu
/home/wenzexu/test/b.js:7
console.log(user[id])
^
ReferenceError: id is not defined
at Object.<anonymous> (/home/wenzexu/test/b.js:7:18)
at Module._compile (node:internal/modules/cjs/loader:1358:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
at Module.load (node:internal/modules/cjs/loader:1208:32)
at Module._load (node:internal/modules/cjs/loader:1024:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
at node:internal/main/run_main_module:28:49
Node.js v20.15.0
可以看到, 在a.js
文件内可以访问隐藏属性id
, 但是在另外的模块b.js
中无法访问隐藏属性.
如果我们在b.js
里面也定义了一个名字为id
的标识符并作为导入对象user
的属性, 由于上述的特性, a.js
中的id
属性和b.js
中的id
属性是完全不同的.
修改后的b.js
文件:
let user = require("./a.js")
// 添加一个隐藏属性
let id = Symbol("id_bjs")
user[id] = "ID from b.js"
// 打印所有属性
console.log(user[id])
console.log(user.name)
执行:
注意id
属性不是被重写了, 而是新建的.
全局标识符¶
若要在不同的模块之间共享一个标识符, 可以将其注册用到全局标识符注册表然后在任何地方都可以访问.
全局注册表中, 使用相同的描述访问时, 取回的是相同的标识符. 如果要从注册表中读取(不存在则创建)标识符, 用[symbol].for([key])
. 该调用会检查全局注册表, 如果有一个描述为[key]
的标识符, 则返回该标识符. 否则将创建一个新的标识符, 描述为[key]
.
例子
目录结构:
a.js
文件:
let user = {
name: "wenzexu"
}
// 将标识符注册到全局标识符注册表
let id = Symbol("id_ajs")
user[id] = "ID from a.js"
// 打印所有属性
console.log(user.name)
console.log(user[id])
// 导出对象
module.exports = user
b.js
定义:
let user = require("./a.js")
// 尝试打印一个公开属性
console.log(user.name)
// 尝试从全局标识符注册表中获取一个标识符
let id = Symbol.for("id_ajs")
console.log(user[id])
执行:
[symbol].keyFor()
¶
[symbol].keyFor([symbol])
按照标识符返回它在全局注册表中的描述.
例子
定义:
// 注册两个标识符到全局注册表
let sym = Symbol.for("name")
let sym2 = Symbol.for("id")
// 尝试获取这两个标识符的描述
console.log(Symbol.keyFor(sym))
console.log(Symbol.keyFor(sym2))
执行:
注意
使用该函数来查找标识符的描述的前提是这个标识符为全局标识符, 否则返回undefined
.
for ... in ...
循环¶
可以使用for ... in ...
循环遍历对象所有的属性.
例子
定义:
let user = {
name: "John",
age: 30,
isAdmin: true
}
for (let key in user) {
console.log(key)
console.log(user[key])
}
执行:
遍历的顺序¶
使用for ... in ...
循环遍历属性的顺序如下:
- 若为整数属性, 按照从小到大顺序遍历
- 若为非整数属性, 按照创建的顺序遍历
例子
方法¶
方法简写¶
在对象字面量中, 有一种更短的方法的语法.
???+ example "例子'
```js
let user = {
sayHi() {
console.log("Hello")
}
}
```
this
的值¶
详情见这里.
引用和复制¶
引用¶
对象变量存储的是对对象的引用.
复制和克隆¶
复制属性¶
可以使用Object.assign([dest], [[src1], [src2], [src3], ...])
实现复制属性.
[dest]
: 目标对象[src1], [src2], [src3], ...
: 多个源对象
该方法会将所有源对象的属性拷贝到目标对象中.
例子
定义:
let user = {
name: "wenzexu"
}
let permission1 = {
canView: true
}
let permission2 = {
canEdit: true
}
Object.assign(user, permission1, permission2)
console.log(user)
执行:
注意
若被拷贝的对象的属性的属性名已经存在, 那么它会被覆盖.
克隆对象¶
Object.assign([dest], [[src1], [src2], [src3], ...])
方法的返回值为目标对象, 我们设置目标对象初始值为{}
实现克隆.
例子
定义:
执行:
或者使用...
语法.
例子
定义:
执行:
深层克隆¶
对象的属性可以是其他对象的引用.
例子
定义:
let user = {
name: "wenzexu",
sizes: {
height: 180,
width: 50
}
}
let clone = {...user}
clone.name = "ricolxwz"
clone.sizes.width++
console.dir(user)
执行:
因此, 为了让拷贝之后的对象成为真正独立的对象, 应该使用过一个拷贝循环来检查源对象的每一个属性, 如果值为一个对象, 也应该复制它的结构. 这就是所谓的"深拷贝". 可以使用lodash
库的_.cloneDeep(obj)
.
创建对象¶
在JS中, 有很多中不同的方式来创建对象, 大致为:
- 使用对象字面量
- 使用
new
关键字搭配构造函数 - 类
使用对象字面量¶
使用{}
创建对象.
使用构造函数¶
如果有许多属性和方法是所有用户都要用到的, 可以使用构造函数创建对象.
构造函数在技术上是常规函数, 但是有两个特殊的地方:
- 命名以大写字母开头(非强制)
- 只能由
new
关键字执行
例子
定义:
function User(name) {
this.name = name
this.isAdmin = false
}
let user = new User("wenzexu")
console.log(user.name)
console.log(user.isAdmin)
执行:
笔记
当一个构造函数使用new
操作符执行的时候, 它按照以下步骤执行:
- 一个新的空对象被创建并分配给
this
- 构造函数体执行. 通常会修改
this
所指向的对象 - 返回
this
所指的对象
可以表示为:
构造函数模式测试¶
在一个函数内部, 可以使用new.target
检查它是否被使用new
进行调用.
- 对于常规调用, 它为
undefined
- 对于使用
new
的调用, 等于该函数
例子
定义:
执行:
Tip
甚至可以让常规调用和new
调用的效果相同.
构造函数的return
¶
构造函数通常没有return
语句, 有return
语句会根据返回内容产生不同的结果.
若return
对象, 则返回该对象; 否则, 返回this
所指的对象.
例子
可选链¶
可选链?.
是一种访问嵌套对象属性的安全的方式. 即使中间的属性不存在, 也不会出现错误.
"不存在的属性"问题¶
这个问题出现的原因在于:
- 无法访问
undefined
的属性 - 无法访问
null
的属性
例子
定义:
执行:
$ node test.js
undefined
/Users/wenzexu/test/test.js:3
console.log(user.address.street)
^
TypeError: Cannot read properties of undefined (reading 'street')
at Object.<anonymous> (/Users/wenzexu/test/test.js:3:30)
at Module._compile (node:internal/modules/cjs/loader:1369:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)
at Module.load (node:internal/modules/cjs/loader:1206:32)
at Module._load (node:internal/modules/cjs/loader:1022:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
at node:internal/main/run_main_module:28:49
Node.js v20.12.0
user.address
为undefined
, 我们尝试访问undefined
的street
属性, 所以报错. 而正常情况下, 我们希望得到的仍然是undefined
.
可选链¶
可选链?.
的语法有三种形式:
[obj]?.[prop]
: 如果[obj]
存在则返回[obj].[prop]
, 否则直接返回undefined
[obj]?.[[prop]]
: 如果[obj]
存在则返回[obj].[[prop]]
, 否则直接返回undefined
[obj].[method]?.()
: 如果[obj].[method]
存在在调用[obj].[method]()
, 否则直接返回undefined
简洁来说, 就是?.
会检查左边部分是否为undefined/null
, 如果不是则继续运算.
例子
短路效应¶
若?.
左边的部分不存在, 即为undefined
/null
, 就会立即停止运算.
-
对象. (n.d.). Retrieved June 22, 2024, from https://zh.javascript.info/object ↩
-
构造器和操作符 “new.” (n.d.). Retrieved June 22, 2024, from https://zh.javascript.info/constructor-new ↩
-
可选链 “?.” (n.d.). Retrieved June 22, 2024, from https://zh.javascript.info/optional-chaining ↩