详解Javascript对象

来源:互联网 发布:js 导航栏点击后变色 编辑:程序博客网 时间:2024/05/29 02:05

详解Javascript对象

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

定义

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

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

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

let user = new Object(); // "object constructor" syntaxlet 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 ); // Johnalert( 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 erroruser.likes birds = true

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

let user = {};// setuser["likes birds"] = true;// getalert(user["likes birds"]); // true// deletedelete 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 variablealert( 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 variablebag[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 existsalert( "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 partalert( String(Math.trunc(Number("49"))) ); // "49", same, integer propertyalert( String(Math.trunc(Number("+49"))) ); // "49", not same ⇒ not integer propertyalert( 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 orderfor (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" referencealert(user.name); // 'Pete', changes are seen from the "user" reference

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

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

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

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

let a = {};let b = {}; // two independent objectsalert( 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 itfor (let key in user) {  clone[key] = user[key];}// now clone is a fully independant cloneclone.name = "Pete"; // changed the data in italert( 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 userObject.assign(user, permissions1, permissions2);// now user = { name: "John", canView: true, canEdit: true }

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

let user = { name: "John" };// overwrite name, add isAdminObject.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 sizesuser.sizes.width++;   // change a property from one placealert(clone.sizes.width); // 51, see the result from the other one

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

1 0
原创粉丝点击