Skip to main content
 首页 » 编程设计

详解Javascript对象

2022年07月19日177www_RR

详解Javascript对象

在Javascript中有7种类型,其中6个为原始类型,因为他们的值仅包含单个内容(如字符串、数值或其他)。
反之对象通常存储包含不同类型的键集合,更加复杂。在Javascript中,对象几乎涉及方方面面,所有我们在深入使用前必须首先理解他们。

定义

对象使用{}创建,可以包含若干个属性。属性是“键:值”方式,其中键必须是字符串类型,即属性名称,值可以是任意类型。

我们想像是带有名称的文档盒子,每个属性值通过键的方式存储在其中,通过名称(键)很方面查找其中的文件(属性值),也可以添加、删除其他文件。

有两种方式定义对象,后者通常被使用。

let user = new Object(); // "object constructor" syntax 
let user = {};  // "object literal" syntax 

对象属性

我们可以通过键值对的方式增加属性:
let user = { // an object
name: “John”, // by key “name” store value “John”
age: 30// by key “age” store value 30
};

属性在“:”前有键(名称或标识),值在冒号右边。上面的示例user对象有两个属性。
1、name属性,值为“John”。
2、age属性,值为30.

对象user可想像为有两个文档的盒子,分别命名为“name”和“age”。

我们随时可以增加、删除、查找文档。属性值可以通过.号访问。

// get fields of the object: 
alert( user.name ); // John 
alert( user.age ); // 30 

值可以是任意类型,我们增加一个布尔值。

user.isAdmin = true; 

使用delete可以删除属性,示例如下:

delete user.age; 

我们也能使用多个单词作为属性的名称,但必须使用引号:
let user = {
name: “John”,
age: 30,
“likes birds”: true // multiword property name must be quoted
};

最后属性后面可以有一个多余的逗号。

let user = { 
  name: "John", 
  age: 30, 
} 

这个特性,是我们更容易增加、删除属性,因为所有行看起来一致。

方括号

对于多个单词的属性,不能直接访问:

// this would give a syntax error 
user.likes birds = true 

因为.号需要键必须为有效的变量进行标识,即不能有空格或其他限制,另一种方式使用方括号。

let user = {}; 
 
// set 
user["likes birds"] = true; 
 
// get 
alert(user["likes birds"]); // true 
 
// delete 
delete user["likes birds"]; 

现在可以访问了,需要注意的是方括号内的字符串需要引号括起来(单引号或双引号)。
方括号也提供了通过变量名称访问属性:
let key = “likes birds”;

// same as user["likes birds"] = true; 
user[key] = true; 

这里变量key是运行时计算的,或依赖用户输入。然后使用该变量访问属性,这种方式给我们很大的灵活性,点号不能使用类似的方式:
let user = {
name: “John”,
age: 30
};

let key = prompt("What do you want to know about the user?", "name"); 
 
// access by variable 
alert( user[key] ); // John (if enter "name") 

计算属性

我们能使用方括号在对象体内,一般称为计算属性。示例如下:

let fruit = prompt("Which fruit to buy?", "apple"); 
 
let bag = { 
  [fruit]: 5, // the name of the property is taken from the variable fruit 
}; 
 
alert( bag.apple ); // 5 if fruit="apple" 

计算属性的意义很明了,[fruit]意味着属性名称应该来自fruit。所以用户输入“apple”,则bag对象的内容为{apple:5}.对应代码的表现为:

let fruit = prompt("Which fruit to buy?", "apple"); 
let bag = {}; 
 
// take property name from the fruit variable 
bag[fruit] = 5; 

方括号比点号更强大,其容许任何一个属性名称和变量,当然也更难写。所以一般属性名称已知且简单,使用点号;如果我们需要更复杂或灵活的方式,使用方括号。

简化属性值

实际开发中我们通常使用变量作为属性值,示例如下:

function makeUser() { 
  let name = prompt("Name?"); 
  let age = prompt("Age?"); 
  return { 
name: name, 
age: age 
  }; 
} 
 
let user = makeUser("John", 30); 
alert(user.name); // John 

示例中,属性名称与赋值变量同名,这种情况下,可以简写。
代替name:name,为name,代码如下:

function makeUser(name, age) { 
  return { 
    name, // same as name: name 
    age   // same as age: age 
  }; 
} 

也可以在对象中同时使用正常属性和简写方式:
let user = {
name, // same as name:name
age: 30
};

属性存在性检查

