怎样把网站做的高大上,学服装设计的就业方向,顺德网站制作案例价位,成都电子商务网站建设公司文章目录 对象文本和属性方括号计算属性 属性值简写属性名称限制属性存在性测试#xff0c;“in” 操作符“for…in” 循环像对象一样排序 总结✅任务你好#xff0c;对象检查空对象对象属性求和将数值属性值都乘以 2 对象引用和复制通过引用来比较克隆与合并#xff0c;Obj… 文章目录 对象文本和属性方括号计算属性 属性值简写属性名称限制属性存在性测试“in” 操作符“for…in” 循环像对象一样排序 总结✅任务你好对象检查空对象对象属性求和将数值属性值都乘以 2 对象引用和复制通过引用来比较克隆与合并Object.assign深层克隆总结 对象
正如我们在 【数据类型】一章学到的JavaScript 中有八种数据类型。有七种原始类型因为它们的值只包含一种东西字符串数字或者其他。
相反对象则用来存储键值对和更复杂的实体。在 JavaScript 中对象几乎渗透到了这门编程语言的方方面面。所以在我们深入理解这门语言之前必须先理解对象。
我们可以通过使用带有可选 属性列表 的花括号 {…} 来创建对象。一个属性就是一个键值对“key: value”其中键key是一个字符串也叫做属性名值value可以是任何值。
我们可以把对象想象成一个带有签名文件的文件柜。每一条数据都基于键key存储在文件中。这样我们就可以很容易根据文件名也就是“键”查找文件或添加/删除文件了。 我们可以用下面两种语法中的任一种来创建一个空的对象“空柜子”
let user new Object(); // “构造函数” 的语法
let user {}; // “字面量” 的语法通常我们用花括号。这种方式我们叫做字面量。
文本和属性
我们可以在创建对象的时候立即将一些属性以键值对的形式放到 {...} 中。
let user { // 一个对象name: John, // 键 name值 Johnage: 30 // 键 age值 30
};属性有键或者也可以叫做“名字”或“标识符”位于冒号 : 的前面值在冒号的右边。
在 user 对象中有两个属性
第一个的键是 name值是 John。第二个的键是 age值是 30。
生成的 user 对象可以被想象为一个放置着两个标记有 “name” 和 “age” 的文件的柜子。 我们可以随时添加、删除和读取文件。
可以使用点符号访问属性值
// 读取文件的属性
alert( user.name ); // John
alert( user.age ); // 30属性的值可以是任意类型让我们加个布尔类型
user.isAdmin true;我们可以用 delete 操作符移除属性
delete user.age;我们也可以用多字词语来作为属性名但必须给它们加上引号
let user {name: John,age: 30,likes birds: true // 多词属性名必须加引号
};列表中的最后一个属性应以逗号结尾
let user {name: John,age: 30,
}这叫做尾随trailing或悬挂hanging逗号。这样便于我们添加、删除和移动属性因为所有的行都是相似的。
方括号
对于多词属性点操作就不能用了
// 这将提示有语法错误
user.likes birds trueJavaScript 理解不了。它认为我们在处理 user.likes然后在遇到意外的 birds 时给出了语法错误。
点符号要求 key 是有效的变量标识符。这意味着不包含空格不以数字开头也不包含特殊字符允许使用 $ 和 _。
有另一种方法就是使用方括号可用于任何字符串
let user {};// 设置
user[likes birds] true;// 读取
alert(user[likes birds]); // true// 删除
delete user[likes birds];现在一切都可行了。请注意方括号中的字符串要放在引号中单引号或双引号都可以。
方括号同样提供了一种可以通过任意表达式来获取属性名的方法 —— 跟语义上的字符串不同 —— 比如像类似于下面的变量
let key likes birds;// 跟 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);// 访问变量
alert( user[key] ); // John如果输入 name点符号不能以类似的方式使用
let user {name: John,age: 30
};let key name;
alert( user.key ) // undefined计算属性
当创建一个对象时我们可以在对象字面量中使用方括号。这叫做 计算属性。
例如
let fruit prompt(Which fruit to buy?, apple);let bag {[fruit]: 5, // 属性名是从 fruit 变量中得到的
};alert( bag.apple ); // 5 如果 fruitapple计算属性的含义很简单[fruit] 含义是属性名应该从 fruit 变量中获取。
所以如果一个用户输入 applebag 将变为 {apple: 5}。
本质上这跟下面的语法效果相同
let fruit prompt(Which fruit to buy?, apple);
let bag {};// 从 fruit 变量中获取值
bag[fruit] 5;……但是看起来更好。
我们可以在方括号中使用更复杂的表达式
let fruit apple;
let bag {[fruit Computers]: 5 // bag.appleComputers 5
};方括号比点符号更强大。它允许任何属性名和变量但写起来也更加麻烦。
所以大部分时间里当属性名是已知且简单的时候就使用点符号。如果我们需要一些更复杂的内容那么就用方括号。
属性值简写
在实际开发中我们通常用已存在的变量当做属性名。
例如
function makeUser(name, age) {return {name: name,age: age,// ……其他的属性};
}let user makeUser(John, 30);
alert(user.name); // John在上面的例子中属性名跟变量名一样。这种通过变量生成属性的应用场景很常见在这有一种特殊的 属性值缩写 方法使属性名变得更短。
可以用 name 来代替 name:name 像下面那样
function makeUser(name, age) {return {name, // 与 name: name 相同age, // 与 age: age 相同// ...};
}我们可以把属性名简写方式和正常方式混用
let user {name, // 与 name:name 相同age: 30
};属性名称限制
我们已经知道变量名不能是编程语言的某个保留字如 “for”、“let”、“return” 等……
但对象的属性名并不受此限制
// 这些属性都没问题
let obj {for: 1,let: 2,return: 3
};alert( obj.for obj.let obj.return ); // 6简而言之属性命名没有限制。属性名可以是任何字符串或者 symbol一种特殊的标志符类型将在后面介绍。
其他类型会被自动地转换为字符串。
例如当数字 0 被用作对象的属性的键时会被转换为字符串 0
let obj {0: test // 等同于 0: test
};// 都会输出相同的属性数字 0 被转为字符串 0
alert( obj[0] ); // test
alert( obj[0] ); // test (相同的属性)这里有个小陷阱一个名为 __proto__ 的属性。我们不能将它设置为一个非对象的值
let obj {};
obj.__proto__ 5; // 分配一个数字
alert(obj.__proto__); // [object Object] — 值为对象与预期结果不同我们从代码中可以看出来把它赋值为 5 的操作被忽略了。
我们将在 【后续章节】中学习 __proto__ 的特殊性质并给出了 解决此问题的方法
属性存在性测试“in” 操作符
相比于其他语言JavaScript 的对象有一个需要注意的特性能够被访问任何属性。即使属性不存在也不会报错
读取不存在的属性只会得到 undefined。所以我们可以很容易地判断一个属性是否存在
let user {};alert( user.noSuchProperty undefined ); // true 意思是没有这个属性这里还有一个特别的检查属性是否存在的操作符 in。
语法是
key in object例如
let user { name: John, age: 30 };alert( age in user ); // trueuser.age 存在
alert( blabla in user ); // falseuser.blabla 不存在。请注意in 的左边必须是 属性名。通常是一个带引号的字符串。
如果我们省略引号就意味着左边是一个变量它应该包含要判断的实际属性名。例如
let user { age: 30 };let key age;
alert( key in user ); // true属性 age 存在为何会有 in 运算符呢与 undefined 进行比较来判断还不够吗
确实大部分情况下与 undefined 进行比较来判断就可以了。但有一个例外情况这种比对方式会有问题但 in 运算符的判断结果仍是对的。
那就是属性存在但存储的值是 undefined 的时候
let obj {test: undefined
};alert( obj.test ); // 显示 undefined所以属性不存在alert( test in obj ); // true属性存在在上面的代码中属性 obj.test 事实上是存在的所以 in 操作符检查通过。
这种情况很少发生因为通常情况下不应该给对象赋值 undefined。我们通常会用 null 来表示未知的或者空的值。因此in 运算符是代码中的特殊来宾。
“for…in” 循环
为了遍历一个对象的所有键key可以使用一个特殊形式的循环for..in。这跟我们在前面学到的 for(;;) 循环是完全不一样的东西。
语法
for (key in object) {// 对此对象属性中的每个键执行的代码
}例如让我们列出 user 所有的属性
let user {name: John,age: 30,isAdmin: true
};for (let key in user) {// keysalert( key ); // name, age, isAdmin// 属性键的值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) 及其它。
因为这些电话号码是整数所以它们以升序排列。所以我们看到的是 1, 41, 44, 49。 ℹ️整数属性那是什么 这里的“整数属性”指的是一个可以在不做任何更改的情况下与一个整数进行相互转换的字符串。 所以49 是一个整数属性名因为我们把它转换成整数再转换回来它还是一样的。但是 “49” 和 “1.2” 就不行了 // Number(...) 显式转换为数字
// Math.trunc 是内建的去除小数部分的方法。
alert( String(Math.trunc(Number(49))) ); // 49相同整数属性
alert( String(Math.trunc(Number(49))) ); // 49不同于 49 ⇒ 不是整数属性
alert( String(Math.trunc(Number(1.2))) ); // 1不同于 1.2 ⇒ 不是整数属性……此外如果属性名不是整数那它们就按照创建时的顺序来排序例如
let user {name: John,surname: Smith
};
user.age 25; // 增加一个// 非整数属性是按照创建的顺序来排列的
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
}现在跟预想的一样了。
总结
对象是具有一些特殊特性的关联数组。
它们存储属性键值对其中
属性的键必须是字符串或者 symbol通常是字符串。值可以是任何类型。
我们可以用下面的方法访问属性
点符号: obj.property。方括号 obj[property]方括号允许从变量中获取键例如 obj[varWithKey]。
其他操作
删除属性delete obj.prop。检查是否存在给定键的属性key in obj。遍历对象for(let key in obj) 循环。
我们在这一章学习的叫做“普通对象plain object”或者就叫对象。
JavaScript 中还有很多其他类型的对象
Array 用于存储有序数据集合Date 用于存储时间日期Error 用于存储错误信息。……等等。
它们有着各自特别的特性我们将在后面学习到。有时候大家会说“Array 类型”或“Date 类型”但其实它们并不是自身所属的类型而是属于一个对象类型即 “object”。它们以不同的方式对 “object” 做了一些扩展。
JavaScript 中的对象非常强大。这里我们只接触了其冰山一角。在后面的章节中我们将频繁使用对象进行编程并学习更多关于对象的知识。
✅任务
你好对象
重要程度: 5
按下面的要求写代码一条对应一行代码
创建一个空的对象 user。为这个对象增加一个属性键是 name值是 John。再增加一个属性键是 surname值是 Smith。把键为 name 的属性的值改成 Pete。删除这个对象中键为 name 的属性。
解决方案
let user {};
user.name John;
user.surname Smith;
user.name Pete;
delete user.name;检查空对象
重要程度5️⃣
写一个 isEmpty(obj) 函数当对象没有属性的时候返回 true否则返回 false。
应该像这样
let schedule {};alert( isEmpty(schedule) ); // trueschedule[8:30] get up;alert( isEmpty(schedule) ); // false打开带有测试的沙箱。 解决方案 只需要遍历这个对象如果对象存在任何属性则 return false。 function isEmpty(obj) {
for (let key in obj) {// 如果进到循环里面说明有属性。return false;
}
return true;
}使用沙箱的测试功能打开解决方案。 对象属性求和
重要程度: 5
我们有一个保存着团队成员工资的对象
let salaries {John: 100,Ann: 160,Pete: 130
}写一段代码求出我们的工资总和将计算结果保存到变量 sum。从所给的信息来看结果应该是 390。
如果 salaries 是一个空对象那结果就为 0。
解决方案
let salaries {John: 100,Ann: 160,Pete: 130
};let sum 0;
for (let key in salaries) {sum salaries[key];
}alert(sum); // 390将数值属性值都乘以 2
重要程度: 3
创建一个 multiplyNumeric(obj) 函数把 obj 所有的数值属性值都乘以 2。
例如
// 在调用之前
let menu {width: 200,height: 300,title: My menu
};multiplyNumeric(menu);// 调用函数之后
menu {width: 400,height: 600,title: My menu
};注意 multiplyNumeric 函数不需要返回任何值它应该就地修改对象。
P.S. 用 typeof 检查值类型。
打开带有测试的沙箱。 解决方案 function multiplyNumeric(obj) {
for (let key in obj) {if (typeof obj[key] number) {obj[key] * 2;}
}
}使用沙箱的测试功能打开解决方案。 对象引用和复制
对象与原始类型的根本区别之一是对象是“通过引用”存储和复制的而原始类型字符串、数字、布尔值等 —— 总是“作为一个整体”复制。
如果我们深入了解复制值时会发生什么就很容易理解了。
让我们从原始类型开始例如一个字符串。
这里我们将 message 复制到 phrase
let message Hello!;
let phrase message;结果我们就有了两个独立的变量每个都存储着字符串 Hello!。 显而易见的结果对吧
但是对象不是这样的。
赋值了对象的变量存储的不是对象本身而是该对象“在内存中的地址” —— 换句话说就是对该对象的“引用”。
让我们看一个这样的变量的例子
let user {name: John
};这是它实际存储在内存中的方式 该对象被存储在内存中的某个位置在图片的右侧而变量 user在左侧保存的是对其的“引用”。
我们可以将一个对象变量例如 user想象成一张写有对象的地址的纸。
当我们对对象执行操作时例如获取一个属性 user.nameJavaScript 引擎会查看该地址中的内容并在实际对象上执行操作。
现在这就是为什么它很重要。
当一个对象变量被复制 —— 引用被复制而该对象自身并没有被复制。
例如
let user { name: John };let admin user; // 复制引用现在我们有了两个变量它们保存的都是对同一个对象的引用 正如你所看到的这里仍然只有一个对象但现在有两个引用它的变量。
我们可以通过其中任意一个变量来访问该对象并修改它的内容
let user { name: John };let admin user;admin.name Pete; // 通过 admin 引用来修改alert(user.name); // Pete修改能通过 user 引用看到这就像我们有一个带有两把钥匙的柜子使用其中一把钥匙admin打开柜子并更改了里面的东西。那么如果我们稍后用另一把钥匙user我们仍然可以打开同一个柜子并且可以访问更改的内容。
通过引用来比较
仅当两个对象为同一对象时两者才相等。
例如这里 a 和 b 两个变量都引用同一个对象所以它们相等
let a {};
let b a; // 复制引用alert( a b ); // true都引用同一对象
alert( a b ); // true而这里两个独立的对象则并不相等即使它们看起来很像都为空
let a {};
let b {}; // 两个独立的对象alert( a b ); // false对于类似 obj1 obj2 的比较或者跟一个原始类型值的比较 obj 5对象都会被转换为原始值。我们很快就会学到对象是如何转换的但是说实话很少需要进行这样的比较 —— 通常是在编程错误的时候才会出现这种情况。
克隆与合并Object.assign
那么拷贝一个对象变量会又创建一个对相同对象的引用。
但是如果我们想要复制一个对象那该怎么做呢创建一个独立的拷贝克隆
这也是可行的但稍微有点困难因为 JavaScript 没有提供对此操作的内建的方法。但很少需要这样做 —— 通过引用进行拷贝在大多数情况下已经满足了。
但是如果我们真的想要这样做那么就需要创建一个新对象并通过遍历现有属性的结构在原始类型值的层面将其复制到新对象以复制已有对象的结构。
就像这样
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 是指目标对象。更后面的参数 src1, ..., srcN可按需传递多个参数是源对象。该方法将所有源对象的属性拷贝到目标对象 dest 中。换句话说从第二个开始的所有参数的属性都被拷贝到第一个参数的对象中。调用结果返回 dest。
例如我们可以用它来合并多个对象
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 }如果被拷贝的属性的属性名已经存在那么它会被覆盖
let user { name: John };Object.assign(user, { name: Pete });alert(user.name); // 现在 user { name: Pete }我们也可以用 Object.assign 代替 for..in 循环来进行简单克隆
let user {name: John,age: 30
};let clone Object.assign({}, user);它将 user 中的所有属性拷贝到了一个空对象中并返回这个新的对象。
还有其他克隆对象的方法例如使用 [spread 语法](展开语法 - JavaScript |MDN (mozilla.org)) clone {...user}在后面的章节中我们会讲到。
深层克隆
到现在为止我们都假设 user 的所有属性均为原始类型。但属性可以是对其他对象的引用。那应该怎样处理它们呢
例如
let user {name: John,sizes: {height: 182,width: 50}
};alert( user.sizes.height ); // 182现在这样拷贝 clone.sizes user.sizes 已经不足够了因为 user.sizes 是个对象它会以引用形式被拷贝。因此 clone 和 user 会共用一个 sizes
就像这样
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能从另外一个看到变更的结果为了解决这个问题我们应该使用一个拷贝循环来检查 user[key] 的每个值如果它是一个对象那么也复制它的结构。这就是所谓的“深拷贝”。
我们可以使用递归来实现它。或者为了不重复造轮子采用现有的实现例如 lodash 库的 _.cloneDeep(obj)。 ℹ️使用 const 声明的对象也是可以被修改的 通过引用对对象进行存储的一个重要的副作用是声明为 const 的对象 可以 被修改。 例如 const user {
name: John
};user.name Pete; // (*)alert(user.name); // Pete看起来 (*) 行的代码会触发一个错误但实际并没有。user 的值是一个常量它必须始终引用同一个对象但该对象的属性可以被自由修改。 换句话说只有当我们尝试将 user... 作为一个整体进行赋值时const user 才会报错。 也就是说如果我们真的需要创建常量对象属性也是可以的但使用的是完全不同的方法。我们将在 【属性标志和属性描述符】 一章中学习它。 总结
对象通过引用被赋值和拷贝。换句话说一个变量存储的不是“对象的值”而是一个对值的“引用”内存地址。因此拷贝此类变量或将其作为函数参数传递时所拷贝的是引用而不是对象本身。
所有通过被拷贝的引用的操作如添加、删除属性都作用在同一个对象上。
为了创建“真正的拷贝”一个克隆我们可以使用 Object.assign 来做所谓的“浅拷贝”嵌套对象被通过引用进行拷贝或者使用“深拷贝”函数例如 _.cloneDeep(obj)。