跳到主要内容

对象✅

原型链?

答案

每个由构造函数创建的对象都有一个隐式引用 [[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() 时会涉及的核心核心操作包括

  1. 赋值 new.target = Foo 指向构造函数, 调用规范 Construct 方法
  2. 创建一个对象 o
  3. 将对象 0.__proto__ = Foo.prototype 绑定原型链,调用规范 OrdinaryCreateFromConstructor
  4. 赋值 this = o 调用规范 [[OrdinaryCallBindThis]]

4延伸阅读

原始封装类型

答案

原始封装类型(Primitive Wrapper Types)是 JavaScript 中为原始值(如 string、number、boolean)提供对象包装的内置对象。主要有三种:StringNumberBoolean

  • 原始类型stringnumberbooleannullundefinedsymbolbigint
  • 原始封装类型:对应的对象类型,分别是 StringNumberBoolean

当你对原始值调用属性或方法时,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 只能检查子有属性

延伸阅读

什么是属性描述符

答案

属性描述符(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 来自定义该运算符的行为。

示例代码

describe('instanceof 运算符', () => {
  test('对象是其构造函数的实例', () => {
    class Animal {}
    const dog = new Animal()
    expect(dog instanceof Animal).toBe(true)
  })

  test('对象是其父构造函数的实例', () => {
    class Animal {}
    class Dog extends Animal {}
    const myDog = new Dog()
    expect(myDog instanceof Animal).toBe(true)
    expect(myDog instanceof Dog).toBe(true)
  })

  test('原始类型不通过 instanceof 检查', () => {
    expect(123 instanceof Number).toBe(false)
    expect('hello' instanceof String).toBe(false)
    expect(true instanceof Boolean).toBe(false)
  })

  test('包装对象是其构造函数及 Object 的实例', () => {
    const stringObject = new String('String created with constructor')
    expect(stringObject instanceof String).toBe(true)
    expect(stringObject instanceof Object).toBe(true)
  })

  test('null 不通过 instanceof 检查', () => {
    expect(null instanceof Object).toBe(false)
  })

  test('undefined 不通过 instanceof 检查', () => {
    expect(undefined instanceof Object).toBe(false)
  })

  test('适用于内置类型', () => {
    const arr = []
    const date = new Date()
    expect(arr instanceof Array).toBe(true)
    expect(arr instanceof Object).toBe(true) // 数组也是对象
    expect(date instanceof Date).toBe(true)
    expect(date instanceof Object).toBe(true) // 日期也是对象
  })

  test('原型链不匹配则返回 false', () => {
    class A {}
    class B {}
    const aInstance = new A()
    expect(aInstance instanceof B).toBe(false)
  })

  test('Object.create(null) 创建的对象不是 Object 实例', () => {
    const obj = Object.create(null)
    // obj 的原型是 null,不在 Object.prototype 的原型链上
    expect(obj instanceof Object).toBe(false)
  })

  test('instanceof 与函数构造函数', () => {
    function Person (name) {
      this.name = name
    }
    const person1 = new Person('Alice')
    expect(person1 instanceof Person).toBe(true)
    expect(person1 instanceof Object).toBe(true)
  })

  test('修改 prototype 会影响 instanceof', () => {
    function C () {}
    function D () {}

    const o = new C()
    expect(o instanceof C).toBe(true) // o 的 [[Prototype]] 是原始 C.prototype

    C.prototype = {} // 重新赋值 C.prototype
    const o2 = new C() // o2 的 [[Prototype]] 是新的 C.prototype

    expect(o2 instanceof C).toBe(true) // o2 检查的是新的 C.prototype
    // o 的原型链中是旧的 C.prototype,它与新的 C.prototype 不同
    expect(o instanceof C).toBe(false)

    D.prototype = new C() // D.prototype 是 C 的一个实例 (使用新的 C.prototype)
    // D.prototype 的 [[Prototype]] 是新的 C.prototype
    const o3 = new D() // o3 的 [[Prototype]] 是 D.prototype
    expect(o3 instanceof D).toBe(true)
    expect(o3 instanceof C).toBe(true) // o3 -> D.prototype -> 新 C.prototype
  })

  test('Object.setPrototypeOf 会影响 instanceof', () => {
    class A {}
    const obj = {}
    expect(obj instanceof A).toBe(false)
    Object.setPrototypeOf(obj, A.prototype) // 改变 obj 的原型
    expect(obj instanceof A).toBe(true)
  })

  test('instanceof 与绑定函数', () => {
    class Base {}
    const BoundBase = Base.bind(null) // .bind(null, arg1, arg2) 同样有效
    const instance = new Base()
    // instanceof 检查的是 BoundBase 的 [[BoundTargetFunction]].prototype,即 Base.prototype
    expect(instance instanceof BoundBase).toBe(true)
  })

  test('Symbol.hasInstance 基本用法', () => {
    class Forgeable {
      static isInstanceFlag = Symbol('isInstanceFlag')

      static [Symbol.hasInstance] (obj) {
        // 确保 obj 不为 null/undefined,是对象,并且有特定标记
        return !!(obj && typeof obj === 'object' && Forgeable.isInstanceFlag in obj)
      }
    }

    const objWithFlag = { [Forgeable.isInstanceFlag]: true }
    const objWithoutFlag = {}
    expect(objWithFlag instanceof Forgeable).toBe(true)
    expect(objWithoutFlag instanceof Forgeable).toBe(false)
  })

  test.skip('Symbol.hasInstance 覆盖 instanceof 默认行为', () => {
    class MyClass {
      static [Symbol.hasInstance] (instance) {
        // 自定义逻辑:如果 instance 有 canBeInstance 属性,则认为是实例
        return instance && instance.canBeInstance === true
      }
    }

    const objWithProperty = { canBeInstance: true }
    const objWithoutProperty = { canBeInstance: false }
    const regularInstance = new MyClass() // 默认情况下,regularInstance instanceof MyClass 为 true

    // 默认情况下,objWithProperty 不是 MyClass 的实例
    // 但 Symbol.hasInstance 覆盖了此行为
    expect(objWithProperty instanceof MyClass).toBe(true)

    // 默认情况下,objWithoutProperty 不是 MyClass 的实例
    // Symbol.hasInstance 也返回 false
    expect(objWithoutProperty instanceof MyClass).toBe(false)

    // 默认情况下,regularInstance 是 MyClass 的实例
    // 但 Symbol.hasInstance 覆盖了此行为,因为它没有 canBeInstance: true
    expect(regularInstance instanceof MyClass).toBe(false)
  })

  test('instanceof 与 Object.create 继承', () => {
    function Shape () {}
    Shape.prototype.isShape = true

    function Rectangle () {
      Shape.call(this) // 调用父构造函数以继承实例属性
    }

    // 设置继承:Rectangle.prototype 继承自 Shape.prototype
    Rectangle.prototype = Object.create(Shape.prototype)
    Rectangle.prototype.constructor = Rectangle // 修复 constructor 指向,使其指向 Rectangle

    const rect = new Rectangle()

    expect(rect instanceof Rectangle).toBe(true)
    expect(rect instanceof Shape).toBe(true)
    expect(rect instanceof Object).toBe(true)
    expect(rect.isShape).toBe(true)
  })

  test('instanceof 与 ! 运算符优先级', () => {
    class MyClass {}
    const myInstance = new MyClass()

    // 正确检查非实例的方式:使用括号确保 instanceof 先执行
    expect(!(myInstance instanceof MyClass)).toBe(false)
    expect(!(null instanceof MyClass)).toBe(true)

    // 错误方式:! 优先级高于 instanceof (通常不是预期行为)
    // !myInstance 先被求值。如果 myInstance 是对象,!myInstance 是 false。
    // 然后 false instanceof MyClass,结果为 false。
    expect(!myInstance instanceof MyClass).toBe(false)

    const nullVar = null
    // !nullVar 先被求值 (为 true)。
    // 然后 true instanceof MyClass,结果为 false。
    expect(!nullVar instanceof MyClass).toBe(false)
  })
})

Open browser consoleTests

延伸阅读

如何判定一个属性来自于对象本身, 还是来自于原型链

答案
方法作用是否检查原型链示例
Object.prototype.hasOwnProperty(prop)判断属性是否为对象自身属性obj.hasOwnProperty('key')
in 操作符判断属性是否存在于对象或其原型链上'key' in obj
Object.getOwnPropertyDescriptor(obj, prop)获取对象自身属性的描述符,若为自身属性返回描述符,否则返回 undefinedObject.getOwnPropertyDescriptor(obj, 'key')
Object.hasOwn(obj, prop)检查对象是否具有指定的自身属性Object.hasOwn(obj, 'key')
提示

hasOwn 是 ES2022 引入的一个静态方法,用于替换 hasOwnProperty() 方法。因为对象的原型可能为 null 导致无法使用 hasOwnProperty() 方法。

  • obj.hasOwnProperty('key')true,则属性来自对象本身。
  • 'key' in objtrueobj.hasOwnProperty('key')false,则属性来自原型链。

防止对象被篡改有哪些方式

答案

防止对象被篡改的常用方式如下表:

方法作用可修改属性值可添加/删除属性递归作用于嵌套对象备注
Object.freeze()冻结对象,禁止添加、删除、修改属性否(需手动递归)最严格,浅冻结
Object.seal()禁止添加/删除属性,但可修改已有属性仅阻止结构变更
Object.preventExtensions()禁止添加新属性,已有属性可删可改仅可删结构不可扩展
const声明变量引用不可变,属性仍可改-仅保护变量绑定,不保护内容
Proxy拦截通过代理拦截操作,自定义只读/不可变行为取决于实现取决于实现可递归实现灵活,适合复杂场景

注意:上述方法默认仅作用于对象本身(浅层),如需深度保护需递归处理嵌套对象。Proxy 可实现更灵活的防篡改策略。

48%