JavaScript基础语法(2、Object对象)

JavaScript基础语法

来源:现代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

构造函数在技术上是常规函数。不过有两个约定:

  1. 它们的命名以大写字母开头。
  2. 它们只能由 “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,如果有的话,遵循以下原则:

  1. 如果 return 返回的是一个对象,则返回这个对象,而不是 this。
  2. 如果 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、对象——原始值转换

转换规则:

  1. 没有转换为布尔值。所有的对象在布尔上下文(context)中均为 true,就这么简单。只有字符串和数字转换。
  2. 数字转换发生在对象相减或应用数学函数时。例如,Date 对象(将在 日期和时间 一章中介绍)可以相减,date1 - date2 的结果是两个日期之间的差值。
  3. 至于字符串转换 —— 通常发生在我们像 alert(obj) 这样输出一个对象和类似的上下文中。
    原网站链接
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值