一. 对象类型
认识对象类型
引言
在 JavaScript 的数据类型中,有一类非常重要且贯穿始终的类型 —— 对象类型(Object)。
"对象类型几乎参与 JS 的所有核心机制(作用域、原型链、this、模块、框架设计等)"。
掌握对象类型,是理解 JavaScript 的关键基础。
什么是对象类型
对象类型是一种用于存储键值对(key-value)的复杂数据类型。
- 每一组数据由:key: value 组成
- 对象可以同时描述:
属性(特性、状态)
方法(行为,本质是函数)
var obj = {
name: 'later',
age: 23,
run: function () {
console.log('running')
}
}对象中的函数,通常称为 方法(method)。
🔔 提示
在 JavaScript 中,对象的方法本质上也是对象的属性,只不过当属性值是函数时,通常称该属性为“方法”。
为什么需要对象类型
基本数据类型(Number、String、Boolean 等)只能表示单一、简单的值,而现实中的事物往往是多维度的。
例如:
- 一个「人」
特性:姓名、年龄、身高
行为:学习、工作、跑步 - 一辆「车」
特性:颜色、重量、速度
行为:行驶、刹车
对象类型的作用:将“属性 + 行为”组织成一个整体,用于描述复杂事物。
对象中 key 和 value 的规则
key(属性名,property name)本质是:字符串 或 Symbol(ES6 新增)
写对象字面量时,字符串 key 的引号通常可以省略
属性之间是以逗号(comma)分割
非字符串类型的 key(如 Number)会被隐式调用 toString() 转换为字符串jsvar obj = { 123: 'number key', // key 实际等价于 '123' true: 'boolean key', // key 实际等价于 'true' name: 'later', // key 实际等价于 'name' }value(属性值, property value)可以是任意类型:基本类型、函数、对象、数组 等
创建对象的方式
JavaScript 中常见的对象创建方式有三种:
对象字面量(最常见,Object Literal)
var obj = {
name: "later"
}语法最简洁
可读性最好
前端开发中使用频率最高
new Object() + 动态添加属性
var obj = new Object() // Object 构造函数
obj.name = "later"本质上与对象字面量一致
实际开发中较少使用
new 构造函数
function Person() {}
var obj = new Person()用于批量创建对象
与原型、面向对象编程密切相关
当前阶段重点掌握:对象字面量方式 即可。后续学习其他两种方式
对象的基本操作
// 定义对象
var obj = {
name: 'later'
}
// 1.访问对象属性:对象.属性名
console.log(obj.name)
// 2.修改对象属性
obj.name = 'sensen'
// 3.添加对象属性
obj.age = 23
// 4.删除对象属性:delete 操作符
delete obj.age点语法 vs 方括号语法
点语法的限制
点语法要求属性名必须是合法的变量标识符:
不能包含空格
不能以数字开头
不能包含特殊字符($、_ 除外)
var obj = {
'good friend': 'later'
}
// 点语法 不支持
obj.good friend // ❌ 语法错误:';' expected.方括号语法(更灵活)
方括号在定义或操作属性时更灵活,方括号内可以是任意的字符串、变量、表达式 等。
var obj = {
'good friend': 'later'
}
obj['good friend'] // ‘later'
var key = 'name'
obj[key] // 等价于 => obj.name- 常用于:
key 中包含特殊字符
key 是变量
对象的遍历(迭代)
遍历对象:表示获取对象中所有的可枚举属性。
方式一:Object.keys() + for 循环(推荐)
Object.keys()方法会返回一个由给定对象的自身可枚举属性组成的字符串数组。
var info = { name: 'later', age: 23 }
var infoKeys = Object.keys(info)
for (var i = 0; i < infoKeys.length; i++) {
var key = infoKeys[i]
var value = info[key]
console.log(`key: ${key}, value: ${value}`)
}
// output:
// key: name, value: later
// key: age, value: 23特点:
只遍历对象 自身的可枚举属性
顺序稳定
更可控,工程中更常见
方式二:for...in 遍历
任意顺序迭代一个对象的除 Symbol 以外的可枚举属性, 包括原型链上继承过来的可枚举属性。
var info = { name: 'later', age: 23 }
for (var key in info) {
var value = info[key]
console.log(`key: ${key}, value: ${value}`)
}特点:
遍历对象 自身的可枚举属性 + 从原型链上继承过来的可枚举属性(可搭配hasOwnProperty()使用,来排除继承过来的属性)
顺序不固定
不遍历 Symbol 属性
二. 内存中的值类型和引用类型
引言
本节目标:
"理解 JavaScript 中「值是如何存储在内存中的」"
"搞清楚 值类型 vs 引用类型 的本质差异"
"为后续理解:函数参数、对象拷贝、闭包、响应式等打基础"
认识内存(Memory)
计算机存储器(Computer memory)是一种利用半导体、磁性介质等技术制成的存储资料的电子设备,其电子电路中的资料以二进制方式存储,是用来存储程序运行过程中产生的数据的硬件设备。
存储器分为主存储器(main memory,简称:主存/内存,又称“内存储器”)和 辅助存储器(auxiliary memory,简称:辅存/外存,又称“外存储器”)。
内存储器与中央处理器(CPU)一起构成主机,用来存放计算机运行时随时需要使用的程序和数据,一切数据要被CPU操作都必须先装入内存。内存的工作速度较快,存储容量较小,主要采用半导体存储器。
目前大部分计算机系统的主存储器主体为动态随机存储器(DRAM),“主存储器”乃至“存储器”一词有时特指DRAM;另外,静态随机存储器(SRAM)与只读存储器(ROM)等也可作主存储器的一部分。
辅助存储器和输入输出设备都属于外设,用来存放CPU运行时暂时不用的各种程序和数据,一般在断电后仍能保存。辅助存储器的存储容量大,工作速度慢,例子如硬盘、U盘、光盘、磁带等。
简单理解:
- 程序运行前:代码在磁盘(外部存储)
- 程序运行时:
被加载到 内存(主存)
由 CPU 直接读取和执行
内存的特点:
- 内存是 CPU 可以直接寻址 的存储空间
- 相比磁盘:
访问速度快
容量较小
断电数据丢失
栈内存(Stack)与 堆内存(Heap)
程序在运行时,先加载到内存中由 CPU 来执行,内存通常可以从逻辑上划分为两个区域:栈内存和堆内存。
| 内存区域 | 特点 | 主要存储 |
|---|---|---|
| 栈内存 | 连续的内存空间、 存取速度快、 生命周期明确(函数调用结束即释放) | 基本(原始)类型的值、 变量名与其对应的值、 函数调用相关信息(调用栈) |
| 堆内存 | 非连续的内存空间、 存取速度慢、 生命周期不固定(由 GC 回收) | 对象类型(Object / Array / Function 等)的实际数据 |
原始(基本)类型占据的空间是在栈内存中分配的
对象(复杂)类型占据的空间是在堆内存中分配的

值类型和引用类型
值类型(Primitive / 值拷贝)
原始类型的保存方式:在变量中保存的是值本身,所以原始类型又称为值类型。
var a = 111
var b = a // a 和 b 互不影响
b = 222
console.log(a) // 111
console.log(b) // 222特点:
值类型保存的是值本身,所以值类型又称为值拷贝
赋值或传参时,会发生 值拷贝
引用类型(Reference / 引用拷贝)
对象类型的保存方式:在变量中保存的是对象的 "引用",所以对象类型又称为引用类型。
var obj1 = { name: '111' }
var obj2 = obj1 // 两个变量指向同一个对象
obj2.name = '222'
console.log(obj1.name) // 222特点:
对象的实际数据存储在 堆内存
变量中保存的是:堆内存地址(引用)
值类型 vs 引用类型
| 对比项 | 值类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈 | 栈(引用) + 堆(实际数据) |
| 变量中保存 | 值本身 | 堆内存地址(引用) |
| 复制行为 | 值拷贝 | 引用拷贝 |
| 相互影响 | 否 | 是 |

🔔 提示
变量名、原始类型都是存储在栈内存中,只有对象类型的值(实际数据)是存储在堆内存中。
对象类型的变量名存储在栈内存中,其保存的并不是实际数据,而是堆中对象类型值的内存地址(指针/引用)。
现象解析
现象一:比较两个变量
var a = 123
var b = 123
console.log(a === b) // true
// 因为原始类型在变量中保存的是值本身,这里是值的比较,123 是等于 123 的
var m = {}
var n = {}
console.log(m === n) // false
// 因为对象类型在变量中保存的是内存地址,只要写的是个对象字面量形式的大括号,在堆内存中就会创建一个新对象,
// 这里是创建了两个对象,在堆中的内存地址不同,所以比较的时候是不相等的原因分析:
值类型:比较的是「值」
引用类型:比较的是「内存地址」
每一个对象字面量 {} 都会在堆中创建一个新对象

现象二:引用赋值(共享同一对象)
var info = {
// 对象类型中的基本数据类型,会直接保存在当前对象所在的堆内存中,
// 因为堆内存本身就是一块空间,空间本身就可以放东西,
// 因此对象类型中的值类型就存储在该对象所在的堆内存中的,所以'18'这个字符串就存储在该对象所在的堆内存中
age: '18',
friend: {
name: "kobe"
}
}
// 虽然 info 变量的值本身是一个对象类型,JS引擎会在堆内存中分配一个空间用来存储 info 的值,
// 但 friend 属性的值是对象类型,JS引擎会在堆内存中新分配一个空间用来存储 info.friend 属性的值,
// 而 var friend 变量实际保存的是值在堆内存中的分配的内存地址
var friend = info.friend
friend.name = "james"
console.log(info.friend.name) // james原因分析:
info.friend 保存的是一个对象引用
friend 拿到的是 同一个引用
修改的是堆中同一块数据

现象三:值类型作为函数参数传递
function foo(a) {
a = 200 // a:100 -> a = 200
}
var num = 100
foo(num) // 传递的参数是值类型,所以传递的是值的副本
console.log(num) // 100原因分析:
函数参数本质是一次 赋值操作
值类型传递的是「值的副本」

现象四:引用类型传递 + 不修改引用值
function foo(a) {
a = { name: 'why' } // 没有修改传过来的 obj,而是创建了一个新对象
}
var obj = { name: 'obj' }
foo(obj) // 引用传递,传递的 obj 值的内存地址
console.log(obj) // { name: 'obj' }原因分析:
传入的是引用(地址)
但函数内部:让参数 a 指向了 新的对象,并没有修改原对象

现象五:引用类型传递 + 修改引用值
function foo(a) {
a.name = 'why' // 对传递过来的 obj 的堆中的 值进行了修改
}
var obj = {name: 'obj'}
foo(obj) // 引用传递,传递的 obj 值的内存地址
console.log(obj) // { name: 'why' }原因分析:
通过引用,直接修改了堆中的对象数据

三. 函数的 this 执行
引言
本节目标:
"理解 this 为什么存在"
"搞清楚 this 到底指向谁"
"建立一套可推导的 this 判断模型,而不是死记结论"
为什么需要 this?
在常见的编程语言中,几乎都有 this 这个关键字(Objective-C 中使用的是 self),但是 JS 中的 this 和常见的面向对象语言中的 this 不太一样:
大多数面向对象的编程语言中,如
Java、C++、Swift、Dart等一系列语言中,this通常只会出现在类的方法中(特别是实例方法)。this代表的是当前实例对象。
JS 中的特殊性
JavaScript 中的 this 与传统面向对象语言 有本质不同:
JS 中 函数是“一等公民”
函数可以:独立调用、作为对象方法调用、作为回调函数、被显式绑定
因此:JS 中的 this,不取决于函数定义在哪,而取决于函数“如何被调用”
不使用 this 的问题
var obj = {
name: 'later',
running: function () {
console.log(obj.name + ' running')
},
eating: function () {
console.log(obj.name + ' eating')
},
}问题:
方法内部 强依赖外部变量名 obj
一旦对象被:赋值给其他变量、拷贝、作为参数传递,方法立刻失效或语义错误。
使用 this 的好处
this 是为了解决“方法复用 + 动态对象”的问题而存在的。
var obj = {
name: 'later',
running: function () {
console.log(this.name + ' running')
},
eating: function () {
console.log(this.name + ' eating')
},
}优势:
方法 与对象名解耦
同一方法可以被多个对象复用
this 在调用时动态确定
this 指向什么?
this 的指向,取决于函数的调用方式,而不是定义位置。
目前掌握两个判断方法,足以解释 80% 的 this 问题:
方法一:默认的方式调用一个函数,this 指向全局对象
方法二:通过对象调用,this 指向调用的对象
默认函数调用(独立调用)
function foo() {
console.log(this)
}
foo() // window解释:
函数独立调用
没有明确的调用者
this 指向全局对象(浏览器中是 window,严格模式下是 undefined)
作为对象的方法调用
var obj = {
bar: function() {
console.log(this)
}
}
obj.bar() // obj解释:
调用点在 obj.bar()
this 指向 调用该方法的对象 obj
四. 工厂函数
创建对象 - 字面量方式的问题
在实际开发中,我们经常会遇到这样的需求:
游戏中创建多个英雄对象(都有名字、技能、价格,但是具体的值又不相同)
学生系统中创建多个学生对象(都有学号、姓名、年龄等,但是具体的值又不相同)
后台系统中创建多个用户对象(都有用户名、密码、权限等,但是具体的值又不相同)
这些对象:
结构相同(属性、方法一致)
数据不同(具体值不同)
当然,最直接的方式是字面量创建一系列的对象:
// 一系列的学生对象
// 重复代码的复用: for/函数
var stu1 = {
name: "why",
age: 18,
running: function() {
console.log("running~")
}
}
var stu2 = {
name: "kobe",
age: 30,
running: function() {
console.log("running~")
}
}
var stu3 = { ... }
var stu4 = { ... }问题非常明显:
❌ 大量重复代码
❌ 不利于维护
❌ 不符合「抽象」思想
有没有一种方法可以批量创建对象,但是又让它们的属性不一样呢?答案是:工厂函数。
创建对象 - 工厂函数
工厂函数的思想
封装一个函数,专门负责“创建(生产)对象”。
每调用一次函数,就返回一个结构相同、数据不同的新对象。
批量创建对象,只需要重复调用这个函数。
工厂模式其实是一种常见的设计模式。
工厂函数示例
function createPerson(name, age, address) {
var p = new Object()
p.name = name
p.age = age
p.address = address
p.running = function() {
console.log(this.name + '在跑步')
}
return p
}
var p1 = createPerson('张三', 18, '广州')
var p2 = createPerson('李四', 22, '深圳')
console.log(typeof p1) // object优点:
✅ 封装创建过程
✅ 消除重复代码
✅ 使用简单直观
工厂函数的缺陷
console.log(typeof p1) // object
console.log(typeof p2) // object问题:
创建的所有对象的类型都是 Object。
无法区分:是 Person?还是 Student?
对象“来自哪里”是丢失的。这正是构造函数要解决的问题。
五. 构造函数与类(ES5)
什么是构造函数?
构造函数是在创建对象时会调用的函数,也称之为构造器(
constructor)。
在其他面向对象语言中:
类是模板
构造函数只是类中的一个方法,称之为构造方法
但在 JavaScript 中有点不一样:
构造函数是类的扮演者,类的表现形式就是构造函数。
在
ES6之前,是通过function来声明一个构造函数(类),允许用new对其进行调用 (如:系统默认给我们提供的Date就是一个构造函数,也可以看作是一个类)
在
ES6之后,JS 可以像别的语言一样,使用class关键字来声明一个类
// ES6 之前
function Person() {}
var p = new Person()
// ES6 之后
class Person {}
var p = new Person()怎么判断一个函数是不是构造函数呢?
构造函数本质上:仍然是一个普通函数,和普通函数没有什么区别。
关键区别在于:是否通过
new来调用。
只要某个函数通过 new 调用,这个函数就称之为:构造函数。
类与对象的关系
类是模板,对象是实例。
类:一类事物的统称
对象:具体的某个事物
比如:
人(Person 类) → 张三 / 李四
水果(Fruit 类) → 苹果 / 香蕉

new 关键字的执行过程
function Coder(name, age, height) {
// 2. 将新创建的这个对象的 `[[prototype]]` 属性,指向该构造函数的 `prototype` 属性
// 3. 将构造函数内部的 `this` 指向新创建的这个对象
// 在内存中 this 指向的是创建出来的空对象,this.xx=xx 这类操作,
// 其实就是给创建出来的空对象添加属性或方法
// 4. 执行构造函数内部的代码(函数体的代码块)
this.name = name
this.age = age
this.height = height
this.writeCode = function() {
console.log('写代码~')
}
// 5. 若函数体内部的代码块没有显式返回一个非空对象,则默认返回新创建的这个对象
}
// 1.在堆内存中创建一个新的空对象
var coder1 = new Coder('张三', 18, 1.8)
var coder2 = new Coder('李四', 22, 1.8)如果通过 new 操作符调用一个函数,JS 引擎会做以下的事情:
- 在堆内存中创建一个新的空对象
- 将该对象的
[[prototype]]属性,指向该构造函数的prototype属性- 将构造函数内部的
this指向新创建的这个对象- 执行构造函数内部的代码(函数体的代码块)
- 若函数体内部的代码块没有显式返回一个非空对象,则默认返回新创建的这个对象
创建对象 - 构造函数
function Coder(name, age, height) {
this.name = name
this.age = age
this.height = height
this.writeCode = function() {
console.log('写代码~')
}
}
var coder1 = new Coder('张三', 18, 1.8)
console.log(coder1) // Coder {name: '张三', age: 18, height: 1.8, writeCode: ƒ}使用构造函数创建对象:
对象有明确的类型(实际是
constructor的属性,这个后续再探讨)
这是工厂函数做不到的
事实上构造函数还有很多其他的特性:
如:原型、原型链、实现继承的方案
比如 ES6 中类、继承的实现
但它也有问题:
方法会被重复创建(后续由 prototype 解决)
七. 函数本身也是对象
函数的本质:也是对象
在 JavaScript 中,function 本身也是一种对象类型,同样是创建在堆内存中的,只不过它的结构和普通对象略有不同。
既然是对象,那么函数天然具备两个能力:
✅ 可以被变量引用
✅ 可以拥有自己的属性和方法
从普通对象推导到函数对象
我们先看最常见的对象创建方式:
// 字面量形式
var obj1 = {}
// 构造函数形式
var obj2 = new Object()这两种方式,本质都是:
在堆内存中创建一个对象,然后将引用赋值给变量
同理,函数也是如此:
var foo1 = function () {}
var foo2 = new Function()可以推导出结论:
foo1、foo2 中保存的,都是堆内存中函数对象的引用
函数和普通对象一样,都存在于堆内存中
console.log(typeof foo1) // function⚠️ 注意:
typeof 返回 function,只是为了更精确地区分
从数据结构角度看,函数本质上仍然属于 object 类型
函数对象的内存行为
创建函数时发生了什么?
在堆内存中开辟一块空间,用来存放:
函数体代码
作用域相关信息
一些内部属性(如
[[Call]]、prototype等)
function sayHello() {}此时:
sayHello 是一个变量,存储在栈内存中
指向堆内存中的函数对象
函数调用时发生了什么?
JS 引擎会创建一个函数执行上下文
执行上下文会被压入调用栈(Call Stack)
| 阶段 | 内存位置 | 说明 |
|---|---|---|
| 函数创建 | 堆内存 | 函数对象本身 |
| 函数调用 | 栈内存 | 函数执行上下文 |
函数对象:可以添加属性
既然函数是对象,那么它也可以像普通对象一样添加属性:
function sayHello() {}
sayHello.age = 18
sayHello.name = 'hello'这在语法上是完全合法的。
函数对象 vs 普通对象
var info = {}
info.age = 18
function sayHi() {}
sayHi.age = 18从 JS 引擎的角度来看:
info 是对象、sayHi 也是对象
二者的本质操作是完全一致的
构造函数上的类方法
直接挂载在构造函数(类)本身上的方法,又称为静态方法
通过「类名」直接调用
function Dog() {}
// 构造函数上(类上面)添加的函数, 称之为类方法
Dog.running = function() {}
Dog.running()七. 全局对象 window
什么是全局对象 window?
在浏览器环境中,存在一个全局对象:window
window 是 浏览器提供的顶层对象,也是 JavaScript 在浏览器中的 全局宿主对象(host object)
在浏览器中:
全局作用域 ≈ window 对象
所有未被包裹在函数或块级作用域中的标识符,都会尝试挂载或访问 window
⚠️ 注意
"window 是 浏览器特有 的"
"在 Node.js 中,对应的全局对象是 global(不是 window)"
作用一:作为作用域链的“最终查找对象”
JavaScript 在访问变量 / 函数时,会遵循 作用域链(Scope Chain) 的查找规则:
当前作用域 → 上层作用域 → ... → 全局作用域(window)
如果在当前作用域和其父级作用域中都找不到对应标识符,会一层层往上查找,最终会尝试从 window 对象上查找
var name = 'later' // 挂载到全局对象 window 上 => window.name
function foo() {
console.log(name) // 在当前作用域中找不到变量 name,往上查找到 window.name
}
foo() // later作用二:浏览器内置 API 的统一入口
浏览器为 JavaScript 提供的 大量内置 API 都挂载在 window 对象上
// 常见示例:
window.setTimeout
window.setInterval
window.console
window.location
window.history
window.document
window.alert在实际使用中,window 通常可以省略不写
setTimeout(() => {}, 1000)
// 等价于
window.setTimeout(() => {}, 1000)这是因为 JS 在解析标识符时,会自动从全局对象中查找
var 声明的“历史遗留问题”
在全局作用域中,var 声明的变量,会被 自动添加到 window 对象上,这是 JavaScript 语言早期设计上的缺陷。
console.log(window.a) // undefined
var a = 10
console.log(window.a) // 10同理,使用 function 声明的全局函数,也会成为 window 的属性
function foo() {}
console.log(window.foo === foo) // true⚠️ 注意
ES6 之后的 let、const 声明的全局变量,不会挂载到 window 上。
八. Console 输出行为详解
一个容易踩坑的现象
var obj = { name: 'later' }
console.log(obj)
obj.name = 'baby'很多人在控制台中展开 obj 时,会看到:
{ name: 'baby' }从而产生疑问:
❓ 我明明是在修改之前就 console.log(obj) 了,为什么看到的是修改后的值?
这并不是 JS 的问题,而是浏览器 DevTools 的设计行为。
控制台输出对象的本质:引用 + 延迟查看
当你执行:
console.log(obj)浏览器实际做的是:
保存一份对该对象的“引用”,而不是当时对象的完整值副本
当你稍后在控制台中点击展开该对象时:
浏览器会重新读取该对象当前的属性值并展示
所以你看到的:
是展开时的当前状态
而不是 console.log 执行那一刻的状态
window 对象的经典示例
console.log(window)
var msg = '000'在控制台中展开 window,你会看到:
window.msg === '000'即使 console.log(window) 写在 msg 定义之前
原因
window 是一个巨大的全局对象
浏览器对其采用了实时视图(Live View)策略
后续对 window 的任何修改,都会反映到之前打印的 window 引用上
⚠️ 这是 DevTools 的调试优化行为,不是 JS 语言规范
如何得到“打印当时的真实值”?
使用 JSON 快照法
var obj = { name: 'later' }
console.log(obj) // devtools 中展开时显示 { name: 'baby' }
console.log('----')
console.log(
JSON.parse(JSON.stringify(obj))
) // devtools 中展开时显示永远是 { name: 'later' }
obj.name = 'baby'

这个写法的本质是:
JSON.stringify:把对象序列化为字符串(值拷贝)
JSON.parse:再从字符串生成一个全新的对象
最终打印的是:
一个与原对象“值相同、引用无关”的新对象
因此:
后续对原对象的修改,不会影响这次打印结果
JSON.stringify 的重要限制
⚠️ JSON 序列化 不是通用的深拷贝方案
undefined、function、symbol,在JSON.stringify过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)
对象中:
JSON.stringify({ a: undefined, b: () => {}, c: Symbol() })
// '{}'数组中:
JSON.stringify([undefined, function(){}, Symbol()])
// '[null,null,null]'浏览器为什么要这样设计?
原因只有一个:提升调试体验
对象往往很大
复制完整快照成本高
实时查看更方便追踪变化
但代价是:
console.log 对象 ≠ 输出当时的值
避免使用
console.log(obj),推荐使用console.log(JSON.parse(JSON.stringify(obj))), 除非你能确保后续不会修改obj
这样可以确保
obj的值是console.log语句执行时输出的值, 否则大多数浏览器会提供一个随着值的变化而不断更新的实时视图。这可能不是你想要的
https://developer.mozilla.org/zh-CN/docs/Web/API/console/log#输出对象
🔔 提示
像查找
window对象上的某些属性或函数,打印在控制台的时候,浏览器可能会展示,也可能会隐藏
