JS对象认知升级

普通属性,排序属性和隐藏类

var obj = {}
obj.p1 = 'str1'
obj.p6 = 'str6'
obj.p2 = 'str2'

obj[1] = 'num1'
obj[6] = 'num6'
obj[2] = 'num2'

for(var p in obj){
   console.log("property: ",obj[p])
}

//
property: num1
property: num2
property: num6
property: str1
property: str6
property: str2

两种属性: 字符串作为键的和数字作为键的属性
键被遍历的顺序似乎是有规律的
1.常规属性
键为字符串的属性
特点: 根据创建时的顺序排序

2.排序属性
属性键值为数字(包含数字字符串)的属性
特点: 按照索引值大小升序排序

为什么设计常规属性和排序属性

  • 提升属性的访问速度
  • 两种线性数据结构保存(elements和properties,先访问排序属性再访问常规属性)

对象内属性
被保存到对象自身的常规属性
内属性的数量限制: 大概是10个
V8为了性能做了优化,当数量不同时,在elements和properties中保存的数量不同,线性或非线性的数据结构也不同, 线性结构查找的时候快, 插入删除的时候慢,v8引擎会自动评估如何性能最优

隐藏类

什么是: 描述了对象的属性布局,包含属性名和属性偏移量
作用: 从时间上提升速度, 从空间上节约内存
动态添加和删除属性会破坏隐藏类,有性能开销
如何守护隐藏类

  • 初始化时保持属性顺序一致
  • 一次性初始化完毕
  • 谨慎使用delete

属性来源, 属性访问控制, 属性冻结

  • 静态属性, 例如: Object.assign
  • 原型属性, 例如: Object.prototype.toString
  • 实例属性, 例如: function Person(name){ this.name = name }
class Person {
  constructor(name,age){
    this.name = name
    this.age = age 
  }
  //这种写法是实例属性
  getName = () =>{
    return this.name
  }
  //这种写法是原型属性
  getAge(){
    return this.age
  }
}

属性描述符

获取属性: Object.defineProperty Object.definePropertied
得到属性: Object.getOwnPropertyDescriptor Object.getOwnPropertyDescriptors
可配置 configurable
可枚举 enumerable
值 value
可更改 writable
访问器函数 get
访问器函数 set

  • 数据属性 value + writable + configurable + enumerable
  • 访问器属性 get + set + configurable + enumerable
const obj = {}
Object.defineProperty(obj,"name",{})
//此时obj.name不可改写
//{
//value:undefined,
//writable:false,
//enumerable:false,
//configurable:false
//}
obj.name=1
console.log(obj.name) //undefined

Object.defineProperty缺点

  • 无法监听数组变化
  • 只能劫持对象的属性, 因此我们需要对每个对象的每个属性进行遍历。如果属性也是对象, 还得进行递归

对象可扩展-Objetc.preventExtensions

  • Object.preventExtensions: 对象变得不可扩展, 也就是永远不能再添加新的属性
  • Object.isExtensible: 判断一个对象是否是可扩展

对象的封闭-Object.seal

  • Object.seal: 阻止添加新属性+属性标记位不可配置
  • Object.isSealed: 检查一个对象是否被密封

对象的冻结-Object.freeze

  • Object.freeze: 不加新属性 + 不可配置 + 不能修改值
  • Object.isFrozen: 检查一个对象是否被冻结
方法新增属性修改描述符删除属性更改属性值
Object.preventExtensions×
Object.seal××(writable有例外)×
Object.freeze××(writable有例外)××

8+种姿势访问原型

1.prototype

  • prototype是一个对象
  • 原型会形成原型链, 原型链上查找属性比较耗时, 访问不存在的属性会访问整个原型链
class Person{
   //私有属性
   #canTalk = true
   //静态属性
   static isAnimal = true
  
   constructor(name, age){
      //实例属性
      this.name = name
      this.age = age
   }
   //原型属性
   sayName(){
     console.log(this.name)
   }  
}

2. __ proto__

  • 构造函数的原型, null以外的对象均有__proto__属性
  • Function, class的实例有prototype以及__proto__属性
  • 普通函数, 祖上第三代上必为null

思考1: __proto__是实例对象的自身属性还是原型上属性
自身属性
思考2:普通对象祖上第几代__proto__为null
2代

3.instanceof

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

思考1: 手写instanceOf

