Skip to content

JavaScript 对象

定义

对象可以用来描述具体的某个事物,本质上是键值对的集合,一系列被映射到唯一标识符的数据就是对象。习惯上,唯一标识符叫做属性(property)或者键(key),数据叫做值(value

JavaScript中的所有引用类型都可以被认为是“对象”(底层是对象或者基于对象)

  • 对象声明
js
const obj = {}
const obj = new Object()
  • 一个完整的对象由属性和方法组成
js
// 传统写法
const obj = {
  uname: '张三',
  age: 18,
  gender: '男',
  sayHi: function () {
    console.log('hi~')
  }
}

// es6简写
const uname = 'andy'
const age = 18
const gender = '女'
const obj = {
  uname, // 此处的uname等同于uname:uname(同名可以简写)
  age,
  gender,
  sayHi() {
    console.log('hi~')
  }
}

如果你的对象有非字符串属性的话,JavaScript 会自动将它们转为字符串。可以说,对象内部的属性名都是字符串

对象使用

image-20240605161319852

声明对象,并添加了若干属性后,可以使用.[]获得对象中属性对应的值,称之为属性访问

  • .语法:获取属性对应的值,后接属性名,不可以为变量。属性名为数字时不可访问
  • []语法:获取属性对应的值,可以解析变量,属性名为数字时也可以访问,还可以访问特殊属性名
js
const fakeArr = {
  0: 'apple',
  1: 'banana',
  2: 'orange'
}

console.log(fakeArr[0]) // apple
console.log(fakeArr.0) // 报错

const person = {
  'un-name': '张三',
  age: 18,
  gender: '男'
}
console.log(person['un-name']) // "张三"
console.log(person.un-name) // 报错

遍历对象

对象内是无序键值对,没有下标,且没有length属性,无法确定长度

image-20240605164648923

对象解构

对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法

  • 赋值运算符左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
  • 对象属性的值将被赋值给与属性名相同的变量,不要和其他变量名冲突
  • 对象中找不到与变量名一致的属性时变量值为undefined
  • 解构的变量可以重新命名
js
const user = {
  uname: '小明',
  age: 18
}
const { name: uname, age } = user // 对象内的属性 : 要赋值的变量名
  • 多级对象解构
js
const pig = {
  name: '佩奇',
  family: {
    mother: '猪妈妈',
    father: '猪爸爸',
    brother: '乔治'
  },
  age: 6
}
//按照解构依次剥离即可, 这里的family不是解构出来的
const { name, family: { mother, father, brother } } = pig
  • 如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中,不常用了解即可
js
let personName, personAge
let person = {
  name: 'Matt',
  age: 27
}; // 此处的分号不能省
({ name: personName, age: personAge } = person)
console.log(personName, personAge) // Matt, 27

属性特征

查看特征

  • 使用 Object.getOwnPropertyDescriptor查看对象属性的描述
  • 使用 Object.getOwnPropertyDescriptors查看对象所有属性的描述

属性包括以下四种特性:

特性说明默认值
configurable能否使用delete、能否修改属性特性、或能否修改访问器属性true
enumerable对象属性是否可通过for-in循环,或Object.keys()读取true
writable对象属性是否可修改true
value对象属性的默认值undefined

使用示例如下:

js
const user = {
  name: "Jay",
  sex: "male",
  age: 18
}

console.log(Object.getOwnPropertyDescriptor(user, "name")) 
// { value: 'Jay', writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptors(user))
/* 
{
  name: { value: 'Jay', writable: true, enumerable: true, configurable: true },
  sex: { value: 'male', writable: true, enumerable: true, configurable: true },
  age: { value: 18, writable: true, enumerable: true, configurable: true }
}
*/

设置特征

  • 使用Object.defineProperty 方法修改属性特性
  • 使用 Object.defineProperties 可以一次设置多个属性
js
const user = {
  name: "Jay",
  sex: "male",
  age: 18
}

Object.defineProperty(user, "name", {
  value: "Zhou",
  writable: false,
  enumerable: false,
  configurable: false
})

// 不允许修改
user.name = "Jane"
console.log(user.name) // Zhou

// 不能遍历
console.log(Object.keys(user)) // ['sex', 'age']

//不允许删除
delete user.name
console.log(user) // {sex: 'male', age: 18, name: 'Zhou'}

//不允许配置
Object.defineProperties(user, {
  name: { value: "Jucy", writable: false },
  age: { value: 20 }
}) // Uncaught TypeError: Cannot redefine property: name

禁止添加

  • Object.preventExtensions 禁止向对象添加属性

  • Object.isExtensible 判断是否能向对象中添加属性

js
const user = {
  name: "Jay",
  sex: "male"
}

Object.preventExtensions(user)
user.age = 25
console.log(user) // {name: "Jay", sex: "male"}
console.log(Object.isExtensible(user)) // false

封闭对象

  • Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
  • Object.isSealed 如果对象是密封的则返回 true,属性都具有 configurable: false
js
const user = {
  name: "Jay",
  sex: "male"
}

Object.seal(user)
console.log(Object.isSealed(user)) // true
console.log(Object.getOwnPropertyDescriptors(user))
/*
{
  name: {
    value: 'Jay',
    writable: true,
    enumerable: true,
    configurable: false
  },
  sex: {
    value: 'male',
    writable: true,
    enumerable: true,
    configurable: false
  }
}
*/

冻结对象

  • Object.freeze 冻结对象后不允许添加、删除、修改属性,writable、configurable都标记为false
  • Object.isFrozen()方法判断一个对象是否被冻结
js
const user = {
  name: "Jay",
  sex: "male"
}

Object.freeze(user)
console.log(Object.isFrozen(user)) // true
console.log(Object.getOwnPropertyDescriptors(user))
/*
{
  name: { value: 'Jay', writable: false, enumerable: true, configurable: false },
  sex: { value: 'male', writable: false, enumerable: true, configurable: false }
}
*/

属性访问器

getter:get 语法将对象属性绑定到查询该属性时被调用的函数

setter:当尝试设置属性时,set 语法将对象属性绑定到要调用的函数,可在类中使用

gettersetter是 JS 提供的存取器特性,使用函数来管理属性,使用场景如下:

  • 用于避免错误的赋值
  • 需要动态监测值的改变
  • 属性只能在访问器和普通属性任选其一,不能同时存在
js
const obj = {
  _name: "Jay",
  // 使用set关键字定义一个setter
  set name(value) {
    if (typeof value === 'string') {
      this._name = value
    } else {
      console.log("Name must be a string.")
    }
  },
  // 使用get关键字定义一个getter
  get name() {
    return this._name
  }
}

obj.name = "Zoe" 
console.log(obj.name) // "Zoe"
// 不会改变 _name 的值
obj.name = 123 // "Name must be a string."

也可以使用Object.defineProperty()在对象上定义gettersetter

js
const obj = {}

Object.defineProperty(obj, 'name', {
  // 设置属性的 getter
  get: function () {
    return this._name
  },
  // 设置属性的 setter
  set: function (value) {
    if (typeof value === 'string') {
      this._name = value
    } else {
      console.log("Name must be a string.")
    }
  },
  // 其他属性特性
  enumerable: true,
  configurable: true
})

obj.name = "Alice"
console.log(obj.name) // Alice

proxy代理

setter/getter是对单个对象属性的控制,而代理是对整个对象的控制,实现了对基本操作的拦截和自定义

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例

js
const proxy = new Proxy(target, handler)
// target表示所要拦截的目标对象
// handler参数为定制拦截对象

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法

js
const user = { name: "Jay" }
const proxy = new Proxy(user, {
  get(obj, property) {
    return obj[property]
  },
  set(obj, property, value) {
    console.log('proxy调用了')
    obj[property] = value
    return true
  }
})
proxy.age = 10
console.log(user) // { name: "Jay", age: 10 }

要使Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象进行操作

js
user.sex = 'male' // 直接修改了原对象,proxy不起作用

Set与Map

Set

Set 类似于值唯一的数组,集合内部数据顺序以插入顺序而定。其基本使用如下图所示:

image-20240604171033960

数组的mapfilter方法也可以间接用于Set

js
let set = new Set([1, 2, 3])
set1 = new Set([...set].filter(x => (x % 2) == 0)) // Set{ 2 }
set2 = new Set([...set].map(x => x * 2)) // Set{ 2, 4, 6 }

使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)