可标识对象特性是其任何属性都可以访问,如果属性不存在也不会报错。访问不存在的属性,仅返回undefined。这时一个常规方法测试属性是否存在,使其和undefined比较。

let user = {}; 
 
alert( user.noSuchProperty === undefined ); // true means "no such property" 

也可以使用in操作来检查属性。语法是:"key" in object。示例如下:

let user = { name: "John", age: 30 }; 
 
alert( "age" in user ); // true, user.age exists 
alert( "blabla" in user ); // false, user.blabla doesn't exist 

注意:在in的左边必须是属性名称,通常需要使用引号括起来。如果省略引号,则意味着是变量,其包含实际被测试的值。示例如下:

let user = { age: 30 }; 
 
let key = "age"; 
alert( key in user ); // true, takes the name from key and checks for such property 

使用in比较存储undefined的值
通常使用严格比较“===undefined”检查没有问题。但是有个特列,这时使用in没有问题。即当对象属性存在,但是其值为undefined

let obj = { 
  test: undefined 
}; 
 
alert( obj.test ); // it's undefined, so - no such property? 
 
alert( "test" in obj ); // true, the property does exist! 

上面代码,属性obj.test确实存在,所以in测试没有问题。这种场景很少发生,因为undefined通常不用来赋值,我们大多数使用null或“unknown”、“empty”值。

“for…in” 循环

为了遍历对象中所有属性(键),有个特别的循环语法:for..in,这个与for(;;)语法不同。语法如下:

for(key in object) { 
  // executes the body for each key among object properties 
} 

示例,我们遍历user对象所有属性:
let user = {
name: “John”,
age: 30,
isAdmin: true
};

for(let key in user) { 
  // keys 
  alert( key );  // name, age, 30 
  // values for the keys 
  alert( user[key] ); // John, 30, true 
} 

注所有for结构,允许在循环体内声明循环变量,如这里的let key。我们也可以使用其他的变量名称代替key,如,for(let prop in obj)也可以。

属性的顺序

如果我们循环一个对象,我们获得属性顺序和他们添加的顺序一致吗?排序的依据是什么?
下面看示例,我们考虑一个对象,使用区域代码作为键。

let codes = { 
  "49": "Germany", 
  "41": "Switzerland", 
  "44": "Great Britain", 
  // .., 
  "1": "USA" 
}; 
 
for(let code in codes) { 
  alert(code); // 1, 41, 44, 49 
} 

如果我们希望49排在第一位,但实际却不是。
USA(1)第一位,然后是Switzerland(41)等。
属性的输出顺序是按照数字的升序排序顺序,因为他们是Integer,所有是 1 41 44 49

Integer属性
integer属性是一个字符串,能够转为整数,其值不变。所以“49”是一个Integer属性名称,因为当他转成整数,仍然相同,但是“+49”和“1.2”则不是。

// Math.trunc is a built-in function that removes the decimal part 
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property 
alert( String(Math.trunc(Number("+49"))) ); // "49", not same ⇒ not integer property 
alert( String(Math.trunc(Number("1.2"))) ); // "1", not same ⇒ not integer property 

另外,如果键是非整数,那么他们按照创建顺序输出,示例:

let user = { 
  name: "John", 
  surname: "Smith" 
}; 
user.age = 25; // add one more 
 
// non-integer properties are listed in the creation order 
for (let prop in user) { 
  alert( prop ); // name, surname, age 
} 

所以,为了修复这个区域代码键问题,我们可以使用非整数属性,在数字前增加“+”,达到目的,象这样:

let codes = { 
  "+49": "Germany", 
  "+41": "Switzerland", 
  "+44": "Great Britain", 
  // .., 
  "+1": "USA" 
}; 
 
for(let code in codes) { 
  alert( +code ); // 49, 41, 44, 1 
} 

按引用拷贝

对象和原始值本质不同是他们存储和按引用拷贝。原始值:string、numbers、boolean,是作为一个整体进行赋值或拷贝。示例:

let message = "Hello!"; 
let phrase = message; 

结果是,我们有两个独立的变量,每个都存储着字符串“Hello!”。

对象与之不同。对象变量不存储对象自身,而是内存地址,即其引用。图示如下:
let user = {
name: “John”
};

这里,对象存储在内存某个地址,变量user有一个引用指向它。当一个对象变量被拷贝(赋值),是引用被拷贝,对象值没有被拷贝。

如果我们想像对象是一个盒子,然后对象有一个键指向它,拷贝变量则赋值键,而不是盒子本身。示例:
let user = { name: “John” };