function instanceOf(instance, cclass){
  let proto = instance.__proto__
  let prototype = cclass.prototype

  while(proto){
    if( proto === prototype ) return true
    proto = proto.__proto__
  }
  return false
}

思考2: Object instanceof Function, Function instanceof Object
本质上都是函数

4.getPrototypeOf

  • 返回对象的原型
  • Object.getPrototypeof, Reflect.getPrototypeOf
  • 内部先 toObject转换, 注意null和undefined

5.setPrototypeOf

  • 指定对象的原型
  • Object.settPrototypeOf, Reflect.setPrototypeOf
  • 原型的尽头是null

6.isPrototypeOf

  • 一个对象是否存在于另一个对象的原型链上
  • 左操作数应该是一个原型

其他

Object.create

方法作用重点和注意事项
prototype获取对象原型1.class ES6转ES5基于prototype 2.toString.call的怪异现象
__ proto __构造函数的原型1.函数祖上第三代__proto__是null 2.普通对象祖上第二代__proto__是null
instanceof构造函数(右侧)的prototype属性是否出现在某个实例对象(左侧)的原型链上1.Object instanceof Function 2.Function instanceof Function
getPrototype获取对象的原型null和undefined没有原型
setPrototype设置对象的原型null可以作为第二个参数,原型的尽头是null
isPrototype对象是否存在于另一个对象的原型链上不会有prototype这一步运算
Object.create使用现有的对象来提供新创建的对象的__proto__创建纯净对象

对象的属性遍历

属性的类型: 普通属性 不可枚举属性 原型属性 Symbol属性 静态属性

方法名普通属性不可枚举属性Symbol属性原型属性
for in××
Object.keys×××
Object.getOwnPropertyNames××
Object.getOwnPropertySymbols××
Reflect.ownKeys×

获取对象的全部静态属性

  • 不要被静态属性误导
  • Reflect.ownKeys = Object.getOwnPropertyNames + Object.getOwnPropertySymbols

获取原型上的所有属性

注意: 是原型上的所有属性,注意所有两字!
思路: 递归并剔除内置属性

function loadAllProperties(instance){
  if(instance == null) return;
  let proto = instance.__proto__
  while(proto){
    result.push(...Reflect.ownKeys(proto))
    proto = proto.__proto__
  }
}

获取所有不可枚举的属性

如何知道某个属性不可枚举?
Object.getOwnPropertyDescriptors
考虑不考虑原型上的不可枚举属性?

//获取原型上所有不可枚举属性
function getNoEnumrable(_obj){
  //获取原型对象
  const keys = Reflect.ownKeys(_obj)
  const result = keys.filter(key => {
     return !Object.getOwnPropertyDescriptor(_obj,key).enumerable
  })
  return result
}

对象的隐式类型转换和注意事项

显式转换:主要通过JS定义的转换方法进行转换, 如String, Object, 显式toString,parseInt/parseFloat等
隐式转换: 编译器自动完成类型转换的方式就称为隐式转换, 总是期望返回基本类型值
何时发生隐式转换

  • 二元 + 运算符
  • 关系运算符> , < , >= , <= , ==
  • 逻辑! , if/while , 三目条件
  • 属性键遍历, for in等
  • 模板字符串

对象隐式转换三大扛把子

  • Symbol.toPrimitive
  • Object.prototype.valueOf
  • Object.prototype.toString
  • 如果 Symbol.toPrimitive 方法存在, 优先调用, 无视valueOf 和 toString方法
  • 否则, 如果期望是"string" --先调用obj.toString()如果返回不是原始值, 继续调用obj.valueOf()
  • 否则, 如果期望值是"number"或"default" – 先调用obj.valueOf() 如果返回不是原始值, 继续调用obj.toString()
//如果未定义[Symbol.toPrimitive],期望值是string,toString和valueOf都没返回原始值,会抛出异常
const obj = {
  value : 10,
  valueOf(){
    return this
  },
  toString(){
    return this
  }
}
console.log(10+obj) //报错

hint-string

  • window.alert(obj)
  • 模板字符串 ${obj}
  • test[obj] = 123
const obj = {
  [Symbol.toPrimitive](hint){
    if(hint == 'number'){
      return 10
    }
    if(hint == 'string'){
      return 'hello'
    }
    return true
  }
}
console.log(`${obj}`)  //hello
obj[obj] = 123
console.log(Object.keys(obj))  //['hello']

