likes
comments
collection
share

JS知识点回顾——对象

作者站长头像
站长
· 阅读数 13

主要回顾对象的属性以及对象属性的遍历

对象属性

先说几个概念

  • 键为字符串的属性,存放到properties对象中
  • 键为数字的属性也叫排序属性,存放到elements对象中,键为数字字符串的属性也是排序属性,有序访问会快于字符串属性
  • 对象内属性,不在properties对象中也不在elements对象中访问更快

设置一个添加属性的函数,然后我们去快照中观察属性

function CustomObj(eCount,pCount){
  for(let i=0;i<eCount;i++){
      this[i] = `e-${i}`;
  }
  for(let i=0;i<pCount;i++){
      this[`p-${i}`] = `p-${i}`;
  }
}
先生成12个属性

const obj = new CustomObj(6,6);

JS知识点回顾——对象 可以看到只有12个属性时,全部生成为对象内属性,并且没有生成properties对象,elements对象中存放的是排序属性;此时访问对象中的属性都是直接访问对象内属性会很快;并且elements对象中存储的属性也是有序的

JS知识点回顾——对象

生成24个属性

const obj = new CustomObj(12,12);

JS知识点回顾——对象 可以看到24个属性时,也是全部生成了对象内属性,出现了properties对象,此时elements对象和properties对象都是有序存放的,properties对象也是下标排序存储的

JS知识点回顾——对象

生成1000个属性

const obj = new CustomObj(100,100);

JS知识点回顾——对象 可以看到properties里面的属性已经不是排序存放的了,说明存储的数据结构发生了变化

总结

可以看到在字符串属性多的情况下,properties将不再是线性排序;线性数据访问使用下标访问非常快;但是添加和删除属性的时候效率就很低;非线性数据结构一般存储在字典中,虽然查找很慢但是插入和删除很快

在上面的截图中都可以看到map,在elements排序属性中是没有的;通常叫map是隐藏类,它描述的是对象的属性布局,因为有隐藏类,对象的访问比较快,在空间上也会减少消耗

  • 添加删除属性都会破坏隐藏类,并且新生成的隐藏类和之前的隐藏类也有会联系,比如map中的back_pointer会指向之前的隐藏类
  • 保护隐藏类:
    • 一次性初始化完我们的属性
    • 保持初始化属性的顺序,比如给obj赋值时第一次时name、age;第二次变成age、name
    • 谨慎使用delete
创建属性

创建属性的方式有很多,这里主要讲一下Object.defineProperty,允许精确地添加或修改对象上的属性

  • configurable:返回一个布尔值,决定了是否可以修改属性描述对象configurable为false时,value、writable、enumerable和configurable都不能被修改了, configurable修改成false是单向操作,无法撤销!以及无法被删除
    • writable只有在false改为true会报错,true改为false是允许的
    • 只要writable和configurable有一个为true,value就允许改动
    • configurable为false时,直接目标属性赋值,不报错,但不会成功
  • enumerable:返回一个布尔值,表示目标属性在 for…in、Object.keys、JSON.stringify 中是否可遍历;如果需要获取对象自身的所有属性,不管是否可遍历,可以使用Object.getOwnPropertyNames方法
  • writable:是一个布尔值,决定了目标属性的值(value)是否可以被改变;如果原型对象的某个属性的writable为false,那么子对象将无法自定义这个属性
  • value:设置值,如果不设置默认是undefined
  • get:访问器函数
  • set:访问器函数

一共是6个属性,但是只可能同时出现4个

数据属性:value+writable+configurable+enumerable

访问器属性:get+set+configurable+enumerable

Vue2是使用Object.defineProperty的get和set监听数据的,缺点是无法监听数据的变化,并且需要递归对象的属性才能监听,所以新增和删除属性也不能被监听

什么都不设置默认值是undefined,不可修改、不可枚举

const obj = {}
Object.defineProperty(obj,'age',{})
obj.age = 1;
console.log(obj.age)
console.log(Object.getOwnPropertyDescriptor(obj,'age'));

JS知识点回顾——对象

