一个前端开发者的救赎之路-JS基础回顾(四)-对象

37 阅读8分钟

基本概念

  1. 对象是一个无序集合,每个属性都有名字和值。属性名通常是字符串(也可以是符号Symbol)
  2. 对象不简单的是key到value的映射,除了维持自己的属性之外,JavaScript对象也可以从其他对象继承属性。这个其他对象称之为其“原型”,对象的方法通常是继承来的属性,而这种“原型式继承”也是JavaScript的主要特性。
  3. 对象不能包含两个同名的属性
  4. 默认情况下,我们所创建的所有属性都是可写、可枚举和可配置的。

原型

  1. 使用new关键字和构造函数调用创建的对象,使用构造函数prototype属性的值作为它们的原型
  2. 记住:几乎所有对象都有原型,但只有少数对象有prototype属性(实际上只有函数对象才有这个属性)。正是这些有prototype属性的对象为所有对象定义了原型
  3. Object.prototype的原型是为数不多的没有原型的对象,因为他不继承任何属性。

创建对象

  1. 字面量形式

    let obj = {
        key: value
    }
    
  2. new关键字

    let obj = new Object()
    
  3. Object.create()

    1. Object.create()用于创建一个新对象,使用第一个参数作为新对象的原型
    let obj = Object.create({x: 1, y: 2}) // obj继承属性x,y
    obj.x + obj.y                         // => 3
    
    1. 传入null可以创建一个没有原型的对象
    2. 创建一个空对象就传入Object.prototype

基本操作

要创建或设置属性,与查询属性一样,可以使用点或方括号

var obj = {},追加属性是`可以直接用obj.xxx`去操作的,我以前一直认为不行,
必须用obj[xxx]去操作,这个obj[]这是用来操作当key是个变量的时候,或者说是特殊字符的时候
 
如果在追加之前就console.log这个obj,会在控制台看到一个{}  

然后去追加,再输出就是有数据的{name:'汴禧'}这样,此时上面那个还是{},
但是点开就会发现里面竟然有追加的属性,这是因为浏览器的机制问题,再点击的时候相当于做了一次访问操作
  • 自有属性和继承属性同名时,继承属性会被新创建的属性隐藏,而不会修改继承属性
  • 查询属性时会用到原型链,而设置属性时不影响原型链,这是JavaScript的一个重要特性。
  • 删除属性:delete关键字, delete obj.xxx
  • 测试属性(检查是否有该属性):in操作符,hasOwnProperty(),propertyIsEnmerable()方法,或者直接查询该属性
    • in操作符要求左边是一个属性名,右边是一个对象。这个对象有该属性的时候返回true,没有该属性的时候返回false
    • hasOwnProperty()方法用来判断测试对象是否有给定名字的属性。对于继承的属性它返回false
    • propertyIsEnmerable()方法用来判断是不是有给定的可枚举属性(因为某些内置属性是不可枚举的)
    • 除了使用in操作符,通常简单的属性查询配合!==确保其不是未定义就可以了。但是in可以区分不存在的属性和存在但被设置为undefined的属性,而简单的属性访问做不到

循环:

for/in,除了使用for/in循环,还可以先获取对象所有属性名的数组,再结合for/of循环遍历数组。有四个函数可以用来获取属性名数组

  • Object.keys()返回对象可枚举自有属性名的数组。不包含不可枚举属性、继承属性或名字是符号的属性
  • Object.values()返回对象的可枚举自有属性值的数组。不包含不可枚举属性、继承属性或名字是符号的属性
  • Object.entries()返回对象的可枚举自有属性键值对的数组。不包含不可枚举属性、继承属性或名字是符号的属性
  • Object.getOwnPropertyNames()与Object.keys()类似,但也会返回不可枚举自有属性名的数组,只要他们的名字是字符串
  • Object.getOwnPropertySymbols()返回名字是符号的自有属性,无论可否枚举
  • Reflect.ownKeys()返回所有属性名,包括可枚举不可枚举属性,以及字符串属性符号属性

扩展对象

  • Object.assign(): Object.assign() 是 JavaScript 中一个非常实用的方法,用于对象的合并和属性拷贝。它于 ES6(ECMAScript 2015)中被引入,可以将一个或多个源对象(source)的所有可枚举属性(enumerable properties)复制到目标对象(target),并返回修改后的目标对象
  • 记住:总是把默认值放在中间,用户选项放在最后,目标对象用空对象!

📊 核心特点与注意事项

