JavaScript|JavaScript基础( 浅聊 Object.defineProperty)
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)有两种主要形式:数据描述符和存取描述符。
- 数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。
- 存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。
- 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';
deleteobj.a;
for(var key in obj){
console.log(key,obj[keys]);
}
文章图片
无法枚举,但是可以取出这个属性名吗?其实再symbol中聊过几种的方式,现在试一下:
Object.keys(obj);
// 得到一般的属性和__proto__上的属性Object.getOwnPropertyNames(obj);
// 得到属于自己的属性
文章图片
configurable 当且仅当该属性的 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 当且仅当该属性的 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 当且仅当该属性的 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;
},
});
})()
描述符默认值汇总:
- 拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
- 属性值和函数的键 value、get 和 set 字段的默认值为 undefined。
- (value 或 writable 一个或两个) 不能和 (get 或set一个或两个)同时出现在描述中。
- 一些不是通过
Object.defineProperty
添加的属性也可以通过Object.defineProperty
进行修改,比如演示hook中的cookie等
其实前面聊了这样多,还有一句话大家要注意到:
该方法允许精确地添加或修改对象的属性。
比如:
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
exportfunction infoObject(data,properobj) {
if(Object.prototype.toString.call(properobj)!=='[object Object]' || properobj===null){
thrownew 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:
测试 - 锐客网
type="module">
import*as t from "./main.js";
文章图片
可见现在生效了back属性无法被遍历出来。
这个时候又有一个想法,是否来一个单独的方法来修改某个属性的属性呢?毕竟如果偶尔需要修改一些而最后不要碰配置的js文件。当然可以,这个前面聊过可以将这个可以修改属性的属性的方法,卸载对象的构造函数的prototype。所以可以如修改infoObject.js文件为如下:
exportfunction infoObject(data,properobj) {
if(Object.prototype.toString.call(properobj)!=='[object Object]' || properobj===null){
thrownew 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]);
}})();
【JavaScript|JavaScript基础( 浅聊 Object.defineProperty)】
文章图片
推荐阅读
- 前端|ajax 和 axios、fetch的区别
- 前端面试|JS的继承方法
- javaScript|AJAX基础使用
- 前端面试|webpack
- WordPress中的”未捕获的TypeError($不是函数” [重复])
- 前端|HTML 实现扫雷游戏
- 当你开始向下滚动时,顶部标题图像会抖动/抽搐,为什么( [关闭])
- 在WordPress中根据季节交换头版
- 静态HTML模板转换为WordPress-JavaScript和Jquery问题