对象
对象是一种复合值,汇聚多个值并允许我们按名字存储和获取这些值
对象可以创建对象字面量,new关键字和Object.create()函数来创建
对象字面量
let empty={}
let point={x:0,y:e}
let p2={x:point.x,y:point.y+1}
使用New创建对象
new 操作符用于创建和初始化一个新对象。new 关键字后面必须跟一个函数调用
let o=new Object() 创建一个空对象
let a=new Array() 创建一个空数组
let d=new Date() 创建一个表示当前时间的日期对象
let r=new Map() 创建一个映射对象
原型
这另一个对象被称为原型,第一个对象从这个原型继承属性
通过对象字面量创建的所有对象都有相同的原型对象
Object.prototype引用这个原型对象,使用new关键字和构造函数调用创建的对象,使用构造函数prototype属性的值作为他们的原型
使用new关键字创建的对象继承Object.prototype
Object.Create
创建一个新对象,使用其第一个参数作为新对象的原型
let o1=Object.create({x:1,y:2})
o1.x+o1
//如果create(null)
如果传入null可以创建一个没有原型的新对象,不过,这样创建的新对象不会继承任何东西,连tostring方法都不存在
let o2=Object.tostring(null)
查询和设置属性
要获取一个属性的值,可以使用点(.)或方括号(【】)操作符。左边一个是一个表达式,值未一个对象
let m={m:1}
m["m"]=2
作为数组的关联对象
JavaScript对象是关联数组
在C C++ Java 强类型语言中,对象有固定数量的属性,并且这些属性的名字必须事先定义,JavaScript是松散类型语言,并没有遵守这个规则,JavaScript程序里面可以定义任意数量的属性,我们去访问的时候是通过标识符来表示,他们不是一种数据类型,因此不能被程序操作
在通过方括号([ ])这种数组表示法访问对象属性时,属性名是通过字符串来表示
继承
JavaScript对象有一组“自有属性”,同时也从他们的原型对象继承一组属性
属性访问错误
属性访问表达式并不总是会返回设置值
查询不存在的属性不是错误,如果o的自有属性和继承属性中都有没找属性x,则属性访问表达式o.x的求值结果为undefined
let m={}
m.x // undefined属性不存在
m.x.length //TypeError :undefined没有length
解决方法:条件式属性访问
m?.x?.length //undefined
删除属性
delete操作符 用于从对象中移除属性
要注意的地方
delete只能删除自生属性不能删除继承属性(要删除继承属性,就必须从定义属性的原型对象上删除,但是这样做会影响到继承改原型的所有对象)
还有一个地方需要注意
使用delete的时候它存在删除会返回true 但是再吃一次执行同样是true 还有一个地方要注意的是对非属性表达式使用delete 同样也是返回true
let x={m:2}
delete x.m //true
delete x.m //true
delete x.toString //true
delete i //true
测试属性
JavaScript对象可以被想象成一组属性,实际开发中经常需要测试这组属性的成员关系,即检查对象是否有一个给定名字的属性,为此,可以使用in操作符,或者hasOwnProperty( ) , propertyIsEnumerable( )方法,或者直接查询相应属性。
in操作符要求左边是一个属性名,右边是一个对象,
let o={x:1}
"x" in o //true
"y" in o //false
"toString" in o //true 因为继承tostring属性
对象的hasOwnProperty()方法用于测试对象是否有给定名字的属性,对继承的属性,他返回false
let o={x:1}
o.hasOwnProperty("x") //true
o.hasOwnProperty("y") //false
o.hasOwnProperty("toString") //false
propertyIsEnumerable()方法细化了hasOwnProperty()测试
let o={x:1}
o.propertyleEnumerable("x") //true
o.propertyleEnumerable("y") //false
object.prototype.propertyleEnumerable("toString") //flase:toString 不可枚举
除了使用in操作符,通过简单的属性查询配合!==
let o={x:undefined}
o.x!==undefined //false 属性x的属性存在但是值是undefined
o.y!==undefined //false 属性y不存在
"x" in o //true 属性存在
"y" in o //false 属性y不存在
"delete" o.x //删除属性x
"x" in o //false 属性x不存在了
枚举属性
除了属性是否存在,有时候也需要遍历或获取对象的所有属性,为此有几种不同的实现方式
for/in循环对指定对象的每个可枚举(自有或继承)属性都会运行一次循环体,将属性的名字赋值循环变量。对象继承的内置方法是不可枚举,但你的代码添加给对象的属性默认是可枚举的
let o={x:1,y:2,z:3} //3个可枚举自有属性
o.propertyIsEnumerable("toString") //false :toString不可枚举
for(let p in o){ //遍历循环属性
console.log(p) //打印x,y,z,但没有toString
}
为防止通过for/in 枚举继承的属性,可以在循环体内部添加一个显示测试
for(let p in o){
if(!o.hasOwnProperty(p)) contiue; //跳过继承属性
}
for(let p in o){
if(typeof o[p]==="function") contiue //跳过所有方法
}
使用for/in循环,有时候可以先获取对象所有属性名的数组,然后再通过for/of循环遍历该数组。有4个函数可以用来取得属性名数组:
- Object.keys()返回对象可枚举自有属性名的数组。不包含不可枚举属性,继承属性或名字是符号的属性
- Object.getOwnPropertyNames()与Object.key()类似,但也会返回不可枚举自有属性名的数组,只要它们的名字是字符串
- Object.getOwnPropertySymbols()返回名字是符号的自有属性,无论是否可枚举
- Reflect.ownKeys()返回所有属性名,包括可枚举和不可枚举属性,以及字符串属性和符号属性
属性枚举顺序
扩展对象
在JavaScript程序中,把一个对象的属性复制到另一个对象上很常见的
let target={x:1},source={y:2,z:3}
for(let key of Object.keys(source)){
target[key]=source[key]
}
target
但因为这是一个
序列化对象
对象序列化是把对象的状态转化为字符串的过程,之后可以从中恢服对象的状态,函数JSON.stringify()和JSON.parse()用于序列化和恢复JavaScript对象。这二个函数使用JSON数据交互格式。其语法与JavaScript对象和数组字面量十分相似
let o={x:1,y:{z:[false,null,""]}}; //定义一个测试对象
let s=JSON.stringify(o) //s==`{"x":1,"y":{"z":[false,null,""]}}`
let p=JSON.parse(s) //p=={x:1,y:{z:[false,null,""]}}
JSON语法是JavaScript语法子集
对象方法
所有JavaScript对象(除了哪些显式创建为没有原型的)都从Object.prototype继承属性。这些继承的属性主要是方法,因为它们几乎无处不在,所以的JavaScript程序而言特别重要
toString()方法
toString()方法不接收参数,返回表示调用它的对象的值的字符串。每当需要把一个对象转换为字符串,JavaScript就会调用该对象的这个方法
默认的toString()方法并不能提供太多信息
let s={x:1,y:1}.toString();//s=="[object object]"
let point ={
x:1,
y:2,
toString:function(){return `(${this.x},${this.y})`;}
}
String(point) //”(1,2)“:toSring()用于转换为字符串
toLocaleString()
对象也都有一个toLocaleString()方法。这个方法的用途是返回对象的本地化字符串表示。object定义的默认toLocaleString()方法本身没有实现任何本地化,而是简单地调用toString()并返回该值
let point={
x:1000,
y:2000,
toString:function(){retrun `(${this.x},${this.y})`},
toLocaleString:function{
retrun `(${this.x.toLocaleString()},${this.y.toLocaleString()})`
};
point.toString() //"(1000,2000)"
point.toLocaleString() //"(1,000,2,000)"
}
valueOf()方法
valueOf()方法与toString()方法很相似,但会在Javascript需要把对象转换为某些非字符串原始值(通常是数值)时被调用。如果在需要原始值的上下问中使用对象,JavaScript会自动调用这个对象的valueOf()方法
let point={
x:3,
y:4,
valueOf:function(){return Math.hypot(this.x,this.y)}
}
Number(point) //5:valueOf()
point>4 //
point>5 //false
point<6 //true
toJSON()方法
object.prototype实际上并未定义toJSON()方法,但JSON.stringify()方法会从要序列化的对象上寻找toJSON()方法,如果要序列化的对象上存在这个方法,就会调用它,然后序列化该方法的返回值,而不是原始对象。Date类定义了自己的toJSON方法,返回一个表示日期的序列化字符串
let point ={
x:1,
y:2,
toString:function(){retrun `(${this.x},${this.y})`},
toJSON:function(){retrun this.toString()}
}
对象字面量扩展语法
简写属性
假设x和y中保存着值,而你想创建一个具有属性x和y且值分别为相应变量值的对象。如果使用基本的对象字面量语法,需要把每个标识符重复二次
let x=1,y=2
let o={
x:x,
y:y
};
简写
let x=1,y=2
let o={x,y}
计算的属性名
我们需要创建一个具有特定属性的对象,但该属性的名字不是编译时可以直接写在源代码中的常量。相反,你需要的这个属性名保存在一个变量里,或者是调用的每个函数的返回值。不能对这种属性使用基本对象字面量。为此,必须先创建一个对象,然后再为它添加想要的属性
const pr_name="p1"
function comName(){return "p"+2}
let o={}
o[pr_name]=1
o[comName()]=2
简写
const pr_name="p1"
function comName(){return "p"+2}
let o={
[pr_name]:1,
[comName()]:2
}
o.p1+o.p2
符号作为属性名
计算属性语法也让另一个非常重要的对象字面量特性成为可能。属性名可以是字符串或符号。如果把符号赋值给一个变量或常量,那么可以使用计算属性语法将该符号作为属性名
const exsion=Symbol("my exsion symbol")
let o={
[exsion]:
}
o[exsion].x=0
扩张操作符
可以在对象字面量中使用”扩展操作符" . . . 把己有对象的属性复制到新对象中
let position={x:0,y:0}
let dimensions={width:100,height:75}
let rect={...position, ...dimensions}
rect.x+rect.y+rect.width+rect.height
如果扩展对象和被扩展对象有同一个属性,那么这个属性的值由后面的对象决定
let o={x:1}
let p={x:0,...o}
p.x //1 覆盖了初始值
let q={...o,x:2}
q.x //2 覆盖了前面对象o的值
如果扩张对象和被扩展对象有同名属性,这个属性的值由后面的对象决定
注意
扩展操作符只扩展对象的自有属性,不扩展任何继承属性
let o=Object.create({x:1}) //o继承属性x
let p={...o}
p.x //undefined
简写方法
在把函数定义为对象的属性时,我们称函数为方法,在之前我们是这样定义的
let square={
area:function(){retrun this.side*this.side},
side:10
}
square.area() //100
对象字面量语法经过扩展,允许一种省略function关键字和冒号的简写方法
let square={
area(){retrun this.side*this.side},
side:10
}
square.area() //100
属性名可以是对象字面量允许的任何形式,也可以使用字符串字面量和计算的属性名,包括符号属性名
const me_name="m"
const symbol=Symbol()
let wthods={
"method with spaces"(x) {retrun x+1},
[me_name](x){retrun x+2},
[symbol](x) {return x+3}
}
wthods["method with spaces"](1) //2
wthods[me_name](1) //3
wthods[symbol](1) //4
属性的获取方法与设置方法
对象属性都是数据属性,即有一个名字和一个普通的值。除了数据属性之外,JavaScript还支持为对象定义访问器属性,这种属性不是一个值,而是一个或二个访问器方法:一个获取方法(getter)和一个设置方法(setter)
如果一个属性既有获取方法也要设置方法,则该属性是一个可读写属性。如果只有一个获取方法,那它就是只读属性。如果只有一个设置方法,那它就是只写属性,读取这种属性始终会得到undefined
let o={
//一个普通的数据属性
dataProp:values,
//通过一对函数定义的一个访问器属性
get accessorProp(){return this.dataProp},
set accessorProp(value){this.dataProp=value}
}
let p={
x:1.0,
y:1.0,
get r(){return Math.hypot(this.x,this.y)},
set r(newvalue){
let oldvalue = Math.hypot(this.x,this.y);
let ratio=newvalue/oldvalue;
this.x*=ratio;
this.y*=b;
},
get theta(){return Math.atan2(this.y,this.x)}
};
p.r
p.theta
与数据属性一样,访问器属性也是可以继承的。因此,可以把上面定义的对象p作为他点的原型,可以给新对象定义自己的x和y属性,而它们将继承r和theta属性
let q=Object.create(p)
q.x=3;q.y=4
q.r
q.theta