ES5严格模式下的保护对象

为什么要保护对象

在旧的js中的对象毫无自保能力,对象的结构和属性值可以被任意修改。

比如下面的例子

var eric = {
    eid: 1,
    ename: "xiaoming",
    money: "10",
    car: "自行车",
    ID: "112345677896541236", //隐私信息不能被随意遍历出来
};
console.log(eric);
delete eric.car; //属性car被删除了
eric.money = "10000000"; //属性money的值被修改了
for (const key in object) {
    console.log(object[key]); //结果所有属性值全部暴露出去
}

输出结果
ES5严格模式下的保护对象

这样子对象就显得十分的脆弱,定义的对象有点任人宰割的情况,那就需要把对象给保 护起来。其实ES5中,已经提供了保护对象属性和结构的新方法,接下来让我们一起学习一下是如何保护对象。

什么是保护对象

保护对象是指阻止对对象的属性值进行不符合规定的篡改,那ES5是如何保护的呢?
其实,ES5中已经把每个对象的属性,变成了一个缩微的小对象,
如下图:
ES5严格模式下的保护对象
是不是像上图画的那样呢?我们来验证一下,我们通过下面代码获得自己的属性的描述信息

var 属性的描述对象=Object.getOwnPropertyDescriptor(对象, "属性名");
<script>
      "use strict"; //记得要开启严格模式
      var eric = {
        eid: 1,
        ename: "xiaoming",
        money: "10",
        car: "自行车",
        ID: "112345677896541236", //隐私信息不能被随意遍历出来
      };
      var obj = Object.getOwnPropertyDescriptor(eric, "eid");
      //var 属性的描述对象=Object.getOwnPropertyDescriptor(对象, "属性名");
      console.log(obj);
    </script>

打印出来结果
ES5严格模式下的保护对象

可以看出对象的属性中真的还有一个缩微的小对象,小对象还有4个属性分别为value(值)、writable(可写)、enumerable(可枚举)、configurable(可配置)。介绍一下writable、enumerable、configurable属性

  1. writable:可写,默认值为true,在默认情况下可以修改属性值。如果writable设为false,则该属性值将无法修改,变成只读模式;
  2. enumerable:可枚举,默认值为true,在默认情况下可以被for in 遍历,如果enumerable设为false,则该属性值将无法被for in遍历,但可以被console.log()打印出来,属于半隐藏;
  3. configurable:可配置,默认值为true,在默认情况下该属性可以被删除掉的,如果configurable设为false,那么该属性不可删除,且writable,enumerable,configurable将无法再次修改;

如何保护对象

writable、enumerable、configurable就像是一个个开关一样,如果想对象中的一个属性的值不想被修改,将writable设为false就可以了,其余也是一样的道理。那应该怎么设置呢?我们可以这样设置

Object.defineProperty(对象, "属性名", {
			writable: true或false,
			    ... : ...
			    ... : ...
		})

还是来看跟上面一样的例子

<script>
      "use strict"; //记得要开启严格模式
      var eric = {
        eid: 1, //只读模式
        ename: "xiaoming", //不被遍历
        money: "10", //不能被删除
        car: "自行车", //不能被删除
        ID: "112345677896541236",
      };
      //保护对象,使用Object.defineProperty(对象, "属性名", {}) 这种方法 每次只能保护一个属性
      Object.defineProperty(eric, "eid", {
        writable: false,     //让eric的eid属性的值不能随便被修改
        configurable: false, //不允许再修改writable
      });
      Object.defineProperty(eric, "ename", {
        enumerable: false,   //让eric的ename属性不能随便被for in遍历
        configurable: false, //不允许再修改enumerable
      });
      Object.defineProperty(eric, "money", {
        enumerable: false,    //让eric的ename属性不能随便被for in遍历
        configurable: false, //不允许再修改enumerable
      });
      Object.defineProperty(eric, "car", {
        configurable: false, //不允许删除car属性
      });
      //修改eid的值  结果失败
      eric.eid = 3;
      console.log(eric.eid);
      // 报错  Cannot assign to read only property 'eid' of object 不能给只读属性赋值
      //用for in 遍历出ename
      for (var key in eric) {
        console.log(`${key}:${eric[key]}`);
        //只遍历出了eid car ID
      }
      //删除属性 money
      delete eric.money; //删除失败
      console.log(eric.money);
      //报错 Cannot delete property 'money' of  不能删除属性money