let admin = user; // copy the reference 

现在我们有两个变量,每个都有一个引用,指向同一对象。

let user = { name: 'John' }; 
 
let admin = user; 
 
admin.name = 'Pete'; // changed by the "admin" reference 
 
alert(user.name); // 'Pete', changes are seen from the "user" reference 

上面代码演示了只有一个对象,就如我们有一个盒子,但是带有两个键,我们通过“admin”修改其值,然后通过另一个“user”获取值,确实已经改变。

比较引用
“==”和严格相对“===”操作,对对象作用是一样的。两个对象相等仅如果他们是同一个对象。

let a = {}; 
let b = a; // copy the reference 
 
alert( a == b ); // true, both variables reference the same object 
alert( a === b ); // true 

如果是两个独立的对象,则不相对,即使都为空。

let a = {}; 
let b = {}; // two independent objects 
 
alert( a == b ); // false 

常量对象
对象声明为const可以被改变,示例如下:
const user = {
name: “John”
};

user.age = 25; // (*) 
 
alert(user.age); // 25 

你可能会认为号行会出错,但是不会,完全没有问题。那是因为const不允许user值自身,即user始终存储同一个对象引用。号行是在对象内部,并没有给user重新赋值。

如果你试图给user赋值,则会出错,示例如下:

const user = { 
  name: "John" 
}; 
 
// Error (can't reassign user) 
user = { 
  name: "Pete" 
}; 

至于如何实现让user.age不能修改,以后再谈。

 克隆和合并,对象赋值

如上所说,拷贝对象变量,只是创建了对同一对象的一个或多个针引用。但我们如何实现复制对象,是对立的拷贝,即克隆。其实是可行的,但有点困难,因为Javascript没有内置方法,事实上,这很少需要,大多数情况下都是赋值引用。

但是如果你确实需要,那么你需要创建一个新对象,然后通过迭代每个属性,然后复制他们在原始值级别,完整地复制原对象的结构。示例:

let user = { 
  name: "John", 
  age: 30 
}; 
 
let clone = {}; // the new empty object 
 
// let's copy all user properties into it 
for (let key in user) { 
  clone[key] = user[key]; 
} 
 
// now clone is a fully independant clone 
clone.name = "Pete"; // changed the data in it 
 
alert( user.name ); // still John in the original object 

我们也可以使用Object.assign方法实现.语法为 Object.assign(dest[, src1, src2, src3...])。参数都是对象,它拷贝src1、…,srcN所有对象的所有属性至dest对象。即从第二个开始的所有参数的属性都复制到第一个,然后返回dest。

示例,我们可以合并几个对象至一个:

let user = { name: "John" }; 
 
let permissions1 = { canView: true }; 
let permissions2 = { canEdit: true }; 
 
// copies all properties from permissions1 and permissions2 into user 
Object.assign(user, permissions1, permissions2); 
 
// now user = { name: "John", canView: true, canEdit: true } 

如果接收对象(user)已经有同名属性,则会覆盖:

let user = { name: "John" }; 
 
// overwrite name, add isAdmin 
Object.assign(user, { name: "Pete", isAdmin: true }); 
 
// now user = { name: "Pete", isAdmin: true } 

我们也可以使用Object.assign方式代替loop实现简单克隆:

let user = { 
  name: "John", 
  age: 30 
}; 
 
let clone = Object.assign({}, user); 

这里拷贝了user所有属性值空对象,然后返回,与loop效果一样,但不都一样。因为我们上面user对象所有属性都是原始类型,但是属性可能是引用类型,指向其他对象,这种情况会怎么呢?代码如下:

let user = { 
  name: "John", 
  sizes: { 
    height: 182, 
    width: 50 
  } 
}; 
 
alert( user.sizes.height ); // 182 

现在做 clone.sizes = user.sizes 是不够的,因为 users.sizes 是对象,只复制了引用。所以clone和user指向同一个对象。
代码如下:
let user = {
name: “John”,
sizes: {
height: 182,
width: 50
}
};

let clone = Object.assign({}, user); 
 
alert( user.sizes === clone.sizes ); // true, same object 
 
// user and clone share sizes 
user.sizes.width++;   // change a property from one place 
alert(clone.sizes.width); // 51, see the result from the other one 

为了实现深复制,我们需要判断user[key]类型,如果为对象类型,需继续复制其下面每个属性,有兴趣的读者可以参考结构克隆算法。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/69053673