js
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])

// 并集
let union = new Set([...a, ...b]) // Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x))) // set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x))) // Set {1}

WeakSet

WeakSet结构与Set类似,有几点区别:

  • WeakSet的成员只能是对象Symbol,而不能是其他类型的值
  • WeakSet中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用
  • WeakSet不可迭代,不能用迭代器方法遍历
  • WeakSet没有clear()
  • WeakSet没有size属性,也没有返回元素数量的方法,因为其大小可能会随着垃圾回收的进行而动态变化
js
const ws = new WeakSet()
const obj = {}
const foo = {}

ws.add(window)
ws.add(obj)

ws.has(window) // true
ws.has(foo) // false

ws.delete(window) // true
ws.has(window) // false

ws.size // undefined
ws.forEach // undefined
ws.forEach(function(item){ console.log('WeakSet has ' + item)})
// TypeError: undefined is not a function

Map

JavaScript 的对象只能用字符串当作键,使用上有一定的限制。为解决该问题,ES6 提供了 Map 数据结构

Map 类似于对象,也是键值对的集合,但是键的范围不限于字符串,各种类型的值(包括对象)都可以当作键。与 Set 一样,Map 的遍历顺序也是插入顺序。其基本使用如下图所示:

image-20240605141446403

js
const map = new Map([
  ['F', 'no'],
  ['T', 'yes'],
])

