类与对象篇(2)

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.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

  1. 用Object.getOwnPropertyNames拿到对象的所以键名数组
  2. 比对键名数组的长度是否相等
  3. 比对键名对应的键值是否相等

缺点:对象属性是对象的情况要进行递归 :::

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) :::

相关推荐
亿牛云爬虫专家12 分钟前
如何在Puppeteer中实现表单自动填写与提交:问卷调查
javascript·爬虫·爬虫代理·puppeteer·问卷调查·代理ip·表单
FIRE24 分钟前
uniapp小程序分享使用canvas自定义绘制 vue3
前端·小程序·uni-app
四喜花露水25 分钟前
vue elementui el-dropdown-item设置@click无效的解决方案
前端·vue.js·elementui
jokerest1231 小时前
web——sqliabs靶场——第五关——报错注入和布尔盲注
前端
焦糖酒1 小时前
终端应用开发沉思录
javascript·前端框架
谢尔登1 小时前
前端开发调试之 PC 端调试
开发语言·前端
每天吃饭的羊1 小时前
在循环中只set一次
开发语言·前端·javascript
斗-匕1 小时前
面试击穿mysql
mysql·面试
_默_4 小时前
adminPage-vue3依赖DetailsModule版本说明:V1.2.1——1) - 新增span与labelSpan属性
前端·javascript·vue.js·npm·开源
也无晴也无风雨6 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络