了解 Object.assign() 的特性,能帮你更好地使用它。

特性/事项说明示例/建议
浅拷贝 (Shallow Copy)如果源对象的属性值是对象,那么复制的是该对象的引用,而非副本。修改复制后对象中的嵌套对象属性,会影响到源对象。需要深拷贝可使用 JSON.parse(JSON.stringify(obj))(有局限性)或第三方库如 Lodash 的 _.cloneDeep()
同名属性覆盖后续源对象的同名属性会覆盖之前的。Object.assign({a:1}, {a:2}) 结果为 {a:2}
跳过null 和 undefined作为源对象时,null 和 undefined 会被忽略,不会报错。Object.assign({}, null, undefined) 返回空对象 {}
处理原始值源对象是原始值(字符串、数字、布尔值)会被转换为包装对象。只有字符串包装对象有可枚举的自有属性(索引对应字符)。Object.assign({}, 'abc') 结果为 {0: 'a', 1: 'b', 2: 'c'}Object.assign({}, 42, true) 结果为 {}
不拷贝继承和不可枚举属性只拷贝源对象自身的可枚举属性。继承属性和 enumerable: false 的属性不会被拷贝。
拷贝 Symbol 属性Symbol 类型的属性也会被拷贝。Object.assign({}, {[Symbol('foo')]: 'bar'}) 会拷贝该 Symbol 属性。
拷贝访问器属性 (Getters)会调用源对象的 getter 和方法取得的值,然后复制到目标对象。复制的是 getter 执行后的返回值,而非 getter 函数本身。

如何准确判断一个变量是Object类型

方法示例优点缺点
typeoftypeof {} === 'object'简单对 null、数组都返回 'object'
instanceof{} instanceof Object检查原型链跨框架时可能失效
constructor{}.constructor === Object直接可被修改,不安全
Object.prototype.toStringObject.prototype.toString.call({})最准确语法稍复杂

序列化对象(转换成字符串)

  • 对象的序列化就是把对象转换为字符串的过程,之后可以从中恢复对象的状态。
  • 方法JSON.sttringfy()和JSON.parse()用于序列化和恢复JavaScript对象
  • 注意:
    • 可以序列化的值包括对象、数组、字符串、有限数值、true、false和null。
    • NaN。Infinity和-Infinity会被序列化为null。
    • 日期对象会被序列化为ISO格式的日期字符串,但JSON.parse()会保持其字符串形式,不会恢复原始的日期对象。
    • 函数、RegExp和Error对象以及undefined值不能被序列化或恢复。
    • JSON.sttringfy()只序列化对象的可枚举自有属性。如果属性名无法序列化,则该属性会从输出的字符串中删除

对象的方法

  • toString(),一般都是返回[object object]
  • toLocalString(),Object未重写就是简单的调用了toString()方法,但是Array、Date和Number都重新定义了自己的toLocalString()方法
  • valueOf(),valueOf()与toString()很类似,但会在JavaScript需要把对象转换为某些非字符串原始值(通常为数值)时被调用。
  • toJSON(),Object.prototype并未定义toJSON()方法,但JSON.sttringfy()方法会从要序列化的对象上寻找toJSON()方法。如果要序列化的对象上存在这个方法,就会调用它,然后序列化该方法的返回值,而不是原始对象

扩展语法

  1. 简写属性,假设变量x,y中保存着值,而你想创建一个具有x和y属性且值分别为相应变量值的对象,在ES6之后就可以这样let obj = {x, y}
  2. 计算的属性名:ES6之后,就是说属性名可以通过计算属性(一个函数返回这个属性名)来定义
  3. 符号Symbol()作为属性名(ES6以后)
  4. 扩展运算符:
    1. 注意:扩展运算符(...)执行的是浅拷贝,而不是深拷贝
    2. 还有一点要注意的,虽然扩展运算服务在我们的代码中只是三个小圆点,但它可能给JavaScript解释器带来巨大的工作量。如果对象有n个属性,把这个属性扩展到另一个对象可能是一种O(n)的操作。这意味着,如果在循环递归函数中通过...向一个大对象不断追加属性,则很可能我们是在写一个低效的O(n²)算法。随着n越来越大,这个可能会成为性能瓶颈。
  5. 简写方法,与简写属性类似,就是ES6之后,我们写方法没必要再func: function(){}这样子,可以直接func() {}
  6. 属性的获取和设置: get()和set(),注意:这不是ES6新增的,这是ES5中引入的