最详细的JavaScript高级教程(十五)对象的属性

概念

ES对于对象的定义是:无序属性的集合,其属性可以包括基本值,对象或者函数。从中我们可以发现,js中的对象有下面的特征

  • 无序的集合,这个就让js中的对象更像是散列表,是一系列无序的键值对的集合
  • 属性包括基本值,对象或者函数。这个把属性可以包括哪些数据类型说的很清楚了

属性类型

ES中有下面两种属性

  1. 数据属性
  2. 访问器属性

顾名思义,数据属性可以保存一个数据值,而访问器属性本身不能保存数据值,但是可以通过getter setter进行访问控制,它的属性存储还需要额外的数据属性。

数据属性中值是如何存储的呢?我们就需要知道数据属性中的四个特性,事先说明,这四个属性的修改在日常开发中很难用到,但是对于我们理解数据属性为什么能够存值,对于理解对象大有益处。

数据属性拥有四个描述其特性的特性值,分别是:

  • Configurable 是否可以配置,是否可以被删除等(注意一旦这个值设置了false,就再也不能修改配置了,连这个值也不能再进行设置了)
  • Enumerable 是否可以使用for in枚举
  • Writable 是否可写
  • Value 存储其值,默认值是undefined,对于属性的读写操作其实是对于这个值的读写

我们需要注意一点,在严格模式下,对于设置了 不可配置或者不可写 特性的属性,对他们进行写操作会报错,在非严格模式下,不生效不报错。

我们使用Object.defineProperty来修改特征值,看下面的例子:

var person = {};
// 注意在设置的时候如果不指定,前三个特性默认值都是false,与默认不一致
Object.defineProperty(person, "name", {
    writable: false,
    value: "Nic"
});
alert(person.name);
person.name = "Pop"; // 严格模式下报错 Cannot assign to read only property 'name' of object '#<Object>'
alert(person.name);

与之相对应的,访问器属性也有四个特性,我们分析一下就知道,访问器属性一定有Get Set 特性,自然不需要writable特性,所以访问器属性有下面四个特性

  • Configurable
  • Enumerable
  • Get
  • Set

定义访问器属性需要使用下面的方法

var book = {
    _year: 2004, //加下划线的变量一般用于表示私有变量,只能通过对象的方法访问该属性(这只是一种约定,本质上这个变量能够被外部访问)
    edition: 1
};
Object.defineProperty(book, "year", {
    get: function(){
        return this._year;
    },
    set: function(newValue){
        if(newValue > 2004){
            this._year = newValue;
            this.edition += newValue - 2004;
        }
    }
});
book.year = 2005;
alert(book.edition); //2

注意:

  • getter 和 setter不是都必须需要指定,当指定其中一个的时候,另一个默认不能操作,即不可读或者不可写
  • 严格模式下尝试对于不可写的属性进行写操作会直接报错

属性的创建

可以可以使用book.name的方法一个一个创建属性,也可以使用Object.defineProperties()方法同时设置很多属性,下面举个例子

