Skip to content

Object

实例的创建

js
let object = {}; // 字面量
new Object(any); // 面向对象的实例化方式。构造出any对应类型的对象实例,俗称装包
Object.prototype.constructor(any); // 函数式的实例化方式,调用返回。效果同上。
Object(any); // 同上。Object === Object.prototype.constructor
exercises
js
/**
 * 实例的创建
 */

test("实例的原型(原型属性指向)是Object", () => {
  expect(Object.getPrototypeOf({})).not.toBeNull();
  expect(Object.getPrototypeOf(new Object())).not.toBeNull();
  expect(Object.getPrototypeOf(Object())).not.toBeNull();
  expect(Object.getPrototypeOf(Object.prototype.constructor())).not.toBeNull();
});
test("Object.create(null, {}) 创建没有原型(原型属性指向为空)的实例", () => {
  const object = Object.create(null, {});
  expect(Object.getPrototypeOf(object)).toBeNull();
});
js
const { constructor } = Object.prototype;

/**
 * Object.constructor(value) 对象实例的构造器
 * @param {Any} value
 * @returns {Object}
 */

describe("Object.constructor(value),测试不同类型的实参", () => {
  test("若value为基本类型,则返回其包装对象(即类型一致、值一致)", () => {
    expect(constructor(Symbol("1")) instanceof Symbol).toBeTruthy();
    expect(constructor(true) instanceof Boolean).toBeTruthy();
    expect(constructor(1n) instanceof BigInt).toBeTruthy();
    expect(constructor(1) instanceof Number).toBeTruthy();
    expect(constructor("") instanceof String).toBeTruthy();
    expect(constructor(null)).toEqual({}); // null的包装对象是空对象{}
    expect(constructor(void 0)).toEqual({}); // undefined的包装对象是空对象{}
    expect(constructor()).toEqual({}); // 缺失参数,返回空对象{}
  });
  test("若value为对象类型(引用),则返回其自身", () => {
    const object = {};
    const array = [];
    const fn = function () {};
    expect(constructor(object)).toBe(object);
    expect(constructor(array)).toBe(array);
    expect(constructor(fn)).toBe(fn);
  });
});

实例的状态

属性表的可操作权限,简称为实例的状态

实例状态\属性表权限record addrecord deletefield modifyvalue modify机制
可扩展状态(extensible,default)yesyesyesyesif( [[Extensible]] === true&& descriptor.configurable === true&& descriptor.writable === true )
不可扩展状态 (non-extensible)noyesyesyesif( [[Extensible]] === false&& descriptor.configurable === true&& descriptor.writable === true )
封存态(sealed)nononoyesif( [[Extensible]] === false&& descriptor.configurable === false&& descriptor.writable === true )
冻结态(frozen)nonononoif( [[Extensible]] === false&& descriptor.configurable === false&& descriptor.writable === false )

说明:

  1. [[Extensible]]为实例的内部属性,决定了其属性表可否扩展(添加属性)
  2. 属性描述符的configurable决定了该属性可否删除或重新定义(删除和重新定义)
  3. 属性描述符的writable决定了该属性可否写入
  4. 由字面量或构造器实例化时,三者均是 true
  5. 状态只能从上往下改变,不可逆!

改变实例状态的方法