</script>

上面代码运行后会有两处报错,第一个是“不能给只读属性赋值”,第二个是“不能删除属性money”,所以就成功的将对象保护起来不被随意的篡改。
但是,使用上面的代码保护有很明显的缺点,就是每个属性都要设置一次这样代码看起来就很臃肿,也不便于管理,还有没有其他简单点的办法呢?ES5中还有下面的方法可以保护对象

Object.defineProperties(对象, {
			属性名:{
				writable: true或false,
				  ... : ...
			},
			属性名:{
				... : ...
			},
			... : { ... }
		})

还是上面的例子

<script>
      "use strict"; //记得要开启严格模式
      var eric = {
        eid: 1, //只读模式
        ename: "xiaoming", //不被遍历
        money: "10", //不能被删除
        car: "自行车", //不能被删除
        ID: "112345677896541236",
      };
      //保护对象,使用Object.defineProperties(对象, {})
      Object.defineProperties(eric, {
        eid: {
          writable: false, //让eric的eid属性的值不能随便被修改
          configurable: false, //不允许再修改writable
        },
        ename: {
          enumerable: false, //让eric的ename属性不能随便被for in遍历
          configurable: false, //不允许再修改enumerable
        },
        money: {
          enumerable: false, //让eric的ename属性不能随便被for in遍历
          configurable: false, //不允许再修改enumerable
        },
        car: {
          configurable: false, //不允许删除car属性
        },
      });
      //修改eid的值  结果失败
      eric.eid = 3;
      console.log(eric.eid);
      // 报错  Cannot assign to read only property 'eid' of object 不能给只读属性赋值
      //用for in 遍历出ename
      for (var key in eric) {
        console.log(`${key}:${eric[key]}`);
        //只遍历出了eid car ID
      }
      //删除属性 money
      delete eric.money; //删除失败
      console.log(eric.money);
      //报错 Cannot delete property 'money' of  不能删除属性money
    </script>

运行的结果还是跟上面的一样的!也可以实现保护对象的功能,但是,上面的保护功能很弱,不灵活,无法使用自定义的规则保护属性值。如何解决这个问题呢?

给程序的属性请保镖——访问器

  1. 什么是访问器属性: 不实际存储属性值,仅提供对另一个保存数据的属性的保护。
  2. 为什么使用访问器属性: 上面的保护不够灵活,无法用自定义规则灵活保护属性值
  3. 什么时候用到访问器属性:只要用自定义规则,灵活保护属性值时,都用访问器属性。

这个访问器就好比给属性请一个保镖,做什么事情都让保镖先试试水,安全了才执行。看看下面的需求

需求:定义一个对象,对象有个eage属性,要求年龄可以修改,但年龄必须在18-45之间

用访问器该怎么做呢?
现在对象中定义一个_eage属性,利用Object.defineProperties里的enumerable属性设置为false将_age隐姓埋名 — 不想让外人随意使用。再从Object.defineProperties里面“请保镖”eage,用eage来代替_eage属性,在eage里面写 get:function(){ reutrn this.受保护的变量}set:function(value){ … } 其中 getset 不能改变,必须这么写。
来看以下代码

<script>
     //要求年龄可修改,但是年龄必须介于18~65之间
    var eric={
      eid:1001,
      ename:"埃里克",
      _eage:25 //隐姓埋名 —— 不想让外人随意使用
    }
    Object.defineProperties(eric,{
      _eage:{ //半隐藏
        enumerable:false,
        configurable:false
      },
      //请保镖: 
      eage:{//冒名顶替
        //保镖一请就是一对儿
        //专门负责从受保护的属性中,获取属性值
        get:function(){
          console.log("自动调用eage的get()")
          //返回受保护的属性_eage的值
          return this._eage;
        },
        //专门负责将要修改的新值,结果验证后,才保存到受保护的属性中
        set:function(value){
          console.log(`自动调用eage的set(${value})`)
          if(value>=18&&value<=65){
            this._eage=value;
          }else{
            throw Error(`年龄必须介于18~65之间!`)
          }
        },
        enumerable:true, //让eage替_eage抛头露面
        configurable:false //不能轻易删除保镖
        //因为保镖不实际保存属性值,所以没有value属性
        //因为writable开关无法灵活保护属性值,所以保镖也没有writable开关
      }
    
    })
      //观察现在eric的对象结构是怎么样的
   	  console.log(eric)
  </script>

