对象
对象和属性
现实世界中的 事物 可以看作是包含很多 属性 的 对象 。
比如 人 包含了 头、躯干、手、腿 等。
可以说 头、躯干、手、腿 是 人这个对象 的 属性。
而 属性也是对象,包含了它的属性,比如:
头还包含 眼睛、耳朵、鼻子、嘴等。
js中 对象(Object) 可以和现实世界中的对象 概念对应
就是包含了一组 属性(Property) 的数据, 属性可以是包含其它属性的对象。
创建对象: 使用字面标记
创建一个 Object 类型的数据 有多种方法,最常见的就是直接 js语言 字面标记(literal notation)
构建
写法如下:
var myCar = {
'brand' : '奔驰',
'country' : '德国',
'price' : 300000
}
这里面的 brand/country/price 都是对象的 属性(英文叫 properties )。
属性之间用逗号隔开,最后一个属性后面可以不用加逗号。
它们的值分别是字符串 奔驰/德国 和数字 300000
有时,我们也把 属性名 称之为 对象的 key(键)
定义时,也可以省略 key的引号,如下
var myCar = {
brand : '奔驰',
country : '德国',
price : 300000
}
如果对象的属性名中有空格 加减号 之类的字符,就必须要加上引号了,如下
var myCar = {
brand : '奔驰',
country : '德国',
'price-in-us' : 300000
}
当然属性也可以是中文字符
var myCar = {
品牌 : '奔驰',
国家 : '德国',
价格 : 300000
}
访问对象的属性,语法是 对象.属性
,比如:
myCar.brand
或者 对象['属性名']
,比如:
myCar['brand']
这种写法常用于属性名在写代码时不确定,是变量的情况,比如:
let prop = prompt('请输入属性名')
myCar[prop]
js中的对象经常被用作 表 存放查询数据,比如
var stockTable = {
'包钢股份' : '600010',
'保利地产' : '600048',
'凤凰光学' : '600071',
'东方航空' : '600115',
'航天机电' : '600151',
'太原重工' : '600169',
'华微电子' : '600360'
}
let name = prompt('请输入股票名称')
console.log(name + ' 对应的代码是 ' + stockTable[name])
可以通过 key 查询到 值。
动态更改属性
添加属性
对象创建后可以随时给它添加属性
可以这样写
myCar.year = '2021-3-3'
也可以这样写
myCar['year'] = '2021-3-3'
第二种写法方便 属性中有特殊字符(空格、加减号等)或者是一个变量的情况,
比如
myCar['year of purchase'] = '2021-3-3'
let attrName = 'year of purchase'
let attrValue = '2021-3-3'
myCar[attrName] = attrValue
修改属性
如果赋值语句前面的的属性名已经存在,会覆盖原来的属性值
比如:
myCar.year = '2021-3-3'
myCar.year = '2022-5-5'
console.log(myCar.year)
myCar['year'] = '2023-6-6'
console.log(myCar.year)
删除属性
如果你要删除对象中一个属性,可以使用 操作符 delete
比如:
delete myCar.year
或者
delete myCar['year']
函数作为对象属性
对象的属性也可以是函数,比如
var myCar = {
brand : '奔驰',
country : '德国',
price : 300000,
showInfo : function() {
console.log(this.brand + ' ' + this.country + ' ' + this.price)
}
}
对象的函数属性,通常也被叫做对象的 方法(英文叫 method )。
对象 的方法其实可以看成是对象所拥有的函数。
也就是说 这个方法,是 属于 这个对象的函数。
调用对象的方法,语法是 所属对象.属性()
比如:
myCar.showInfo()
注意 showInfo 函数代码中的 this
在 JavaScript 中, this
关键字 代表了 当前执行环境,术语是Context,上下文的意思。
关于this,后面还有进一步的介绍
这里,我们只要知道,当 this
出现在对象方法中,所对应的执行环境,
就是指 调用这个方法 所通过的那个对象
,也就是 .
前面的对象
通过 myCar.showInfo()
这行代码调用的showInfo,里面的 this就是 myCar
对象
创建对象:使用构造函数
我们还可以 使用 构造函数(constructor function)
来创建对象
如下
function Car(brand, country, price) {
this.brand = brand
this.country = country
this.price = price
}
var myCar1 = new Car('凯美瑞2020', '日本', 200000);
var myCar2 = new Car('特斯拉Model 3', '美国', 300000);
这里的 Car 就是一个构造函数。
构造函数其实本质上就是一个函数。任何函数都可以当作构造函数来使用。
只是,用来构造的函数里面,通常会 为将来所要构造对象
设置一些属性。
是通过如下的语法定义的
this.brand = brand
this.country = country
this.price = price
函数里面的 this
就代表了要构建的对象,
上面的语句,就给对象添加了 brand、country、price 这些属性。
通常构造函数的首字母大写
构造函数定义好以后,后续要构造对象,通过如下语句
let myCar1 = new Car('奔驰', '德国', 500000);
let myCar2 = new Car('特斯拉Model 3', '美国', 300000);
使用 new
操作符 后面调用构造函数,并传入参数
这样构建的对象就会拥有这些属性
> myCar1
Car {brand: '凯美瑞2020', country: '日本', price: 200000}
通常,我们可以说,myCar1、myCar2 就是一个 Car 类型的 对象
, 或者 对象实例
原型 和 原型链
内置构造函数 Object
js 有个内置的构造函数 Object
Object
翻译成中文,也叫 对象,为方便区分,我直接用 英文 Object 称呼它,不给他起中文名
前面说过的直接 字面标记语法 创建的对象
var myCar = {
brand : '奔驰',
country : '德国'
}
其实, js引擎对这样的写法会自动调用Object构造函数,等同于下面的写法
var myCar = new Object()
myCar.brand = '奔驰'
myCar.country = '德国'
所以,所有 用 字面标记 创建的对象 都是 Object 类型的 对象实例。
函数本身也是对象
js中函数也是对象,也可以为其动态的添加属性。
比如
function func1(){
console.log('in func1')
}
func1.name = 'func1'
func1.desc = 'func1 函数的作用是...'
我们可以把函数看作 内置一段代码的 特殊对象。
使用原型创建对象
内置函数 Object
有个 create
方法(函数对象也可以有自己的方法属性)
可以用它直接创建对象。
看如下示例
// 汽车类型
var Car = {
type : '汽车',
hasTire : true
}
// 电动车类型
var ElectricCar = Object.create(Car)
ElectricCar.type = '电动车'
ElectricCar.hasBattery = true //有电池
// 汽油车类型
var GasolineCar = Object.create(Car)
GasolineCar.type = '汽油车'
GasolineCar.hasEngine = true //有发动机
var myCar1 = Object.create(ElectricCar)
myCar1.owner = '白月黑羽'
var myCar2 = Object.create(GasolineCar)
myCar2.owner = '白月黑羽'
Object.create 返回一个对象, 而create 的 参数 对象 就是 返回对象的 原型(prototype)。
上面的示例:
ElectricCar 的 原型是 Car
myCar1 的 原型是 ElectricCar
myCar1 ---> ElectricCar ---> Car
myCar2 ---> GasolineCar ---> Car
这样就形成一个 原型链
对象 有这个原型 到底 有什么用 呢?
对象会 继承(inherit)
原型链 对象 里面的属性。
怎么个 继承
法?
js有个规则:
用 对象.属性
的方式访问一个属性,
优先访问自身的属性,如果自身没有该属性,就尝试访问原型里面的这个属性;
如果原型对象里面也没有该属性, 就访问其原型的原型;
如果还没有,就在其原型链一直访问下去
所以可以像下面这样写代码:
// hasBattery 是 myCar1 原型 ElectricCar 里面的属性
console.log(myCar1.hasBattery)
// hasTire 是 myCar1 原型的原型 Car 里面的属性
console.log(myCar1.hasTire)
// owner 是 myCar1 自身 的属性
console.log(myCar1.owner)
注意:
myCar1 对象本身的属性并没有 hasBattery、type ,
所以
console.log(myCar1)
结果是 是 { owner: '白月黑羽' }
如果对象自身属性和 原型链上的属性 同名,使用哪个,按原型链次序,首先会优先使用自身属性
var Car = {
type : '汽车',
hasTire : true
}
var ElectricCar = Object.create(Car)
ElectricCar.type = '电动汽车'
ElectricCar.hasTire = '4个轮子'
var myCar1 = Object.create(ElectricCar)
myCar1.type = '白月黑羽的电动汽车'
//使用自己的属性
console.log(myCar1.type)
//使用原型链最靠近自身的属性
console.log(myCar1.hasTire)
Object.prototype
其实上面原型链还少了最后一环
myCar1 ---> ElectricCar ---> Car ---> Object.prototype
myCar2 ---> GasolineCar ---> Car ---> Object.prototype
因为Car 是 字面标记
创建,其实是从 Object 对象创建的,
它的原型是 Object 的属性 prototype
Object.prototype本身没有原型了, 所以原型链到此结束。
其实:js中所有的对象 的原型链 最后都是终结于 Object.prototype
所以,js 所有的对象都可以访问 Object.prototype 里面的属性
比如
myCar1.toString()
myCar1.hasOwnProperty('owner')
myCar1.hasOwnProperty('type')
toString
和 hasOwnProperty
都是 Object.prototype 里面的属性
toString
是把对象转化为字符串表示的,对象可以自己添加该属性方法,重新实现该方法。在打印结果和字符串格式化拼接时有用。
hasOwnProperty
是用来判断参数 是否是 对象自身的属性
原型的好处
对象和其原型的这种特性, 我们可以说对象 继承(Inheritance)
了其原型的 属性。
这种继承有什么好处?
为什么我们不直接就都使用对象自身的属性 ,而是要放一部分到原型中呢?
对象自身的属性 和 其原型链里面的属性 使用起来有什么区别 呢?
如果我们要开发一个游戏,里面有大量的汽车对象。
如果没有原型继承的特性, 我们每创建一个对象就要指明它所有的属性,比如:类型、是否有轮胎、是否有发动机、是否有电池等等。
如下
var myCar1 = {
type : '电动车',
hasTire : true,
hasBattery : true,
hasEngine : false,
owner : '白月黑羽',
... // 其它属性
}
var myCar2 = {
type : '汽油车',
hasTire : true,
hasBattery : false,
hasEngine : true,
owner : '白月黑羽',
... // 其它属性
}
而很多同一类型的车,这些属性都是相同的。
重复赋予对象这些属性,一个明显的缺点是:代码写起来麻烦。
而且另外一个缺点是:浪费内存空间。
自身的属性 是 每个构造对象 独有 的, 有多少个对象,就有多少份
而原型属性 是 所有构造对象 共有 的, 只有一份。
看下面这个例子
var Car = {
type : '汽车',
hasTire : true
}
var ElectricCar = Object.create(Car)
ElectricCar.hasBattery = true
var myCar1 = Object.create(ElectricCar)
myCar1.owner = '白月黑羽'
var yourCar1 = Object.create(ElectricCar)
yourCar1.owner = '麦克'
// 改变对象自身属性,只影响对象本身
yourCar1.owner = '麦克2'
// 不影响其它对象,输出还是 '白月黑羽'
console.log(myCar1.owner)
// 而改变原型,影响的是全部使用该原型的对象
Car.type = '汽车2'
// 之后,所有使用该原型的对象 其属性都会改变
console.log(myCar1.type)
console.log(yourCar1.type)
上面这个例子可以看出,原型属性 是 其创建的 对象 共享的,只有一份。
我们可以把公共的不变的属性(也就是这个类型的公共属性,比如 type/hasTire)放到原型对象中。
这样从原型创建出来的对象就继承了这些属性,可以使用。代码既简洁,又节省内存。
构造函数 和 原型
构造函数 定义
上面的原型示例中
myCar1 = Object.create(ElectricCar)
myCar1.owner = '白月黑羽'
我们新建对象要设置本身的属性,得在创建好对象后,单独再指定。这样比较麻烦
如果,我们 要创建的对象 需要原型,并且希望在创建时,就指定其自身的属性(比如通过参数传入属性值)
可以使用 函数构建
,并设置函数的 prototype 属性
。
每个js函数 都自动有一个 prototype属性
。
比如
function Car() {}
console.log(Car.prototype)
可以发现这个 prototype 对应的值也是一个对象。
注意:这个prototype对象里面有一个 constructor 属性指向其函数自身。
这个 prototype 就是 这个函数 所 构建出来的 对象的 原型
注意这个原型 不是该函数 自身的原型, 而是 这个函数 所构建出来的 对象的原型。
所以 函数构建出来的对象 会 继承(inherit)
prototype 里面的属性。
我们可以在这个prototype 里面放一些属性。
来看个例子
// 汽车类型
function Car(price, owner) {
this.price = price
this.owner = owner
}
// 给函数的 prototype 对象里面添加属性
Car.prototype.type = '汽车'
Car.prototype.hasTire = true
Car.prototype.price = '未知'
Car.prototype.showInfo = function (){
console.log('车型:' + this.type + ' - 车主:' + this.owner + ' - 售价:'+ this.price)
}
/* 注意
不要 这样设置prototype属性 ,这样就重新创建了一个prototype对象
Car.prototype = {type:'汽车', hasTire:true}
*/
// 构造对象
var myCar1 = new Car(300000, '白月黑羽')
- 注意点1:构造函数里面通过关键字
this
指代 构造的对象
this.price = price
this.owner = owner
这样就可以给 要构建的对象添加属性。
- 注意点2:构造对象 是通过关键字操作符
new
来创建的
new Car(300000, '白月黑羽')
new 后面加上 构造函数名 和 括号。
括号里面的参数是传递给 构造函数 的。
- 注意点3:我们可以把 构造函数 称之为一种 类型
通过构造函数 构建的对象 都是 这个类型的 具体 实例
实例的英文叫 instance
js 中 有个关键字操作符 instanceof
用来判定一个对象 是否是 一个类型对象 的实例
// 下面表达式的返回值为true, 表示是一个Car类型的实例
myCar1 instanceof Car
- 注意点4: 函数对象的 prototype
给函数的prototype添加属性后,根据前面讲的规则:
所有用 Car 构造的对象,它们的原型就是 Car.prototype 对应的对象
比如
// 对象本身没有该属性,使用 prototype里面的 '汽车'
myCar1.type
// 对象本身和prototype里面都有
// 优先使用对象本身的,结果是300000
myCar1.price
// 可以使用prototype里面的函数属性
myCar1.showInfo()
构造函数原型链
我们可以创建函数原型链,如下所示
// 汽车类型
function Car(price, owner) {
this.price = price
this.owner = owner
}
// 给函数的 prototype 对象里面添加属性
Car.prototype.type = '汽车'
Car.prototype.hasTire = true
Car.prototype.showInfo = function (){
console.log('车型:' + this.type + ' - 车主:' + this.owner + ' - 售价:'+ this.price)
}
// 电动车类型
function ElectricCar(price, owner) {
Car.call(this, price, owner)
}
// 设定 ElectricCar 原型为 Car
ElectricCar.prototype = Object.create(Car.prototype);
ElectricCar.prototype.type = '电动车'
ElectricCar.prototype.hasBattery = true
ElectricCar.prototype.hasEngine = false
// 汽油车类型
function GasolineCar(price, owner) {
Car.call(this, price, owner)
}
// 设定 GasolineCar 的原型为 Car
GasolineCar.prototype = Object.create(Car.prototype);
GasolineCar.prototype.type = '汽油车'
GasolineCar.prototype.hasBattery = false
GasolineCar.prototype.hasEngine = true
// 构建实例
var myCar1 = new ElectricCar(300000, '白月黑羽')
var myCar2 = new GasolineCar(500000, '白月黑羽')
上面的代码形成的原型链如下:
myCar1 ---> ElectricCar.prototype ---> Car.prototype ---> Object.prototype
myCar2 ---> GasolineCar.prototype ---> Car.prototype ---> Object.prototype
所以
myCar1.price // 结果是 300000
myCar1.type // 结果是 '电动车'
myCar1.hasBattery // 结果是 true
myCar1.hasTire // 结果是 true
一个对象,是其原型链上所有 构造类型 的 实例
所以
// 原型链上的,是一个实例,结果是 true
myCar1 instanceof ElectricCar
// 原型链上的,是一个实例,结果是 true
myCar1 instanceof Car
// 不是原型链上的,不是是一个实例,结果是 false
myCar1 instanceof GasolineCar
对象常见操作
检查对象是否有某个属性
前面讲过,如果要检查对象是否自身有某个属性,可以使用 hasOwnProperty
方法
比如
myCar1.hasOwnProperty('owner')
如果,不仅仅是自身属性,还要检查原型链上是否有某个属性,直接用关键字 in 即可
比如
'owner' in myCar1
得到对象所有属性
Object.keys
方法可以返回 对象的 所有属性名
一个数组
var obj1 = {
a: 'somestring',
b: 42
};
// 以 obj1 为原型创建 obj2 对象
var obj2 = Object.create(obj1)
obj2.c = 33
obj2.d = 44
Object.keys(obj2) // 返回 [ 'c', 'd' ]
得到对象所有属性和值
Object.entries
方法可以返回 对象的 所有属性和值
到一个数组,方便我们遍历
var obj1 = {
a: 'somestring',
b: 42
};
// 以 obj1 为原型创建 obj2 对象
var obj2 = Object.create(obj1)
obj2.c = 33
obj2.d = 44
Object.entries(obj2) // 返回 [ [ 'c', 33 ], [ 'd', 44 ] ]