开头
因为js特性的原因,所以创建对象有很多中形式,各自有各自的优缺点/对应使用的场景,而且关于对象也会涉及到原型等比较重要的知识,所以总结一下
正文
关于js对象
定义
- 根据红皮书的描述:无序属性的集合,属性可包含基本值、对象、函数。
- 总结:包含多个键值对的无序集合。
属性类型
- 根据红皮书地址,ECMA-262标准定义了一些对象的内部属性
- 主要分为数据属性 和访问器属性
- 数据属性就是对象存放数据的属性
- 访问器属性不包含数据(一个内部机制),包含一对getter/setter函数的属性,在读写数据时调用
数据属性的4个描述行为的特性
- configurable-表示该属性能否被delete删除
- enumerable-表示该属性能否被for-in循环
- writable-表示该属性的值能否被修改
- value-存储数据的发那个
- 代码示范
let sx = {
name: "sx",
age: 22,
};
// 通过Object.defineProperty来管理目标对象的属性行为
// 添加对sx对象的name属性的拦截器
Object.defineProperty(sx, "name", {
// 定义能否delete删除该属性 默认为true
configurable: false,
// 定义能否通过forin循环到该属性,默认为true
enumerable: false,
// 定义能否修改该属性,默认为true
writable: false,
// 如果这里定义,会覆盖初始化对象时的值
// value:"wade"
});
// 即使做出了修改/删除的操作,但无效
sx.name = "shuaxin";
delete sx.name;
console.log(sx);
for (let key in sx) {
console.log(`${key}=${sx[key]}`);
}
访问器属性(属性的描述符)
- 不包含数据
- 包含一对getter、setter函数,不是必须,无需定义,是对象内部的机制
- 当读取对象的属性时,默认调用getter函数,返回属性值
- 当对属性进行数据写入时,默认调用setter,并传入新值
- 代码示范
let sx = {
name: "shuaxin",
age: 0,
// _的属性表示该属性只能通过对象方法来访问
_sex: "女",
};
// 创建属性拦截器
Object.defineProperty(sx, "sex", {
// 当读取sex属性的值时,触发,返回_sex的值
get() {
console.log(this._sex);
return this._sex;
},
// 当对sex属性进行写入操作时触发
set(newVal) {
// 判断_sex和sex是否一致,否在做修改
if (this._sex !== newVal) {
this._sex = newVal;
}
},
});
sx.sex = "男";
定义多个属性
- 可以同时创建对多个属性的拦截器
- 使用defineProperties
let obj = {
name: "shuaxin",
age: 22,
_sex: "男",
};
Object.defineProperties(obj, {
name: {
configurable: false,
enumerable: false,
writable: false,
},
age: {
configurable: false,
enumerable: false,
writable: false,
},
sex: {
get() {
console.log(this._sex);
return this._sex;
},
// 当对sex属性进行写入操作时触发
set(newVal) {
// 判断_sex和sex是否一致,否在做修改
if (this._sex !== newVal) {
this._sex = newVal;
}
},
},
});
关于创建对象的各种方式(下面按创建对象实例后能否识别对象类型来划分)
无法识别对象类型的
无法识别对象类型的表现为,typeof的结果都是Object
字面量创建
let obj = {
name: "obj",
};
- 优点:创建简单、直观
- 缺点:会产生大量重复的代码,而且无法识别对象类型
使用object构造函数创建
let obj = new Object();
obj.name = "obj";
- 优缺点和字面量创建一样
工厂模式创建
function createObj(name) {
let obj = new Object();
obj.name = name;
return obj;
}
let obj = createObj("obj");
- 工厂模式是设计模式的一种
- 优点:封装了创建对象时的细节,减少了代码的冗余
- 缺点:同样这种方式也无法判断对象的类型
寄生构造函数模式
function Person(name) {
let obj = new Object();
obj.name = name;
return obj;
}
let sx = new Person("sx");
- 优缺点和工厂模式一致
稳妥构造函数模式
function Person(str) {
let name = str;
let obj = new Object();
obj.getName = function () {
return name;
};
obj.setName = function (str) {
name = str;
};
return obj;
}
let sx = Person("sx");
console.log(sx.name); //undefined
console.log(sx.getName()); //sx
- 优点:保证数据安全性,除非对外提供可操作函数,否则无法访问
- 缺点:无法判断实例的对象类型
可以识别对象类型的
表现为可以使用instanceof来判断具体的类型,比如
let obj = new Object();
console.log(typeof obj); //object
function Person(name) {
this.name = name;
}
let sx = new Person("sx");
console.log(sx instanceof Person); //true
自定义构造函数创建
function Person(name) {
this.name = name;
}
let shuaxin = new Person("shuaxin");
- 优点:可以定义对象的类型
- 缺点:因为每次调用一次该构造函数都会创建一片空间来存储一模一样的对象的属性,当创建许多个该类型对象时,会存在内存空间浪费的问题
使用原型创建
- 关于原型:每一个函数都有一个prototype属性,该属性指向一个对象(原型对象),该对象保存的是该类型对象的所有实例共享的属性/方法
- 对象实例可以通过proto来访问原型对象
function Person() {}
Person.prototype.name = "shuaxin";
- 优点:可以把属性存到原型对象里
- 缺点:因为所有实例共享同样的属性,所以导致数据读写会出现混乱
组合使用构造函数模式和原型模式
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function () {
console.log(`你好我是${this.name}`);
};
- 优点:结合了构造函数和原型模式2者的优点
- 缺点:需要合理分配那些存储在函数内部,哪些在原型,否则适得其反;其次就是封装性相对来说不是很好
动态原型模式
function Person(name) {
this.name = name;
if (typeof this.sayhi !== "function") {
Person.prototype.sayhi = function () {
console.log(`大家好,我是${this.name}`);
};
}
}
let shuaxin = new Person("shuaxin");
- 优点:结局了封装原型、组合模式封装性不好的问题
关于对象的一些api
Object.assign(target,…source)
- 给对象复制源对象的可枚举属性,返回操作完成后的目标对象
- 参数:目标对象,源对象(可支持多个)
const man = {
name: "shuaxin",
};
const sex = {
sex: "男",
};
let res = Object.assign(man, sex);
console.log(man); // {name:"shuaxin",sex:"男"}
console.log(res); // {name:"shuaxin",sex:"男"}
Object.create(proto,{propertiesObject})
- 创建一个新对象
- 参数:新建对象的原型对象,要添加到自身的属性(以及访问器属性)
- 应用:实现继承
let man = {
sex: "男",
};
let sx = Object.create(man, {
// 注:这里如果不设置枚举属性为true,默认为false,即不能被forin遍历到
age: {
value: 22,
},
name: {
value: "shuaxin",
},
});
console.log(sx); //{name:"shauxin",age:22}
console.log(sx.__proto__); //{sex:"男"}
Object.entries(target)
- 返回目标对象的可枚举(enumerable)属性
let sx = {};
Object.defineProperties(sx, {
name: {
value: "shuaxin",
enumerable: true,
},
idCard: {
value: "199x-x-x-x",
enumerable: false,
},
});
console.log(Object.entries(sx)); //[["name", "shuaxin"]]
Object.freeze(target)/Object.isFrozen(target)/ Object.isExtensible(targst)
- freeze:冻结对象,使其属性不能删除、修改,也不能增加属性
- isFrozen:检查对象是否被冻结,返回Boolean
- isExtensible:判断对象是否可扩展(即也可以判断对象是否被冻结)
- 注:影响对象是否可扩展的操作有 Object.preventExtensions,Object.seal 或 Object.freeze
let sx = {
name: "shuaxin",
};
Object.freeze(sx);
sx.name = "update";
sx.sex = "男";
delete sx.name;
console.log(sx); //{name:"shuaxin"}
console.log(Object.isFrozen(sx));
Object.fromEntries(obj)
- 把键值对结构(ES6的map)转换成对象
let list = [
["name", "shuaxin"],
["sex", "男"],
];
let map = new Map([
["name", "shuaxin"],
["sex", "男"],
]);
console.log(Object.fromEntries(list)); //{name: "shuaxin", sex: "男"}
console.log(Object.fromEntries(map)); //{name: "shuaxin", sex: "男"}
Object.getOwnPropertyDescriptor(obj)/Object.getOwnPropertyDescriptors(obj)
- 获取目标对象某个属性的访问器属性情况
- 需注意:只能指定存储在对象自身的属性,原型的属性无效
let shuaxin = {
name: "shuaxin",
};
// {value: "shuaxin", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(shuaxin, "name"));
let sx = {
name: "shuaxin",
sex: "男",
};
// { name: {value: "shuaxin", writable: true, enumerable: true, configurable: true}, sex: {…}}
console.log(Object.getOwnPropertyDescriptors(sx));
Object.values(target)
- 以数组形式返回对象自身所有属性的值
- symbol作key的无法获取到
const SEX = Symbol();
let shuaxin = {
[SEX]: "男",
name: "shuaxin",
};
console.log(Object.values(shuaxin));
Object.getOwnPropertyNames(target)/Object.getOwnPropertySymbols(target)/Object.keys(target)
- getOwnPropertyNames:以数组形式返回对象自身的所有属性名,除过以symbol作为key的属性
- getOwnPropertySymbols:以数组形式返回对象自身的所有symbol做key的属性名
- keys:以数组格式返回对象自身可枚举的属性
const sex = Symbol("sex");
let sx = {
[sex]: "男",
};
Object.defineProperties(sx, {
name: {
value: "shuaxin",
enumerable: true,
},
idCard: {
value: "199x-x-x-x",
enumerable: false,
},
});
// {name: "shuaxin", idCard: "199x-x-x-x", Symbol(sex): "男"}
console.log(sx);
// ["name", "idCard"]
console.log(Object.getOwnPropertyNames(sx));
// [Symbol(sex)]
console.log(Object.getOwnPropertySymbols(sx));
// ["name"]
console.log(Object.keys(sx));
Object.getPrototypeOf(target)
- 获取对象的原型对象
let man = {
sex: "男",
};
let sx = Object.create(man, {
name: {
value: "shuaxin",
},
});
// {sex: "男"}
console.log(Object.getPrototypeOf(sx));
// 同上
console.log(sx.__proto__);
Object.is()
- 判断2个值是不是相同,判断规则如下
- 两个值都是 undefined
- 两个值都是 null
- 两个值都是 true 或者都是 false
- 两个值是由相同个数的字符按照相同的顺序组成的字符串
- 两个值指向同一个对象
- 两个值都是数字并且
- 都是正零 +0
- 都是负零 -0
- 都是 NaN
- 都是除零和 NaN 外的其它同一个数字
- 和==还有===的区别:
- 这种相等性判断逻辑和传统的 == 运算不同,== 运算符会对它两边的操作数做隐式类型转换(如果它们类型不同),然后才进行相等性比较,(所以才会有类似 "" == false 等于 true 的现象),但 Object.is 不会做这种类型转换。 这与 === 运算符的判定方式也不一样。=== 运算符(和== 运算符)将数字值 -0 和 +0 视为相等,并认为 Number.NaN 不等于 NaN
Object.seal(target)/Object.isSealed(target)
- 判断对象是否密封
- 表现为不能对以存在属性进行删除,不能新增属性
- 可以修改已存在属性,这也是与freeze的区别
- isSealed用来判断对象是否是密封
- isExtensible也可以判断
Object.preventExtensions(target)/Object.isExtensible(target)
- 让对象无法扩展
- 表现形式为,无法新增属性;但是可以对已存在属性进行修改和删除
- isExtensible判断对象是否可扩展
let sx = {
name: "shuaxin",
};
Object.preventExtensions(sx);
sx.name = "update";
sx.sex = "男";
// {name: "update"}
console.log(sx);
delete sx.name;
// {}
console.log(sx);
Object原型上的一些api
isPrototypeOf
- 判断一个对象是否存在于另一个对象的原型链上
let man = {
sex: "男",
};
let sx = Object.create(man);
// true
console.log(man.isPrototypeOf(sx));
hasOwnProperty
- 判断对象自身是否含有某个属性
let man = {
sex: "男",
};
let sx = Object.create(man, {
name: {
value: "shuaxin",
},
});
// false
console.log(sx.hasOwnProperty("sex"));
// true
console.log(sx.hasOwnProperty("name"));
propertyIsEnumerable
- 判断该属性属性是否可枚举
- 如果没有该属性,返回false