给你打通对象内部属性

121 阅读8分钟

一、什么是对象🧐

对象是由属性方法构成,每个属性或方法都由一个名称来标识,这个名称映射到一个值,值可以是数据或者函数。对象就是一组没有特定顺序的值

创建对象的两种形式✨

1.构造函数🍦

左边属性名,右边属性值。由于属性名和变量名是一样的,简写属性名语法出现了。简写属性名只要使用变量名(不用再 写冒号)就会自动被解释为同名的属性键

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

2.字面量(与上面等价)🍜

let name = "Nicholas";
let person = { 
name, //简写!!!
age: 29,
job: "Software Engineer", 
sayName() { console.log(this.name); } };

访问对象属性或者很简单,对象名.属性或对象名.方法名() 进行调用即可,比如person.nameperson.sayName()

Object.defineProperty()

方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

Object.defineProperty(obj, prop, descriptor)
  • obj

    要定义属性的对象。

  • prop

    要定义或修改的属性的名称或 Symbol 。

  • descriptor

    要定义或修改的属性描述符。

[返回值]

被传递给函数的对象。

内部属性🍰

JavaScript 中使用一些内部特性来描述属性的特征,比如描述属性是否可以枚举,是否可以删除修改等等,我们无法访直接问属性的这些特征,但是可以通过[[]]的方式来将某个特性标识为内部属性。这些内部属性分为数据属性和访问器属性。

1.数据属性🍲
  • [[Configurable]] :表示属性是否可以通过delete 删除并 重新定义,是否可以修改它的特性,以及是否可以把它改为访问 器属性(下文会讲到)。默认情况下(指的是使用字面量的形式),所有直接定义在对象上的属性的这个特性都是true ,如前面的例子所示。
  • [[Enumerable]] :表示属性是否可以通过for-in 循环返回。默认情况下是 true
  • [[Writable]]:表示属性的值是否可以被修改。默认情况下是true
  • [[Value]] :包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为undefined 。

注意:数据属性的值一般都为ture或false,但是也可以写为1、0、null、undefined等等,但是为了代码规范,尽量写成true/false,感兴趣可的小伙伴可自行测试呐

 // 'use strict'
        let person = { name: "Nicholas" };
        Object.defineProperty(person,'age',{
            value:19,
            enumerable:1,//true 内部转为了布尔类型
            configurable:null, //false
        })
        console.log(Object.getOwnPropertyDescriptor(person,'age'));
        delete person.age; //严格模式下直接报错,非严格模式不报错,并且继续编译
        console.log(person.age);//并没有删除属性
        console.log(Object.keys(person));
        for (let i in person) {
            console.log(person[i]);
        }

image.png

例如🍕

let person = { name: "Nicholas" };

创建了一个名为person的对象,内部有个属性叫name,值为"Nicholas",意味着[Value]被设置为"Nicholas" ,读取和写入都在这个位置。该属性可通过delete person.name;将属性删除也就是说:Configurable:true,可重新赋值进行修改,意为可写Writable:true,当然也可以直接用for ... in遍历出来,意为可枚举Enumerable:true,这也恰好印证了默认情况下所有的数据属性都为true

修改属性的默认特性,就必须使用Object.defineProperty()方法。这个方法接收3个参数:要给其添加属性的对象、属性的名称和 一个描述符对象。最后一个参数,即描述符对象上的属性可以包含: configurable 、enumerable 、writable 和value ,跟相关特性的名称一一对应。根据要修改的特性,可以设置其中一个或多个值。

  • [[Writable]]🍥 此时的name属性赋予了一个只读的属性,以后不能再修改了,严格模式下,抛出错误

image.png

  • [[Configurable]]
  'use strict'//注意是严格模式下!!!
        let person = { name: "Nicholas" };
        Object.defineProperty(person, 'age', {
            value: 19,
            configurable: false,
            // writable:true,
            // enumerable:true
        });
        // 第一种情况
        // delete person.age; //控制台报错,不可删除

        //第二种情况 报错err: Cannot redefine property: age
        // Object.defineProperty(person, 'age', {
        //     value: 19,
        //     configurable:true
        // });

       //第三种情况 如果writable:true,意味着值可更改,当`configurable:false,writable:true`
        //[[value]]可以改变并且修改任何非writable属性会导致错误。
        // 但是仅`configurable: false`  [[value]]不可配置,并且其余的属性只要与第一次配置的属性不一致都会报错
        Object.defineProperty(person, 'age', {
            value: 19,
            // writable:false,
            // enumerable:true,
            // configurable:true
        });
        console.log(person.age);
  • [[Enumerable]]🍥

