前端查缺补漏系列(一)JS对象及其扩展

一. 前言

一转眼,发现自己大半年没有更新文章了。最近过的太安逸,没有什么危机感。感觉自己越来越躺平,技术上面也开始退步。思来想去还没到可以躺平的时候(穷,没钱😭😭),还是认认真真的更新文章,多多巩固前端基础和学习新的技术。

考虑的马上也是面试的好时候了(我说的是以前哈)。写一点js相关的基础文章吧,也是自己的一些沉淀。欢迎各位jym来查缺补漏。

二. 对象的几种创建方式

2.1 字面量创建

这个方式是我们平时用的最多的,通过{}创建。

js 复制代码
    const p = {
        name: 'zztj',
        age: 20
    }

内部原理:

  • 分配内存,创建一个普通对象,然后继承object.prototype
  • 直接继承object.prototype上的原始属性。
  • 创建的属性会直接挂载到对象本身。

2.2 Object.create

基于现有的对象作为原型,然后创建对象。

内部原理:

  • 创建一个新对象,将该对象的prototype指向传入的第一个参数(此为必传)
js 复制代码
Object.create(proto, ‌propertiesObject)

1. 参数1: proto创建该对象的原型。(必须为object | null)
2. 参数2: 定义新对象的自身属性。(要满足Object.defineProperty()的参数格式)

下面用几个例子来看一下用法。

2.2.1 创建对象(无原型继承)

php 复制代码
const obj = Object.create(null, { name: { value: "Test", writable: true } })

可以看出他的原型上啥都没。

2.2.2 创建对象(继承)

php 复制代码
const obj = Object.create(null, { name: { value: "11111", writable: true } })
const obj2 = Object.create(obj, { age: { value: 20, writable: true } })

可以看出他的原型上有name属性。

2.3 构造函数

通过new关键字,调用构造函数,创建对象。

内部原理:

  • 创建对象:在内存中创建一个新的空对象 {}
  • 继承原型:将该对象的__proto__指向创建该对象构造函数的protoType
  • 初始化属性:将构造函数的this指向,指向该对象。(初始化构造函数内部属性)
  • 返回结果:若该构造函数没有返回值,则返回该对象。
js 复制代码
function Person(name, age) {
    this.name = name
    this.age = age
}

const obj1 = new Person('ztmj', 20)

这种构造函数创建对象的原型链结构是这样的:

js 复制代码
obj1.__proto__ === Person.prototype
Person.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

2.4 工厂函数

通过封装一个函数,用来创建对象,然后直接返回该对象。

内部原理:

  • 手动创建对象并返回,不依赖 new or 原型链
  • 每次调用该函数,都会产生新的对象,所以属性和方法,都是该对象本身的。
js 复制代码
function createObjectFactory(name, age) {
    const obj = {}
    obj.name = name
    obj.age = age
}

三. 对象属性修饰符

在js中,通过属性的修饰符可以精准控制对象属性的行为。 通过Object.defineProperty可以修改修饰符。 主要通过以下几个修饰符来控制。

3.1 value

属性的值。

js 复制代码
   const obj1 = { name: 'ztmj' }
   Object.getOwnPropertyDescriptor(obj1, 'name') // { value: "ztmj"} 

3.2 writable

该属性是否可以被修改。默认是true。如果手动修改为false,则该属性不可修改。

js 复制代码
   const obj1 = { name: 'ztmj' }
   Object.getOwnPropertyDescriptor(obj1, 'name') // { writable: true} 

3.3 enumerable

该属性是否可以被枚举。如果为false,对象的某些操作会忽略当前属性。

下面来列举一下,哪些操作会忽略??

先创建一个对象,把里面的一个属性设置为不可枚举

js 复制代码
const obj = { name: 'ztmj', age: 20}
Object.defineProperty(obj, 'age', {
  enumerable: false
})

Object.getOwnPropertyDescriptors(obj)  // 看一下是否修改成功

