javaScript对象
对象
对象用来存储键值对和更复杂的实体。
- 创建
let user=new Object();//构造函数的语法
let user={};//字面量的语法
- 用点号或方括号访问属性值,方括号比点符号更强大。它允许任何属性名和变量,但写起来也更加麻烦
- delete 操作符移除属性
- 用多字词语作为属性名,必须加上引号
- 属性名会被自动转换成字符串
列表中的最后一个属性以都好结尾,叫做尾随或悬挂逗号,便于添加、删除和移动属性。
-
使用ocnst声明的对象可以被修改其中的内容,而不能指向另一个对象。
-
计算属性
let bag={
[fruit]:5,//其中fruit为变量名
}
- 属性值简写,也可以和正常方式混用。
function makeUser(name, age) {
return {
name, // 与 name: name 相同
age, // 与 age: age 相同
// ...
};
}
- 属性存在性测试:In操作符
javaScript可以访问任何属性,若属性不存在则会得到undefined,检查属性是否存在用In
通常情况下不要给属性赋值为undefined,赋值为null;
- for…in循环
for(let key in user)
{
...
}
属性键的顺序:若为整数类型则按从大到小排序,若不是整数类型则按照定义的先后顺序排序。
对象引用和复制
与原始类型相比,对象的根本区别之一是对象是“通过引用”被存储和复制的,与原始类型值相反:字符串,数字,布尔值等 —— 始终是以“整体值”的形式被复制的。
复制对象时复制了其引用(内存地址),通过复制后的引用进行对象的修改会影响对象本身。
-
引用的比较:只有引用指向的两个对象为同一对象时,两者才相等。两个独立但内容相等的对象并不相等。
-
克隆与合并:若真想完全复制一个对象,则需要创建一个新的对象,并通过遍历现有属性的结构,复制到新对象中。或者可以采用object.assgin
Object.assign(dest,[src1,src2,src3,...])
可以用它来合并多个对象
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
// 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中
Object.assign(user, permissions1, permissions2);
// 现在 user = { name: "John", canView: true, canEdit: true }
//或者:
let clone = Object.assign({},user)
- 深层克隆
当属性为其他对象的引用时,拷贝后的属性会与原属性共用同一对象。
为了解决此问题,我们应该使用会检查每个 user[key]
的值的克隆循环,如果值是一个对象,那么也要复制它的结构。这就叫“深拷贝”。
工具:使用现成的实现,例如 JavaScript 库 lodash 中的 _.cloneDeep(obj)。
垃圾回收
javascript的内存管理是自动的;
可达性
内部算法
- 垃圾收集器找到所有的根,并“标记”(记住)它们。
- 然后它遍历并“标记”来自它们的所有引用。
- 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
- ……如此操作,直到所有可达的(从根部)引用都被访问到。
- 没有被标记的对象都会被删除。
对象方法
在对象中属性的值可以是一个函数,函数就是该对象的action,该函数称为对象方法。
let user = {
name: "John",
age: 30
};
user.sayHi=function(){
alert("Hello!");
};
user.sayHi();//Hello!
- 方法简写
let user={
sayHi(){
alert("Hello");
}
};
//和上面的完全等价,可以省略function,只写sayHi().
let user={
sayHi: function(){
alert("Hello!");
}
};
用this访问对象内部的信息。
this的值是在代码运行时计算出来的,取决于代码上下文。
在没有对象的情况下调用this==undefined,this将会是全局对象。
- 注意:箭头函数没有自己的this
构造器和操作符new
构造函数
命名以大写字母开头,只能由new操作符来执行。
从技术上讲,任何函数都可以用作构造器。即:任何函数都可以通过 new
来运行,它会执行上面的算法。“首字母大写”是一个共同的约定,以明确表示一个函数将被使用 new
来运行。
构造器不能被再次调用,因为它不保存在任何地方,只是被创建和调用。因此,这个技巧旨在封装构建单个对象的代码,而无需将来重用。
构造器通常没有return语句
如果没有参数,可以省略new后的括号。
可选链?.
不存在的属性的问题
要验证属性是否存在,用&&和?:都比较麻烦,需要重复写对象属性名。
如果可选链 ?.
前面的部分是 undefined
或者 null
,它会停止运算并返回该部分,也就是undefined。
let user={};
alert(user?.address?.street);//undefined,不会报错
//即使对象user不存在,上述读取地址操作也没关系
不要过度使用可选链
我们应该只将 ?.
使用在一些东西可以不存在的地方。
例如,如果根据我们的代码逻辑,user
对象必须存在,但 address
是可选的,那么我们应该这样写 user.address?.street
,而不是这样 user?.address?.street
。
所以,如果 user
恰巧因为失误变为 undefined,我们会看到一个编程错误并修复它。否则,代码中的错误在不恰当的地方被消除了,这会导致调试更加困难。
- 短路效应
若?.左部不存在,就会立即停止运算,后面又任何函数调用或者副作用都不会执行。
- 其他变体:?.() ?.[]
用?.()调用一个可能不存在的函数,用?.[]从一个可能不存在的对象上安全地读取属性。
?.可以和delete一起使用,但不能用作左值。
- 总结
obj?.prop
—— 如果obj
存在则返回obj.prop
,否则返回undefined
。obj?.[prop]
—— 如果obj
存在则返回obj[prop]
,否则返回undefined
。obj.method?.()
—— 如果obj.method
存在则调用obj.method()
,否则返回undefined
。
Symbol类型
对象的属性键只能是字符串类型或者Symbol类型。
Symbol是唯一标识符的基本类型。
Symbol值表示唯一的标识符,创建时可以给Symbol一个描述,也成为Symbol名,在代码调试时有用。
let id=Symbol("id");
Symbol不会被自动转换为字符串。调用.toString()可以显示转换;或者获取symbol.description;
- 隐藏属性
应用:为第三方代码添加标识符;
对象字面量中的symbol:使用方括号将其括起来,因为我们需要Id的值作为键,而不是字符串id。
- symbol在for…in中会被跳过,但Object.assign会同时复制字符串和symbol属性。
- 全局symbol
全局symbol注册表:保证每次访问相同名字的symbol返回相同的symbol值。
在注册表中读取(不存在则创建)Symbol,使用Symbol.for(key)
。该调用会检查全局注册表,如果有一个描述为 key
的 Symbol,则返回该 Symbol,否则将创建一个新 Symbol(Symbol(key)
),并通过给定的 key
将其存储在注册表中。
- 反向调用:symbol.keyFor(sym),通过全局Symbol返回一个名字。
内部使用全局Symbol注册表来查找Symbol的键,所以其不适用于非全局Symbol.
具体查看https://zh.javascript.info/symbol教程。
对象-原始值转换
对象被运算时会自动转换为原始值。
对象到原始值的转换,是由许多期望以原始值作为值的内建函数和运算符自动调用的。
这里有三种类型(hint):
"string"
(对于alert
和其他需要字符串的操作)"number"
(对于数学运算)"default"
(少数运算符)