for (let key of map.keys()) {
  console.log(key)
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value)
}
// "no"
// "yes"

for (let [key, value] of map.entries()) {
  console.log(key, value)
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value)
}
// "F" "no"
// "T" "yes"

WeakMap

WeakMap结构与Map结构类似,也是用于生成键值对的集合。有以下几点区别:

  • WeakMap只接受对象(null除外)和Symbol作为键名,不接受其他类型的值
  • WeakMap的键名所指向的对象,不计入垃圾回收机制,键值正常引用
  • WeakMap不可迭代,不能用迭代器方法遍历
  • WeakMap没有clear()
  • WeakMap没有size属性
  • WeakMap只有四个方法可用:get()set()has()delete()
js
const wm = new WeakMap()

// size、forEach、clear 方法都不存在
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined

WeakMap用途如下:

js
let myWeakmap = new WeakMap()
// 将DOM节点存入Map
myWeakmap.set(document.getElementById('logo'),{ timesClicked: 0 })
// DOM节点删除,timesClicked自动消失
document.getElementById('logo').addEventListener('click', function () {
  let logoData = myWeakmap.get(document.getElementById('logo'))
  logoData.timesClicked++
}, false)

拷贝

浅拷贝

浅拷贝只复制对象的第一层属性。如果属性是一个基本数据类型,那么它会复制这个值;如果属性是一个引用类型,那么它只会复制引用的地址,而不是引用的对象本身

  • 拷贝对象:Object.assgin()、展开运算符{...obj}
  • 拷贝数组:Array.prototype.concat()或者[...arr],当然其他返回新数组的方法也为浅拷贝
js
// 对象拷贝
const obj = {
  name: 'jack',
  age: 18
}
const newObj1 = Object.assign({}, obj)
const newObj2 = { ...obj }

//数组拷贝
const arr = [1, 2, 3]
const newArr1 = [].concat(arr)
const newArr2 = [...arr]

