相信对于对象属性大家都或多或少的知道一些,那么本文从属性说开去,看看大家对属性的了解是否有遗漏的部分。
也许你觉得定义属性很简单啊,我直接.prop = xxx,就可以定义个对象了啊,从未深入了解,这在大多数情况下没有任何问题。但在某些情况下就不够用了。
我们知道的使用方式是这样的:
let param = {
id:'',
number:13
}
param.query = 'sfwefw'
param['say']= () => {
console.log(111)
}
console.log(param.number)
那么我们先追本溯源看下对象定义属性官方的玩法吧。 官方对属性分为两种,一种是数据属性,另一种访问器属性。(这些属性值为了区别于我们理解的普通属性,我们用两对括号体现)
简单表格统计下他们的特征
属性 | 内容 | 特征 |
---|---|---|
数据属性 | configurable,enumerable,writable,value | 其中123均为布尔型,默认为true,分别代表可删除、可枚举、可修改,第四个为true |
访问器属性 | configurable,enumerable,getter,setter | 后面两个是非必须的 |
虽然似乎说的很明白,但还是一脸懵逼,感觉对自己没什么影响啊。那么干货来了,我通过几个经典的高频点来延伸的帮助大家理解这部分。
作为经常使用对象的我们,想必对这个语法并不陌生,虽然我们一般情况下很少直接这样用,因为更多业务场景下是属性的精准使用,不会通过循环的方式,原因有以下几个方面。当然你可以跳过这部分。
1 如果默认使用属性循环来展示数据,有很多不必要展示的数据都要过滤筛选掉,比较低效麻烦 2 属性的循环访问不一定符合我们需要展示的顺序,这点才是致命的,导致我们在业务需要的时候更多的时候是固定顺序固定访问对象属性 3 如果对对象属性期望按照顺序,会大大的增加数据改造的成本,增加不可复用的解耦成本
回到正文,重头戏来了,作为常识需要了解到两点。
第一点,for in循环可以访问到对象具有的所有可枚举属性; 第二点 对象具有的属性可能是多来源的,可能是自己新建的,可能是构造函数新建的,可能是来源于构造函数的继承;可能是来源于原型,可能是来源于原型式的继承。其中我们可以通过hasOwnProperty来判断这个属性是否是自有属性(构造函数来的是判断不出的)。
//正常的构造函数以及对象属性赋值,call .apply构造函数继承方式的属性都可以正常获取,并且属于对象自有属性
let Animal = function (){
this.bigtype = 'animal'
}
let Person = function () {
this.name = '人类'
this.sex = '男女'
Animal.call(this)
}
let zhangsan = new Person('zhangsan','male')
zhangsan.type = 'animal'
for(let p in zhangsan){
console.log(zhangsan.hasOwnProperty(p),`${p}:${zhangsan[p]}`)
}
特别说明:为什么构造函数之后对象的属性都是自有属性呢? 这个要和new关键字有关了,其关键的四个步骤是创建新的对象,然后构造函数的作用域指向新对象(this指向新对象),执行构造函数中的代码,返回新对象。所以自然通过this赋值的都是新对象的属性了。
无论是通过原型修改属性还是原型链继承的其他原型,其均不属于对象自己,均是向上追溯的原型对象的,所以hasOwnProperty均为false.
需要注意的是 :1 如果你需要继承其他原型,又需要修改原型的某个值,要先继承在修改值,不然你修改的值就丢失了。2 继承原型要在实例化对象之前,写在调用之前是无效的。
let Animal = function (){
this.bigtype = 'animal'
}
let Person = function () {
this.name = '人类'
this.sex = '男女'
// Animal.call(this)
}
Person.prototype = new Animal()
Person.prototype.belong = 'zhang'
let zhangsan = new Person('zhangsan','male')
for(let p in zhangsan){
console.log(zhangsan.hasOwnProperty(p),`${p}:${zhangsan[p]}`)
}
没错,我们一般情况下使用for,in循环获取属性,但有些属性我们也希望得到。通过上面的for in的例子,你可以通过for in +hasOwnProperty 的方式得到对象可枚举非原型属性以及可枚举原型属性。那么还有其他方法么?肯定有的。下面进行表格说明。
方法 | 内容 | 备注 |
---|---|---|
for in | 可枚举,自身以及继承属性 | 对象以及继承,可枚举,不含 Symbol 属性 |
Object.keys(obj) | 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性键名 | 对象自身可枚举,不含 Symbol 属性 |
Object.getOwnPropertyNames(obj) | 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名 | 对象自身,包括不可枚举属性 |
Object.getOwnPropertySymbols(obj) | 返回一个数组,包含对象自身的所有 Symbol 属性的键名 | 对象自身,symbol |
Reflect.ownKeys(obj) | 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。 | 对象自身,全部属性 |
来源 | 指向 |
---|---|
对象 | 对象自身 |
构造函数 | 返回新对象 |
原型 | 原型 |
纯函数调用 | 外部环境全局,浏览器或者node |
一般我们也用不到这个,但vue的数据双向绑定就是基于这个实现的,其在data属性中定义的数据,全部对其属性的属性定义中追加了虚拟dom的事件,所以能够实现双向绑定。也正因为这个属性的兼容问题,导致了vue不支持ie低版本哦。
参考源码:vue框架使用get,set源码地址
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有