JavaScript对象
JavaScript 对象
对象是包括属性与方法的数据类型,在JS中大部分数据类型其实都是对象,例如:String/Number/Math/RegExp/Date
。对象和函数、数组一样,都是引用类型。
对象的声明
JS中的对象声明方式有如下几种
(1) 使用字面量形式声明对象是最简单的方式,在JS中使用一对花括号{}
代表对象类型。
let zhangSan =
{
fristName: "张",
lastName: "三",
age: 19,
getName : function()
{
return `${this.fristName}${this.lastName}`;
}
}
(2)使用构造函数方式创建对象
function User(firstName,lastName,age)
{
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
let lisi = new User("李","四",22);
(3)其实字面量形式内部也是通过使用构造函数 new Object
创建的
let zhangSan = new Object();
属性和方法
js中的属性是这个对象的属性即对象中定义的变量,方法是这个对象所拥有的功能即对象中的函数,拿上面的对象zhangSan为例,fristName、lastName、age
就是对象的属性,而getName
就是方法。JS中定义属性和方法采用 :
来给对象的属性和方法初始化,:
左边是属性(方法)名,右边是值。属性和方法统称为对象的成员。
成员的访问
(1)JS中可以用.
来访问对象中的成员
let zhangSan =
{
name : "张三",
age : 22,
getName: function()
{
return this.name;
}
}
//访问属性
console.log(zhangSan.name);//张三
//访问方法
console.log(zhangSan.getName());//张三
(2)JS中还可以使用[]
访问成员
//访问属性
console.log(zhangSan["name"]);//张三
//访问方法
console.log(zhangSan["getName"]());//张三
使用.
操作成员更简洁,而当成员名不是合法变量的情况下就只能采用[]
语法
let zhangSan =
{
name : "张三",
age : 22,
getName: function()
{
return this.name;
}
}
zhangSan["a b"] = "这是不合法的变量名";
//变量名中含有空格,只能通过[]访问
console.log(zhangSan["a b"]);//这是不合法的变量名
添加和删除
对象和方法的属性可以动态的添加或删除。
- 对于已经存在的属性,如果没有进行权限和作用域的设定,值将被覆盖。
let zhangSan =
{
name : "张三",
age : 22,
getName: function()
{
return this.name;
}
}
//对一个已经存在的属性赋值将覆盖掉该属性的值
zhangSan.name = "李四";
console.log(zhangSan.name);//李四
- 对于不存在的属性进行赋值,该属性将被创建,可以利用这个特性后天给对象添加属性
let zhangSan =
{
name : "张三",
age : 22,
getName: function()
{
return this.name;
}
}
//对一个不存在的属性赋值将添加该属性
zhangSan.birthday = "1998/04/15"
console.log(JSON.stringify(zhangSan,null,2));
输出:
{
"name": "张三",
"age": 22,
"birthday": "1998/04/15"
}
- 也可以使用Object.assign进行赋值操作
"use strict";
let zhangSan = {name:"张三",age : 22};
zhangSan = Object.assign(zhangSan,{age : 23},{sex : "男"});
console.log(JSON.stringify(zhangSan,null,2));
输出:
{
"name": "张三",
"age": 23,
"sex": "男"
}
JSON.stringify(zhangSan,null,2)
可以将对象zhangSan转换为json格式数据,并设置缩进值为2,可以直观输出js对象
- 删除成员可以使用关键字
delete
//接上面的代码
delete zhangSan.birthday;
console.log(JSON.stringify(zhangSan,null,2));
输出:
{
"name": "张三",
"age": 22
}
引用类型特性
对象和函数、数组(本质上它们也是对象)一样,都是引用类型,引用类型进行复制时只会复制引用。所谓复制只会引用地址,即当对象被赋值时,只是把对象所在内存的引用复制了一份,而两个引用共有一块内存。不难理解,当其中一个引用操作其成员,会导致其他引用的一起改变。
而值类型进行赋值时会在内存中对原数据进行赋值,看似两个的值是相同的,实则保存在同一块内存
let zhangSan =
{
name : "张三",
age : 22,
getName: function()
{
return this.name;
}
}
let zhangSan_1 = zhangSan;
zhangSan_1.age = 18;
console.log(JSON.stringify(zhangSan,null,2));
输出:
{
"name": "张三",
"age": 18
}
当我们修改zhangSan_1的值,zhangSan的值也跟着一起改变了
this
this
指当前对象的引用,不同环境下的this
指向不同的内容,this
只指向当前对象。this
是为了解决下列的问题:
- 当对象成员函数的形参名与成员属性名相同时,浏览器无法理解该名称是代表谁
- 看下面的代码,当删除了
birthday
变量,但函数体内还在使用造成错误,而使用了this
就不会出现错误。
let zhangSan =
{
name : "张三",
birthday : new Date("1998/12/25"),
getAge : function()
{
let age = new Date().getFullYear() - this.birthday.getFullYear();
return age;
}
}
console.log(zhangSan.getAge()); //22
- 在严格模式下方法中的全局的this值为undefined,这是为了防止无意的修改window对象
展开语法
展开语法不仅可以用于类数组序列,还可以用于对象。使用...
可以展示对象的结构
let zhangSan =
{
name : "张三",
birthday : new Date("1998/12/25"),
getAge : function()
{
let age = new Date().getFullYear() - this.birthday.getFullYear();
return age;
}
}
let new_zhangSan = {...zhangSan}
console.log(JSON.stringify(new_zhangSan,null,2));
输出:
{
"name": "张三",
"birthday": "1998-12-24T16:00:00.000Z"
}
解构赋值
基本用法
解构是一种更简洁的赋值特性,可以将对象对应成员赋值给左值。
let zhangSan =
{
name : "张三",
birthday : new Date("1998/12/25"),
getAge : function()
{
let age = new Date().getFullYear() - this.birthday.getFullYear();
return age;
}
}
//使用 : 运算符 左边为右值对象成员,右边为接收的变量名
let {name:MyName,birthday:MyBirthday} = zhangSan;
//如果接收的变量名与右值的对应成员名相同,可以省略:
let {name,birthday} = zhangSan;
console.log(name,birthday);//张三,"1998-12-24T16:00:00.000Z"
函数解构
函数返回值直接解构到变量
function getZhangSan(birthDate)
{
let age = new Date().getFullYear() - birthDate.getFullYear();
return {
name : "张三",
age
}
}
let {name,age} = getZhangSan(new Date("1998/03/15"));
console.log(name,age);//张三 22
解构语法可以直接使用在函数传参上
function getZhangSan({name,birthDate})
{
let age = new Date().getFullYear() - birthDate.getFullYear();
return {
name,
age
}
}
let ZhangSan = {name : "张三",birthDate : new Date("1998/03/22")}
let newZhangSan = getZhangSan(ZhangSan);
console.log(newZhangSan.age) // 22
遍历对象
keys/values/entries
使用系统提供的Object静态方法可以方便获取对象属性与值。
let ZhangSan = {name : "张三",birthDate : new Date("1998/03/22")}
console.log(Object.keys(ZhangSan));
console.log(Object.values(ZhangSan));
console.log(Object.entries(ZhangSan));
输出:
for/in和for/of
对象也可以使用for/in
和for/of
进行遍历,for/of
用于遍历迭代对象,不能直接操作对象。但Object
对象的keys/values/entries
方法返回的是迭代对象。
"use strict";
let zhangSan = {name:"张三",age : 22};
for (const key in zhangSan)
{
console.log(key);//name age
}
"use strict";
let zhangSan = {name:"张三",age : 22};
for (const key of Object.keys(zhangSan)) {
console.log(key); // name age
}
for(const value of Object.values(zhangSan)){
console.log(value); // 张三 22
}
对象拷贝
因为对象的引用类型特性,直接将对象赋值给另一对象会导致两个对象引用同一内存。改变其中一个的属性或方法会牵连另一对象。如果要实现两个对象互不牵连可以使用浅拷贝
和深拷贝
两种方式进行对象的拷贝。浅拷贝不会将对象的嵌套数据和引用类型进行拷贝。
浅拷贝
(1)使用展开语法 ...
进行拷贝
"use strict";
let zhangSan = {name : "张三",age : 30}
let lisi = {...zhangSan}
(2)使用for/in
语法对对象进行遍历拷贝
"use strict";
let zhangSan = { name: "张三", age: 30 }
let lisi = {}
for (const key in zhangSan) {
lisi[key] = zhangSan[key];
}
(3) Object.assign
函数可简单的实现浅拷贝,它是将两个对象的属性叠加后面对象属性会覆盖前面对象同名属性。
"use strict";
let zhangSan = { name: "张三", age: 30 }
let lisi = Object.assign({},zhangSan)
深拷贝
浅拷贝不会将深层的数据拷贝,要进行深层次的拷贝就需要用到深拷贝。深拷贝利用递归算法进行对象的层层拷贝,可以进行深层次的拷贝
使用浅拷贝后,虽然第一层的内容进行了拷贝,但是第二层的对象zhangSan
里的还是牵连在了一起.
"use strict";
let obj ={
name : "张三",
zhangSan : {
age : 20
}
}
//直接进行浅拷贝
let obj_1 = {...obj}
obj_1.zhangSan.age = 30;
console.log(JSON.stringify(obj,null,2));
console.log(JSON.stringify(obj_1,null,2));
输出:
{
"name": "张三",
"zhangSan": {
"age": 30
}
}
{
"name": "张三",
"zhangSan": {
"age": 30
}
}
使用递归进行深拷贝:
let oj = {
name : "obj1",
obj2 : {
name : "obj2"
}
}
function copy(object){
//因为数组也是引用类型,兼容数组的复制
let obj = object instanceof Array ? [] : {};
for(const [key,value] of Object.entries(object)){
//如果对象成员是引用类型,再次递归拷贝,否则直接结束递归
obj[key] = typeof value == "object" ? copy(value) : value;
}
return obj;
}
let object = copy(oj);
console.log(JSON.stringify(object,null,2));
输出:
{
"name": "obj1",
"obj2": {
"name": "obj2"
}
}
属性特征
三类对象两类属性
-
内置对象(native object) 是由ECMScript规范定义的对象或者类。例如:函数,数组,日期,正则…
-
宿主对象(host object) 是由js编译器所嵌入的宿主环境(web浏览器)所定义的。比如客户端js中表示网页结构的HTMLElement对象就是宿主环境创建的对象。宿主环境定义的对象可以直接使用的话,我们也可以把它们当做内置对象。
-
自定义对象(user-defined object) 由运行中的js创建的对象。
-
自有属性(own property) 直接在对象当中定义的属性,区别于继承属性。
-
继承属性(inherited property) 在对象原型中定义的属性。
属性描述
JS中可以对属性的访问特性进行控制。
描述 | 说明 | 默认值 |
---|---|---|
configurable | 能否使用delete、能否需改属性特性、或能否修改访问器属性 | true |
enumerable | 对象属性是否可通过for-in循环,或Object.keys() 读取 | true |
writable | 对象属性是否可修改 | true |
value | 对象属性的默认值 | undefined |
使用 Object.getOwnPropertyDescriptor
查看对象属性的描述。
let zhangSan = {
name : "张三",
age : 30
}
let desc = Object.getOwnPropertyDescriptor(zhangSan,"name");
console.log(JSON.stringify(desc,null,2));
输出:
{
"value": "张三",
"writable": true,
"enumerable": true,
"configurable": true
}
使用 Object.getOwnPropertyDescriptors
查看对象所有属性的描述
let zhangSan = {
name : "张三",
age : 30
}
let desc = Object.getOwnPropertyDescriptors(zhangSan,"name");
console.log(JSON.stringify(desc,null,2));
设置属性描述
使用Object.defineProperty
方法修改属性描述的值
//设置zhangSan name属性的属性描述
Object.defineProperty(zhangSan,"name",{
value : "未命名", // 设置name的默认值为未命名
writable: false, // 设置name为只读
enumerable : false,// 设置name属性不允许遍历
configurable : false,// name字段不允许访问
});
使用 Object.defineProperties
可以一次设置多个属性,具体参数和上面介绍的一样。
属性封闭
除了上面的直接控制对象属性的操作权限,js还封装了一些方法给我们封闭对象的属性
封闭对象
Object.seal()
方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
冻结对象
Object.freeze
冻结对象后不允许添加、删除、修改属性,writable、configurable
都标记为false
禁止添加
Object.preventExtensions
禁止向对象添加属性
属性访问器
属性访问器有两种:getter/setter
getter方法用于获得属性值,setter方法用于设置属性,这是JS提供的存取器特性即使用函数来管理属性。
属性访问器的特点
-
当程序查询访问器属性值时,js调用getter方法。这个方法返回的就是属性存取表单式的值。
-
当程序设置访问器属性值是,js调用setter方法。这个方法将赋值表达式右边的计算结果,传入setter。
-
访问器属性不具有可写性,如果同时具有getter和setter方法那它就是一个读写属性。也可以只拥有其中一个,来作为只读或者只写属性。
下面的代码将展示如何对一个用户对象设置其年龄属性的访问器监控:
let user = {
data : {
name : "张三",
age : 20
},
set age(value){
if(typeof value != "number" || value < 0 || value % 1 != 0){
//如果设置的年龄不是数字,或 小于 0 或不是整数,将报错
throw new Error("年龄不合法");
}
else{
this.data.age = value
}
},
get age(){
return this.data.age;
}
}
console.log(user.age);//20
user.age = 12.5;//Uncaught Error: 年龄不合法