①当enumerable:false时

image.png

②当enumerable:true

image.png

在调用Object.defineProperty() 时,configurableenumerablewritable 的值如果不指定,则都默认为false 。 多数情况下,可能都不需要Object.defineProperty() 提供的这 些强大的设置,但要理解JavaScript对象,就要理解这些概念。

  • [[value]]设置属性的值,读取和写入都在这个位置,🍥

    1. 当属性为不可配置的时候不能更改[[value]]

    2. 不可配置可写,[[value]]可以改变并且修改任何非writable属性会导致错误,上面的例子已经演示过了哦

2.访问器属性🍟

访问器属性不包含数据值。相反,它们包含一个获取(getter)函数 和一个设置(setter)函数,不过这两个函数不是必需的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必 须决定对数据做出什么修改。访问器属性有4个特性描述它们的行为。

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

访问器属性是不能直接定义,必须使用 Object.defineProperty()获取函数和设置函数不一定都要定义。只定义获取函数意味着属性是只读的,尝试修改属性会被忽略。在严格模式下,尝试写入只定义了 获取函数的属性会抛出错误。类似地,只有一个设置函数的属性是不 能读取的,非严格模式下读取会返回undefined ,严格模式下会抛出错误。

  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

访问到对象year的属性,比如输出book.year就会调用get方法。并且返回一个值。book.year = 2018;就会走set方法,newValue就是接收到的值2018。需要注意的是,在这里很容易忽略ConfigurableEnumerable这两个属性和上面的数据属性类似,如果在defineProperty中不定义为true,默认也是为false。

思考:如果用for in时 year属性是否能够遍历出来呢

答案是不能,在defineProperty中year默认不可枚举

初学者的坑🌚🌚

初学者大多数都会犯的一个错误,就是搞不懂为什么要用_year来模仿私有成员,现在我们把_year删掉用year代替

大家觉得这段代码对么?😇

 let book = {
            // year_: 2017,
            edition: 1
        };
        Object.defineProperty(book, "year", {
            get() {
                return this.year;
            }
        });
        console.log(book.year);

控制台的错误是

image.png

出现这样的错误很简单,当调用book.year的时候调用了get方法,返回的值又是去找get方法,成了一个死循环,解决办法就是要么定义一个伪私有属性,或者直接返回一个值。

敲重点🧐:不能同时指定访问器和值或可写属性

 Object.defineProperty(book, "year", {
            get() {
                return 1;
            },
            // writable:true  //不能与上面的get共存
            // value:2  //不能与上面的get共存
        });

普通属性和有访问器属性的一些区别💕

image.png

3.定义多个属性与读取属性的特征

使用Object.getOwnPropertyDescriptor() 方法可以取得指定属性 的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述 符的属性名。返回值是一个对象,对于访问器属性包含configurable 、 enumerable 、get 和set 属性,对于数据属性包含configurable 、 enumerable 、writable 和value 属性。

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

唯一的区别是所有属性都是同时定义,用Object.getOwnPropertyDescriptor() 方法可以取得指定属性 的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述 符的属性名。返回值是一个对象,访问器属性与数据属性返回值不一样,请自行观察Object.getOwnPropertyDescriptors() 静 态方法。这个方法实际上会在每个自有属性上调用 Object.getOwnPropertyDescriptor() 并在一个新对象中返回它们

console.log(Object.getOwnPropertyDescriptors(book)); 
// { edition: { 
// configurable: false, 
// enumerable: false,
// value: 1,
// writable: false 
// },
// year: {
// configurable: false, 
// enumerable: false,
// get: f(), 
// set: f(newValue), 
// }, 
// year_: { 
// configurable: false, 
// enumerable: false,
// value: 2017, 
// writable: false 
// }  }