Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
可以修改对象以及添加一个对象,这个最应该是逆向JavaScript中最常用的一个方法了,例如对于某些网站加密算法,查询不到,但是其最后的数据在cookie
中,就会出现下面写法:
(function(){
'use strict'
Object.defineProperty(document, 'cookie', {
get: function() {
debugger;
return "";
},
set: function(value) {
debugger;
return value;
},
});
})()
同立即执行函数(不懂可以看我前面文章:传送门),将自己的debug断点打进去。当然不聊其在逆向的时候具体用法,主要讲解Object.defineProperty()
的规则可以多更多对对象的操作。
虽然很多时候用于作为数据劫持,常用的场景爬虫逆向,但是Object.defineProperty()
自然不是为这个而存在,还是老规矩,扯逻辑上案例。
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
先看一下格式,然后再解释这一句话。
Object.defineProperty(obj, prop, descriptor)
obj 要定义属性的对象。
prop 要定义或修改的属性的名称或 Symbol 。
descriptor 要定义或修改的属性描述符。
返回值 :被传递给函数的对象。
其实这个最主要了解的是descriptor这个描述可以设置很多,毕竟上面hook的时候写get和set方法,具体是什么意思?以及其有没有其它的属性操作?
对象里目前存在的属性描述符(descriptor)有两种主要形式:数据描述符和存取描述符。
configurable 默认为 false。
当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
enumerable 默认为 false。
当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
value 默认为 undefined。
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
writable 默认为 false。
当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
下面演示一下:
// 创建一个对象
var obj={};
// 添加一个a的属性
var newobj=Object.defineProperty(obj,'a',{value:1});
// 判断obj 和newobj是否是一个对象
obj==newobj
然后如下看:
所以一般不会将返回值赋值给一个变量名,直接用自己的即可。
补充一点:defineProperty()
方法是内置对象是构造函数上的方法,所以其生成的对象是无法调用的。比如用obj.defineProperty 就会报错。
注意:
默认情况下,使用 Object.defineProperty()
添加的属性值是不可修改(immutable)的。
也不可删除,也不可枚举。
obj.a='2';
delete obj.a;
for(var key in obj){
console.log(key,obj[keys]);
}
无法枚举,但是可以取出这个属性名吗?其实再symbol中聊过几种的方式,现在试一下:
Object.keys(obj);// 得到一般的属性和__proto__上的属性
Object.getOwnPropertyNames(obj);// 得到属于自己的属性
当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
这一句话如何理解?还是记住官网所说,然后老规矩代码演示再理解。
试一下将其修改的true试一下:
先说一下 添加属性可以多次添加 无论configurable 值是否为false或者true
var obj={};
Object.defineProperty(obj,'a',{
value:1
})
Object.defineProperty(obj,'b',{
value:2
})
而是另一个事情,那就是虽然默认属性值不可修改,但是可以通过修改configurable 的值,然后通过Object.defineProperty进行修改
var obj={};
Object.defineProperty(obj,'a',{
value:1,
//configurable : false 默认就是false 不写等于就是写了
})
Object.defineProperty(obj,'a',{
value:2
})
var obj={};
Object.defineProperty(obj,'a',{
value:1,
configurable : true
})
Object.defineProperty(obj,'a',{
value:2
})
如果通过赋值修改可就要报错了,毕竟这个不是属性值可通过赋值运算符修改,而是属性本身可以修改,不可以如下尝试:
当然一个可以如果改成configurable是true还有可以删除属性。
var obj={};
Object.defineProperty(obj,'a',{
value:1,
//configurable : false 默认就是false 不写等于就是写了
})
delete obj.a;
然后改成true:
var obj={};
Object.defineProperty(obj,'a',{
value:1,
configurable : true
})
delete obj.a;
总结: configurable如果修改为true,那这个属性可以被删除,和通过Object.defineProperty重写赋值。
当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
演示:
var obj={};
Object.defineProperty(obj,'a',{
value:1,
// configurable : true, 当然也可以一起用
enumerable:true
})
for(var key in obj){
console.log(key,obj[key])
}
当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
var obj={};
Object.defineProperty(obj,'a',{
value:1,
// configurable : true, 当然也可以一起用
//enumerable:true, 当然也可以一起用
writable:true
})
obj.a='2';
get 默认为 undefined。
属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
set 默认为 undefined。
属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
当然按照存取描述符是否和数据描述一样是否默认不可修改不可枚举?
如下演示:
var obj={};
var oldvar =12;
Object.defineProperty(obj,'a',{
// 这个地方是得到值,优点像是java中的geter方法
get: function(){
return oldvar;
},
// 这个地方是得到值,优点像是java中的seter方法
set: function(newvar){
oldvar =newvar;
}
})
obj.a=23
for(var key in obj){
console.log(key,obj[keys]);
}
取出描述符可以和数据描述符搭配使用,但是也不是所有都可以:value 或 writable 一个或两个) 不能和 (get 或set一个或两个)同时出现在描述中。
来举一个例子尝试:
var obj={};
Object.defineProperty(obj,'a',{
value:1,
get:function(){
return '2'
}
})
取出属性值的时候调用get方法,赋值的时候调用set方法。
为什么这样说呢?下面演示:
var obj={};
obj.a='1';
Object.defineProperty(obj,'a',{
get:function(){
return '2'
},
set :function(newvar){
console.log('赋值语句',newvar);
}
})
所以因为这个属性,很多时候会将其最作为数据劫持用。而再逆向hook的时候只是为了找到是否赋值,所以在外面声明一个变量名。
(function(){
'use strict'
var value_var=''; // 防止get劫持了返回值,所以通过这个变量而让这个cookie正常赋值以及可以取值而在外面申请一个过渡变量
Object.defineProperty(document, 'cookie', {
get: function() {
debugger;
return value_var;
},
set: function(value) {
debugger;
value_var =value;
},
});
})()
描述符默认值汇总:
Object.defineProperty
添加的属性也可以通过 Object.defineProperty
进行修改,比如演示hook中的cookie等如果简单聊一下具体的使用其实也没有多少意义,或者在爬虫逆向的时候用hook。
其实前面聊了这样多,还有一句话大家要注意到:该方法允许精确地添加或修改对象的属性。
比如:
var obj={
a:1,
b:2,
c:3
}
这样定义很随手就简单的定义,但是现在有一个要求:那就是a是一个特殊属性,不可删除,b属性要不删除,要不不可改,c数据作为一个隐藏属性不可枚举。
这样的要求就不是简单通过赋值就行了,需要的就是通过Object.defineProperty
对已添加的属性的属性再次赋予其独特的要求。
要求: 来一个student数据,现在我们对这个数据进行一个要求,那就是其属性id只可枚举,name可以枚举和修改,不可以删除,back属性可修改不可以枚举和删除。
前面聊过模块了,所以直接用模块进行演示。
第一个js文件:student.js
export {student}
var student=[
{
id:'001',
name:'张三',
back:'市长儿子'
},
{
id:'002',
name:'李四',
back:'局长儿子'
}
]
第二个js文件:properties.js
export {properties}
var properties={
id:{
configurable: false,
enumerable :true,
writable : false
},
name:{
configurable: false,
enumerable :true,
writable : true
},
back:{
configurable:false,
enumerable : false,
writable :true,
}
}
第三个js文件:main.js
import {student} from "./student.js";
import {properties} from "./properties.js";
import {infoObject} from "./infoObject.js"
;(()=>{
const studentInfo=infoObject(student,properties);
var t=studentInfo[0];
// 测试结果
for(var key in t){
console.log(key,t[key]);
}
})();
第四个文件调用Object.defineProperty的js文件:infoObject.js
export function infoObject(data,properobj) {
if(Object.prototype.toString.call(properobj)!=='[object Object]' || properobj===null){
throw new TypeError('属性描述文件不是对象或者是空,检测一下');
}
// 判断存储数据是否为数组
if (!Array.isArray(data)){
return defineObject(data,properobj);
}
// 如果传递是一个对象就直接调用
return data.map(item=>{
return defineObject(item,properobj);
});
}
function defineObject(data,properobj) {
let newobj={};
for(let key in data){
Object.defineProperty(newobj,key,{
// ... 作为扩展符号 如果不了解可以看前面文章 箭头函数 根据rest参数进行了扩展
...properobj[key],
value:data[key]
})
}
return newobj;
}
调用的html:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试title>
<script type="module">
import * as t from "./main.js";
script>
head>
<body>
body>
html>
可见现在生效了back属性无法被遍历出来。
这个时候又有一个想法,是否来一个单独的方法来修改某个属性的属性呢?毕竟如果偶尔需要修改一些而最后不要碰配置的js文件。当然可以,这个前面聊过可以将这个可以修改属性的属性的方法,卸载对象的构造函数的prototype。所以可以如修改infoObject.js文件为如下:
export function infoObject(data,properobj) {
if(Object.prototype.toString.call(properobj)!=='[object Object]' || properobj===null){
throw new TypeError('属性描述文件不是对象或者是空,检测一下');
}
// 判断存储数据是否为数组
if (!Array.isArray(data)){
return defineObject(data,properobj);
}
// 如果传递是一个对象就直接调用
return data.map(item=>{
return defineObject(item,properobj);
});
}
function defineObject(data,properobj) {
// 在这里弄成一个有构造函数而创建对象即可
let newobj=new constructorObj();
for(let key in data){
Object.defineProperty(newobj,key,{
// ... 作为扩展符号 如果不了解可以看前面文章 箭头函数 根据rest参数进行了扩展
...properobj[key],
value:data[key]
})
}
return newobj;
}
function constructorObj(){};
// 这里不要使用箭头函数,有时候this不好把握,说实话我也一般只要有this就不哟箭头因为把握不住
constructorObj.prototype.setInfo= function (obj_pro,key,value){
Object.defineProperty(this,obj_pro,{
[key]:value
})
}
然后在修改main.js
import {student} from "./student.js";
import {properties} from "./properties.js";
import {infoObject} from "./infoObject.js"
;(()=>{
console.log('student-----',student)
const studentInfo=infoObject(student,properties);
var t=studentInfo[0];
for(var key in t){
console.log(key,t[key]);
}
console.log('----------------------',t)
t.setInfo('back','enumerable',true)
for(var key in t){
console.log(key,t[key]);
}
})();