JS中微妙的对象属性
1.对象的自有属性的属性和Object.defineProperty(target,key,option)添加的属性有什么区别?
const obj = { name:'bob' };
Object.defineProperty(person,'age',{ value:12 });
console.log(obj.age);//?12
console.log(Object.keys(obj));//?['name']
console.log(Object.getOwnPropertyNames(obj))//?['name','age']
/*
大多数人认识Object.defineProperty()这个API
是因为Vue2,使用Vue2的工程师细心的应该注意过一个问题:
为什么输出的响应式对象需要点开才能看到属性,这是为什么?
如上,我们使用Object.defineProperty()为对象添加了属性
value,其余属性没有定义,它们的值是什么?
直接量字面量创建的对象的属性的默认的属性描述符是什么样的?
*/
//1)对象的自有属性的属性描述符
const nameDescriptor = Object.getOwnPropertyDescriptor(obj,'name')
//{value: 'bob', writable: true, enumerable: true, configurable: true}
console.log(nameDescriptor)
//2)通过Object.definePropery()添加的属性不设置属性描述符时的实际属性描述符的情况
const ageDescriptor = Object.getOwnPropertyDescriptor(obj,'age')
console.log(ageDescriptor)
//{value: 123, writable: false, enumerable: false, configurable: false}
/*
对象中的属性描述符有:
enumerable:boolean,可枚举性,默认true;
configurable:boolean,配置性,默认true;
value:any,属性的初始值;
writable:boolean,是否可写默认值是true
除此之外还有大家熟知的getter和setter
这两个和上面的属性描述符是冲突的,所以有
getter/setter就不要写以上的属性描述符
上面出现了惊人的场景:虽然我们没有设置属性描述符
但是使用Object.defineProperty()定义的属性
属性描述符是false
所以上面的结果比较出乎意料,下面判断对象是否为空对象的案例也与此有关
*/
/*
以上用到几个API,简单介绍一下:
1.Object.keys(object)
和Object.values(object)方法类似
接收一个对象作为参数,返回对象的可枚举属性
enumerable为true的key组成的数组和可枚举属性
的value组成的数组,只找对象自有属性属性值,不会
去对象的原型对象上找。
特别强调:必须是可枚举属性
2.Object.getOwnPropertyNames()接收对象参数,返回
对象自有属性的可枚举不可枚举的key组成的数组,
只找对象的自有属性
不会去对象的原型对象上找。
特别强调:无论属性是否可枚举都会获取到
3.Object.getOwnPropertyDescriptor(object,key)
获取对象的某个属性的属性描述符对象
*/
2.如何判断一个对象是否为空对象?
这是一个老生常谈的问题,由于object对象是引用数据类型,存储于计算机内存的堆内存中 通过指针访问,所以对象是否为空对象的判断不能使用 obj === {} 或者 obj == {} 网上流行的方式: 1.JSON.stringify(object) === '{}' 2.for in循环方式 3.Object.keys(object) 4.Object.getOwnPropertyNames(object)
我们逐一测试:
//方式1:JSON.stringify(object) === "{}"判断是否为true
const object = { name : 'Andy' },
emptyObj = {};
let result = JSON.stringify(emptyObj) === "{}",
result2 = JSON.stringify(object) === '{}'
console.log(result)//true
console.log(result2)//false
/*
如果对象真的是空对象,那JSON.stringfiy(object)
的判断结果没问题,但是当对象里只有方法呢?
*/
const object2 = { func(name){ console.log(name) } };
const result3 = JSON.stringify(object2) === '{}';
console.log(result3)//true
/*
对象中的方法比较特殊,本质还是对象的属性,只不过值是函数
被称为对象的方法,所以 JSON.stringify() 的第一个坑出现
JSON.stringify()不能转化对象的方法,或者说它会自动忽略
对象中的方法,导致判断失效,其实至此,JSON.stringify()
判断空对象已经失效了
*/
//测试JSON.stringify()转化circular对象
const object3 = { name:'object3' };
object3.a = 1;
object3.self = object3
const result4 = JSON.stringify(object3) === '{}'//error
/*
Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
--- property 'self' closes the circle
*/
/*
circular对象:对象中的某个属性值是对象自身
简单说就是可以 '无限循环点开' 的对象
很遗憾,JSON.stringify()不能转化circular对象
所以目前发现JSON.stringify()转化对象的两个
*/
/*
结论:判断对象是否为空对象请彻底pass JSON.stringify()方法
*/
//方式2:for in方式
/*
看到网上有一种方式是封装for in方法,
如果进入对象的循环体则表示不是空对象,
否则是空对象,那看过上面的阐述应该能
想到对象的enumerable属性对循环遍历的影响
*/
function isEmpty(object){
/*前置判断略过*/
for(const key in object){
return false
}
return false
};
/*
首先它会有一个致命问题是,当对象里的所有属性不可枚举时
这个方法会失效,演示过程略过,它和上面Object.keys()
无法获取不可枚举属性的原理是一致的
*/
/*
然后我们考虑一种情况:for in 是否会遍历对象的原型对象上的
属性呢?
根据以往的经历:我们for in 一个直接量字面量创建的对象时,它
并没有枚举Object.prototype上的属性方法,因为它们默认是不可枚举的。
有看官大佬应该已经想到了:Object.create(prototype:object,option?:object)
*/
const object = Object.create({ property1 : 'props1' });
console.log(object);//{}
console.log(object.__proto__)//{ property1 : 'props1' }
const result = isEmpty(object)
console.log(result)//false
/*
我们发现for in 遍历了使用Object.create()方法创建的空对象
以及空对象的原型对象,所以for in 方法失效
*/
/*
Object.create()方法接收两个参数,第一个参数是要创建的对象的原型对象
第二个参数也是对象,是可选参数,是新对象的访问器属性集合,是属性描述符对象
我们简易的模拟一下Object.create()方法更直观:
*/
function createObject(proto, config) {
/* 前置判断略过 */
const object = {}
Object.setPrototypeOf(obj, proto)
if (config && Object.getOwnPropertyNames(config).length > 0) {
for (const key in config) {
if (Object.prototype.hasOwnProperty.call(config, key)) {
const element = config[key];
Object.defineProperty(obj, key, element)
}
}
}
return object
}
const originObj = { a: 12, b: '213' };
const handler = {
name: {
enumerable: false,//不可枚举
configurable: true,
writeable: true,
value: '张三'
}
}
const result1 = createObject(originObject,handler)
console.log(result1)//{name: '张三'} __proto__ { a: 12, b: '213' }
console.log(result1.__proto__)//{a: 12, b: '213'}
console.log(Object.getOwnPropertyNames(result1))//['name'];不管可不可枚举都能拿到
console.log(Object.keys(result1))//[],name属性确实是不可枚举的
/*
分析一下上述代码中的API:
1. Object.setPrototypeOf(target,__proto__)
设置一个对象的原型对象,
2.与之相对应的Object.getPropertyOf(target)
获取一个对象的原型对象
之所以要提一下这个没用到的API,是因为__proto__
已经被废弃了,不过ES目前是强制要求各大浏览器还可以通过
__proto__获取到实例对象的隐式原型,并且ES也提供了
两个获取设置修改对象的原型对象的API
3.Object.prototype.hasOwnProperty.call(this, key)
这种修改this指向的方式既冗长又不美观,难道程序员
就为了凑字?
这种写法的原因1:
目标对象没有这个方法,还想用这个方法,比如:
//在函数体中:
var arg = [].slice.call(arguments)
arguments是个类数组对象,我们想将其快速转为数组,这在ES5中
很常见.
Object.prototype.hasOwnProperty(key)方法确实是个成员方法
但是hasOwnProperty并不是关键字,这意味着你可以直接量字面量
创建一个包含hasOwnProperty方法的对象,此时object.hasOwnProperty(key)
默认访问的是对象自身的,而不是原型对象上的hasOwnProperty(原型链机制)
所以VS Code中生成的代码中有这种修改this指向的用法,这能有效提升
代码的严谨性
*/
//方式3: Object.keys(object)
基于上述的代码示例,Object.keys()会在对象自身找可枚举属性组成数组,所以这个方法不严谨
//方式4:Object.hasOwnPropertyNames()
目前我所知的最好的最简洁的判断对象是否为空对象
至此,据我所知,判断对象是否为空对象只有最后这种方式比较严谨
3.常见场景之判断一个对象是否存在某个属性,后执行对应操作
const object = { age : 12 };
//如果object存在name属性,控制台打印name的值
if(object.name){
console.log(object.name)
}
/*
这看起来没有什么问题,很符合我们的编程习惯,但是如果
我执行了如下代码,再执行上面的if判断呢?
*/
object.name = undefined;
object.position = null;
if(object.name) console.log(object.name)
else console.log('undefineds')//'undefineds'
/*
首先说对象中是可以存储任意值的,
声明变量不要显式赋值为undefined,但是可以赋值为null
而对象的属性可以赋值为任意值,
如果访问对象不存在的属性(原型链上也不存在)时会得到undefined
但是一个对象的某个属性就是undefined,这时判断就可能出问题:
我们发现上面走了else 分支
object.name不存在得到undefined是假值
object有name属性但是值是undefined是假值
*/
if(object.name) console.log(object.name)
else console.log('undefineds')//'undefineds'
if(Object.prototype.hasOwnProperty.call(object,'name')) console.log(object.name)//undefined
else console.log('undefineds')
/*
确定是要判断一个对象的属性是否存在还是判断一个对象
的某个属性值是否是真值是非常值得思考的问题,
hasOwnProperty(key)方法返回布尔值,判断一个对象是否
有某个自有属性
*/
4.使用对象的计算属性属性优化if...else分支语句
/*
打个预防针:不要一提到计算属性就是Vue中的computed计算属性
其实还有一种说法:object[variable] = value,传入变量动态获取
值,考虑以下代码:
*/
function getVal (key){
const object = { a:1,b:2,c:3 };
if(key === 'a'){
return object.a
}else if(key === 'b'){
return object.b
}else if(key === 'c'){
return object.c
}
throw new Error(key + 'doesn`t exist in object')
}
/*
if...else...分支语句本身条理清晰,但是条件过多会显得代码
冗余,当然将if...else...转换成Switch也是不错的选择,
有道是程序员都是懒人,所以我们应该在保证功能的前提下
尽可能精简代码,所以用对象的计算属性试试看:
*/
function getValue (key) {
const object = { a:1,b:2,c:3 };
if(Object.prototype.hasOwnProperty.call(object, key)){
return object[key]
}
throw new Error(key + 'doesn`t exist in object')
}
//其实还可以使用其他工具优化,比如数组方法:
function getValues(key){
const object = { a:1,b:2,c:3 };
const keyArray = Object.getOwnPropertyNames(object)
if(keyArray.includes(key)) return object[key]
throw new Error(key + 'doesn`t exist in object')
}
5.目前js中对象的键值的合法值都有哪些数据类型?
/*
乍一聊起这个问题很多人还是有些犯迷糊的,先说结论:
1.string,这个不用说都知道
2.number,好像也行吧?
3.Symbol,Symbol是什么鬼?
*/
const object = { a:1, b:2, c:3}
object[1] = 10
object[Symbol('ES')] = 20
console.log(object)//{1: 10, a: 1, b: 2, c: 3, Symbol(ES): 20}
console.log(Symbol('ES') == Symbol('ES'))//false
console.log(Symbol('ES') === Symbol('ES'))//false
/*
Symbol(description)
即便传入了相同的description也永远不会相等也不全等
数值作为对象合法的键值也就解释了为什么说数组是特殊的对象:
数组可以看作键值为依次递增的对象
而Symbol()方法会创造出唯一的值,它没有字面量表示方式,
但是可以转存于一个变量中,放在公共位置避免重复
比较典型的场景是Vue中需要Provide(key,data)inject(key)
时可以将一个Symbol()值存放在一个公共的位置,组件引入并使用
它
*/
说了很多但是其实内容没有多少,但是比较细节,纯手码,也是想到哪写到哪,今天暂时告一段落
欢迎大佬斧正,欢迎志同道合的朋友提出宝贵看法意见,成长的道路有你也有我!