笔记参考javascript.info中文站
对象简介
对象(Object)是 Javascript 中最重要的一种数据类型
我们可以用花括号 {} 来创建一个对象,其中包括对象的元素,每个元素又包括一个键 “key”(字符串)和一个值 “value”(任何类型)
我们可以用下面两种语法中的任一种来创建一个空的对象:
let user = new Object(); // “构造函数” 的语法
let user = {}; // “字面量” 的语法,更常用
1. 基本操作
创建对象: 创建新对象的时候就可以进行初始化,将一系列键值对录入对象中,每一对键值最后需要加一个 “,”
let user = {
name: "John",
age: 30,
}
增删改查: 使用点符号访问属性, delete
是删除的关键词,其余三种操作点符号直接进行即可
user.isAdmin = true;
delete user.age; // 删
alert( user.name ); // 查
user.sex = "woman" // 增
user.name = "Ann" // 改
方括号: 当键的字符串中含有空格时,需要方括号将其括起来在进行点符号操作,内部使用
let user = {};
// 设置
user["likes birds"] = true;
// 读取
alert(user["likes birds"]); // true
此外,方括号中的字符串也可以用变量代替,这样更灵活,但也更麻烦
所以,当属性名是已知且简单的时候,就使用点符号。如果我们需要一些更复杂的内容,那么就用方括号。
2. 属性名称
在开发过程中,会经常遇到属性名跟变量名一样的情况,例如:
function makeUser(name, age) {
return {
name: name,
age: age,
// ……其他的属性
};
}
在这种情况下,我们可以简写属性名:
function makeUser(name, age) {
return {
name, // 与 name: name 相同
age, // 与 age: age 相同
// ...
};
}
除此之外,属性名还有一个特点:没有任何限制
保留字也可以使用,数字也可以使用,因为他们都会默认转化成字符串
3. “in” 操作符
操作符 “in” 可以让我们检查所有属性,即使不存在也没关系,会返回 undefined
()
let user = { name: "John", age: 30 };
alert( "age" in user ); // true,user.age 存在
alert( "blabla" in user ); // false,user.blabla 不存在。
4. “for…in” 循环
为了遍历一个对象的所有键(key),可以使用一个特殊形式的循环:for..in
举个例子:
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// keys
alert( key ); // name, age, isAdmin
// 属性键的值
alert( user[key] ); // John, 30, true
}
上面的例子中看起来是按照创建的顺序遍历的,但事实上,当属性是整数的字符串时,将按照数字从小到大遍历
举个例子:
let codes = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
// ..,
"1": "USA"
};
for(let code in codes) {
alert(code); // 1, 41, 44, 49
}
对象引用和复制
1. 对象复制的特点
普通数据类型的复制都是直接将数据本身复制一份到新的变量中,而对象不同,他复制的是对象的 “引用” ,也就是对象所在的 “地址”,而不是对象本身的内容。
因此普通数据复制后修改新变量并不会影响原有变量的值,但对象所依附的新变量和就变量事实上所指的是同一个对象的地址,那么在修改时就会同步修改,导致不可预测的问题。
2. 通过引用来比较
两个对象比较时,实际上比较的是他们的引用,因此复制对象的两个变量看似不同,但判定相同;键值对完全一致的两个对象看似相同,但判定不是同一个引用,因此不同
let a = {};
let b = a; // 复制引用
alert( a == b ); // true,都引用同一对象
alert( a === b ); // true
let a = {};
let b = {}; // 两个独立的对象
alert( a == b ); // false
3. 克隆与合并
为了避免 1.对象复制的特点 中所说的同步修改的问题,避免复制引用,而是直接复制内容,要怎么做呢?
原理就是用 for...in
语句一个个赋值,但我们不需要重复造轮子,这里有一个函数可以直接用:
Object.assign(dest, [src1, src2, src3...])
dest
是目标对象,src数组则是被复制的对象,数组中所有对象的所有属性都会按照顺序拷贝到 dest
对象中,如果被拷贝的属性的属性名已经存在,那么它会被覆盖
当src数组只有一个对象时,就是浅拷贝
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
当src数组有多个对象时,则是对象合并
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 }
4. 深层克隆
浅拷贝克隆可以解决大多数问题,但是当对象的属性本身也是对象的时候,拷贝的属性又会变成引用,此时依然存在 “同步修改” 的潜在危险,于是我们需要用到深层拷贝
原理是:监视所有属性,当属性不是对象时,正常拷贝;当属性的值是对象时,在拷贝函数内部再调用一次拷贝函数,递归执行。
同样的,也有现成的函数:lodash 库的 _.cloneDeep(obj)
举个例子:
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false,两个对象已经不是同一个引用
垃圾回收
对于 Javascript,开发者设置的一切数据都是需要占用内存的,因此如果不及时清理,计算机就无法持续运行,于是就有了垃圾回收
1. 可达性(Reachability)
JavaScript 中主要的内存管理概念是可达性。
首先,列出一些固定的 “根” ,即函数中的参数和局部变量、代码中的全局变量等
然后就可以判断其余的变量是否可以通过根访问到,如果可以就是可达的
2. 例1
// user 具有对这个对象的引用
let user = {
name: "John"
};
这里有一个全局对象 user
,它有一个属性 name
,对应的值为 “John”
因此,user
是根,name可以通过 user
访问到,是可达的
如果我们此时重写 user
:
user = null;
name
这个属性并不会直接消失,而是会和对象 user
失去联系,无法被访问到,在下次垃圾回收中被判定为不可达,因此被清理掉
3. 例2
那么,如果一个对象有两个引用它的变量呢?
// user 具有对这个对象的引用
let user = {
name: "John"
};
let admin = user;
此时再将 user
重写,name
依然可以被 admin
访问到,并没有完全和 “根” 失去联系,是可达的
4. 例3
再看一个更复杂的例子
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
我们通过 marry()
函数让两个人(对象)结婚了,并最终返回了一个 family 对象,并赋予了二人各自的身份(wife / husband)
他们的关系就变成这样了:
现在进行这些操作:
delete family.father;
delete family.mother.husband;
于是,family 中没有了 father ,wife 也不再和 husband 联系
结果就是, 只有 John 认为 Ann 还是他的妻子,但这个家已经没有他的位置了,因为他失去了 “父亲” 和 “丈夫” 两重身份。
那么现在,John 所在的对象就是不可达的,尽管他还可以访问 Ann,但是已经没有可以访问他的 “根”了,所以他会被清理。
5. 内部算法
垃圾回收的基本算法被称为 “mark-and-sweep”,虽然叫 “根” 但实际上这是一种关系图算法,通过迭代标记来遍历图的所有节点,没有被标记的就是不可达节点,被清理
在此不多介绍