我们可以看到,age的enumerable变成了false。继续下一步:

  • for...in
js 复制代码
    for (let key in obj) { 
        console.log(key)
    }

可以看到,age没有被遍历到。

  • Object.keys()
js 复制代码
Object.keys(obj)  // 结果只有[name]
  • JSON.stringify()
js 复制代码
JSON.stringify(obj)
  • Object.assign()
js 复制代码
const obj2 = { sex: 'man' }
Object.assign(obj2, obj1) // {sex: 'man', name: 'ztmj'}

以上4种方法,不可枚举属性无法处理。

PS: 这里要注意一下: ES6规定,所有Class的原型的方法都是不可枚举的

js 复制代码
const A = class {
  func() {}
}
Object.getOwnPropertyDescriptor(A.prototype, 'func').enumerable  // false

四. 属性的遍历

4.1 遍历方法

既然提到了对象的属性修饰符,那就说一下属性的遍历吧。

在js中常用的属性遍历方法有5种。

  • for...in 遍历对象自身,和继承的可枚举属性(不包含Symbol属性)
  • Object.keys(obj) 遍历对象自身(不含继承)的可枚举的属性(不包含Symbol属性),返回一个数组
  • Object.getOwnPropertyNames(obj) 遍历对象自身(不含继承)的所有属性(不包含Symbol属性),返回一个数组
  • Object.getOwnPropertySymbols(obj) 遍历自身的所有symbol属性。返回一个数组
  • Reflect.ownKeys(obj) 遍历自身的所有属性,啥样都行。

看起来有点混乱,其实就三点:

  • 继承的原型是否可以遍历
  • 不可枚举属性是否可以遍历
  • symbol属性是否可以遍历

看一下例子来说明一下上面的区别:

js 复制代码
const instance = {
    name: 'ztmj',
    age: 20,
    sex: 'man',
    [Symbol('get')]: 'gets',  // 创建symbol属性
    hb: 'ball'
}

Object.defineProperty(instance, 'sex', { enumerable: false })

const sub = { other: 'other' }
instance.__proto__ = sub   // 原型上加属性

for (let key in instance ) { console.log(key) }  // name age hb other
Object.keys(instance) // (3) ['name', 'age', 'hb']
Object.getOwnPropertyNames(instance)  // (4) ['name', 'age', 'sex', 'hb']
Object.getOwnPropertySymbols(instance)  // [Symbol(get)]
Reflect.ownKeys(instance)    // (5) ['name', 'age', 'sex', 'hb', Symbol(get)]

总结一下:来个非常抽象的图吧。

5.1 遍历的属性顺序

对象遍历也是分先后顺序的。

step1: 先遍历属性名为数值的属性。按照数字的大小进行遍历顺序(从小到大)

step2: 然后所有属性名为字符串的属性。按照生成时间排序

step3: 最后是symbol类型。按照生成时间排序

举个例子:

js 复制代码
const time = {
    name: 'ztmj',
    age: 20,
    [Symbol('get')]: 'gets',
    hb: 'ball',
    '2': '2222',
    18: '18',
    5: '55555',
    sex: 'man',
   [Symbol('post')]: 'posts',  // 创建symbol属性

}

Object.getOwnPropertyNames(time) //  ['2', '5', '18', 'name', 'age', 'hb', 'sex']

五. Object.is()

这个其实用的不多(反正俺用的很少。。。),但是还是要单独说一下。

主要是用于比较两个值是否真正意义上的全等。 常用的 ===== 是有缺陷的。

  • == 会自动转换数据类型
  • === 无法比较 NaN 和 -0 、 +0
js 复制代码
    1 == '1'   // true
    NaN === NaN  // false
    -0 === +0   // true

其实在ES6提出"Same-value equality"(同值相等)算法,用来解决这个问题。Object.is就是部署了这个方法。 他与 ===的区别在于可以正确的比较 NaN 和 -0 、 +0