深拷贝

深拷贝会创建一个完全独立的新对象,其中所有的属性都是原始对象属性的副本。对于引用类型的属性,深拷贝会递归地复制这些属性,直到所有的属性都被复制完毕。深拷贝实现原理是递归

  • 递归实现
js
//方法一
function deepCopy (newObj, oldeObj) {
  for (let k in oldeObj) {
    if (oldObj[k] instanceof Array) {
      newObj[k] = []
      deepCopy(newObj[k], oldeObj[k])
    } else if (oldObj[k] instanceof Object) {
      newObj[k] = {}
      deepCopy(newObj[k], oldeObj[k])
    } else {
      newObj[k] = oldObj[k]
    }
  }
}

//方法二
function cloneDeep (oldObj) {
  let newObj = Array.isArray(oldObj) ? [] : {}
  for (let k in oldObj) {
    if (typeof oldObj[k] !== 'object') {
      newObj[k] = oldObj[k]
    } else {
      newObj[k] = cloneDeep(oldObj[k])
    }
  }
  return newObj
}
  • lodash库中cloneDeep内部实现了深拷贝,本质就是更彻底的递归,官网地址

  • 序列化与反序列化:先JSON.stringify()序列化,再JSON.parse()反序列化

js
const obj = {
  uname: 'pekiy',
  age: 18,
  hobby: ['篮球', '足球'],
  family: {
    baby: 'littlePekiy'
  }
}
// 序列化时会丢弃对象中的函数和 undefined 属性 
const o = JSON.parse(JSON.stringify(obj))

定时器

间歇函数

setInterval 是 JavaScript 中用于周期性调用函数的方法,它会按照指定的时间间隔(以毫秒为单位)重复执行指定的函数

javascript
let timeId = setInterval(func[, delay, arg1, arg2, ...]) // 返回值为定时器开启的ID

定时器函数可以开启和关闭定时器

js
// 开启
let timerId = setInterval(function() {
    console.log('hi~~~')
}, 1000)

// 关闭
clearInterval(timerId)

延迟函数

全局的 setTimeout()方法设置一个定时器,一旦定时器到期,就会执行一个函数或指定的代码片段,仅执行一次

js
let timeId = setTimeout(functionRef, delay, param1, param2, /* … ,*/ paramN)
// setInterval()和setTimeout()共享同一个 ID 池

关闭延时函数

js
let timerId = setTimeout(functionRef, delay)
clearTimeout(timerId)

日期对象

日期在 JS 中使用十分频繁,这里拿出来单独说明

日期对象Date是用来表示日期和时间的对象,用于获取当前系统日期和时间。有四种方法可以创建Date对象:

js
const d = new Date()
const d = new Date(milliseconds) // 参数为毫秒
const d = new Date(dateString) // 日期字符串
const d = new Date(year, month, day, hours, minutes, seconds, milliseconds)

日期对象使用时,有几种比较常见的时间格式:

方法时间格式说明
Date()Fri May 17 2024 09:59:55 GMT+0800 (中国标准时间)常见的时间格式(非标准)
new Date()2024-05-19T16:08:33.517ZISO 8601 日期时间格式
getTime()
Date.now()
+new Date()
1716134913517时间戳,为1970年1月1日以来的毫秒数

日期对象方法:

方法作用说明
getFullYear()获得年份获取四位年份
getMonth()获得月份取值为0~11
getDate()获取月份中的每一天不同月份取值也不相同
getDay()获取星期取值为0~6
getHours()获取小时取值为0~23
getMinutes()获取分钟取值为0 ~59
getSeconds()获取秒取值为0~59
toLocaleString()返回该日期对象的字符串(包含日期和时间)2099/9/2018:30:43
toLocaleDateString()返回日期对象日期部分的字符串2099/9/20
toLocaleTimeString()返回日期对象时间部分的字符串18:30:00
最近更新