JS 对象 数组 元组(伪元素) 深浅拷贝 Proxy Reflect Symbol
谈谈你对 Object中获取key的认识
对象的可枚举属性
在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for...in 循环遍历(除非该属性名是一个Symbol)。如果一个属性同时满足以下条件,我们就称它为可枚举属性:
- 可枚举属性是指那些内部 "enumerable" 标志设置为 true 的属性。
- 对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true。
- 但是对于通过 Object.defineProperty 等定义未有属性,该标识值默认为 false。
- 其中js中基本包装类型的原型属性是不可枚举的,如Object, Array, Number等。
- **该属性是对象的自有属性,而不是内置的,**也就是对象本身就包含该属性,包括继承属性,但不是从原型链继承而来。使用 hasOwnProperty() 方法可以检测自有属性;
javascript
for (const key in object) {
if (Object.hasOwnProperty.call(object, key)) {
}
}
- 没有通过Object.defineProperty 来设置属性的enumerable为false的;
查找和判断对象属性
- Object.getOwnPropertySymbols()返回一个全部键为Symbol类型的数组。
- Reflect.ownKeys() 返回一个key组成的数组(包含key为Symbol类型,不可枚举)
- Object.keys() 仅遍历对象本身,并将所有可以枚举的属性组合成一个数组返回(不含继承属性和Symbol等不可枚举的属性)
- Object.getOwnPropertyNames() 返回一个包含对象本身的所有属性名(不包含继承属性和Symbol,但是它会把不可枚举也返回)
- Object.getOwnPropertyDescriptors() 获取所有键key的描述符的对象组合成一个对象返回(包含Symbol和不可枚举的)
- Object.hasOwnProperty()判断对象自身属性中是否具有指定的属性, 返回一个布尔值
- Object.propertyIsEnumerable() 判断此对象是否包含某个属性,并且判断这个属性是否可枚举。
需要注意的是:如果判断的属性存在于Object对象的原型内,不管它是否可枚举都会返回false ⚠️ 注意:( for in )或 (for of )循环拿不到以Symbol作为属性的值(未做过修改,默认的情况下)
jsx
var symbolGender = Symbol("gender")
var symbolName = Symbol("name")
var obj = { age: 18, [symbolName]: "风", }
obj.nationality = "china"
// 将对象转化为迭代器,查处出对象所有自身属性并且yield出去,就可以使用 for...of
Object.prototype[Symbol.iterator] = function* () {
const arr = Reflect.ownKeys(this)
for (const item of arr) {
yield [item, this[item]]
}
}
Object.defineProperty(obj, "age", {
enumerable: false,
configurable: false,
writable: false
});
// 坑以Symbol为键,即使设置了enumerable=true,它依旧是不可枚举的,但是它不可以被 for...in遍历,
Object.defineProperty(obj, symbolGender, {
value: "male",
enumerable: true,
configurable: false,
writable: false
});
// obj中的symbol类型可以直接输出
console.log(obj); //{ nationality: 'china', [Symbol(name)]: '风', [Symbol(gender)]: 'male' }
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(name), Symbol(gender) ]
console.log(Reflect.ownKeys(obj)); // [ 'age', 'nationality', Symbol(name), Symbol(gender) ]
console.log(Object.keys(obj)); // [ 'nationality' ]
console.log(Object.getOwnPropertyNames(obj)); // [ 'age', 'nationality' ]
console.log(Object.getOwnPropertyDescriptors(obj));
/*
{
age: {
value: 18,
writable: false,
enumerable: false,
configurable: false
},
nationality: {
value: 'china',
writable: true,
enumerable: true,
configurable: true
},
[Symbol(name)]: { value: '风', writable: true, enumerable: true, configurable: true },
[Symbol(gender)]: {
value: 'male',
writable: false,
enumerable: true,
configurable: false
}
}
*/
console.log(Object.hasOwnProperty.call(obj,"age")); // 等价于obj.hasOwnProperty("age")
console.log(obj.hasOwnProperty("age")); //true
console.log(obj.hasOwnProperty("nationality")); //true
console.log(obj.hasOwnProperty(symbolGender)); //true
console.log(obj.hasOwnProperty(symbolName)); //true
console.log(obj.propertyIsEnumerable("age")); //false
console.log(obj.propertyIsEnumerable("nationality")); //true
console.log(obj.propertyIsEnumerable(symbolGender)); //true
console.log(obj.propertyIsEnumerable(symbolName)); //true
for (const key in obj) {
console.log(key, obj[key]) // nationality china
}
for (const k of Object.keys(obj)) {
console.log(k, obj[k]) // nationality china
}
Object.keys(obj).forEach(key => console.log(key, obj[key]))
for (const [k, v] of Object.entries(obj)) {
console.log(k, v) // nationality china
}
for (const [k, v] of obj) {
console.log(k, v)
/*
age 18
nationality china
Symbol(name) 风
Symbol(gender) male
*/
}
html
<script>
class Pserson {
constructor(name) {
this.name = name;
}
say() { }
}
class Student extends Pserson {
constructor(name, age) {
super(name);
this.age = age;
}
}
let stu = new Student("一缕清风", 18);
console.log(stu); // Student {name: '一缕清风', age: 18}
console.log(Object.getOwnPropertyNames(stu)); // [ 'name', 'age' ]
console.log(Object.hasOwnProperty.call(stu, "say")); //不去判断原型上的方法 false
console.log(Object.getOwnPropertyDescriptors(stu)); //不去判断原型上的方法 false
/*
{name: {...}, age: {...}}
age : {value: 18, writable: true, enumerable: true, configurable: true}
name : {value: '一缕清风', writable: true, enumerable: true, configurable: true}
[[Prototype]] : Object
*/
for (const key in stu) {
// if (Object.hasOwnProperty.call(stu, key)) {
console.log(key, stu[key]); // age 18
// }
}
</script>
怎么获取对象上的属性
javascript
let obj = { name: '一缕清风', age: 22, gender: '男', address: '广东' }
// 获取对象对象上的属性
var arr = Object.keys(obj)
var arr = Object.getOwnPropertyNames(obj)
var arr = Reflect.ownKeys(obj)
// 遍历对象上的属性
for (let propName in obj) {
console.log(propName, obj[propName]);
}
for (const [k, v] of Object.entries(obj)) {
console.log("Object.entries:", k, v);
}
for (let i = 0; i < arr.length; i++) {
console.log(arr[i], obj[arr[i]]);
}
var values = Object.values(obj)
for (let i = 0; i < values.length; i++) {
console.log(values[i]);
}
Object中prototype的方法
tsx
//lib.es5.d.ts
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
interface ObjectConstructor {
new(value?: any): Object;
(): any;
(value: any): any;
readonly prototype: Object;
getPrototypeOf(o: any): any;
getOwnPropertyDescriptor(o: any, p: PropertyKey): PropertyDescriptor | undefined;
getOwnPropertyNames(o: any): string[];
create(o: object | null): any;
create(o: object | null, properties: PropertyDescriptorMap & ThisType<any>): any;
defineProperty<T>(o: T, p: PropertyKey, attributes: PropertyDescriptor & ThisType<any>): T;
defineProperties<T>(o: T, properties: PropertyDescriptorMap & ThisType<any>): T;
seal<T>(o: T): T;
freeze<T>(a: T[]): readonly T[];
freeze<T extends Function>(f: T): T;
freeze<T extends {[idx: string]: U | null | undefined | object}, U extends string | bigint | number | boolean | symbol>(o: T): Readonly<T>;
freeze<T>(o: T): Readonly<T>;
preventExtensions<T>(o: T): T;
isSealed(o: any): boolean;
isFrozen(o: any): boolean;
isExtensible(o: any): boolean;
keys(o: object): string[];
}
// lib.es2017.object.d.ts
interface ObjectConstructor {
values<T>(o: { [s: string]: T } | ArrayLike<T>): T[];
values(o: {}): any[];
entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
entries(o: {}): [string, any][];
getOwnPropertyDescriptors<T>(o: T): {[P in keyof T]: TypedPropertyDescriptor<T[P]>} & { [x: string]: PropertyDescriptor };
}
// lib.es2015.core.d.ts
interface ObjectConstructor {
assign<T extends {}, U>(target: T, source: U): T & U;
assign<T extends {}, U, V>(target: T, source1: U, source2: V): T & U & V;
assign<T extends {}, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W;
assign(target: object, ...sources: any[]): any;
getOwnPropertySymbols(o: any): symbol[];
keys(o: {}): string[];
is(value1: any, value2: any): boolean;
setPrototypeOf(o: any, proto: object | null): any;
}
Object.freeze()
那么如果我们想定义一个不可被修改的对象,应该怎么办呢!那就要用到Object.freeze()了。它的作用是冻结一个对象,被冻结的对象有以下几个特性:
- 不能添加新属性
- 不能删除已有属性
- 不能修改已有属性的值
- 不能修改原型
- 不能修改已有属性的可枚举性、可配置性、可写性
- 可以冻结数组,数组的本质是对象
- Object.freeze()只支持浅冻结,下面我们封装一个深冻结函数,日后可直接使用
jsx
function deepFreeze(obj) {
// 获取所有属性
var propNames = Object.getOwnPropertyNames(obj)
propNames.forEach(item => {
var prop = obj[item]
// 如果某个属性的属性值是对象,则递归调用
if (prop instanceof Object && prop !== null) {
deepFreeze(prop)
}
})
// 冻结自身
return Object.freeze(obj)
}
var obj = { name: '张三', info: { a: 1, b: 2 } }
deepFreeze(obj)
obj.name = '李四'
console.log(obj) // { name: '张三', info: { a: 1, b: 2 } }
obj.info.a = 100
console.log(obj.info) // {a: 1, b: 2}
Object.definedProperty()
Object.seal()
Object.seal()方法可以让对象不能被扩展、删除属性等等 模拟Object.freeze()原理主要用到两个关键方法,Object.definedProperty()、Object.seal()
javascript
function myFreeze(obj) {
if (obj instanceof Object) {
Object.seal(obj);
let p;
for (p in obj) {
if (obj.hasOwnProperty(p)) {
Object.defineProperty(obj, p, {
writable: false
});
myFreeze(obj[p]);
}
}
}
}
Object.assign()
Object.assign和扩展运算法都是浅拷贝
jsx
let outObj = {
sum: { a: 1, b: 2 }
}
let newObj1 = Object.assign({}, outObj)
newObj1.sum.a = 2
console.log(outObj) // {sum: {a: 2, b: 2}}
let newObj2 = { ...outObj }
newObj2.sum.a = 3
console.log(outObj) // { sum: { a: 3, b: 2 } }
- Object.assign() 方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。
- 扩展操作符(...) 使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它扩展操作符会复制ES6的 symbols 属性。
Object.fromEntries()
将键值对列表转换为一个对象 我们知道 Object.entries() 是将对象转成一个自身可枚举属性的键值对数组。同样,我们也可以把键值对数组转成了对象
jsx
// 我们知道 Object.entries() 是将对象转成一个自身可枚举属性的键值对数组。
// 同样,我们也可以用Object.fromEntries()把键值对数组转成了对象
const nestedArray = [
['key 1', 'value 1'],
['key 2', 'value 2']
]
console.log(Object.fromEntries(nestedArray));
/*
{ 'key 1': 'value 1', 'key 2': 'value 2' }
*/
// 使用构造函数,使用实例方法"set"增加条数
const map = new Map([
['key 1', 'value 1'],
])
map.set('key 2', 'value 2')
console.log(Object.fromEntries(map));
/*
{ 'key 1': 'value 1', 'key 2': 'value 2' }
*/
如何判断JS中两个对象是否相等?
通过JSON.stringify(obj)
:::tips 判断两个对象 JSON.stringify() 转后的字符串是否相等: 优点:用法简单,对于顺序相同的两个对象可以快速进行比较得到结果 缺点:这种方法有限制就是当两个对比的对象中key的顺序不是完全相同时会比较出错 :::
jsx
var a1 = { id: 1, name: 2, c: { age: 3 } };
var a2 = { id: 1, name: 2, c: { age: 3 } };
var b1 = { name: 2, id: 1, c: { age: 3 } }
console.log(JSON.stringify(a1) === JSON.stringify(a2)); // true
console.log(JSON.stringify(a1) === JSON.stringify(b1)); // false
Object.getownPropertyNames()+ 递归
:::tips
- 用Object.getOwnPropertyNames拿到对象的所以键名数组
- 比对键名数组的长度是否相等
- 比对键名对应的键值是否相等
缺点:对象属性是对象的情况要进行递归 :::
jsx
function isObjectValueEqual(a, b) {
var aProps = Object.getOwnPropertyNames(a);
var bProps = Object.getOwnPropertyNames(b);
if (aProps.length != bProps.length) return false;
for (var i = 0; i < aProps.length; i++) {
var propName = aProps[i]
// 这里忽略了值为undefined的情况
// 故先判断两边都有相同键名
if (!b.hasOwnProperty(propName)) return false;
var propA = a[propName]
var propB = b[propName]
if ((propA instanceof Object)) {
if (arguments.callee(propA, propB)) {
// return true 这里不能return ,后面的对象还没判断
} else {
return false
}
} else if (propA !== propB) {
return false
} else { }
}
return true
}
var a1 = { id: 1, name: 2, c: { age: 3 } };
var a2 = { id: 1, name: 2, c: { age: 3 } };
var b1 = { name: 2, id: 1, c: { age: 3 } }
var b2 = { name: 2, id: 1, c: { age: "36" } }
var c1 = { name: 2, id: 1, c: { age: 3 }, age: 19 }
console.log("a1== a2:", isObjectValueEqual(a1, a2)); //true
console.log("a1== b1:", isObjectValueEqual(a1, b1)); //true
console.log("b1== b2:", isObjectValueEqual(b1, b2)); //false
console.log("b1== c1:", isObjectValueEqual(b1, c1)); //false
参考文献
:::info 类与对象篇(2) :::