理解JS对象

180 阅读7分钟

理解JS对象

1.理解对象

​ 创建自定义对象的通常方式是创建一个Object的一个实例,然后给它添加属性和方法

eg:

let person = new Object();
person.name = "Nocholas";
person.age = 29;
person.job = "Sofaware Engineer";
person.sayName = function () {
    console.log(this.name);
}
console.log(person);

​ 这个例子创建了一个名为person的对象,而且有三个属性值 name age job 和一个方法sayName。这个创建对象的方法再以前是很流行的。然而在现在,大多采用对象字面量的方法

eg:

let person = {
    name: "Nocholas",
    age:29,
    jog:"Software Engineer",
    sayName(){
        console.log(this.name);
    }
}
console.log(person);

​ 这种创建对象的方法更加简洁明了,在写代码的时候,我更喜欢这种写法。。

2.属性的类型

​ 对象里的属性分为两种:数据属性 和 访问器属性。首先我们看看数据属性。

2.1数据属性

​ 数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。数据属性有 4 个特性描述它们的行为。

  1. [[Configurable]]:翻译:adj. 可配置的;结构的 表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特 性都是 true,如前面的例子所示。

  2. [[Enumerable]]:翻译:adj. 可列举的;可点数的 表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对 象上的属性的这个特性都是 true,如前面的例子所示。

  3. [[Writable]]:翻译:adj. 可写的,能写成文的 表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的 这个特性都是 true,如前面的例子所示。

  4. [[Value]]:翻译:值 包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性 的默认值为 undefined

    将属性添加到对象之后 Configurable Enumerable Writable 都会被默认设置为true。而value会被设置为指定值。要修改属性的默认特性,我们必须使用

    Object.defineProperty()

    翻译:定义属性

    let person = {};
    Object.defineProperty(person,"name",{
        writable: false,   // 设置为false后无法被修改
        value: "Nicholas"
    });
    console.log(person.name);
    person.name = "Greg";
    console.log(person.name);
    

    这个例子创建了一个名为 name的属性,并把它赋值 Nicholas,并且它的writeable属性被设置为 false 。那么这个值就不能修改了,在非严格模式下重新给name赋值会被忽略,而在严格模式下 则会抛出错误。

    类似的规则,也使用与其它数据属性,下面我们来试试 configurable这个属性,这个属性设置为false后,那么相应的属性就不能删除了。

    let person = {};
    Object.defineProperty(person,"name",{
        configurable: false, // 设置false后无法被删除了
        value: "Nicholas"
    });
    console.log(person.name);
    delete person.name;
    console.log(person.name);
    

    还是老样子,非严格模式不报错,严格模式下抛出错误。

    此外:此外,一个属性被定义为不可配置之后,就 不能再变回可配置的了。

    也就是说这玩意是单程票,改了就改不回去了。这些数据属性,可能我们在开发中用的不多,单通过它们可以更好的理解JS中的对象。

2.2访问器属性