var book = {};
Object.defineProperties(book, {
    _year:{
        value: 2004
    },
    edition:{
        value: 1
    },
    year: {
        get: function(){
            return this._year;
        },
        set: function(newValue){
            if(newValue > 2004){
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

这样创建的属性是同时的。

读取属性特征

读取属性特征值通过使用Object.getOwnPropertyDescriptor来获取属性特征值

var book = {};
Object.defineProperties(book, {
    _year:{
        value: 2004
    },
    edition:{
        value: 1
    },
    year: {
        get: function(){
            return this._year;
        },
        set: function(newValue){
            if(newValue > 2004){
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

var des = Object.getOwnPropertyDescriptor(book, 'year');
alert(des.configurable); //false
alert(des.value); // undefined
alert(des.get); //function
Object.defineProperty(book, 'year', {
    writable: false,
    configurable: true
}); // 报错

属性定义的特征值默认值

我们需要注意的是:普通定义通过a.b=1的方式定义的属性,前三个属性都是true,使用 defineProperty 或者 defineProperties定义的属性,前三个的特性的默认值都是false。

属性的遍历

在学习属性的遍历的知识的时候,需要先学习下一课的原型对象的知识。

  • 访问所有可读的可枚举的属性—使用for in
    var o = {
    toString: function() {
      return '123';
    }
    };
    // for in 会枚举所有可以访问的并且可以枚举的属性
    // 原来的toString方法虽然可以访问,但是被设置为不能枚举
    // 复写了之后的toString方法可以枚举,所以可以被遍历到
    for (var prop in o) {
        if (prop == 'toString') {
          alert('Found toString');
        }
    }
    
  • 访问所有可枚举的实例属性(不带原型属性)—使用Object.keys
    function Person() {}
    Person.prototype.name = 'w';
    Person.prototype.age = 20;
    var keys = Object.keys(Person.prototype);
    alert(keys); //name,age
    var p1 = new Person();
    p1.name = 's';
    alert(Object.keys(p1)); //name
    
  • 访问所有实例属性(无论是否可以枚举)—getOwnPropertyNames
    function Person() {}
    Person.prototype.name = 'w';
    Person.prototype.age = 20;
    var props = Object.getOwnPropertyNames(Person.prototype);
    alert(props); //constructor,name,age 其中constructor属性不可枚举
    

属性不可以改变的对象

我们知道使用const创建不能改变的值类型,当我们想创建一个属性不可变的对象的时候需要使用下面的方法:

创建不能修改的对象应该用Object.freeze

var o = Object.freeze({ name: '123' });
alert(o.name);
o.name = '345'; //严格模式报错
alert(o.name);

这个方法只能冻结对象的属性,如果对象的属性还是对象,如果都需要冻结,需要用到递归

var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
    });
};

ES6 - 对象的解构赋值

我们之前讲的数组的解构赋值是将数组中的值拿出来赋值给一系列的变量,对象的解构赋值就是将对象中的属性拿出来为一系列的变量赋值。

与数组解构赋值同理,对象解构赋值的语法:左边的变量要用大括号括起来,右边传递一个对象。

数组解构赋值的关键是 结构相同就可以赋值,即需要位置相同,对象解构赋值的关键是名字相同,即变量名要与属性名相同才能正确赋值。

先看一个对象解构赋值的例子:

// 与顺序无关,以名称为基准赋值
let { a, b } = { b: '1' };
alert(a); //undefined
alert(b); //1

我们也是注意下面一些原则:

  • 对象解构赋值也可以用于方法,即可以把一个方法赋值给一个变量
    // 简化名称
    let log = console;
    log('132');
    
  • 虽然是靠属性名匹配的,但是不是说就不能把变量名随便起了,我们可以使用下面的方式把属性赋值给其他名称的变量
    // 冒号前面是模式,标识属性名
    let { b: test } = { b: '1' };
    alert(test);
    
  • 对象的解构赋值可以嵌套,看下面的例子
    // 如果有下面这个对象,我们需要把y的值取出来赋值给变量test
    let obj = {
      p: [
        'Hello',
        { y: 'World' }
      ]
    };
    // 我们需要使用这种方式
      let {
        p: [, { y: test }] //需要注意我们之前学的,数组的解构是根据位置的,所以这里一定要加逗号让索引到第二个
      } = obj;
      alert(test); //World
    
  • 解构赋值的时候,如果属性在原型对象上,也是可以正常赋值的
  • 对象的解构赋值也可以添加默认值,添加方法和数组的解构赋值一样,使用等号
    let { a: test = 1 } = { b: '1' };
    alert(test); //1
    
  • 已经声明过的变量进行解构赋值会报错,必须加括号
    let test;
    { test } = { test: '1' }; // 报错,解释器会以为{ test }是一个代码块
    
    let test;
    ({ test } = { test: '1' }); //不报错,正确写法
    
  • 数组是特殊的对象,他的属性名是0 1 2,所以可以对数组使用对象的解构赋值
    var arr = [1, 2];
    let { 0: a, 1: b } = arr;
    alert(a); // 1
    alert(b); // 2
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章