JavaScript基础语法
第二部分 Object对象基础知识
1、对象
对象用来存储键值对和更复杂的实体。
//创建一个空的对象
let user = new Object(); //方法一
//let user = {}; //方法二
//创建一个带数据的对象
let user = {
name: "John", // 键 "name",值 "John"
age: 30, // 键 "age",值 30
"likes birds": true, //如果key是多个词语组成,必须➕引号
};
//访问其中属性
//点符号后面要求key是符合标识符书写规范的(字母、数字、_、$,不能以数字开头)
//像上面的多词语属性名就不能直接写在.后。用[]就可以
alert( user.name );
//添加一个属性
user.isAdmin = true;
//删除一个属性
delete user.age;
使用[ ],可用于任何字符串:[ ]中的字符串不能省略引号,单双引号都可以
let user = {};
// 设置
user["likes birds"] = true;
// 读取
alert(user["likes birds"]); // true
// 删除
delete user["likes birds"];
计算属性:创建一个对象时,我们可以在对象字面量中使用方括号,这叫做计算属性。
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5, // 属性名是从 fruit 变量中得到的
};
//如果用户输入 "apple",bag 将变为 {apple: 5}。
alert( bag.apple ); // 5
“in”操作符:相比于其他语言,JavaScript 的对象有一个需要注意的特性:能够被访问任何属性。即使属性不存在也不会报错!
读取不存在的属性只会得到 undefined,但是这种方法有局限。
let user = {
test:undefined
};
alert( user.noSuchProperty === undefined ); // true 意思是没有这个属性
alert( obj.test ); // 显示 undefined,但是该属性存在
//用“in”检查属性是否存在:
alert( "test" in obj ); // true,属性存在!
// "in"的左边必须是属性名。通常是一个带引号的字符串。
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 codes = {
"+49": "Germany",
"+41": "Switzerland",
"+44": "Great Britain",
// ..,
"+1": "USA"
};
for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
}
2、对象引用和复制
对象与原始类型的根本区别之一是,对象是“通过引用”存储和复制的,而原始类型:字符串、数字、布尔值等 —— 总是“作为一个整体”复制。
赋值了对象的变量 存储的不是对象本身,而是该对象“在内存中的地址” —— 换句话说就是对该对象的“引用”。
当一个对象变量被复制 —— 引用被复制,而该对象自身并没有被复制。
当通过其中一个变量修改对象的值后,再通过另一个变量访问时,值已经变了。
//如果我们想完整复制一个对象时,可以通过遍历,再把遍历结果放入空的对象中。
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.assign方法可以达到同样的效果,
语法是:Object.assign(dest, [src1, src2, src3...])
- 第一个参数dest是目标对象,后面是源对象
- 该方法将所有源对象的属性拷贝到目标对象dest中(如果源对象中有值,则拼接到后面)
- 调用结果返回dest
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
Object.assign(user, permissions1, permissions2);
// 现在 user = { name: "John", canView: true, canEdit: true }
// 如果被拷贝的属性名已经存在,则会被覆盖。
缺点:当要拷贝的目标对象的属性中有对象类型时,Object.assign方法只能拷贝地址。
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); // true,同一个对象
// user 和 clone 分享同一个 sizes
user.sizes.width++; // 通过其中一个改变属性值
alert(clone.sizes.width); // 51,能从另外一个获取到变更后的结果
可以使用 lodash 库的 _.cloneDeep(obj) 进行“深拷贝”。
使用 const 声明的对象也是可以被修改的。
const user = {name: "John"};
user.name = "Pete"; // (*)
alert(user.name); // Pete
3、垃圾回收
垃圾回收机制是自动执行的,我们不能强制执行或阻止。
JavaScript 中主要的内存管理概念是可达性。
“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。
- 这里列出固有的可达值的基本集合,这些值明显不能被释放:
- 当前执行的函数,它的局部变量和参数。
- 当前嵌套调用链上的其他函数、它们的局部变量和参数。
- 全局变量。
这些值被称作 根(roots)。
- 如果一个值可以通过引用链从根访问任何其他值,则认为该值是可达的。
比方说,如果全局变量中有一个对象,并且该对象有一个属性引用了另一个对象,则该对象被认为是可达的。而且它引用的内容也是可达的。
内部算法
垃圾回收的基本算法被称为 “mark-and-sweep”。
定期执行以下“垃圾回收”步骤: - 垃圾收集器找到所有的根,并“标记”它们。
- 然后它遍历并“标记”来自它们的所有引用。
- 然后它遍历标记的对象并标记它们的引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
- ……如此操作,直到所有可达的(从根部)引用都被访问到。
- 没有被标记的对象都会被删除。
4、this
let user = {
name: "John",
age: 30,
sayHi() {
// "this" 指的是调用该方法的对象,即user
alert(this.name);
}
};
user.sayHi(); // John
箭头函数没有自己的this,在箭头函数中引用this,this的值取决于外层的正常的函数
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // Ilya
当我们并不想要一个独立的 this,反而想从外部上下文中获取时,箭头函数很有用。
5、构造函数和new
构造函数在技术上是常规函数。不过有两个约定:
- 它们的命名以大写字母开头。
- 它们只能由 “new” 操作符来执行。
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
当一个函数被new操作符执行时,执行以下步骤:
function User(name) {
// 1、创建一个空对象并分配给this。this = {};(隐式创建)
// 2、修改this,添加属性
this.name = name;
this.isAdmin = false;
// 3、return this;(隐式返回)
}
//new User("Jack");时做的就是以上3步
任何函数(除了箭头函数)都可以用做构造器,即都可通过new 来运行。
通常构造器没有return,如果有的话,遵循以下原则:
- 如果 return 返回的是一个对象,则返回这个对象,而不是 this。
- 如果 return 返回的是一个原始类型,则忽略,返回this。
我们不仅可以将属性添加到this中,还可以添加方法。
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
6、可选链“?.”
当想要读取一个属性时,因为 user.address 为 undefined,尝试读取 user.address.street 会失败,并收到一个错误。
let user = {}; // 一个没有 "address" 属性的 user 对象
alert(user.address.street); // Error!
但是我们希望不存在的话,返回的是undefined,而不是error。
方案一:
let user = {};
alert(user.address ? user.address.street : undefined);
方案二:
let user = {};
alert( user.address && user.address.street && user.address.street.name ); // undefined(不报错)
方案三(可选链):
let user = {};
alert( user?.address?.street ); // undefined(不报错)
//?.前面的值不存在的话,直接返回undefined,如果存在,则.访问下一个属性
7、symbol类型
除了字符串类型可以作为对象属性外,只有symbol类型可以作为对象属性,其他类型作对象属性时都会自动转为字符串类型。
可以使用 Symbol()
来创建这种类型的值:
let id = Symbol();
//也可以给他一个symbol名
let id = Symbol("id");
//symbol 保证是唯一的。即使我们创建了许多具有相同描述的 symbol,它们的值也是不同。描述只是一个标签,不影响任何东西。
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
众所周知,alert会将任何值转换为字符串显示,但是无法转换symbol值,必须将symbol值toString()
后才可以,或者获取 symbol.description
属性,只显示描述(description):
let id = Symbol("id");
alert(id); // 类型错误:无法将 symbol 值转换为字符串。
alert(id.toString()); // Symbol(id)
alert(id.description); // id
隐藏属性:symbol 允许我们创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。
例如:
let user = { // 属于另一个代码
name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // 我们可以使用 symbol 作为键来访问数据
由于 user 对象属于另一个代码库,所以向它们添加字段是不安全的,因为我们可能会影响代码库中的其他预定义行为。但 symbol 属性不会被意外访问到。第三方代码不会知道新定义的 symbol,因此将 symbol 添加到 user 对象是安全的。
如果我们要在对象字面量 {…} 中使用 symbol,则需要使用方括号把它括起来。
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // 把id的值作为键
};
symbol在for…in中会被跳过,Object.keys(obj) 也会忽略它们,但是Object.assign 会同时复制字符串和 symbol 属性。
8、对象——原始值转换
转换规则:
- 没有转换为布尔值。所有的对象在布尔上下文(context)中均为 true,就这么简单。只有字符串和数字转换。
- 数字转换发生在对象相减或应用数学函数时。例如,Date 对象(将在 日期和时间 一章中介绍)可以相减,date1 - date2 的结果是两个日期之间的差值。
- 至于字符串转换 —— 通常发生在我们像 alert(obj) 这样输出一个对象和类似的上下文中。
原网站链接