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 add | record delete | field modify | value modify | 机制 |
---|---|---|---|---|---|
可扩展状态(extensible,default) | yes | yes | yes | yes | if( [[Extensible]] === true && descriptor.configurable === true && descriptor.writable === true ) |
不可扩展状态 (non-extensible) | no | yes | yes | yes | if( [[Extensible]] === false && descriptor.configurable === true && descriptor.writable === true ) |
封存态(sealed) | no | no | no | yes | if( [[Extensible]] === false && descriptor.configurable === false && descriptor.writable === true ) |
冻结态(frozen) | no | no | no | no | if( [[Extensible]] === false && descriptor.configurable === false && descriptor.writable === false ) |
说明:
[[Extensible]]
为实例的内部属性,决定了其属性表可否扩展(添加属性)- 属性描述符的
configurable
决定了该属性可否删除或重新定义(删除和重新定义) - 属性描述符的
writable
决定了该属性可否写入 - 由字面量或构造器实例化时,三者均是 true
- 状态只能从上往下改变,不可逆!
改变实例状态的方法
ecma | api | 机制 | 状态 |
---|---|---|---|
5 | Object.preventExtensions(obj) [💥] | 属性表的可扩展关闭 [[Extensible]] = false | 不可扩展态 |
5 | Object.seal(obj) [💥] | 属性表的可配置关闭 descriptor.configurable = false | 封存态 |
5 | Object.freeze(obj) [💥] | 属性表的可写入关闭 descriptor.writable = false | 冻结态 |
5 | Object.isExtensible(obj) | if([[Extensible]] === true | 是否为可扩展态 |
5 | Object.isSealed(obj) | if([[Extensible]] === false && descriptor.configurable === false | 是否为封存态 |
5 | Object.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(); // !
});
});
属性描述符(集)
使用"属性描述符"数据结构,来创建实例(定义对象实例的属性)
ecma | api | describe |
---|---|---|
5 | Object.create(proto[,propDescriptors]) [🎹] | 创建实例。根据指定的原型对象和属性描述符集 |
5 | Object.defineProperty(obj,propName,propDescriptor) [💥,🎹] | 定义单个属性,到对象实例上去 |
5 | Object.defineProperties(obj,propDescriptors) [💥,🎹] | 定义属性集合,到对象实例上去 |
5 | Object.getOwnPropertyDescriptor(obj) [🎹,own] | 查询对象的属性描述符 |
5 | Object.getOwnPropertyDescriptors(obj) [🎹,own] | 查询对象的属性描述符集 |
1 | Object.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);
});
});
方法
ecma | api | describe | note |
---|---|---|---|
2015 | Object.assign(target[,...sources]) [💥, 🎹, own] | 分配sources属性给target对象 | sources自右向左读取,属性为自身可枚举 |
2017 | Object.keys(obj) [own] | 键名数组 | 不返回:键名是Symbol类型的、不可枚举类型的 |
2017 | Object.values(obj) [own] | 键值数组 | 同上 |
2017 | Object.entries(obj) [own] | 转换object为entries数据结构 | 不返回:键名是Symbol类型的 |
2017 | Object.fromEntries(obj) [🎹] | 转换entries为object数据结构 | 支持Symbol类型的值 |
2015 | Object.setPrototypeOf(obj,proto) [💥] | 设置原型 | 本质是修改内部属性[[Prototype]],因性能问题不推荐,推荐使用 create 来创建新对象 |
2015 | Object.getPrototypeOf(obj) [own] | 查找原型 | 自身的原型对象,不会向上追溯 |
2015 | Object.prototype.isPrototypeOf(obj,proto) | 判断原型 | 等同于 Object.prototype.getPrototypeOf(obj) === proto |
2015 | Object.getOwnPropertySymbols(obj) [🎹, own] | 获取自身属性(Symbol类型)数组 | 仅仅返回键类型为Symbol的 |
5 | Object.getOwnPropertyNames(obj) [own] | 获取自身属性(String类型)数组 | 仅仅返回键类型为String的 |
3 | 自身属性中有没有该属性 | 已废除。推荐Object.hasOwn | |
2015 | Object.hasOwn(obj,propName) [🎹, own] | 自身属性中有没有该属性 | 仅返回自身属性 |
2015 | Object.is(value1,value2) | 两个栈值是否相同 | 与===的区别:前者将0、-0视为相同,将NaN与NaN视为不相同 |
1 | Object.prototype.toLocaleString() | 对象的本地化字符串表示 | 参考国际化 |
1 | Object.prototype.toString() | 对象类型的字符串表示 | Object.prototype.toString.call(any) // 返回'[object 类型]' |
1 | Object.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(); // 地址不同
});
});