前言
高级前端开发工程师个别很少会关注对象的属性类型,因为简略的工作简直不须要应用到,然而随着业务的复杂度进步或者须要本人造轮子的时候,理解对象的属性类型很有必要。
上面列举几种应用场景:
1.不想被遍历的属性
大多数的状况,在循环遍历的时候,咱们只想关怀本身的属性,而在对象和数组中会有一些自带的外部属性,如果这些属性都可能遍历进去,不仅对编码造成麻烦,对性能也是一种节约。
所以在数据属性中呈现了enumerable
,它能够管制在应用for-in
或Object.keys()
等办法时是否返回该属性。
enumerable
: 是否通过 for-in
/Object.key()
等办法返回属性,默认为true。
- 留神:for-in能循环继承属性,最好应用
Object.key()
代替
默认不能被遍历的外部属性
如数组中自带length属性无奈被遍历
var arr = ['a', 'b']Object.getOwnPropertyDescriptor(arr, 'length')// {// configurable: false// enumerable: false // 可枚举性为false// value: 2// writable: true// }Object.keys(arr) // 仅输入下标属性[0, 1],未输入length属性
定义一个不想被遍历的属性
Object.defineProperty(arr, '2', { enumerable: true, value: 'c'}); Object.getOwnPropertyDescriptor(arr, '2')// {// configurable: false// enumerable: false // 可枚举性为false// value: 2// writable: true// }Object.keys(arr) // 仅输入下标属性[0, 1],未输入下标2
2. 不想被批改的属性
个别设置的属性值都是能被外界批改的,但如果是开发插件或者组件的状况下,为了防止影响性能运行,有些属性是不心愿被外界批改和删除,于是就呈现了configurable属性。
不许动我的属性
var config = { host: 'http://shuairuoyang.cn'}Object.defineProperty(config, 'author', { value: '瘦弱羊', configurable: false // 默认是false})delete config.author; // false 删除失败delete config.host; // true 删除胜利console.log(config) // {author: '瘦弱羊'}
我不动你的属性
以vue源码举个栗子。
- vue版本:2.6.11
defineReactive是vue响应式的外围办法,它对obj的key属性定义了get和set 操作,从而实现了数据的监听和响应。如果被监听的对象obj的key属性不可被配置,则函数间接返回,数据无奈被监听。
// vue/src/core/observer/index.jsexport function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return // 属性不可配置,间接返回,前面就不执行了 } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, // 如果下面不对property.configurable === false的状况 进行判断,这里就会报错 get: function reactiveGetter () { // ... }, set: function reactiveSetter (newVal) { // ... } })}
举个栗子:在 data 中设置一个不响应的属性。
<template> <div> <p>响应的属性-age:{{responsizeAge}}</p> <p>不响应的属性-name:{{responsizeName}}</p> <button type="button" @click="chagne">点击扭转</button> </div></template><script>export default { data() { return { person: (function() { const obj = { age: 18 // 响应属性 } Object.defineProperty(obj, 'name', { configurable: false, // 不想响应了 writable: true, value: 'tony' }) return obj }()) } }, watch: { 'person.age': function(newVal, oldVal) { console.log('age watch>>>>', newVal, oldVal) }, 'person.name': function(newVal, oldVal) { console.log('name watch>>>', newVal, oldVal) // 没有响应,这里不会输入 } }, computed: { responsizeAge() { return this.person.age + 100 }, responsizeName() { return 'china--' + this.person.name } }, methods: { chagne() { this.person.name = 'jack-' + Math.random() this.person.age++ console.log('this.person>>>', this.person) // 间接查看对象数据有更新,但界面和其余中央并没有响应 console.log(Object.getOwnPropertyDescriptors(this.person)) } }}</script>
- age:响应属性,扭转后界面会同步批改,也能被watch和computed监听到
- name:configurable为false,非响应属性,批改后其余中央并不会同步批改
看看person对象属性的形容对象
总结:如果当前遇到很大的对象,而有局部内容不想被监听,可是应用这种形式解决,缩小被监听元素的个数,从而进步vue的运行性能。
3. 动静的属性值
一个属性值能够是一个简略的值,也能够是一个或者两个办法代替,它们就是getter和setter。
=
赋值同时也能够影响其余属性
var p = { x: 1.0, y: 1.0, get r() { return Math.sqrt(this.x * this.x + this.y * this.y) }, set r(newValue) { var oldValue = Math.sqrt(this.x * this.x + this.y * this.y) var ratio = newValue/oldValue; this.x = ratio this.y = ratio }}console.log(p) // {r: 1.4142135623730951, x:1, y:1}p.r = 100 // 调用r的set办法console.log(p) // 三个属性值都变了 // {r: 99.99999999999999, x:70.71067811865474, y:70.71067811865474}
举个栗子
再以vue源码里的defineReactive()
办法举个栗子
- vue版本:2.6.11
在data设置属性的时候,能够给属性定义setter办法,如果有则调用,没有则间接赋值。
如果被从新赋值,则调用dep.notify()办法去告诉所有的watcher(视图、computed、watch等)
// vue/src/core/observer/index.jsexport function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) { // ... let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // ... }, 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() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() // 音讯订阅器:如果数据被从新赋值,由它告诉所有的watcher } })}
从下面的源码中能够看到,如果属性值本人设置了get和set办法,会优先调用自定义的set和get,这样就能够实现一些特定场景。
<script>export default { data() { return { person: { realAge: 18, // 真正的年龄值 get age() { return 18 }, set age(newValue) { this.realAge = newValue + 18 } } }; }, mounted() { this.person.age = 30 // 无论设置成多少,都只会返回18 console.log(this.person) // {age: 18, realAge: 48} },};</script>
4. 高级办法
1. 创立一个对象常量
联合writeble: false 和 configurable:false 就能够创立一个真正的常量属性(不可批改,重定义或删除)
var myObject = {};Object.defineProperty( myObject, "FAVORITE_NUMBER", { value: 42, writable: false, configurable: false} );
2. 禁止拓展
如果你想禁止一个对象增加新属性并且保留已有属性,能够应用Object.preventExtensions(..)
var myObject = { a:2};Object.preventExtensions( myObject );myObject.b = 3; myObject.b; // undefined
3. 密封
Object.seal(..)
会创立一个“密封”的对象,这个办法实际上会在一个现有对象上调用Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false。所以,密封之后不仅不能增加新属性,也不能重新配置或者删除任何现有属性(尽管能够批改属性的值)。
4. 解冻
Object.freeze(..)
会创立一个解冻对象,这个办法实际上会在一个现有对象上调用Object.seal(..) 并把所有“数据拜访”属性标记为writable:false,这样就无奈批改它们的值。这个办法是你能够利用在对象上的级别最高的不可变性,它会禁止对于对象自身及其任意
间接属性的批改(不过就像咱们之前说过的,这个对象援用的其余对象是不受影响的)。
你能够“深度解冻”一个对象,具体方法为,首先在这个对象上调用 Object.freeze(..),而后遍历它援用的所有对象并在这些对象上调用 Object.freeze(..)。然而肯定要小心,因为这样做有可能会在无心中解冻其余(共享)对象。