js 复制代码
Object.is(NaN, NaN)  // true
Object.is(-0, +0) // false

六. Object.assign()

6.1 基本用法

Object.assign方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。 (这里注意不可枚举的属性,是不能合并的。)

js 复制代码
let t = { a: 1 };
let s1 = { b: 2, c: 3 };
Object.defineProperty(s1, 'c', { enumerable: false })
Object.assign(t, s1); // {a: 1, b: 2}

6.2 覆盖原则

如果在合并的过程中,出现属性的同名,则后面的属性值会覆盖前面的属性值

js 复制代码
let t = { a: 1, test: 'test1' };
let s1 = {  test: 'test2', b: 2 };
let s2 = {  test: 'test3'};
Object.assign(t, s1, s2)  // {a: 1, test: 'test3', b: 2}
Object.assign(t, s2, s1)  // {a: 1, test: 'test2', b: 2}

6.3 转换原则

如果第一个参数接收的为非对象类型,会先进行转换成对象,然后在执行下一步。

css 复制代码
let t = '1'
let s = { a: 'a'}
Object.assign(t, s)  // String {'1', a: 'a'}

如果当前类型不能转为对象就会error

javascript 复制代码
let t = null
let s = { a: 'a'}
Object.assign(t, s)  // Uncaught TypeError: Cannot convert undefined or null to object

如果其他位置,出现非对象类型, 处理流程会有所不同

  • 还是把非对象类型转为对象。不同的是,不能转的,直接就会跳过。所以传入null、undefined就不会报错。
js 复制代码
let t = null
let s = { a: 'a'}
Object.assign(s, t)  // {a: 'a'}
  • 但是,除了字符串会类数组形式,合并入对象,其他值都不会产生效果。也是直接忽略。
js 复制代码
let t = null
let t2 = true
let t3 = 'abc'
let t4 = '111'
let s = {a: 'a'}
Object.assign(s, t, t2, t3, t4)  // {0: '1', 1: '1', 2: '1', a: 'a'}

这里有点奇怪的地方: 为什么'abc'没有了,按照类数组的概念,结果应该是{ 0: 'a', 1: 'b', 2: 'c', 3: 1, 4: 1, 5: 1, a: 'a' }。答案还是替换原则。相当于,已经占了位,前面的0、1、2属性已经存在,后面的就会替换掉

我们可以替换一下位置看看:

js 复制代码
let t = null
let t2 = true
let t3 = 'abc'
let t4 = '111'
Object.assign(s, t, t2, t4, t3)  // {0: 'a', 1: 'b', 2: 'c', a: 'a'}

PS:为什么只有字符串可以??

因为只有字符串的包装对象,会产生可枚举属性

七. 总结

简单的总结一下js里面的对象。不算难,也算是对自己知识的总结。欢迎大家在评论区多多交流,有问题欢迎指出!

相关推荐
司宸5 分钟前
学习笔记八 —— 虚拟DOM diff算法 fiber原理
前端
阳树阳树6 分钟前
JSON.parse 与 JSON.stringify 可能引发的问题
前端
让辣条自由翱翔10 分钟前
总结一下Vue的组件通信
前端
dyb11 分钟前
开箱即用的Next.js SSR企业级开发模板
前端·react.js·next.js
前端的日常12 分钟前
Vite 如何处理静态资源?
前端
前端的日常13 分钟前
如何在 Vite 中配置路由?
前端
兮漫天13 分钟前
bun + vite7 的结合,孕育的 Robot Admin 靓仔出道(一)
前端
PineappleCoder14 分钟前
JS 作用域链拆解:变量查找的 “俄罗斯套娃” 规则
前端·javascript·面试
兮漫天14 分钟前
bun + vite7 的结合,孕育的 Robot Admin 靓仔出道(二)
前端
用户479492835691519 分钟前
面试官:为什么很多格式化工具都会在行尾额外空出一行
前端