对象属性的类型
  • 普通属性:普通的属性比如自己定义的字符串属性
  • 不可枚举的属性:通过Object.defineProperty定义时标注enumerable为false;还有比如Object.prototype.toString等
  • 原型属性,来自原型链;比如:Object.prototype.toString
  • Symbol属性:标记唯一性
  • 静态属性,不需要我们定义就能使用的属性
const symbolSay = Symbol.for("say1");
class Person {
  // 静态属性
  static flag = "人";
  static getFlag() {
    return Person.flag;
  }
  static [Symbol.for("symbolPro")](){
    return "symbolPro"
  }
  constructor(name){
    // 实例属性
    this.name = name;
    this[symbolSay] = "hello";
  }
  // 原型属性
  getName() {
    return this.name;
  }
  // 实例方法
  getAge = () => {
    return 15;
  }
}
const p = new Person("张三");
console.log(p)

JS知识点回顾——对象

对象的可扩展

Object.preventExtensions

设置之后对象不可扩展,但是可以删除和修改值;使用Object.isExtensible()检查对象是否可扩展

const obj1 = {name:"jhon"};
const obj2 = {name:"candy"};
Object.preventExtensions(obj1);
obj1.age = 10;
delete obj1.name;
console.log(obj1) // 得到空对象 {}
console.log(Object.isExtensible(obj1)) // false
console.log(Object.isExtensible(obj2)) // true
对象的封闭

Object.seal;阻止添加新属性并且属性标记为不可配置,不可扩展也不可删除;使用Object.isSeal()判断对象是否被封闭

对象的冻结

Object.freeze():不可扩展、不可配置、不可修改;使用Object.isFrozen()判断对象是否被冻结;使用Object.defineProperty还会报错

JS知识点回顾——对象

对象的遍历

获取对象的全部静态属性

获取除了原型属性之外的属性,然后将其他属性排除

使用Reflect.ownKeys可以获取除原型属性之外的属性,包括普通属性、不可枚举属性和Symbol属性

Reflect.ownKeys = Object.getOwnPropertyNames + Object.getOwnPropertySymbols

let keys = Object.getOwnPropertyNames(_obj); keys = keys.concat(Object.getOwnPropertySymbols(_obj)); 这样得到的keys等同于Reflect.ownKeys(_obj)得到的keys

function getOwnPropertyStatics(_obj) {
  // 需要排除的属性
  const KNOWN_STATICS = {
    name:true,
    length:true,
    prototype:true,
    caller:true,
    callee:true,
    arguments:true,
    arity:true
  }
  let res = [];
  const keys = Reflect.ownKeys(_obj);
  console.log(keys)
  for(let i=0;i<keys.length;i++){
      if(!KNOWN_STATICS[keys[i]]){
        res.push(keys[i])
      }
  }
  return res;
}
console.log(getOwnPropertyStatics(Person))

JS知识点回顾——对象

获取对象原型上的属性

for in 能拿到原型上的属性但是拿不到symbol属性;所以只能遍历原型使用Reflect.ownKeys()获取属性

class Grand {
  gName = "Grand";
  gGetName() {
    return this.gName;
  }
}
Grand.prototype[Symbol.for("gAge")] = "G-12";
class Parent extends Grand {
  pName = "123";
  pGetName(){
    return this.pName;
  }
}
Parent.prototype[Symbol.for("pAge")] = "p-12";
class Child extends Parent {
  cName = "123";
  cGetName(){
    return this.cName;
  }
}
const c = new Child();
function logAllProperties(instance) {
  if(instance === null) return [];
  let result = [];
  let proto = instance.__proto__;
  while (proto) {
    result.push(...Reflect.ownKeys(proto));
    proto = proto.__proto__;
  }
  return result;
}
console.log(logAllProperties(c))// 这里没有排除重复属性

JS知识点回顾——对象

获取不可枚举属性

先获取所有属性,然后判断是否可枚举

const obj = {
  name:"yang",
  age:15
}
Object.defineProperty(obj, "val",{
  value:25,
  enumerable:false
})
Object.defineProperty(obj, "price",{
  value:35,
  enumerable:false
})
console.log(Reflect.ownKeys(obj)) // ['name', 'age', 'val', 'price']
function getNoEnumerable(_obj) {
  const keys = Reflect.ownKeys(_obj);
  return keys.filter((key)=>{
      // Object.prototype.propertyIsEnumerable.call(_obj,key),都是判断是否可枚举
      return !Object.getOwnPropertyDescriptor(_obj,key).enumerable
  })
}
console.log(getNoEnumerable(obj)) // ['val', 'price']

