7. 对象
1 对象的定义
对象有两种定义方式:声明式和构造式:
// 声明式
let myObj = {
name: "Moxy",
};
// 构造式
let myObj = new Object();
myObj.name = "Moxy";
2 对象的类型
数据类型
JavaScript 一共有 8 个基本数据类型:
- 原始类型:Null,Undefined,Boolean,Number,BigInt,String,Symbol;
- 对象类型:Object
引用类型
引用类型都继承自对象类型 Object,换句话说,引用类型的原型对象,其原型链最终都指向 Object.prototype
以下是引用类型:
- 基本包装类型:Boolean,Number,BigInt,String,Symbol;
- Array
- Date
- RegExp
- Function
- Error
- Map
- ...
这些引用类型也可以称为 内置对象,其是由 JavaScript 引擎在运行开头,就通过 object
创建好的一系列类型。所以这些类型的原型链都指向 object.prototype
。
自动装箱与类型判断
// 控制台如下输出:
let str1 = "Moxy";
str1.__proto__; // String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
str1 instanceof String; // false
let str2 = new String("Moxy");
str2.__proto__; // String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
str2 instanceof String; // true
要点 1:定义的两种方式
-
str1
是一个字面量,通过字面量的形式直接定义的基本数据类型 string。 -
str2
是一个对象,通过 new 运算符创建的一个 String 类型的对象。
所以,instanceof
运算符在判断 str1
是否属于 String
的时候,返回了 false
。因为它本就不是通过 包装类型 String 创建的。
要点 2:自动装箱、自动拆箱
基本数据类型 String
,Number
,BigInt
,Symbol
,Boolean
在 必要 时,JavaScript 会自动把字面量转换成一个对应的包装类型对象。在完成对应操作后,就自动还原成原本的字面量形式。这就是自动装箱 / 拆箱。
- ”必要时“ 这个时机的触发,通常是 调用相关方法时触发,比如对字符串的基本操作:
toString()
、length()
、charAt()
,等等都会触发。
所以,文中对 str1.__proto__
触发了自动装箱,先把 str1 装箱为 String 类型的实例化对象,然后通过原型链查找 __proto__
自然可以找到 String.prototype
,即 String 的原型对象。
2 对象的属性
2.1 对象是属性的集合
对象的内容时由一些存储在特定命名位置的、任意类型的值组成的,我们称之为 属性。
JavaScript 中对象独有的特色是:
对象具有高度的动态性,这是因为 JavaScript 赋予了使用者在运行时为对象添改状态和行为的能力。
实际上 JavaScript 对象的运行时是一个 “属性的集合”。
属性以字符串或者 Symbol 为 key,以数据属性特征值或者访问器属性特征值为 value。
JavaScript 中,其实没有 属性 和 方法 的区分。JavaScript 内部只有一个东西,就是属性 —— 一个 key / value 对。
- 如果 value 保存了一个字面量,直接保存在栈内存中。那么保存了一个基本数据类型值(boolean、number、string 等)。
- 如果 value 保存了一个地址值,指向了堆内存中的某个地方。
- 如果指向了一个 function,那么这个属性也是一个 方法;
- 如果指向了一个 array,那么这个属性也是一个 数组;
- ....
这就是为什么在 对象的继承 中,我们分别分析 基本类型属性、引用类型属性 和 方法,继承后是否可以复用了。从根本上,是在直接字面量分析还是地址值。
2.2 属性的访问
属性访问方式有两种:
- 属性访问:
instance1.name
- 键访问:
instance1["name"]
两者的区别:
- 属性访问:代码写起来更便捷,但是
.
操作符要求属性名满足标识符的命名规范; - 键访问:支持更强大。
["..."]
可以接受任意 UTF-8/Unicode 字符串作为属性名,比如带有叹号的变量名,只能通过键访问instance["Super-fun!"]
;- 支持 表达式 访问名,可以把变量名放进去。比如定义
var a = "name"
,那么p1[a]
在运行时转化为p1.name
。- 也成为:可计算属性名,比如
instance1[ 1 + 2 ]
。
- 也成为:可计算属性名,比如
- 在对象中,属性名永远都是字符串,即使通过变量传递过来 number 等其他值,也会被自动的转换为字符串。
2.3 属性的特性
对象的属性,在 ES5 开始新增了属性描 述符,也就是属性的特性。对象属性分为 数据属性 和 访问器属性,其 [[内部特性]] (或者说属性描述符)分别有(2):
数据属性描述符 Data Properties Descriptor:
[[Configurable]]
:属性是否可以:1. 通过delete
删除、或直接重定义;2. 修改它的[[Configurable]]
;3. 修改为访问器属性。[[Enumberable]]
:属性是否可以通过 for-in 遍历。console.log()
[[Writable]]
:属性的值是否可修改。[[Value]]
:属性的值。
访问器属性 Accessor Properties Descriptor:
[[Configurable]]
:属性是否可以:1. 通过delete
删除、或直接重定义;2. 修改它的[[Configurable]]
;3. 修改为数据属性。[[Enumberable]]
:属性是否可以通过 for-in 遍历。[[Get]]
:getter 函数,读取该属性时调用。默认值为 undefined。[[Set]]
:setter 函数,写入该属性时调用。默认值为 undefined。
❗️:vue2 中响应式的实现就是利用了 getter 和 setter。在组件 return 后,vue 会把 return 的对象全部遍历一边。对属性设置额外的 getter 和 setter,实现当某个属性值产生变化时,通过 setter 就会让其他有依赖的属性值一起发生改变,从而实现响应式。
❗️:console.log()
无法打印访问器属性。
❗️:当 enumberable
属性设置为 false 时, chrome 在console.log
时会把该属性的颜色变淡,以提示该属性不可遍历:
var obj = {
name: "ninjee",
age: 18,
};
Object.defineProperty(obj, "address", {
value: "北京市",
});
console.log(obj); // 下图可以看到,address 颜色变淡了
定义特性
当我们只在对象上定义某个属性时,
[[Configurable]]
、[[Enumberable]]
、[[Writable]]
会默认为 true;[[value]]
、[[get]]
、[[set]]
:默认为 undefined;- 可删改
Configurable
、可遍历、可改值;
当我们通过属性描述符(下面的方法)定义属性时,
[[Configurable]]
、[[Enumberable]]
、[[Writable]]
会默认为 false,需要手动修改。
// 定义单个属性的特性
Object.defineProperty(person, "name", {
writable: false, // 不必写,默认就是false
value: "Moxy"
});
// 定义多个属性的特性
Object.defineProperties(person, {
age: {
get() {
return this.age_
}
set(newValue) {
if (newValue > 0) this.age_ = newValue
}
},
age_: {
value: 12,
}
})
特性的报错
[[Configurable]]
:设置为 false 后,是单向操作,无法撤销(无法再重新设置为 true)。- 修改任何特性都会 直接报错,
[[Configurable]]
、[[Enumberable]]
、[[Writable]]
。 delete
删除属性会失败,返回 false 提示删除失败,不报错。
- 修改任何特性都会 直接报错,
[[Writable]]
:设置为 false 后,修改属性值,会 静默失败。严格模式下会 报错。
// configurable = false, 不可以重新修改 Configurable 特性:
Object.defineProperty(person, "name", {
configurable: true,
});
// 报错 Uncaught TypeError: Cannot redefine property: name
// Configurable = false, 不可以 delete 删除属性
delete person.name; // false
// 返回 false 提示删除失败,但不报错。
// writable = false, 不可以重新赋值
person.name = "ninjee"; // 静默失败,严格模式下报错
// 不可以转化为访问器属性
// 可以修改 enumberable、writable、value 等特性