JavaScript总结【2】对象Object基础

本文详细介绍了JavaScript中的对象基础,包括属性命名、调用、计算属性,以及对象引用、复制、垃圾回收机制。重点讲解了this在对象方法中的作用、构造函数和new操作符的工作原理,还探讨了可选链、Symbol类型的应用,以及对象的原始值转换和属性配置。此外,文章还讨论了如何使用getter和setter进行属性访问控制,以及如何利用Symbol创建隐藏属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Object(对象)基础

通过使用带有可选 属性列表 的花括号 {…} 来创建对象。一个属性就是一个键值对(“key: value”),其中键(key)是一个字符串(也叫做属性名),值(value)可以是任何值。

对象属性

属性命名

  • 属性名可以是任何字符串或者 symbol,其他类型自动转换为字符串
  • **属性名简写:**部分情况下省略:...
  • **属性存在测试:**使用in操作符

属性调用

  • **点符号:**点符号要求 key 是有效的变量标识符。这意味着:不包含空格,不以数字开头,也不包含特殊字符(允许使用 $_
  • 方括号:方括号中的字符串必须放在引号中,方括号中变量 key 可以是程序运行时计算得到的,也可以是根据用户的输入得到的,点符号不行

计算属性

当创建一个对象时,我们可以在对象字面量中使用方括号。这叫做 计算属性,如let bag = { [fruit + 'Computers']: 5}

对象引用和复制

引用及比较

地址,两对象===必须在地址相同为统一对象时成立

克隆与合并

  1. Object.assign

    • Object.assign(dest, [src1, src2, src3...])dest对象与src合并,改变了dest对象并且返回值也为合并的结果!
    • 注意,只拷贝了一层,即多层引用无法复制,进行克隆时可传入空对象。
    • 存在多个同名属性时后面的会覆盖前面的
  2. 深拷贝:递归实现

    • 递归
    • 使用现有库
// 造轮子
function deepClone (sourceObj, targetObj) {
    let cloneObj = targetObj || {}
    if(!sourceObj || typeof sourceObj !== "object" || sourceObj.length === undefined){
        return sourceObj
    }
    if(sourceObj instanceof Array){
        cloneObj = sourceObj.concat()
    } else {
        for(let i in sourceObj){
            if (typeof sourceObj[i] === 'object') {
                cloneObj[i] = deepClone(sourceObj[i], {})
            } else {
                cloneObj[i] = sourceObj[i]
            }
        }
    }
    return cloneObj
}

// lodash函数库
// npm install lodash
import lodash from 'lodash'
let targetOj = lodash.cloneDeep(sourceObj)

JavaScript垃圾回收机制

可达性分析

”可达“值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。

基本步骤

垃圾回收的基本算法被称为 “mark-and-sweep”。定期执行以下**“垃圾回收”步骤:**

  • 垃圾收集器找到所有的根,并“标记”(记住)它们。
  • 然后它遍历并“标记”来自它们的所有引用。
  • 然后它遍历标记的对象并标记它们的引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象
  • ……如此操作,直到所有可达的(从根部)引用都被访问到。
  • 没有被标记的对象都会被删除
    javascriptInfo 垃圾回收机制

优化算法

  • 分代收集:辈分越高,检查频次越低
  • 增量收集:CMS垃圾回收机制,三色标记原理,分部分处理
  • 闲时收集:如其名

对象方法中的this

  1. 谁调用指向谁
  2. 箭头函数上下文

对象的构造函数和操作符new

构造函数

**构造函数:**技术所就是常规的函数,但我们预定:

  • 命名以大写字母开头
  • 它们只能由 new 操作符来执行

new操作符

  1. new执行时的步骤:

    • 一个新的空对象被创建并分配给 this
    • 函数体执行。通常它会修改 this,为其添加新的属性
    • 返回 this 的值
// 过程类似于:
function User(name) {
  // this = {};(隐式创建)

  // 添加属性到 this
  this.name = name;
  this.isAdmin = false;

  // return this;(隐式返回)
}
  1. 检测是否使用了new调用函数

可以在函数内部使用new.target判断是否使用new调用的

可选链?.

首先在此约定对象存在即非undefined、非null

可选链是什么

**什么是可选链:**它不是运算符,而是一种特殊语法结构

// 这个用户信息有没有地址?有没有街道?
let user = {}; // user 没有 address 属性
alert( user?.address?.street ); // undefined(不报错)

&&相比可以少些几遍属性名

注意:使用钱必须声明变量,不能过度使用,其同样具有短路效应(不存在时就短路了)

可选链的变体

  • ?.() 用于调用一个可能不存在的函数
  • 与前面一样,但不使用点符号,而使用方括号访问属性,即使用?[]
  • delete一起使用,如delete user?.name; // 如果 user 存在,则删除 user.name

注意:可以用来删除,但不能用来写入!

Symbol类型

什么是symbol

“Symbol” 值表示唯一的标识符,它不会自动转换为字符串。

let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false

获取symbol描述的属性

使用sym.description即可

创建隐藏属性

let user = { // 属于另一个代码
 name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // 使用 Symbol 作为键来访问数据

在对象中使用symbol作为字面量

let id = Symbol("id");

let user = {
 name: "John",
 [id]: 123 // 而不是 "id":123
};

symbol的键

  • symbol 在for…in 中会被跳过
  • Object.keys(user) 也会忽略它
  • Object.assign(...)能复制到它

全局symbol

  • Symbol.for(key)可以在全局注册表中读取symbol不存在则创建它
  • Symbol.for(key) 完全相反,通过symbolf返回symbol内的名字(书写时哪个字符串),未找到返回undedined

系统Symbol

以下都是symbol对象

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive

对象的原始值转换

对象原始值三种转换

  1. boolean:所有的对象在布尔上下文(context)中均为true,哪怕是空对象。所以对于对象,不存在 to-boolean 转换,只有字符串和数值转换。
  2. number:数值转换发生在对象相减或应用数学函数时。例如,Date 对象(将在 日期和时间 一章中介绍)可以相减,date1 - date2 的结果是两个日期之间的差值
  3. string:至于字符串转换 —— 通常发生在我们像 alert(obj) 这样输出一个对象和类似的上下文中

ToPrimitive

  1. 转换调用顺序—>转换能得到什么
  • 调用 obj[Symbol.toPrimitive](hint) —— 带有 symbol 键 Symbol.toPrimitive(系统 symbol)的方法,如果这个方法存在的话
  • 否则,如果 hint 是 "string" —— 尝试 obj.toString()obj.valueOf(),无论哪个存在。
  • 否则,如果 hint 是 "number""default" —— 尝试 obj.valueOf()obj.toString(),无论哪个存在
  1. Symbol.toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

const object1 = {
  [Symbol.toPrimitive](hint) {
    if (hint === 'number') {
      return 42;
    }
    return null;
  }
};

console.log("x"+object1); // expected output: 42
console.log("x"+object1); // expected output: "xnull"
  1. toString和valueOf
// 只要你进行了转换,对象没有实现Symbol.toPrimitive他就会调用,可以重写它
let user = {
  name: "John",
  money: 1000,

  // 对于 hint="string"
  toString() {
    return `{name: "${this.name}"}`;
  },

  // 对于 hint="number" 或 "default"
  valueOf() {
    return this.money;
  }

};

alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500

对象属性配置

  1. 常用属性标志

以下默认均为true

  • writable — 如果为 true,则值可以被修改,否则它是只可读的。
  • enumerable — 如果为 true,则会被在循环中列出,否则不会被列出。
  • configurable — 如果为 true,则此特性可以被删除,这些属性也可以被修改,否则不可以
  1. 获取和修改属性标志

    • 获取:

      • let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
      • Object.getOwnPropertyDescriptors(obj):允许一次获取全部属性
    • 修改:

      • Object.defineProperty(obj, propertyName, descriptor)

      • Object.defineProperties():允许一次定义多个属性

let user = {};

Object.defineProperty(user, "name", {
  value: "John"
});

let descriptor = Object.getOwnPropertyDescriptor(user, 'name');

alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
  "value": "John",
  "writable": false,
  "enumerable": false,
  "configurable": false
}
 */

访问器属性

get 和 set 访问器属性
let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// set fullName 将以给定值执行
user.fullName = "Alice Cooper";

alert(user.name); // Alice
alert(user.surname); // Cooper

注意:一个属性要么是访问器(具有 get/set 方法),要么是数据属性(具有 value),但不能两者都是

// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
  get() {
    return 1
  },

  value: 2
});
更聪明的 getter 和 setter

Getter/setter 可以用作“真实”属性值的包装器,以便对它们进行更多的控制

let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short, need at least 4 characters");
      return;
    }
    this._name = value;
  }
};

user.name = "Pete";
alert(user.name); // Pete

user.name = ""; // Name 太短了……

注意:从技术上讲,外部代码可以使用 user._name 直接访问 name。但是,这儿有一个众所周知的约定,即以下划线 "_" 开头的属性是内部属性,不应该从对象外部进行访问。

访问器属性的重要作用

兼容性,它们允许随时通过使用 getter 和 setter 替换“正常的”数据属性,来控制和调整这些属性的行为。

// 加入原来我们的User对象是这样的
function User(name, age) {
  this.name = name;
  this.age = age;
}

// 现在我们想存储birthdaty,而不是age
function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;
}

// 添加访问器属性改进
function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  // 年龄是根据当前日期和生日计算得出的
  Object.defineProperty(this, "age", {
    get() {
      let todayYear = new Date().getFullYear();
      return todayYear - this.birthday.getFullYear();
    }
  });
}

let john = new User("John", new Date(1992, 6, 1));
alert( john.birthday ); // birthday 是可访问的
alert( john.age );      // ……age 也是可访问的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值