访问器属性不包括数据值。它们包含一个获取 getter函数 和一个设置 setter函数,不过这两个函数不是必须的。在设置访问属性时,会调用获取还能输返回一个有效值。在写入访问器属性时,会调用设置函数并传入新值,访问器属性有四个特性描述它们的行为

  1. [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性 都是 true。
  2. [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对 象上的属性的这个特性都是 true。
  3. [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。
  4. [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。

访问器属性不能直接定义,必须使用Object.defineProperty()

eg:

// 访问器属性
// 定义一个对象,包含私有成员year_和公共成员edition
let book = {
    year_: 2017,
    edition: 1
};
Object.defineProperty(book,"year",{
    get(){
        return this.year_;
    },
    set(newValue){
        if (newValue > 2017){
            this.year_ = newValue;
            this.edition+=newValue-2017
        }
    }
});
book.year = 2018;
console.log(book.edition); // 2

这个例子里面 book 对象 有两个默认属性 year_ 和 edition 。这是访问器属性的典型使用场景,即设置一个值会导致其它值发送一些变化。 获取函数和设置函数不一定都需要定义,只定义获取函数意味着属性时只读的,只有设置函数的属性意味着不能读取,非严格模式会返回 undefined 严格模式下会抛出错误。

3.定义多个属性

​ 在一个对象上同时定义多个属性 我们可以采用 **Object.defineProperties()**方法。它接收两个参数

eg:

let book = {};
Object.defineProperties(book,{
    year_:{
        value:2017
    },
    edition:{
        value:1
    },
    year:{
        get(){
            return this.year_;
        },
        set(newValue){
            if(newValue>2017){
                this.year_=newValue;
                this.edition+=newValue-2017
            }
        }
    }
});

定义一个属性和多个属性的唯一区别就是,所有属性是同时定义的,并且数据属性的configurable enumerable writable 特性值都是false。这也意味着。。无法删除,无法遍历,无法修改。。。黑人问号.jpg??????好吧我也不知道为啥这样搞

4.读取属性的特性

使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的描述接收两个参数 属性所在的对象和属性名,返回一个对象。

eg:

let book = {};
Object.defineProperties(book, {
    year_: {
        value: 2017
    },
    edition: {
        value: 1
    },
    year: {
        get() {
            return this.year_;
        },
        set(newValue) {
            if (newValue > 2017) {
                this.year_ = newValue;
                this.edition += newValue - 2017
            }
        }
    }
});
let descriptor = Object.getOwnPropertyDescriptor(book, "year_");
console.log(descriptor);
descriptor = Object.getOwnPropertyDescriptor(book, "year");
console.log(descriptor);

输出:

{
  value: 2017,
  writable: false,
  enumerable: false,
  configurable: false
}
{
  get: [Function: get],
  set: [Function: set],
  enumerable: false,
  configurable: false
}

ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()(其实也就是多了个s)静态方法。这个方法实际上 会在每个自有属性上调Object.getOwnPropertyDescriptor()并在一个新对象中返回它们。

用这个方法我们就可以用这个方法来直接输出了

let book = {};
Object.defineProperties(book, {
    year_: {
        value: 2017
    },
    edition: {
        value: 1
    },
    year: {
        get() {
            return this.year_;
        },
        set(newValue) {
            if (newValue > 2017) {
                this.year_ = newValue;
                this.edition += newValue - 2017
            }
        }
    }
});
console.log(Object.getOwnPropertyDescriptors(book));

输出:

{
  year_: {
    value: 2017,       
    writable: false,   
    enumerable: false, 
    configurable: false
  },
  edition: { value: 1, writable: false, enumerable: false, configurable: false },
  year: {
    get: [Function: get],
    set: [Function: set],
    enumerable: false,
    configurable: false
  }
}

5.合并对象

  1. 合并两个对象,有时候也称为“混入”,因为目标对象通过混入元对象的属性得到了增强。ECMAScript6专门为合并对象提供了Object.assign()方法这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举属性复制到目标对象。

    eg:

    // 合并对象
    let dest,src,result;
    /**
     * 简单复制
     */
    dest = {};
    src = {id:'src'};
    result = Object.assign(dest,src);
    // Object.assign修改目标对象
    // 也会返回修改后的目标对象
    console.log(dest === result);
    console.log(dest !== src);
    console.log(result);
    console.log(dest);
    true
    true
    { id: 'src' }
    { id: 'src' }
    

    多个源对象

    eg:

    /**
     * 多个源对象
     */
    let dest,src,result;
    dest = {};
    result = Object.assign(dest,{a:'foo'},{b:'bar'});
    console.log(result);
    

​ 获取函数与设置函数

​ eg:

/**
 * 获取函数与设置函数
 */
 let dest,src,result;
dest = {
    set a(val){
        console.log(`You input ${val}`);
    }
};
src = {
    get a(){
        console.log("get it");
        return 'foo';
    }
};
Object.assign(dest,src);
// 调用 src 的获取方法
// 调用 dest 的设置方法并传入参数"foo" 
// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来
console.log(dest);

​ Object.assign()实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性,则使 用最后一个复制的值。此外,从源对象访问器属性取得的值,比如获取函数,会作为一个静态值赋给目 标对象。换句话说,不能在两个对象间转移获取函数和设置函数。

覆盖属性:

/**
 * 覆盖属性
 */
let dest,src,result;
dest = {id:'dest'};
result = Object.assign(dest,{id:'src1',a:'foo'},{id:'src2',b:'bar'});
console.log(result);

对象引用

let dest,src,result;
dest = {};
src ={a:{}};
Object.assign(dest,src);
// 浅复制意味着只会复制对象的引用
console.log(dest);
console.log(dest.a === src.a);
//{ a: {} }
//true