JS知识点回顾——对象

访问对象的原型

prototype:获取对象原型

原型会形成原型链,原型链上查找比较耗时,访问不存在的属性时会访问整个原型链,prototype是一个对象

class Person {
  // 私有属性
  #canTalk = true;
  // 静态属性
  static isAnimal = true;
  constructor(name,age) {
    // 实例属性
    this.name = name;
    this.age = age;
  }
  // 原型属性
  sayName() {
    console.log(this.name);
  }
}

用ES5实现

  • 对于私有属性ES5使用闭包
  • 静态属性直接属性
  • 实例属性使用this挂载
  • 原型属性使用Object.defineProperty挂载到构造函数的原型上

Boolean.prototype上有一个属性叫BooleanData而Object.prototype.toString方法返回Boolean的条件是有BooleanData属性

所以Object.prototype.toString(Boolean.prototype) // [Object Boolean]

proto

构造函数的原型,除null外的对象都有__proto__属性

  • Function、class的实例既有prototype又有__proto__属性
  • __proto__是原型上的属性,不是自身属性
  • 一个普通对象,祖上两代为null;new出来的对象不是普通对象
  • 一个普通函数,祖上三代必为null
function a() {}
console.log(a.__proto__.__proto__.__proto__) // null
function Person() {}
const p = new Person();
console.log(p.__proto__.__proto__.__proto__) // null
instanceof

检测构造函数的prototype属性是否出现在某个实例对象的原型上

手写

function myInstanceof(left,right) {
  let proto = left.__proto__;
  const prototype = right.prototype;
  while (proto) {
    if(proto === prototype){
      return true;
    }
    proto = proto.__proto__;
  }
  return false;
}

Object instanceof Function 和 Function instanceof Object

我们先获取实例的所有原型

function getPrototypeArr(instance) {
  const protoArr = [];
  let proto = instance.__proto__;
  while (proto) {
    protoArr.push(Object.prototype.toString.call(proto));
    proto = proto.__proto__;
  }
  return protoArr;
}
console.log(getPrototypeArr(Function))
// ['[object Function]', '[object Object]']
console.log(getPrototypeArr(Object))
// ['[object Function]', '[object Object]']

所以Function的原型上有Object;Object的原型上也有Function;所以上面的两个都是true

getPrototypeOf:返回指定对象的原型

Object.getPrototypeOf或者Reflect.getPrototypeOf是一样的

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法;它不是一个构造函数,它里面包含了对象的所有操作

在 ES5 中,如果 obj 参数不是对象,则会抛出 TypeError 异常。在 ES2015 中,该参数将被强制转换为 Object。

console.log(Object.getPrototypeOf('123')) // String

对于null和undefined是会报错的

setPrototypeOf:指定对象的原型

Object.setPrototyprOf或者Reflect.setPrototypeOf;原型链的尽头是null

const obj = {a:1};
console.log(obj.toString());
Reflect.setPrototypeOf(obj,null);
console.log(obj.toString());

JS知识点回顾——对象

isPrototypeOf

检查一个对象是否存在于另一个对象的原型链上;因为是出现在对象的原型链上,所以要保证另一个对象一定是原型

区别:Object.isPrototypeOf、Object.prototype.isPrototypeOf、Reflect.isPrototypeOf、Function.isPrototypeOf

console.log(Object.isPrototypeOf({})) // false;Object不是原型
console.log(Object.prototype.isPrototypeOf({})) // true  Object.prototype是原型
console.log(Reflect.isPrototypeOf({})) // false
console.log(Function.isPrototypeOf({})) // false

和instanceof一样,去取了右操作数的原型

Object.create

创建纯净对象,使用现有对象来提供新创建的对象__proto__

接受两个参数第二个参数是描述,在ES3时需要判断描述是否是undefined然后抛出错误

Object.prototype.myCreate = function(p){
    function F(){};
    F.prototype = p;
    return new F();
}
转载自:https://juejin.cn/post/7246206157898645562
评论
请登录