防止浏览器控制台修改网页数据与函数的方法(续)

        前文《防止浏览器控制台修改网页数据与函数的方法》给出了几个解决方法,需要对对象的每个属性或函数使用Object.defineProperty()配置其元特性configurable、enumerable、writable等。显然,这些操作缺乏灵活、不够实用、不能通用。

       1、javascript对象的元特性

        javascript对象的成员(数据或函数)均有一组元特性,约束成员的是否可配置(configurable)、可枚举(enumerable)、可改写(writable),以及提供value值和getter/setter读写器。一旦configurable为false,那么不能重定义其他的特性(writable除外,仍然可以由true改为false)。如果configurable为true,即使wrtiable为false,仍然可以通过Object.defineProperty()或Reflect.defineProperty()函数修改其value(此时不能通过类似obj.val = 1的方式修改其值)。

        定义或修改元特性的语法如下:

  • Object.defineProperty(obj, propName, {元特性}, value)
  • Reflect.defineProperty(obj, propName, {元特性}, value)

        Object与Reflect两个同名方法的区别在于:前者定义成功返回对象、失败抛出异常;后者成功返回true、失败返回false。另一个区别是,Reflect支持的最低版本是ES6(与Proxy一样)。

        2、保护window和对象(以String.prototype为例)上新增的属性和函数       

        ES6中的Proxy类和Reflect类提供了通用、一致的便捷方法,只需要给被保护的对象(如window对象、String.prototype原型、String构造函数等)关联一个代理对象,在代理对象上新增属性或函数,就可以自动配置其元特性{configurable: false, enumerable: true, writable: false},从而保护数据(属性值)或函数被删除或修改。

        下面代码是一个关联window对象和String.prototype原型的两个代理类,分别增加其函数sayOk(),然后修改函数定义,结果是:修改后的函数无效。

<!DOCTYPE html>
<script>

	/* 定义代理处理器handler,同时也是window的一个不可修改的函数 */
	/* Reflect与Object的defineProperty调用区别,后者可能抛出TypeError */
	Object.defineProperty(window, "handler", {
			configurable: false, enumerable: true, writable: false, value: {
				set(target, prop, propValue, receiver) {
					Object.defineProperty(target, prop, {configurable: false, enumerable: true, writable: false, value: propValue});
				}
			}
	}); 

	const WindowProxy = new Proxy(window, handler); /* window 的代理 */
	const StringProtoProxy = new Proxy(String.prototype, handler); /* String原型的代理 */

	WindowProxy.sayOk = function(){console.log("win ok.");};  /* 使用代理新增函数 */
	StringProtoProxy.sayOk = function(){console.log("str ok.")};  /* 使用String原型代理新增函数 */
	
	sayOk();       /* 输出win ok.,新增window的函数成功 */
	"ok".sayOk();  /* 输出str ok.,新增String原型的函数成功 */
	
	window.sayOk = function(){console.log("win no.");};  /* 在window上重定义函数 */
	String.prototype.sayOk = function(){console.log("win no.");}; /* 在String原型上重定义函数 */
	
	sayOk();       /* 仍然输出win ok.,在window上重定义函数无效 */
	"ok".sayOk();  /* 任然输出str ok.,在String原型上重定义函数无效 */
	
	try{
		WindowProxy.sayOk = function(){console.log("win no.");};  /* 在window代理上重定义函数报异常 */
		StringProtoProxy.sayOk = function(){console.log("str no.");}; /* 在String原型代理上重定义函数报异常 */
	} catch(err){
		console.log("重定义函数异常: " + err);  /* 输出异常消息,不能重定义 */
	}
	
	sayNo = function(){console.log("win no.");};  /* 在window对象上新增函数 */
	String.prototype.sayNo = function(){console.log("str no.");};  /* 在String原型上新增函数 */
	
	sayNo();       /* 输出 win no.,在window上新增函数成功 */	
	"ok".sayNo();  /* 输出 str no.,在String原型上新增函数成功 */

	sayNo = function(){console.log("win no no.");}  /* 在window对象上重定义函数 */
	String.prototype.sayNo = function(){console.log("str no no.");};  /* 在String原型上重定义函数 */
	
	sayNo();       /* 输出 win no no.,在window上重定义函数成功 */	
	"ok".sayNo();  /* 输出 str no no.,在String原型上重定义函数成功 */	

</script>
</html>

        上文的技术要点是:创建一个通用的处理器handler,并关联到一个代理Proxy对象。然后,在代理对象上添加函数,这些新增的函数将自动配置成{configurable: false, enumerable: true, writable: false}元特性,使得它们不能被删除或修改。同样,新增数据属性结果相同。

        3、保护网页<input>元素的value值

        前文《防止浏览器控制台修改网页数据与函数的方法》提出方法时:获取该值后另存为window对象的一个不可修改的数据属性,可以有效保护该数据被删除或修改,但使用起来不够直接明了。

        下面代码可以直接保护<input>元素的value属性,使得该值正常读取、不能被删除或修改(代码中还给出了jQuery读写的示例)。

<!DOCTYPE html>
<head>
<script type="text/javascript" src="./jquery/jquery-1.12.4.min.js"></script>
</head>
<body>
	输入文本1:<input id="tb1" type="text" value = "1" /> <br/>
	输入文本2:<input id="tb2" type="text" value = "2" />
</body>
<script>

	/* 1) value是原型的,该语句等价新建input对象的自有属性value   */
	/* 2) 建议锁住defaultValue,在程序中使用defaultValue代替value */
	lockInputValue = function(id){
		let e = document.getElementById(id);
		if(!e || e instanceof HTMLInputElement === false) throw new Error("the param is not valid.");
		Object.defineProperty(e, "value", {configurable: false, enumerable: true, writable: false, value: e.value});	
	} 
	
	let tb1 = document.getElementById("tb1");		
	console.log(tb1.value);        // 输出1;
	lockInputValue("tb1");	       // 锁住tb的value属性
	tb1.value = "111";             // 在UI界面上输入111,结果一样
	console.log(tb1.value);        // 仍然输出1,修改值无效
	$('#tb1').val("111");          // jQuery修改value值
	console.log($("#tb1").val());  // 仍然输出1,修改值无效
</script>
</html>

        因为是保护已经存在的若干对象,不需要使用代理Proxy,直接使用Object.defineProperty()即可。

        需要特别指出,不同于配置一般对象的元特性,Object.defineProperty()设置value的元特性时,还需要给出value值。原因是,value不是HTMLInputElement元素的自有属性,而是其原型上继承来的属性,而Object.defineProperty()只能配置对象自有属性的元特性。事实上,上述代码在配置value元特性时,等价新建了一个<input>对象的自有value属性(该属性隐藏了原型的value属性,相当于派生类隐藏了基类属性),在配置其元特性时需要给出并保留原value的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值