属性描述符以及方法

对象的属性有两种类型:数据属性和访问器属性

  • 数据属性 (有4种特性)

    1.Configurable(可配置性):决定属性是否可以删除(delete)、是否可以再次修改属性的特性以及是否可以转化为访问器属性。 一旦通过Object.defineProperty()方法将特性设置为false,后面就无法对该属性其他特性进行修改;也无法删除该属性。(有一例外,configurable:false情况下,只允许writable的状态由true变为false,无法由false变为true)默认值为true

    var person = { 
      name: "zhangsan",
      age: 18,
      sex: "male"
    }
    Object.defineProperty(person, "age" , { configurable: false });
    delete person.age;   //  无法删除age属性, 严格模式下会报TypeError
    Object.defineProperty(person, "age", { writable: false })
    person.age = 20;
    console.log( Object.getOwnPropertyDescriptor(person, "age"))
     //  {"value":18,"writable":false,"enumerable":true,"configurable":false}
    
    1. Enumerable (可枚举性):决定属性是否出现在对象的可枚举属性中。比如for...in 、Object.keys() 、Object.assign() 、JSON.stringify() 遍历的都是enumerable:true的属性。默认值为true
      可以通过obj.propertyIsEnumerable(prop)检查属性prop是否可枚举,必须是对象自身属性,不查找原型链上的。
    1. writable (可写性):决定是否修改属性的value。默认值为true
    1. value (数据值):表示属性的数据值。默认值为undefined

    扩展: 对象的不变性
    当希望对象或者属性是不可改变时,有以下实现方法:

    1. 对象常量
 // 定义一个常量属性
 var myObj = {};
 Object.defineProperty(myObj, "FAVORITE_NUMBER", { configurable: false, writable: false, value: 1 })
  1. 禁止扩展
    Object.preventExtensions(obj)
 // 禁止一个对象添加新属性,保留现有属性
 var myObj2 = {a: 1};
 Object.preventExtensions(myObj2);
 myObj2.b = 1; 
 console.log(myObj2.b)  // undefined; 严格模式下会报TypeError
  1. 密封
    Object.seal( ... )会创建一个密封的对象,这个方法实际会对一个对象调用Object.preventExtensions()方法,并把所有属性标记configurable: false
    意味着这个对象无法添加新属性,并无法删除任何已有属性以及无法配置属性的特性。(但可以修改属性value)

  2. 冻结
    Object.freeze(...)会创建一个冻结对象,这个方法是在Object.seal(...)的基础上对所有属性标记writable: false。意味着这个对象无法对所有直接属性进行修改。

注意:以上方法创建的都是浅不变形,意思是它们只会影响目标对象和直接属性。如果目标对象引用了其他对象(数组、对象、函数等),其他对象内容不受影响,仍然是可变的
主要看属性值存储的方式(简单类型存储在栈内存,复杂类型存储在堆内存),对象的属性只是相当于指针,指向值存储的位置。所以引申一下,delete删除对象属性,并不会释放内存,只是一个操作而已。

var foo = {a: 1};
var arr = [1]
var myObj2 = {a: 1, b: 2, foo, arr};
Object.freeze(myObj2);
// 引用属性不受影响,仍然可变
arr.push(2); 
foo.b = 2;
// 直接属性无法修改
myObj2.a = 3;  
console.log(Object.isExtensible(myObj2)); // false
console.log(myObj2) //  {"a":1,"b":2,"foo":{"a":1,"b":2},"arr":[1,2]}

深度冻结一个对象,即在一个对象上调用Object.freeze(...),然后遍历它引用的所有对象并在这些对象上调用Object.freeze(...)。有一定风险,可能会无意中冻结其他(共享)对象。所以最好不要使用。

(function deepFreeze(obj){
    Object.freeze(obj);
    for(var prop in obj){
        if(obj[prop] && ((typeof obj[prop] === "object") || (typeof obj[prop] === 'function'))){
                deepFreeze(obj[prop])
        }
    }
})(myObj2)
console.log(Object.isExtensible(myObj2)); // false
arr.push(2) // 会报TypeError,Cannot add property 1, object is not extensible
  • 访问器属性

    1.Configurable:同上

    1. Enumerable: 同上
    2. getter:读取属性时调用的函数。默认值为undefined
    3. setter:写入属性时调用的函数。默认值为undefined
var obj = {
  // 给a定义一个getter
    get a() {
       return 2;
    }
};

obj.a = 3;
console.log(obj.a)     //  2

obj对属性a进行赋值会失败,首先是并没有对属性a进行set赋值操作;然而即使有了合法的setter,也只会返回2,所以getter这么定义是没有意义的。

为何需要访问器属性或者讲什么时候使用访问器属性?
(1)当属性之间有关联,或者需要对属性进行一些额外操作时

 let machine = {
   id: 1010,
   get num() {
     return `${this.id}-${this.id}` 
   },
   set num(id) {
     this.id = id
   }
}
 machine.id= 1201;
 console.log(machine.num)  // 1201-1201

(2) 实现数据的私有化

   // (1)利用块作用域
   {
   let  engine= "Ⅰ类型";
   var machine = {
       id: "1001",
       get type() {
           return engine + this.id
       },
       set type(val) {
           engine = val
       }
   }
}
var engine = "Ⅲ类型";
machine.type = "Ⅱ类型";
console.log(machine.type); // Ⅱ类型1001

// (2)利用函数
function getMachine() {
   var engine = "一类型";
   return {
      id: "1001",
       get type() {
           return engine + this.id
       },
       set type(val) {
           engine = val
       }
   }
}
let machine1 = getMachine();
console.log(machine1.type) // 一类型1001

(3)项目后期维护需要对某个对象属性进行格式统一修改,为了最大程度减少工作量,可以将该属性改为访问器属性。举例:更改日期格式,由原来的yyyy.MM.dd改为yyyy-MM-dd
let obj = { date:"2018.08.08"};

// 更改后的版本
let editedObj = {
  _date : "2018.08.08",
  get date(){
    return this._date.split(".").join("-")
  },
  set date(time){
    this._date = time
  }
}

扩展:

let myObj = { a: 1, b: undefined }; 
console.log(myObj.b);  // undefined
console.log(myObj.c);  // undefined

当属性返回值为undefined时,有可能是属性中存储的undefined,也有可能属性不存在,返回undefined。所以如何判断属性是否存在?
当属性访问时,会先在对象中查找是否有名称相同的属性,如果找到即返回这个属性的值;如果查找不到,会遍历可能存在的[[prototype]]链,也就是原型链。如果仍然没有,才会返回 undefined

两种方法:in操作符和 hasOwnProperty() 检查对象中是否包含某个属性

无论属性是否枚举,只要存在,都会返回true。区别只在于是否查找原型链

// in 操作符 会检查属性名称是否存在对象及其原型链中 
console.log("b" in myObj) // true
console.log("c" in myObj)  // false
//  hasOwnProperty(...)只会检查属性名称是否在对象自身上
myObj.hasOwnProperty("c")  // false

注意: in 操作符是检查对象的属性名称是否存在,在数组中 3 in [2, 3, 6] 中会返回false,属性3在数组中指的是索引。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容