js对象属性

前言

相信对于对象属性大家都或多或少的知道一些,那么本文从属性说开去,看看大家对属性的了解是否有遗漏的部分。

属性的定义与使用

也许你觉得定义属性很简单啊,我直接.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 后面两个是非必须的

虽然似乎说的很明白,但还是一脸懵逼,感觉对自己没什么影响啊。那么干货来了,我通过几个经典的高频点来延伸的帮助大家理解这部分。

for in 循环遍历的属性

作为经常使用对象的我们,想必对这个语法并不陌生,虽然我们一般情况下很少直接这样用,因为更多业务场景下是属性的精准使用,不会通过循环的方式,原因有以下几个方面。当然你可以跳过这部分。

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 或字符串,也不管是否可枚举。 对象自身,全部属性

属性中的this是什么

来源 指向
对象 对象自身
构造函数 返回新对象
原型 原型
纯函数调用 外部环境全局,浏览器或者node

访问器get,set使用

一般我们也用不到这个,但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()
    }
  })
}

发布者

Robinson Zhang

热爱前端,热爱分享,坚持高频写作,从小白到大师只是时间问题。