一、一个JavaScript对象有很多属性。一个对象的属性可以被解释成一个附加到对象上的变量。对象的属性和普通的JavaScript变量基本没什么区别,仅仅是属性属于某个对象。
1、属性定义了对象的特征
二、对象的名字和属性的名字都是大小写敏感的
属性值简写
一、在实际开发中,我们通常用已存在的变量当做属性名。
【示例1】属性名跟变量名一样
function makeUser(name, age) {
return {
name: name,
age: age,
// ……其他的属性
};
}
let user = makeUser("John", 30);
alert(user.name); // John
1、这种通过变量生成属性的应用场景很常见,在这有一种特殊的属性值缩写方法,使属性名变得更短。
2、可以用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。
四、其他类型会被自动地转换为字符串。
【示例1】当数字0被用作对象的属性的键时,会被转换为字符串”0”:
let obj = {
0: "test" // 等同于 "0": "test"
};
// 都会输出相同的属性(数字 0 被转为字符串 "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (相同的属性)
1、这里有个小陷阱:一个名为proto的属性。我们不能将它设置为一个非对象的值:
let obj = {};
obj.__proto__ = 5; // 分配一个数字
alert(obj.__proto__); // [object Object] — 值为对象,与预期结果不同
(1)我们从代码中可以看出来,把它赋值为5的操作被忽略了。
五、属性值可以是任何类型。
属性排序:像对象一样排序
一、对象有顺序吗?换句话说,如果我们遍历一个对象,我们获取属性的顺序是和属性添加时的顺序相同吗?
1、回答:“有特别的顺序”:整数属性会被进行排序,其他属性则按照创建的顺序显示。
2、详情如下:
【示例1】一个带有电话号码的对象:
let codes = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
// ..,
"1": "USA"
};
for(let code in codes) {
alert(code); // 1, 41, 44, 49
}
(1)对象可用于面向用户的建议选项列表。如果我们的网站主要面向德国观众,那么我们可能希望49排在第一。
(2)但如果我们执行代码,会看到完全不同的现象:
- USA (1) 排在了最前面
- 然后是 Switzerland (41) 及其它。
(3)因为这些电话号码是整数,所以它们以升序排列。所以我们看到的是1, 41, 44, 49。
(4)整数属性:指的是一个可以在不做任何更改的情况下与一个整数进行相互转换的字符串。
① 所以,“49” 是一个整数属性名,因为我们把它转换成整数,再转换回来,它还是一样的。但是 “+49” 和 “1.2” 就不行了:
// 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" ⇒ 不是整数属性
(5)如果属性名不是整数,那它们就按照创建时的顺序来排序,例如:
let user = {
name: "John",
surname: "Smith"
};
user.age = 25; // 增加一个
// 非整数属性是按照创建的顺序来排列的
for (let prop in user) {
alert( prop ); // name, surname, age
}
(6)为了解决电话号码的问题,我们可以使用非整数属性名来欺骗程序。只需要给每个键名加一个加号”+”前缀就行了。
【示例1】以下代码运行结果与预想的一样。
let codes = {
"+49": "Germany",
"+41": "Switzerland",
"+44": "Great Britain",
// ..,
"+1": "USA"
};
for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
}
属性配置
使用getters与setters
【见】属性的getter和setter:https://www.yuque.com/tqpuuk/yrrefz/ee9led
设置属性
一、给对象设置属性会创建自有属性。获取和设置属性的唯一限制是内置getter或setter的属性。
定义属性的时候赋值
【实例1】创建一个myCar的对象然后给他三个属性,make,model,year
var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;
1、对象中未赋值的属性的值为undefined,而不是null
myCar.noProperty; // undefined
访问属性
一、【对象属性查找机制】在访问一个对象的属性时,JavaScript将执行下面的步骤
1、检查对象自身是否存在。如果存在,返回值。
2、如果本地值不存在,检查原型链(通过proto属性)
3、如果原型链中的某个对象具有指定属性,则返回值。
4、如果这样的属性不存在,则对象没有该属性,返回undefined
【示例1】
// 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
let f = function () {
this.a = 1;
this.b = 2;
}
/* 这么写也一样
function f() {
this.a = 1;
this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}
// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;
// 不要在 f 函数的原型上直接定义 f.prototype = {b:3, c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c
// (其实就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 这就是原型链的末尾,即 null,
// 根据定义,null 就是没有 [[Prototype]]。
// 综上,整个原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为 1
console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为 2
// 原型上也有一个'b'属性,但是它不会被访问到。
// 这种情况被称为"属性遮蔽 (property shadowing)"
console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看它的原型上有没有
// c是o.[[Prototype]]的属性吗?是的,该属性的值为 4
console.log(o.d); // undefined
// d 是 o 的自身属性吗?不是,那看看它的原型上有没有
// d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 找不到 d 属性,返回 undefined
二、遵循ECMAScript标准,someObject.[[Prototype]]符号是用于指向someObject的原型。
1、从ECMAScript6开始,[[Prototype]]可以通过Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。
(1)这个等同于JavaScript的非标准但许多浏览器实现的属性proto
2、不应该与构造函数func的prototype属性相混淆。
(1)被构造函数创建的实例对象的[[prototype]]指向func的prototype属性。
(2)Object.prototype属性表示Object的原型对象。
访问属性的方法
一、方括号比点符号更强大。它允许任何属性名和变量,但写起来也更加麻烦。
1、所以,大部分时间里,当属性名是已知且简单的时候,就使用点符号。如果我们需要一些更复杂的内容,那么就用方括号。
点符号
一、可以通过点符号来访问一个对象的属性
objectName.propertyName
方括号
一、对象有时也被叫作关联数组,因为每个属性都有一个用于访问它的字符串值。
【实例1】可以按如下方式访问myCar对象的属性
myCar["make"] = "Ford";
myCar["model"] = "Mustang";
myCar["year"] = 1969;
方括号标记访问
一、一个对象的属性名可以是任何有效的 JavaScript 字符串,或者可以被转换为字符串的任何类型,包括空字符串。
二、一个属性的名称如果不是一个有效的 JavaScript 标识符(例如,一个由空格或连字符,或者以数字开头的属性名),就只能通过方括号标记访问。
【示例1】方括号标记法在属性名称是动态判定(属性名只有到运行时才能判定)时非常有用。
// 同时创建四个变量,用逗号分隔
var myObj = new Object(),
str = "myString",
rand = Math.random(),
obj = new Object();
myObj.type = "Dot syntax";
myObj["date created"] = "String with space";
myObj[str] = "String value";
myObj[rand] = "Random Number";
myObj[obj] = "Object";
myObj[""] = "Even an empty string";
console.log(myObj);
打印得到的myObj
{
"": "Even an empty string"
0.2801533826338063: "Random Number"
[object Object]: "Object"
date created: "String with space"
myString: "String value"
type: "Dot syntax"
}
二、方括号中的所有键都将转换为字符串类型,因为JavaScript中的对象只能使用String类型作为键类型。
1、在上面的代码中,当将键obj添加到myObj时,JavaScript将调用obj.toString()方法,并将此结果字符串用作新键。
通过存储在变量中的字符串来访问
一、
var propertyName = "make";
myCar[propertyName] = "Ford";
propertyName = "model";
myCar[propertyName] = "Mustang";
属性存在性测试 / 检查是否存在给定键的属性
一、相比于其他语言,JavaScript 的对象有一个需要注意的特性:能够被访问任何属性。即使属性不存在也不会报错!
判断属性是否存在的方法
noSuchProperty
一、读取不存在的属性只会得到undefined。
【示例1】判断一个属性是否存在:
let user = {};
alert( user.noSuchProperty === undefined ); // true 意思是没有这个属性
“in” 操作符(推荐)
一、操作符”in”可以检查属性是否存在。
二、语法:
"key" in object
【示例1】
let user = { name: "John", age: 30 };
alert( "age" in user ); // true,user.age 存在
alert( "blabla" in user ); // false,user.blabla 不存在。
1、in的左边必须是属性名。通常是一个带引号的字符串。
2、如果我们省略引号,就意味着左边是一个变量,它应该包含要判断的实际属性名。
【示例1】
let user = { age: 30 };
let key = "age";
alert( key in user ); // true,属性 "age" 存在
三、为何会有in运算符呢?与undefined进行比较来判断还不够吗?
1、大部分情况下与undefined进行比较来判断就可以了。
2、但有一个例外情况,这种比对方式会有问题,但in运算符的判断结果仍是对的。
(1)那就是属性存在,但存储的值是undefined的时候:
let obj = {
test: undefined
};
alert( obj.test ); // 显示 undefined,所以属性不存在?
alert( "test" in obj ); // true,属性存在!
① 在上面的代码中,属性obj.test事实上是存在的,所以in操作符检查通过。
② 这种情况很少发生,因为通常情况下不应该给对象赋值undefined。我们通常会用null来表示未知的或者空的值。
访问嵌套对象属性的安全的方式
一、可选链?.是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。
“不存在的属性”的问题
一、假设我们有很多个user对象,其中存储了我们的用户数据。
1、我们大多数用户的地址都存储在user.address中,街道地址存储在user.address.street中,但有些用户没有提供这些信息。
2、在这种情况下,当我们尝试获取user.address.street,而该用户恰好没提供地址信息,我们则会收到一个错误:
let user = {}; // 一个没有 "address" 属性的 user 对象
alert(user.address.street); // Error!
3、这是预期的结果。JavaScript 的工作原理就是这样的。因为user.address为undefined,尝试读取4、user.address.street会失败,并收到一个错误。
4、但是在很多实际场景中,我们更希望得到的是undefined(表示没有street属性)而不是一个错误。
二、在 Web 开发中,我们可以使用特殊的方法调用(例如document.querySelector(‘.elem’))以对象的形式获取一个网页元素,如果没有这种对象,则返回null。
// 如果 document.querySelector('.elem') 的结果为 null,则这里不存在这个元素
let html = document.querySelector('.elem').innerHTML; // 如果 document.querySelector('.elem') 的结果为 null,则会出现错误
1、同样,如果该元素不存在,则访问null的.innerHTML时会出错。
2、在某些情况下,当元素的缺失是没问题的时候,我们希望避免出现这种错误,而是接受html = null作为结果。
三、实现方案:if 或 ?
可能最先想到的方案是在访问该值的属性之前,使用if或条件运算符?对该值进行检查,像这样:
let user = {};
alert(user.address ? user.address.street : undefined);
1、这样可以,这里就不会出现错误了
2、但是不够优雅。
(1)就像你所看到的,”user.address”在代码中出现了两次。对于嵌套层次更深的属性就会出现更多次这样的重复,这就是问题了。
3、让我们尝试获取user.address.street.name。
(1)我们既需要检查user.address,又需要检查user.address.street:
let user = {}; // user 没有 address 属性
alert(user.address ? user.address.street ? user.address.street.name : null : null);
(2)这样就太扯淡了,并且这可能导致写出来的代码很难让别人理解。
4、甚至我们可以先忽略这个问题,因为我们有一种更好的实现方式,就是使用&&运算符:
let user = {}; // user 没有 address 属性
alert( user.address && user.address.street && user.address.street.name ); // undefined(不报错)
5、依次对整条路径上的属性使用与运算进行判断,以确保所有节点是存在的(如果不存在,则停止计算),但仍然不够优雅。
6、就像你所看到的,在代码中我们仍然重复写了好几遍对象属性名。例如在上面的代码中,user.address被重复写了三遍。
7、这就是为什么可选链?.被加入到了 JavaScript 这门编程语言中。那就是彻底地解决以上所有问题!
可选链?.
可选链操作符 / Optional chaining (?.)
性能
一、在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。
二、试图访问不存在的属性时会遍历整个原型链。
三、遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。
四、要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从Object.prototype继承的hasOwnProperty方法。
【实例1】
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
console.log(g.hasOwnProperty('vertices')); // true
console.log(g.hasOwnProperty('nope')); // false
console.log(g.hasOwnProperty('addVertex')); // false
console.log(g.__proto__.hasOwnProperty('addVertex')); // true
1、JavaScript中处理属性并且不会遍历原型链的方法:hasOwnProperty、Object.keys()。
五、检查属性是否为undefined是不能检查其是否存在的。该属性可能已存在,但其值刚好被设置成了undefined
枚举一个对象的所有属性/循环/遍历
一、从ECMAScript5开始,有三种原生的方法用于列出或枚举对象的属性
1、for…in循环
(1)该方法依次访问一个对象及其原型链中所有可枚举的属性。
(2)一般遍历时不用for…in,因为会遍历出原型链的属性(编辑器也会提示:for…in loops iterate over the entire prototype chain and iterate over the resulting array)
2、Object.getOwnPropertyNames(o)
(1)该方法返回对象o自身包含(不包括原型中)的所有属性(无论是否可枚举)的名称的数组。
3、Object.keys(o)
(1)该方法返回对象o自身包含(不包括原型中)的所有可枚举属性的名称的数组
二、ECMAScript5之前,没有原生的方法枚举一个对象的所有属性。但是可以通过以下函数完成
function listAllProperties(o) {
var objectToInspect
var result = []
for (objectToInspect = 0; objectToInspect !== null; objectToInspect = Object.getPrototypeOf(objectToInspect)) {
result = result.concat(Object.getOwnPropertyNames(objectToInspect))
}
return result
}
1、这在展示“隐藏”(在原型中的不能通过对象访问的属性,因为另一个同名的属性存在于原型链的早期)的属性时很有用。
2、如果只想列出可访问的属性,那么只需要去除数组中的重复元素即可。
for…in
一、for..in循环也会迭代继承的属性。
例如:
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// Object.keys 只返回自己的 key
alert(Object.keys(rabbit)); // jumps
// for..in 会遍历自己以及继承的键
for(let prop in rabbit) alert(prop); // jumps,然后是 eats
1、如果这不是我们想要的,并且我们想排除继承的属性,那么这儿有一个内建方法obj.hasOwnProperty(key):如果obj具有自己的(非继承的)名为key的属性,则返回true。
2、因此,我们可以过滤掉继承的属性(或对它们进行其他操作):
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
if (isOwn) {
alert(`Our: ${prop}`); // Our: jumps
} else {
alert(`Inherited: ${prop}`); // Inherited: eats
}
}
二、这里我们有以下继承链:rabbit从animal中继承,animal从Object.prototype中继承(因为animal是对象字面量{…},所以这是默认的继承),然后再向上是null:
1、方法rabbit.hasOwnProperty来自哪儿?我们并没有定义它。从上图中的原型链我们可以看到,该方法是Object.prototype.hasOwnProperty提供的。换句话说,它是继承的。
2、如果for..in循环会列出继承的属性,那为什么hasOwnProperty没有像eats和jumps那样出现在for..in循环中?
(1)原因:它是不可枚举的。
① 就像Object.prototype的其他属性,hasOwnProperty有enumerable:false标志。
② for..in只会列出可枚举的属性。这就是为什么它和其余的Object.prototype属性都未被列出。
添加属性
一、在JavaScript中,可以在运行时为任何对象添加属性,而不必受限于构造函数提供的属性。添加特定于某个对象的属性,只需要为该对象指定一个属性值
mark.bonus = 3000
1、这样mark对象就有了bonus属性,而其他WorkerBee则没有该属性。
二、如果您向某个构造函数的原型对象中添加新的属性,那么该属性将添加到从这个原型中继承属性的所有对象中。
【示例1】可以通过如下的语句向所有雇员中添加specialty属性
Employee.prototype.specialty = 'none'
1、只要JavaScript执行了该语句,则mark对象也将具有specialty属性,其值为’none’。
2、下图表示了在Employee原型中添加该属性,然后在Engineer的原型中重载该属性的效果。
三、为构造器提供参数以初始化实例的属性值,【见】继承属性#继承的方式-构造器-构造函数创建对象,添加本地属性和属性值:https://www.yuque.com/tqpuuk/yrrefz/mgghgg
本地属性与继承属性
对象通过隐式Prototype链能够实现属性和方法的继承,但prototype也是一个普通对象,就是说它是一个普通的实例化的对象,而不是纯粹抽象的数据结构描述。所以就有了这个本地属性与继承属性的问题。
首先看一下设置对象属性时的处理过程。JS定义了一组attribute,用来描述对象的属性property,以表明属性property是否可以在JavaScript代码中设值、被for in枚举等。
obj.propName=value的赋值语句处理步骤如下:
1. 如果propName的attribute设置为不能设值,则返回
2. 如果obj.propName不存在,则为obj创建一个属性,名称为propName
3. 将obj.propName的值设为value
可以看到,设值过程并不会考虑Prototype链,道理很明显,obj的内部[[Prototype]]是一个实例化的对象,它不仅仅向obj共享属性,还可能向其它对象共享属性,修改它可能影响其它对象。
用上面CF, Cfp的示例来说明,实例对象cf1具有本地属性q1, q2以及继承属性CFP1,如果执行cf1.CFP1=””,那么cf1就具有本地属性CFP1了,测试结果如下:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
var cf1=new CF("aaa", "bbb");
var cf2=new CF(111, 222);
document.write(cf1.CFP1 + "<br />"); //result: CFP1 in Cfp
document.write(cf2.CFP1 + "<br />"); //result: CFP1 in Cfp
//it will result in a local property in cf1
cf1.CFP1="new value for cf1";
//changes on CF.prototype.CFP1 will affect cf2 but not cf1, because there's already a local property with
//the name CFP1 in cf1, but no such one in cf2
CF.prototype.CFP1="new value for Cfp";
document.write(cf1.CFP1 + "<br />"); //result: new value for cf1
document.write(cf2.CFP1 + "<br />"); //result: new value for Cfp
语义上的混乱?
还是使用上面CF, Cfp示例的场景。
根据Prototype的机制,我们可以说对象cf1, cf2等都继承了对象Cfp的属性和方法,所以应该说他们之间存在继承关系。属性的继承/共享是沿着隐式Prototype链作用的,所以继承关系也应当理解为沿着这个链。
我们再看instanceOf操作,只有cf1 instanceOf CF才成立,我们说cf1是CF的实例对象,CF充当了类的角色,而不会说cf1是Cfp的实例对象,这样我们应当说cf1继承自CF? 但CF充当的只是一个第三方工厂的角色,它跟cf1之间并没有属性继承这个关系。
把CF, Cfp看作一个整体来理解也同样牵强。
Prototype就是Prototype,没有必要强把JavaScript与面向对象概念结合起来, JavaScript只具备有限的面向对象能力,从另外的角度我们可以把它看成函数语言、动态语言,所以它是吸收了多种语言特性的精简版。
继承属性
【见】继承属性:https://www.yuque.com/tqpuuk/yrrefz/mgghgg
删除属性
一、可以用delete操作符删除一个不是继承而来的属性。
【实例1】删除一个属性
var myobj = new Object
myobj.a = 15
myobj.b = 12
delete myobj.a
二、如果一个全局变量不是用var关键字声明的话,也可以用delete删除
g = 17
delete g