对象
“普通对象(plain object)”,或者就叫对象。
字面量
let user = new Object(); // “构造函数” 的语法
let user = {}; // “字面量” 的语法
文本和属性
一个对象属性由键值
let user = { // 一个对象
name: "John", // 键 "name",值 "John"
age: 30 // 键 "age",值 30
};
可以通过delete 删除属性
delete user.age;
方括号
访问属性
let user = {};
user["likes birds"] = true;
alert(user["likes birds"]); // true
点符号
let user = {};
user["likes"] = true;
alert(user.likes); // true
计算属性
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5, // 属性名是从 fruit 变量中得到的
};
alert( bag.apple ); // 5 如果 fruit="apple"
//属性是不是固定,而是随机指定的。
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};
// 从 fruit 变量中获取值
bag[fruit] = 5;
属性值简写
function makeUser(name, age) {
return {
name: name,
age: age,
};
}
简写成
function makeUser(name, age) {
return {
name, // 与 name: name 相同
age, // 与 age: age 相同
};
}
属性名称限制
对象属性不受系统保留关键字影响。
// 这些属性都没问题
let obj = {
for: 1,
let: 2,
return: 3
};
alert( obj.for + obj.let + obj.return ); // 6
甚至变量为 int 0 也可以,系统会自动转化成 "0"
let obj = {
0: "test" // 等同于 "0": "test"
};
alert( obj["0"] ); // test
alert( obj[0] ); // test (相同的属性)
obj.0 //这个会失败
属性存在判断
- 直接判断 undefined
let user = {};
alert( user.aaa === undefined ); // true 意思是没有这个属性.
- in 操作符
let user = { name: "John", age: 30 }; alert( "age" in user ); // true,user.age 存在 alert( "blabla" in user ); // false,user.blabla 不存在。 //可以是变量判断 let user = { age: 30 }; let key = "age"; alert( key in user ); // true,属性 "age" 存在
注意 如果刚好定义的属性是 undefined 那么 直接判断undefined 和 in 都会返回true
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
}
根据创建顺序
let user = {
name: "John",
surname: "Smith"
};
user.age = 25; // 增加一个
// 非整数属性是按照创建的顺序来排列的
for (let prop in user) {
alert( prop ); // name, surname, age
}
总结
对象是具有一些特殊特性的关联数组。 它们存储属性(键值对),其中:
- 属性的键必须是字符串或者 symbol(通常是字符串)。
- 值可以是任何类型。 我们可以用下面的方法访问属性:
- 点符号:
obj.property。 - 方括号
obj["property"],方括号允许从变量中获取键,例如obj[varWithKey]。 其他操作: - 删除属性:
delete obj.prop。 - 检查是否存在给定键的属性:
"key" in obj。 - 遍历对象:
for(let key in obj)循环。
对象引用和复制
对象是通过引用地址,进行存储和复制,而原始类型,则是直接保存实际的字符串,数字或者ture false。
对象复制
只是简单进行引用复制,所有关联的东西还是同一个对象
let user = { name: "John" };
let admin = user; // 复制引用
//这时候操作 user 和 admin是等价的
引用比较
let a = {};
let b = a; // 复制引用
alert( a == b ); // true,都引用同一对象
alert( a === b ); // true
//通过字面量定义的对象是两个独立的引用地址
let a = {};
let b = {}; // 两个独立的对象
alert( a == b ); // false
对象复制/合并
通过遍历键值 循环赋值
let user = {
name: "John",
age: 30
};
let clone = {}; // 新的空对象
// 将 user 中所有的属性拷贝到其中
for (let key in user) {
clone[key] = user[key];
}
// 现在 clone 是带有相同内容的完全独立的对象
clone.name = "Pete"; // 改变了其中的数据
alert( user.name ); // 原来的对象中的 name 属性依然是 John
使用 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 }
Object.assign 克隆一个对象
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
注意 有可能复制的对象嵌套对象,这时候就需要深度拷贝。
垃圾回收
可达性
“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。
- 这里列出固有的可达值的基本集合,这些值明显不能被释放。
- 全局变量。
- 当前函数的局部变量和参数。
- 嵌套调用时,当前调用链上所有函数的变量与参数。
- 这些值被称作
根roots
- 如果一个值可以通过引用或引用链从根访问任何其他值,则认为该值是可达的。
在 JavaScript 引擎中有一个被称作 [垃圾回收器] 的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的。
场景1
// user 是 根往下引用的对象
let user = {
name: "John"
};
user = null;// 当设置null 时候, 原来的 { name: "John"} 对象已经没人指向,则会被回收
场景2 互相关联
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
//当执行下面代码时候
delete family.father;
delete family.mother.husband;
下面结果
由于 John 已经没有被引用,虽然他指向 Ann,但是垃圾回收原则是以 是否可达标准。 所以 John 会被回收哦。
- 同理 则 family = null;
- 则所有对象都会被回收。
回收的算法
mark-and-sweep 垃圾收集器是 通过定期 标记和清除 来实现 优化方案:
- 分代收集
- 增量收集
- 闲时收集
总结
主要需要掌握的内容:
- 垃圾回收是自动完成的,我们不能强制执行或是阻止执行。
- 当对象是可达状态时,它一定是存在于内存中的。
- 被引用与可访问(从一个根)不同:一组相互连接的对象可能整体都不可达。
对象方法
通过函数表达式赋值给对象属性的函数被称为 方法。
let user = {
name: "John",
age: 30
};
//方式1
user.sayHi = function() {
alert("Hello!");
};
user.sayHi(); // Hello!
//方式2 也可以先声明后赋值
function sayHi() {
alert("Hello!");
};
user.sayHi = sayHi;
方法简写
//方式1
user = {
sayHi: function() {
alert("Hello");
}
};
//方式2
let user = {
sayHi() {
alert("Hello");
}
};
方法中"this"
this 是 调用该方法的对象。即“点符号前”的是什么对象
let user = {
name: "John",
age: 30,
sayHi() {
alert(this.name);//当前对象
//等价于
alert(user.name);
}
};
this 可以用于任何函数,即使它不是对象的方法。
function sayHi() { alert( this); }//这里不报错 ,输出 window
- 严格模式下的 this 值为 undefined
- 非严格模式下的 this 值为 window
this通过代码上下文决定值的内容。是运行时计算出来的,
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
user.f = sayHi;
admin.f = sayHi;
user.f(); // John(this == user)
admin.f(); // Admin(this == admin)
箭头函数没有this
let user = {
a: "a",
sayHi() {
let fn = () => alert(this.a);
fn();
}
};
user.sayHi(); // a
this的值是在程序运行时得到的。- 一个函数在声明时,可能就使用了
this,但是这个this只有在函数被调用时才会有值。 - 可以在对象之间复制函数。
构造函数
通过构造函数创建对象,等价于字面量{...}
- 建议命名:以大写字母开头。
- 它们只能由
"new"操作符来执行。
创建流程
- 一个新的空对象被创建并分配给
this。 - 函数体执行。通常它会修改
this,为其添加新的属性。 - 返回
this的值。
function User(name) {
// this = {};(隐式创建)
// 添加属性到 this
this.name = name;
this.isAdmin = false;
// return this;(隐式返回)
}
let user = new User("Jack")
//等价于 字面量
let user = {
name: "Jack",
isAdmin: false
};
任何函数 (除了箭头函数)都可以用作构造器。即可以通过 new 来运行
构造器主要实现:可重用的对象创建代码
new function() { … }
- 匿名立即执行函数
- 用于创建单个复杂对象的代码,
let user = new function() {
this.name = "John";
this.isAdmin = false;
};
构造器模式测试:new.target
function User() {
alert(new.target);
}
// 不带 "new":
User(); // undefined 这里只是调用了一下方法
// 带 "new":
new User(); // function User { ... } 这里是实例了一个对象
构造器的 return
- 如果
return返回的是一个对象,则返回这个对象,而不是this。
function BigUser() {
this.name = "aaa";
return { name: "bbb" }; // <-- 返回这个对象
}
alert( new BigUser().name ); // aaa,得到了那个对象
//不返回,正常返回
function SmallUser() {
this.name = "John";
return; // <-- 返回 this
}
alert( new SmallUser().name ); // John
省略括号
let user = new User; // <-- 没有参数
// 等同于
let user = new User();
构造器中的方法
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
可选链 "?."
如果 ?. 左边部分不存在,就会立即停止运算(“短路效应”)
let user = {}; // user 没有 address 属性
alert( user?.address?.street ); // undefined(不报错)
let user = null;
alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
?. 前的变量必须已声明
// ReferenceError: user is not defined
user?.address;
?.() 方法调用存在判断
let userAdmin = {
admin() {
alert("I am admin");
}
};
let userGuest = {};
userAdmin.admin?.(); // I am admin
userGuest.admin?.(); // 啥都没有(没有这样的方法)
?.[] 属性访问存在判断
let user1 = {
firstName: "John"
};
let user2 = null; // 假设,我们不能授权此用户
let key = "firstName";
alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined
alert( user1?.[key]?.something?.not?.existing); // undefined
//优化删除属性
delete user?.name;
?. 不能用于赋值
let user = null;
user?.name = "John"; // Error,不起作用
// 因为它在计算的是 undefined = "John"
总结
可选链 ?. 语法有三种形式:
obj?.prop—— 如果obj存在则返回obj.prop,否则返回undefined。obj?.[prop]—— 如果obj存在则返回obj[prop],否则返回undefined。obj.method?.()—— 如果obj.method存在则调用obj.method(),否则返回undefined。
Symbol 类型
- 对象的属性键只能是字符串类型或者 Symbol 类型
- “Symbol” 值表示唯一的标识符。用于隐式保护变量,有点像java里面的private修饰符概念。
- 优势在于,当使用第三方库,自己想定义新的属性, 如果原来的第三方库有for..in,则不会遍历出来,不影响原来的代码。
// id 是 symbol 的一个实例化对象
let id = Symbol();
// id 是描述为 "id" 的 Symbol
let id = Symbol("id");
Symbol 不会被自动转换为字符串
let id = Symbol("id");
alert(id); // 类型错误:无法将 Symbol 值转换为字符串。
//应该使用.description 或者 .toString()
let id = Symbol("id");
alert(id.description); // id
let id2 = Symbol("id");
let id3 = Symbol("id");
id2 === id3 // false 每次都是独立的唯一值,id字符串只是用于标识数据
可以动态设置属性,同时不允许直接 user.id 方式访问,保护变量,只能通过user[id]访问
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
};
在 for…in 中会被跳过
Symbol 属性不参与 for..in 循环。也不支持 Object.keys(user)
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age (no symbols)
// 使用 Symbol 任务直接访问
alert( "Direct: " + user[id] );
Object.assign可以复制相关信息
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = Object.assign({}, user);
alert( clone[id] ); // 123
全局 symbol
全局访问,统一保存在注册表里。统一管理
// 从全局注册表中读取
let id = Symbol.for("id"); // 如果该 Symbol 不存在,则创建它
// 再次读取(可能是在代码中的另一个位置)
let idAgain = Symbol.for("id");
// 相同的 Symbol
alert( id === idAgain ); // true
注意 使用 Symbol("id") === Symbol.for("id") 永远不等,因为Symbol("id")每次都是唯一值。
Symbol.keyFor
只要是全局的symbol,可以根据symbol获取name
// 通过 name 获取 Symbol
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// 通过 Symbol 获取 name
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
非全局symbol 可以使用description访问name
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
alert( Symbol.keyFor(globalSymbol) ); // name,全局 Symbol
alert( Symbol.keyFor(localSymbol) ); // undefined,非全局
alert( localSymbol.description ); // name
对象 — 原始值转换
由许多期望以原始值作为值的内建函数和运算符自动调用的。
这里有三种类型(hint):
"string"(对于alert和其他需要字符串的操作)"number"(对于数学运算)"default"(少数运算符)
调用逻辑按
- objSymbol.toPrimitive
- hint 是 string 调用
obj.toString()和obj.valueOf() - hint 是 number/default 调用
obj.valueOf()和obj.toString()
ToPrimitive
Symbol.toPrimitive的内建 symbol,它被用来给转换方法命名
obj[Symbol.toPrimitive] = function(hint) {
// 返回一个原始值
// hint = "string"、"number" 和 "default" 中的一个
}
//例子
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
alert(hint);
}
};
// 转换演示:
alert(user); // string
alert(+user); // number
alert(user + 500); // default
toString/valueOf
如果没有 `Symbol.toPrimitive`,那么 JavaScript 将尝试找到它们,并且按照下面的顺序进行尝试:
- 对于 “string” hint,
toString -> valueOf。 - 其他情况,
valueOf -> toString。
let user = {
name: "John",
money: 1000,
// 对于 hint="string"
toString() {
return "string"
},
// 对于 hint="number" 或 "default"
valueOf() {
return "number/default"
}
};
alert(user); // string
alert(+user); // number/default
alert(user + 500); // number/default
只保留一种处理方式,那就用toString
let user = {
name: "John",
toString() {
return this.name;
}
};
alert(user); // toString -> John
alert(user + 500); // toString -> John500
注意 返回值 :必须返回一个原始值,而不是对象。
高阶使用:
let obj = {
// toString 在没有其他方法的情况下处理所有转换
toString() {
return "2";
}
};
alert(obj * 2); // 4,对象被转换为原始值字符串 "2",之后它被乘法转换为数字 2。