hint-number

  • 一元+,位移
  • -*/关系运算符
  • Math.pow, String.prototype.slice等很多内部方法

吃透 JSON 和 toJSON

JSON格式
JSON是一种轻量级的、基于文本的、与语法无关的语法,用于定义数据交换格式
JSON键:只能是字符串,必须双引号包裹
最后一堆键值对后不能加逗号
值:number值必须是十进制

对象字面量
是创建对象的一种快捷方式, 字面量的性能优于使用new 构建, var obj = {}

JSON.parse()
注意:第二个参数函数,可以用来过滤数据或者调整数据
遍历顺序:先遍历子属性再返回父属性

const jsonStr = `{"name":"帅哥", "age":18, "IDCard":"xxxxx" }`
var obj = JSON.parse(jsonStr,function(key , value){
  if(key == 'IDCard'){
     return undefined
  } else {
     return value;
  }
})

JSON.stringify

  • 语法:JSON.stringify(value,[,replacer,[,space]])
  • 第二个参数replacer:过滤属性或者处理值(如果是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的JSOn字符串中;如果改参数为null或者未提供,则所有的属性都会被序列化)
  • 第三个参数space:美化输出格式(如果参数是个数字,它代表有多少的空格,上限为10,该值若小于1,则意味着没有空格;如果参数为字符串,当字符串长度超过10个字母,取前10个字母,该字符串被作为空格,如果改参数没有提供或者为null,将没有空格)
const person = {
  name: '帅哥',
  age: 45,
  birth: '1990-01-01'
}

// 第二个参数为函数
var jsonString = JSON.stringify(person, function(key,value){
   if(typeof(value) === 'string'){
     return undefined;
   }
   return value;
}) // {"age":45}

// 第二个参数为数组
JSON.stringify(person, ['name','age']);
// {"name":"帅哥","age":45}

// 第三个参数
JSON.stringify(person, null, '\t');
//{
//	"name":"帅哥",
//	"age":45,
//	"birthday":"1990-01-01"
//}

规则-undefined、任意的函数、symbol

  • 作为对象属性值,自动忽略
  • 作为数组,序列化返回 null
  • 单独序列化时,返回 undefined

JSON.stringify其他规则

  • Date返回 ISO 字符串
  • 循环饮用报错
  • NaN,infinity,null都会作为null
  • BigInt 报错
  • Map/Set/WeakMap等对象,仅序列化可枚举属性

toJson

对象拥有toJSON方法,toJSON会覆盖对象默认的序列化行为

var project = {
   "name":"牙膏",
   "count":10,
   "orderDetail":{
      "orderId": 7263721832
   },
   toJSON(){
     return{
        name:'牙膏'
     }
   }
}
JSON.stringify(product); //{"name":"牙膏"}

对象的多种克隆方式及注意事项

  • 意义:保证原数据的完整性和独立性
  • 常见场景:复制数据,函数如参,class构造函数等

浅克隆

  • 只克隆对象的第一层级
  • 如果属性值是原始数据类型,拷贝其值,也就是我们常说的值拷贝
  • 如果属性值是引用类型,拷贝其内存地址,也就我们常说的引用拷贝

常见的浅克隆:ES6扩展运算符… Object.assign. for in 和其他的一层遍历复制
数组常见的浅克隆:ES6扩展运算符… slice [].concat

深度克隆

  • 克隆对象的每个层级
  • 如果属性值是原始数据类型,拷贝其值,也就是我们常说的值拷贝
  • 如果属性值是引用类型,递归克隆
方法方式优点缺点备注
浅克隆性能高数据可能不完全独立一层属性全是值类型,等同于深度克隆
深度克隆不影响原对象性能低,时间和空间消耗更大保持数据独立性,让函数无副作用

深度克隆方法1 JSON.stringify+JSON.parse

function clone(obj){
   return JSON.parse(JSON.stringify(obj))
}
  • 优点:纯天然,无污染
  • 只能复制普通键的属性,Symbol类型的无能为力
  • 循环引用对象,比如Window不能复制
  • 函数,Date,Rege,Blob等类型不能复制
  • 性能差

深度克隆方法2 消息通讯-BroadcastChannel等等

  • window.postMessage
  • Broadcast Channel
  • Shared Worker
  • Message Channel

特点

  • 循环引用对象不能复制,如:Windows
  • 函数不能复制
  • 同步变成异步

深度克隆方法3 手写深度克隆

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值