输出的结果
ES5严格模式下的保护对象
如果外界想要修_eage属性值时,都会先去修改eage这个保镖,如果修改的值符合set规定的规则后,才保存到受保护的属性中,就像下图所示
ES5严格模式下的保护对象
在上面的代码的下边加上以下代码

//外界
    //试图读取eric的年龄时
    console.log(eric.eage)
    //试图修改eric的年龄为26
    eric.eage=26;
    console.log(eric.eage)
    //试图修改eric的年龄为-2
    eric.eage=-2;

输出结果:
ES5严格模式下的保护对象
这样子就做出这个需求了,那么还有一个问题,上面代码中的set:function({})中的this指向的是谁呢?通过上面打印出了eric其实答案就已经出来了。这里的this是指eric对象。

保护对象的结构

有三种级别

  1. 防扩展: 阻止为对象添加新属性
    在旧js中: 可以随时给对象添加新属性
    如何禁止为对象添加新属性:使用Object.preventExtensions(对象)
    示例: 阻止为对象添加新属性:
<script>
    "use strict";

    var eric={
      eid:1001,
      ename:"埃里克"
    }
    //希望eid只读
    Object.defineProperty(eric,"eid",{
      writable:false,
      configurable:false
    })
    //防止对eric添加新属性: 
    Object.preventExtensions(eric);
    //试图为对象添加新的不同名的eid属性,另起炉灶
    eric.Eid=1003; //报错: 
    //Cannot add property Eid, object is not extensible
    //  不能 添加  属性    Eid(因为)对象是  不   扩展 可以
    //                                    不可扩展
    console.log(eric);
  </script>
  1. 密封:
    什么是密封:既 阻止给对象添加新属性,又阻止删除对象的现有属性
    为什么要用密封:因为几乎对象中的所有属性,都应该是禁止删除的,但是每个属性都要写configurable:false,太麻烦了!所以几乎定义对象都应该使用密封
    如何使用密封:Object.seal(对象)
    原理: seal()做了两件事:
    1). 自动调用Object.preventExtensions()阻止对当前对象的扩展
    2). 自动为每个属性都添加configurable:false,从此我们不需要再手动为每个属性添加configurable:false

示例: 密封一个对象

<script>
    "use strict";

    var eric={
      eid:1001,
      ename:"埃里克"
    }
    //希望eid只读,且不能删除
    //希望ename也不能删除
    Object.defineProperties(eric,{
      eid:{
        writable:false,
        //configurable:false
      },
    })
    //密封对象: 
    Object.seal(eric)

    //试图添加新属性: 
    //eric.Eid=1003;//报错: 
    //Cannot add property Eid, object is not extensible
    //试图删除eid属性
    //delete eric.eid; //报错: Cannot delete property 'eid'
    //试图删除ename属性 
    //delete eric.ename; //报错: Cannot delete property 'ename'
    console.log(eric);
  </script>
  1. 冻结:
    什么是冻结: 既不能添加删除现有属性,又不能修改属性值,是一种十分严格的保护模式。
    什么时候用: 如果多个模块共用的对象,就不应该让某一个模块擅自修改对象的属性值,一旦修改,牵一发而动全身。
    怎么用:Object.freeze(对象)
    原理: 做了三件事:
    1). 也自动调用preventExtensions()阻止添加新属性
    2). 也自动为每个属性添加configurable:false
    3). 自动设置每个属性的writable:false
    示例: 冻结对象
<script>
    "use strict";

    var obj={
      host:"192.168.0.100",
      port:3306,
      db:"xz"
    }
    //希望obj对象中所有属性,禁止修改,禁止删除
    //且禁止给obj添加新属性
    Object.freeze(obj);

    //尝试给obj添加新属性: 
    //obj._host="127.0.0.1"; //报错: Cannot add property _hosts
    //尝试删除obj中现有属性
    //delete obj.host; //报错: Cannot delete property 'host'
    //尝试修改obj中的host属性值: 
    //obj.host="localhost"; //报错: Cannot assign to read only property 'host'
  </script>
东哥笔记

上一篇:JS高阶-函数构造:ES5继承,ES6类相关语法,this指向call/apply/bind,构造函数


下一篇:ELK的安装配置及使用