对象✅
原型链?
答案
每个由构造函数创建的对象都有一个隐式引用 [[Prototype]]
(称为该对象的原型)。该引用指向其构造函数的 prototype
属性的值。
而 prototype
的值内部也会存在 [[Prototype]]
指向其自身原型,如此层层递进,这便构成了原型链。
js 通过原型链实现继承机制,当访问对象的属性或方法时,js 引擎会首先在对象自身查找,如果没有找到,则会基于内部的 [[Prototype]]
逐级向上查找。
直到找到该属性或方法,或者到达原型链的终点 null
。
简单理解,可以把 [[Prototype]]
当做链表的 next
属性,原型链搜索就是链表 next
遍历查找的过程
有如下方式操作原型链
方法 | 作用 | 注意事项 |
---|---|---|
Object.getPrototypeOf(obj) | 获取对象的原型 | - |
.__proto__ | 获取或设置对象的原型 | 为访问器属性,不建议直接修改 |
new | 创建一个新对象,并将其原型设置为构造函数的 prototype | - |
constructor.prototype | 获取构造函数的原型 | prototype 不可删除, 可通过 protoype.xx 动态追加属性 |
Object.setPrototypeOf(obj, prototype) | 设置对象的原型 | 性能较差,不建议使用 |
Object.create(proto, [propertiesObject]) | 创建一个新对象,指定其原型 | - |
class extends | 创建一个继承自另一个类的类 | 本质是原型链语法糖引擎内部实现了父类和子类的原型链绑定 |
延伸阅读
new
答案
new 用来简化对象的创建相比普通函数,内部的核心操作包括,假设构造函数为 Foo
,则执行 new Foo()
时会涉及的核心核心操作包括
- 赋值
new.target = Foo
指向构造函数, 调用规范 Construct 方法 - 创建一个对象 o
- 将对象
0.__proto__ = Foo.prototype
绑定原型链,调用规范 OrdinaryCreateFromConstructor - 赋值
this = o
调用规范[[OrdinaryCallBindThis]]
4延伸阅读
- new operator 规范对 new 操作的描述
原始封装类型
答案
原始封装类型(Primitive Wrapper Types)是 JavaScript 中为原始值(如 string、number、boolean)提供对象包装的内置对象。主要有三种:String
、Number
和 Boolean
。
- 原始类型:
string
、number
、boolean
、null
、undefined
、symbol
、bigint
。 - 原始封装类型:对应的对象类型,分别是
String
、Number
、Boolean
。
当你对原始值调用属性或方法时,JS 会临时用对应的封装类型包装它。例如:
const str = 'hello'
console.log(str.toUpperCase()) // 输出 "HELLO"
这里 "hello"
是原始字符串,但 JS 会自动用 String
对象包装它,使你可以调用 toUpperCase()
。
你可以显式地用 new
关键字创建封装对象:
const s = new String('abc')
const n = new Number(123)
const b = new Boolean(false)
但实际开发中不推荐这样做,因为这样创建的是对象而不是原始值:
typeof 'abc' // "string"
typeof new String() // "object"
注意事项
- 自动装箱:JS 会自动把原始值临时转换为对应的对象,调用完方法后立即销毁。
- 不要用 new 创建封装对象:用
new String()
、new Number()
、new Boolean()
得到的是对象,不是原始值,可能导致类型判断出错。 - 比较时的陷阱:
const a = 'abc'
const b = new String('abc')
console.log(a == b) // true(值相等)
console.log(a === b) // false(类型不同)
- 原始封装类型让原始值也能像对象一样调用方法。
- 推荐直接使用原始类型,不要用
new
创建封装对象。 - 注意类型判断和比较时的差异。
用过 Object 哪些方法
答案
静态属性和方法
方法/属性 | 描述 |
---|---|
Object.assign(target, ...sources) | 将一个或多个源对象的可枚举属性复制到目标对象,并返回目标对象 |
Object.create(proto, [propertiesObject]) | 使用指定的原型对象和属性创建一个新对象 |
Object.defineProperties(obj,descriptors ) | 定义或修改对象的多个属性 |
Object.defineProperty(obj, prop, descriptor) | 定义对象中的新属性或修改现有属性的配置 |
Object.freeze(obj) | 冻结一个对象,使其属性无法修改、添加或删除 |
Object.entries(obj) | 返回一个给定对象自身可枚举属性的 [key, value] 数组 |
Object.fromEntries() | 将一个键值对列表转换为对象,例如 Map 转换为对象 |
Object.getOwnPropertyDescriptor(obj, prop) | 返回指定对象上一个自有属性对应的属性描述符 |
Object.getOwnPropertyDescriptors(obj) | 返回指定对象所有自有属性对应的属性描述符 |
Object.getOwnPropertyNames(obj) | 返回指定对象所有自有属性的名称 |
Object.getOwnPropertySymbols(obj) | 返回指定对象所有自有属性的 Symbol 名称 |
Object.getPrototypeOf(obj) | 返回指定对象的原型(__proto__ ) |
Object.groupBy(obj,cb) | 将可迭代的对象各项按照 cb 函数返回 key 进行分组 |
Object.hasOwn(obj, prop) | 检查对象是否具有指定的属性(自身属性,不包括原型链) |
Object.is(value1, value2) | 判断两个值是否相同, 注意相比严格相当核心区别 +0 === -0 返回 false, NaN === NaN 返回 true |
Object.isExtensible(obj) | 判断对象是否可扩展 |
Object.isFrozen(obj) | 判断对象是否被冻结 |
Object.isSealed(obj) | 判断对象是否被封闭 |
Object.keys(obj) | 返回一个由给定对象的所有可枚举自身属性的名称组成的数组 |
Object.preventExtensions(obj) | 防止向对象添加新属性 |
Object.seal(obj) | 封闭一个对象,防止向对象添加删除属性,但允许修改现有属性 |
Object.setPrototypeOf(obj, proto) | 设置指定对象的原型(__proto__ ) |
Object.values(obj) | 返回一个给定对象所有可枚举属性值的数组 |
原型上方法和属性
方法/属性 | 描述 |
---|---|
Object.prototype.constructor | 指向创建此对象的构造函数。 |
Object.prototype.__proto__ | 指向对象内部的 [[Prototype]] 属性, 是一个访问器属性 |
Object.prototype.hasOwnProperty(prop) | 返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。注意由于原型可以被重写,所以后续使用 Object.hasOwn() 方法更为安全。 |
Object.prototype.isPrototypeOf(object) | 返回一个布尔值,指示调用它的对象是否在指定对象的原型链中。 |
Object.prototype.propertyIsEnumerable(prop) | 返回一个布尔值,表示指定的属性是否可枚举。 |
Object.prototype.toLocaleString() | 返回一个表示对象的字符串,该字符串与执行环境的区域设置相对应。 |
Object.prototype.toString() | 返回一个表示该对象的字符串。 |
Object.prototype.valueOf() | 返回指定对象的原始值。 |
此外还包括一些废弃的方法
方法/属性 | 描述 |
---|---|
Object.prototype.__defineGetter__(prop, getter) | 定义一个属性的 getter 方法。已废弃,建议使用 Object.defineProperty() 。 |
Object.prototype.__defineSetter__(prop, setter) | 定义一个属性的 setter 方法。已废弃,建议使用 Object.defineProperty() 。 |
Object.prototype.__lookupGetter__(prop) | 返回指定属性的 getter 方法。已废弃,建议使用 Object.getOwnPropertyDescriptor() 。注意对于原型链上访问器属可以检查但是 getOwnPropertyDescriptor 只能检查子有属性 |
Object.prototype.__lookupSetter__(prop) | 返回指定属性的 setter 方法。已废弃,建议使用 Object.getOwnPropertyDescriptor() 。注意对于原型链上访问器属可以检查但是 getOwnPropertyDescriptor 只能检查子有属性 |
延伸阅读
- MDN Object 参考 详细列举了 Object 的所有方法和属性。
什么是属性描述符
答案
属性描述符(Property Descriptor)是一个对象,用于描述对象属性的特性。它包含了属性的各种信息,如是否可写、是否可枚举、是否可配置等。 属性描述符分为两种类型:数据描述符和访问器描述符。 数据描述符用于描述属性的值,而访问器描述符用于描述通过 getter 和 setter 方法访问的属性。 数据描述符包含以下属性:
属性 | 描述 |
---|---|
value | 属性的值 |
writable | 属性是否可写(默认为 false) |
enumerable | 属性是否可枚举(默认为 false) |
configurable | 属性是否可配置(默认为 false) |
访问器描述符包含以下属性:
属性 | 描述 |
---|---|
get | 属性的 getter 方法 |
set | 属性的 setter 方法 |
enumerable | 属性是否可枚举(默认为 false) |
configurable | 属性是否可配置(默认为 false) |
属性描述符可以通过 Object.getOwnPropertyDescriptor() 方法获取,也可以通过 Object.defineProperty() 方法设置。 |
instanceof 的作用
答案
instanceof 操作符用来检测对象的原型链是否包含构造函数的 prototype 属性。其返回值是一个布尔值。 可以通过 Symbol.hasInstance 来自定义该运算符的行为。
示例代码
延伸阅读
如何判定一个属性来自于对象本身, 还是来自于原型链
答案
方法 | 作用 | 是否检查原型链 | 示例 |
---|---|---|---|
Object.prototype.hasOwnProperty(prop) | 判断属性是否为对象自身属性 | 否 | obj.hasOwnProperty('key') |
in 操作符 | 判断属性是否存在于对象或其原型链上 | 是 | 'key' in obj |
Object.getOwnPropertyDescriptor(obj, prop) | 获取对象自身属性的描述符,若为自身属性返回描述符,否则返回 undefined | 否 | Object.getOwnPropertyDescriptor(obj, 'key') |
Object.hasOwn(obj, prop) | 检查对象是否具有指定的自身属性 | 否 | Object.hasOwn(obj, 'key') |
hasOwn 是 ES2022 引入的一个静态方法,用于替换 hasOwnProperty()
方法。因为对象的原型可能为 null 导致无法使用 hasOwnProperty()
方法。
- 若
obj.hasOwnProperty('key')
为true
,则属性来自对象本身。 - 若
'key' in obj
为true
且obj.hasOwnProperty('key')
为false
,则属性来自原型链。
防止对象被篡改有哪些方式
答案
防止对象被篡改的常用方式如下表:
方法 | 作用 | 可修改属性值 | 可添加/删除属性 | 递归作用于嵌套对象 | 备注 |
---|---|---|---|---|---|
Object.freeze() | 冻结对象,禁止添加、删除、修改属性 | 否 | 否 | 否(需手动递归) | 最严格,浅冻结 |
Object.seal() | 禁止添加/删除属性,但可修改已有属性 | 是 | 否 | 否 | 仅阻止结构变更 |
Object.preventExtensions() | 禁止添加新属性,已有属性可删可改 | 是 | 仅可删 | 否 | 结构不可扩展 |
const 声明 | 变量引用不可变,属性仍可改 | 是 | 是 | - | 仅保护变量绑定,不保护内容 |
Proxy 拦截 | 通过代理拦截操作,自定义只读/不可变行为 | 取决于实现 | 取决于实现 | 可递归实现 | 灵活,适合复杂场景 |
注意:上述方法默认仅作用于对象本身(浅层),如需深度保护需递归处理嵌套对象。Proxy
可实现更灵活的防篡改策略。