ecmaapi机制状态
5Object.preventExtensions(obj) [💥]属性表的可扩展关闭 [[Extensible]] = false不可扩展态
5Object.seal(obj) [💥]属性表的可配置关闭 descriptor.configurable = false封存态
5Object.freeze(obj) [💥]属性表的可写入关闭 descriptor.writable = false冻结态
5Object.isExtensible(obj)if([[Extensible]] === true是否为可扩展态
5Object.isSealed(obj)if([[Extensible]] === false && descriptor.configurable === false是否为封存态
5Object.isFrozen(obj)if([[Extensible]] === false && descriptor.configurable === false && descriptor.writable === false是否为冻结态
💥🎹own/proto
方法有副作用键支持 Symbol遍历自身属性/遍历自身及原型链所有属性
exercises
js
const {
  preventExtensions,
  seal,
  freeze,
  isExtensible,
  isSealed,
  isFrozen,
  getOwnPropertyDescriptor,
  getOwnPropertyDescriptors,
} = Object;

/**
 * 1.Object.preventExtensions(obj) 属性表不可扩展。记录不能添加
 * 2.Object.seal(obj) 属性表封存。记录不能添加、删除,字段不能改
 * 3.Object.freeze(obj) 属性表冻结,只读。
 * @param {Object} obj
 * @return {Object} 引用
 */

describe("实例状态的改变", () => {
  test("实例可扩展时", () => {
    const object = { name: "status" };
    expect(getOwnPropertyDescriptor(object, "name")).toMatchObject({
      configurable: true,
      writable: true,
      enumerable: true,
    });
    expect(isExtensible(object)).toBeTruthy(); // 查看实例
    expect(isSealed(object)).not.toBeTruthy();
    expect(isFrozen(object)).not.toBeTruthy();
  });
  test("实例不可扩展时", () => {
    const object = { name: "status" };
    preventExtensions(object);
    expect(getOwnPropertyDescriptor(object, "name")).toMatchObject({
      configurable: true,
      writable: true,
      enumerable: true,
    });
    expect(isExtensible(object)).not.toBeTruthy(); // !
    expect(isSealed(object)).not.toBeTruthy();
    expect(isFrozen(object)).not.toBeTruthy();
  });
  test("实例封存态时", () => {
    const object = { name: "status" };
    seal(object);
    expect(getOwnPropertyDescriptor(object, "name")).toMatchObject({
      configurable: false, // !
      writable: true,
      enumerable: true,
    });
    expect(isExtensible(object)).not.toBeTruthy(); // !
    expect(isSealed(object)).toBeTruthy();
    expect(isFrozen(object)).not.toBeTruthy(); // !
  });
  test("实例冻结态时", () => {
    const object = { name: "status" };
    freeze(object);
    expect(getOwnPropertyDescriptor(object, "name")).toMatchObject({
      configurable: false, // !
      writable: false, // !
      enumerable: true,
    });
    expect(isExtensible(object)).not.toBeTruthy(); // !
    expect(isSealed(object)).toBeTruthy(); // !
    expect(isFrozen(object)).toBeTruthy(); // !
  });
});

属性描述符(集)

使用"属性描述符"数据结构,来创建实例(定义对象实例的属性)

ecmaapidescribe
5Object.create(proto[,propDescriptors]) [🎹]创建实例。根据指定的原型对象和属性描述符集
5Object.defineProperty(obj,propName,propDescriptor) [💥,🎹]定义单个属性,到对象实例上去
5Object.defineProperties(obj,propDescriptors) [💥,🎹]定义属性集合,到对象实例上去
5Object.getOwnPropertyDescriptor(obj) [🎹,own]查询对象的属性描述符
5Object.getOwnPropertyDescriptors(obj) [🎹,own]查询对象的属性描述符集
1Object.prototype.propertyIsEnumerable(propName) [🎹]属性可枚举(自身的)
💥🎹own/proto
方法有副作用键支持 Symbol遍历自身属性/遍历自身及原型链所有属性
exercises
js
const { defineProperty, defineProperties } = Object;

/**
 * Object.defineProperty(obj,propName,propDescriptor) 给对象实例定义一个属性
 * @param {Object} obj - 引用对象实例
 * @param {Descriptor} Descriptor - 属性描述符
 * @returns {Object} 引用
 * --------------------------------------------
 * Object.defineProperties(obj,propDescriptors) 给对象实例定义多个属性
 * @param {Object} obj - 引用对象实例
 * @param {PropDescriptors} propDescriptors - 属性描述符集
 * @returns {Object} 引用
 */

describe("defineProperties接口,支持Symbol键", () => {
  test("", () => {
    let object = {};
    const skey = Symbol("1");
    defineProperties(object, { [skey]: { value: "symbol" } });
    expect(object[skey]).toBe("symbol");
  });
});
describe("属性描述符的缺省值", () => {
  test("defineProperty接口,全为false", () => {
    let object = {};
    defineProperty(object, "key", { value: "value" });
    const { writable, configurable, enumerable } =
      Object.getOwnPropertyDescriptor(object, "key");
    expect(writable).toBeFalsy();
    expect(configurable).toBeFalsy();
    expect(enumerable).toBeFalsy();
  });
  test("create接口,全为false", () => {
    let object = Object.create(null, { key: { value: "value" } });
    const { writable, configurable, enumerable } =
      Object.getOwnPropertyDescriptor(object, "key");
    expect(writable).toBeFalsy();
    expect(configurable).toBeFalsy();
    expect(enumerable).toBeFalsy();
  });
  test("字面量,全为true", () => {
    let object = { key: "value" };
    const { writable, configurable, enumerable } =
      Object.getOwnPropertyDescriptor(object, "key");
    expect(writable).toBeTruthy();
    expect(configurable).toBeTruthy();
    expect(enumerable).toBeTruthy();
  });
});

describe("属性描述符enumerable为false,该属性没有枚举权限", () => {
  const object = {};
  defineProperties(object, {
    enu: {
      enumerable: true,
    },
    noenu: {},
  });
  test("for接口枚举不到该属性", () => {
    let keys = [];
    for (let key in object) {
      keys.push(key);
    }
    expect(keys.includes("noenu")).toBeFalsy();
  });
  test("方法接口枚举不到该属性", () => {
    const keys = Object.keys(object);
    expect(keys.includes("noenu")).toBeFalsy();
  });
});

describe("属性描述符configurable为false,属性没有配置权限", () => {
  const object = {};
  defineProperties(object, {
    key: {},
  });
  test("该属性重新定义时报错", () => {
    expect(() => {
      defineProperties(object, {
        key: { configurable: true },
      });
    }).toThrow(TypeError);
  });

  test("该属性被删除时报错", () => {
    expect(() => {
      delete object.key;
    }).toThrow(TypeError);
  });
});

describe("属性描述符writable为false,属性没有写入权限", () => {
  const object = {};
  defineProperties(object, { key: { writable: false } });
  test("该属性值写入时报错", () => {
    expect(() => {
      object.key = 0;
    }).toThrow(TypeError);
  });
});

方法

ecmaapidescribenote
2015Object.assign(target[,...sources])
[💥, 🎹, own]
分配sources属性给target对象sources自右向左读取,属性为自身可枚举
2017Object.keys(obj) [own]键名数组不返回:键名是Symbol类型的、不可枚举类型的
2017Object.values(obj) [own]键值数组同上
2017Object.entries(obj) [own]转换object为entries数据结构不返回:键名是Symbol类型的
2017Object.fromEntries(obj) [🎹]转换entries为object数据结构支持Symbol类型的值
2015Object.setPrototypeOf(obj,proto) [💥]设置原型本质是修改内部属性[[Prototype]],因性能问题不推荐,推荐使用 create 来创建新对象
2015Object.getPrototypeOf(obj) [own]查找原型自身的原型对象,不会向上追溯
2015Object.prototype.isPrototypeOf(obj,proto)判断原型等同于 Object.prototype.getPrototypeOf(obj) === proto
2015Object.getOwnPropertySymbols(obj)
[🎹, own]
获取自身属性(Symbol类型)数组仅仅返回键类型为Symbol的
5Object.getOwnPropertyNames(obj) [own]获取自身属性(String类型)数组仅仅返回键类型为String的
3Object.prototype.hasOwnProperty(obj,propName)自身属性中有没有该属性已废除。推荐Object.hasOwn
2015Object.hasOwn(obj,propName) [🎹, own]自身属性中有没有该属性仅返回自身属性
2015Object.is(value1,value2)两个栈值是否相同与===的区别:前者将0、-0视为相同,将NaN与NaN视为不相同
1Object.prototype.toLocaleString()对象的本地化字符串表示参考国际化
1Object.prototype.toString()对象类型的字符串表示Object.prototype.toString.call(any) // 返回'[object 类型]'
1Object.prototype.valueOf()将原始值转化为对应类型的对象/或者相反Object.prototype.valueOf.call(1) // number object
💥🎹own/proto
方法有副作用键支持 Symbol遍历自身属性/遍历自身及原型链所有属性
exercises
js
const { assign } = Object;

/**
 * Object.assign(targetObj[,...sourceObjs]) 向左批量复制自身可枚举属性给target。支持Symbol类型属性
 * @param {Object} targetObj 引用对象
 * @param {Object} sourceObjs 待复制属性的对象
 * @returns {Object} 引用对象本身
 * 机制:source上使用原型的getter,target上使用原型的setter。因此原型属性的复制要避免使用该方法,或者原型对象避免成为targetObj。
 * 若要对原型的属性进行复制,请使用getOwnPropertyDescriptor/Object.defineProperty组合。
 */
test("支持symbol属性", () => {
  let target = {};
  const skey = Symbol(1);
  const source = { [skey]: 2 };
  assign(target, source);
  expect(target[skey]).toBe(2);
});
test("向左进行批量属性覆盖", () => {
  let target = { name: "target" };
  const source1 = { name: "source1", a: 1 };
  const source2 = { name: "source2", b: 2 };
  assign(target, source1, source2);
  expect(target).toEqual({
    name: "source2",
    a: 1,
    b: 2,
  });
});
js
const { create } = Object;

/**
 * Object.create(proto[,propDescriptors]) 创建对象,支持symbol
 * @param {Object} proto - 原型对象
 * @param {PropDescriptors|Object} propDescriptors - 属性描述符集
 * @returns {Object}
 */

test("create方法,支持Symbol键", () => {
  const skey = Symbol(1);
  let object = Object.create(null, { [skey]: { value: "symbol" } });
  expect(object[skey]).toBe("symbol");
});
js
const { entries, fromEntries } = Object;

/**
 * Object.entries(object) 数据结构转换(object-->entries)
 * @param {Object} object
 * @returns {Entries}
 */
/**
 * Object.fromEntries(entries) 数据结构转换(entries-->object),支持symbol
 * @param {Entries} entries
 * @returns {Object}
 */

test("entries方法不支持symbol,采用忽略方式", () => {
  const object = { [Symbol(1)]: 1, str: 2 };
  expect(entries(object)).toEqual([["str", 2]]); // 只返回字符串类型的键值对
});
test("fromEntries方法不支持symbol,采用报错方式", () => {
  const skey = [Symbol(1)];
  const entries = [[[skey], 1]];
  expect(() => {
    fromEntries(entries);
  }).toThrow();
});
js
const { is } = Object;

/**
 * Object.is(value1,value2) 两个栈值是否相同
 * @param {Any} value
 * @returns {Boolean}
 */

describe("Object.is判断2个参数的栈值是否相同", () => {
  test("原始类型和值类型的比较,考量数据和类型", () => {
    expect(is(1, 1)).toBeTruthy();
    expect(is(1, 1n)).toBeFalsy(); // 类型不同
    expect(is(0, -0)).toBeFalsy(); // 数值符号不同
    expect(is(0, +0)).toBeTruthy();
    expect(is(NaN, NaN)).toBeTruthy();
    expect(is(void 0, void 0)).toBeTruthy();
    expect(is(null, null)).toBeTruthy();
    expect(is(Symbol("1"), Symbol("1"))).toBeFalsy(); // 值不同
  });
  test("引用类型的比较,false", () => {
    const arr = [];
    expect(is(arr, arr)).toBeTruthy(); // 地址相同
    expect(is(arr, [])).toBeFalsy(); // 地址不同
  });
});

Released under the MIT License.