js逆向学习笔记【一篇就够】
- 不够再来一篇
文章目录
算法还原
白盒还原
- 直接扣算法,或者是标准算法
- 理解对方 js 的意思,能够翻译成其他语言,可能会占用较长的分析时间
黑盒还原
- 直接整体调用(加密复杂,更新频繁,整体关联度高)
- 不需要关注算法逻辑,需要模拟浏览器环境,需要对抗环境检测
RPC 调用
- 算法复杂度高,浏览器环境难以模拟
- 找到算法位置,暴露出来,直接 rpc 调用,需要保证浏览器状态(内存泄漏,保活)
浏览器自动化
- 无法逆向
- 接近真人,但是有大量的自动化痕迹;
基本数据类型
- 数值 Number:整数和小数
- 字符串 String:文本
- 布尔值 Boolean:布尔值,true 表示真,false 表示假
- undefined:未定义,或者不存在
- null:表示空值
- 对象 Object:各种值组成的集合
原始类型
- 数值
- 字符串
- 布尔值
- 原始类型就是最基本的数据类型,不能再进行细分;
- undefined 和 null 一般看成是两个特殊值;
合成类型
- 对象
- 狭义的对象 Object
- 数组 Array
- 函数 Function
- 一个对象往往是由多个类型的值组成,可以看成是一个存放各种值的容器
查看类型
-
typeof:返回一个值的数据类型
-
instanceof:表示对象是否是某个构造函数的实例
-
Object.prototype.toString
-
typeof
可以用来检查一个未声明的变量,而不报错;// 基本数据类型 var tmp1 = "字符串"; var tmp2 = 1; var tmp3 = 1.1; var tmp4 = true; // 特殊类型 var tmp5= undefined; var tmp6 = null; // null 是一个 object // 对象 var tmp7 = {}; // 对象 var tmp8 = []; // 数组 var tmp9 = function(){}; // 函数 console.log("typeof(tmp1)", typeof (tmp1)); console.log("typeof(tmp2)", typeof (tmp2)); console.log("typeof(tmp3)", typeof (tmp3)); console.log("typeof(tmp4)", typeof (tmp4)); console.log("typeof(tmp5)", typeof (tmp5)); console.log("typeof(tmp6)", typeof (tmp6)); console.log("typeof(tmp7)", typeof (tmp7)); console.log("typeof(tmp8)", typeof (tmp8)); console.log("typeof(tmp9)", typeof (tmp9)); // function 因为有类object 的操作, 所以也属于 object console.log("tmp9.name", tmp9.name); // 获取函数名 //typeof(tmp1) string //typeof(tmp2) number //typeof(tmp3) number //typeof(tmp4) boolean //typeof(tmp5) undefined //typeof(tmp6) object //typeof(tmp7) object //typeof(tmp8) object //typeof(tmp9) function //tmp9.name tmp9
null undefined 和布尔值
null 和 undefined 的区别
- null 表示一个空对象,undefined 表示未定义;
- null 转为数值的时候为 0, undefined 转为数值的实收为 NaN;
boolean
布尔值表示真和假,true 表示真,false 表示假;
下列运算符会返回布尔值:
- ! (not)
- 相等运算符: =, , !, !=
- 比较运算符: >=, <=, <,>
表示 false 的值
在自动数据转换中,下列值会表示 false:
- undefined
- null
- false
- 0
- NaN
- “” 或者 ‘’ 空字符串
其他的值都会被当成 true;
空数组 [] 和空对象 {} 对应的布尔值都是 true;
数值
在 js 中,所有的数值都是 64 位浮点数的形式进行存储的,也就是说在 js 底层,没有整数只有浮点数;
因为浮点数精度的问题,js 在进行浮点数运算的时候经常会出现问题:
console.log(0.1 + 0.2);
// 0.30000000000000004
进制
- 十进制:没有前导,直接用数值表示
- 八进制:有前缀 0o 或者 0O
- 十六进制:有前缀 0x 或者 0X
- 二进制:有前缀 0b 或者 0B
默认情况下,js 内部会将八进制,十六进制,二进制转为十进制;
NaN
NaN 是 js 中的特殊值,表示非数字 (Not a Number), 主要出现在字符串解析成数字出错的时候;
- NaN 不是独立的数据类型,它是一个特殊值,它的数据类型依然是 Number
- NaN 不等于任何值,包括它本身 (不等于本身可以用来检测某个值是否是 NaN)
- NaN 和任何数运算,得到的结果都是 NaN;
Infinity
Infinity 用来表示无穷,一般出现在两种场景下:
- 正数的数值太大,或者负数的数值太小;
- 非 0 的数除以 0, 得到 Infinity
- js 中数值正向溢出或者负向溢出或者被 0 除都不会报错,所以单纯的数学运算几乎没有可能抛出异常
- Infinity 大于一切数值 (除了 NaN); -Infinity 小于一切数值 (除了 NaN)
- Infinity 和 NaN 比较,总是返回 false;
全局 api
parseInt(string[,radix])
parseInt(string[,radix])
将字符串解析成数值;如果入参非字符串,则会调用入参的 toString
方法转换为字符串再进行转换;如果设置了第二个参数 radix, 则会将字符串按指定的 radix 进制转换成十进制;返回数值或者 NaN
var a = '0xf';
console.log(a, 16); // 将 16 进制的 0xf 转为十进制
// 15
parseFloat(string)
parseFloat(string)
将字符串入参解析成浮点数;返回浮点数或者 NaN ;
var a = "4.567";
console.log(parseFloat(a)); // 4.567
// 当入参有非法字符, 则只保留合法部分进行转换
var b = "4.567abcd";
console.log(parseFloat(b)); // 4.567
// 第二个小数点忽略
var c = "1.2.3";
console.log(parseFloat(c)); // 1.2
// 起始为非法字符则直接返回 NaN
var d = "aaa1.2"; // NaN
console.log(parseFloat(d));
isNaN()
判断某个入参是否是 NaN; 可以利用 NaN 的不等性来进行判断
var a = NaN;
if (a != a ){
console.log("it is NaN");
}
isFinite()
判断某个入参是否是 Infinity;
字符串
- 字符串和数组相似,都支持使用 [] 运算符来通过指定索引来获取值;
- length: 可以获取字符串的长度
- 字符串不能通过 [] 运算符和索引来修改字符串的值
字符集
js 中使用的字符集为 Unicode 字符集,所有的字符串都使用 Unicode 表示;
var f\u006F\u006F = 'abc';
console.log(f\u006F\u006F);
base64 转码
- 浏览器:
- btoa:任意值转为 base64 编码
- atob:base64 值解码
- 非 ASCII 码 (如中文) 要转码之后再 base64;
encodeURIComponent
decodeURIComponent
- Nodejs
var base64encode = Buffer.from("js").toString("base64");
var base64decode = Buffer.from(base64encode,'base64').toString();
对象
- 对象就是一组键值对 key-value 的集合,是一种无序的复合数据集合
- 对象的每个键名又称为属性 property; 它的值可以是任意数据类型;
- 如果一个对象的某个属性的值是函数,则通常将这个属性称为该对象的方法,可以像函数一样调用这个方法;
- 属性可以动态创建,不必在对象声明的时候就全部定义;
对象引用
- 如果不同的变量名指向同一个对象,那么他们都是对这个对象的引用;也就是说这些变量都指向了同一个内存地址,修改其中任意一个的值都会影响到其他的变量;
- 如果取消某一个变量对于原对象的引用,不会影响到其他引用该对象的变量
- 这种引用只局限于对象,如果两个变量指向同一个原始类型的值,这些变量都是对原始类型的值的拷贝,改变任意变量都不会影响其他变量
属性查看
Obejct.keys()
可以查看该对象自身的属性,继承来的属性无法查看
var a = {
hello: function(){
consoel.log("hi");
},
table: [1, 2, 3],
name: "kevin",
age: 21,
married: false
}
console.log(Object.keys(a));
//[ 'hello', 'table', 'name', 'age', 'married' ]
属性删除
- delete 用于删除对象的属性,删除成功后返回 true
- 删除一个不存在的属性,delete 不会报错,而且返回 true
- 只有一种情况,delete 命令会返回 false; 那就是删除一条存在的属性,但是这条属性被定义成不能删除;(定义该属性不能删除 defineProperty)
- 只能删除对象自身的属性,继承来的属性不能删除
var a = {
hello: function(){
consoel.log("hi");
},
table: [1, 2, 3],
name: "kevin",
age: 21,
married: false
}
delete a.age, delete a.hello;
console.log(Object.keys(a));
// [ 'table', 'name', 'married' ]
属性存在判断
- in 运算符可以用于检查对象是否包含某个属性;检查的是键名,存在这个属性返回 true, 不存在则返回 false;
hasOwnProperty()
: 判断某个属性是否是该对象自身的属性;
JSvar a = {
hello: function () {
consoel.log("hi");
},
table: [1, 2, 3],
name: "kevin",
age: 21,
married: false
}
console.log(a.hasOwnProperty('table'));
if ("table" in a) {
console.log("table is property of a");
}
属性遍历
- for in 循环可以用于遍历对象的全部属性;不仅可以遍历对象自身的属性,还可以遍历对象继承来的属性;
- 如果只想遍历对象自身的属性,可以配合
hasOwnProperty()
进行筛选
var a = {
hello: function(){
consoel.log("hi");
},
table: [1, 2, 3],
name: "kevin",
age: 21,
married: false
}
for (const aKey in a) {
// 使用 hasOwnProperty 进行筛选
if (a.hasOwnProperty(aKey)) {
console.log(aKey);
}
}
函数
函数声明
js 中函数声明有三种方式
- 使用 function 申明
function a(){}
- 函数表达式
var a = function(){}
- Function 构造函数
var a = Function("a","b", "return a+b")
或者var a = new Function("a","b", "return a+b")
这两种方式效果一样
函数是一等公民
js 将函数看成是一个值,与其他数据类型一样,凡是可以使用其他数据类型的地方都可以使用函数,例如:
- 可以将函数赋值给变量或者对象的属性
- 可以将函数作为参数传递给其他函数
- 可以将函数作为其他函数的返回值
函数变量名提升
js 中全局变量名存在变量提升,函数内部的局部变量也存在变量提升;
function outer() {
console.log(a); // undefined 说明全局变量存在变量提升
console.log(b); // undefined 说明局部变量存在变量提升
var b = 2;
}
outer();
var a = 1;
js 中函数的声明也存在变量提升,可以先调用该方法,再定义该方法
b();
function b() {
console.log("b called");
}
函数的属性和方法
- name (属性):返回函数的名字
- length (属性):返回函数预期传入的形参数量
- toString () 方法:返回函数的字符串源码
函数作用域
- 作用域 scope 是指变量存在的范围
- es5 中 js 只有两个作用域
- 全局作用域:变量在整个程序中一直存在,所有地方都可以读取到该变量
- 函数作用域:变量只在函数内部存在
- es6 中新增了块级作用域
- es5 中 js 只有两个作用域
- 函数外部声明的变量就是全局变量,它可以在函数内部读取到;
- 在函数内部声明的变量就是局部变量,函数外部无法读取;
- 函数本身的作用域就是其声明时所在的作用域,与其运行时的作用域无关;
- 函数内部声明的函数,作用域绑定函数内部 (闭包)
函数参数省略
js 中函数的参数不是必须的,允许省略函数的参数;
函数的 length 属性只和函数声明时形参的个数有关,和实际调用时传入的参数个数无关;
参数传递方式
- 函数的参数如果是原始数据类型 (数值,字符串,布尔), 参数传递使用按值传递的方式,在函数内部修改参数的值不会影响函数外部
- 如果函数的参数是复合数据类型 (数组,对象,其他函数), 参数的传递方式是按址传递,传入的是引用的地址,因此在函数内部修改参数,会影响到原始值;
arguments 对象
- 因为 js 允许函数有不定数目的参数,所以需要在函数体的内部可以读取到所有参数,这就是 arguments 对象的由来
- arguments 对象包含了函数运行时的所有参数,这是一个类数组对象,
arguments[0]
就是第一个参数; - arguments 对象只能在函数内部使用
- arguments.length 可以获取函数调用时入参的真正个数
- arguments.callee 属性可以获取对应的原函数
- arguments 对象是一个类数组对象,如果要让他使用真正的数组方法,需要将 arguments 转换成数组:
Array.prototype.slice.call(arguments)
- 新建数组,遍历 arguments 将元素 push 到新数组中;
闭包
- 要理解闭包首先要理解 js 的作用域;前面提到的 js 在 es5 中只有两种作用域:
- 全局作用域
- 函数作用域
- 在函数的内部可以全局作用域的变量
- js 中特有的链式作用域结构,子级会向上一级一级寻找所有父级的变量,父级的所有变量对于子级来说都是可见的,反之不成立;
var a = 1;
var b = 2;
function f() {
var b = 3;
console.log(a, b);
function f1() {
var b = 4;
console.log(a, b);
}
f1();
}
f(); // 1 3// 1 4
链式作用域查找
子级会优先使用自己的作用域,如果变量存在则使用,不存在则会依次向上寻找,直至全局作用域;
闭包定义
- 闭包可以简单理解成定义在一个函数内部的函数
- 闭包最大的特点就是它可以记住自己诞生的环境,本质上,闭包就是将函数内部和函数外部连接起来的桥梁;
- 闭包最大的用处有两个
- 可以直接读取到外层函数内部的变量
- 可以让这些变量始终保存在内存中,闭包让自己诞生的环境一直存在
通过闭包实现简单的计数器
function count() {
var count = 0;
function f() {
count++;
console.log("count", count);
}
return f;
}
f = count();
f();
f();
f();
立即调用函数表达式
js 中有三种立即调用函数的方式
var f = function(){}();
(function(){}())
(function(){})()
通常情况下,只对匿名函数使用这种立即执行的表达式,这样有两个目的:
- 不必为函数命名,避免污染全局环境
- 立即调用函数的内部会形成单独的作用域,可以封装一些外部无法读取的私有变量
eval 命令
- eval 可以接受一个字符串,并将字符串当做代码执行
- eval 没有自己的作用域,都是使用当前运行的作用域,所以 eval 会修改当前作用域下的变量的值
- eval 的本质是在当前作用域中,注入代码,经常用于混淆和反爬
eval 别名调用
eval 的别名调用在 nodejs 下无法跑通,需要在浏览器下运行;
需要注意,在 eval 通过别名调用的时候,作用域永远是全局作用域;
var a = 1;
var e = eval;
(function () {
var a = 2;
e("console.log(a);"); // eval 在别名调用的时候使用全局作用域
}())
数组
- 数组 Array 是按次序排列的一组值
- 每个值的位置都有对应的索引
- 数组使用 来表示
- 任何类型的数据都可以放入数组中
- 本质上,数组是特殊的对象,typeof 查看数组的类型返回的是 object
- Object.keys () 可以返回数组的键名 (索引)
数组的属性
- length: 表示数组的元素个数,这个属性是可写的,可以直接修改数组的 length 属性,来实现清空数组或删除数组中元素的效果
- 数组本质上是特殊的对象,支持使用点操作符对数组添加属性;
var a = 1;
var e = eval;
(function () {
var a = 2;
e("console.log(a);"); // eval 在别名调用的时候使用全局作用域
}
())
var a = [1, 1.1, true, {}, [], "hello", null, undefined];
console.log("a.length", a.length);
a.name = "add a.name property";
for (const aKey in a) {
console.log(aKey, a[aKey]);
}
console.log("Object.keys(a)", Object.keys(a));
a.length = 0;
console.log("a", a);
console.log("a['name']", a['name']);
console.log("a.name", a.name);
console.log("a[0]", a[0]);
数组循环
- for in 循环
- for 循环
- while 循环
- forEach : 只有数组才有该方法;该方法接受一个回调函数,回调函数入参为 value 和 key;
var a = 1;
var e = eval;
(function () {
var a = 2;
e("console.log(a);"); // eval 在别名调用的时候使用全局作用域
}
())
var a = [1, 1.1, true, {}, [], "hello", null, undefined];
console.log("a.length", a.length);
a.name = "add a.name property";
for (const aKey in a) {
console.log(aKey, a[aKey]);
}
console.log("Object.keys(a)", Object.keys(a));
a.length = 0;
console.log("a", a);
console.log("a['name']", a['name']);
console.log("a.name", a.name);
console.log("a[0]", a[0]);
var a = [1, 1.1, true, {}, [], "hello", null, undefined];
for infor(const aKey in a) {
console.log("aKey:", aKey, "value:", a[aKey]);
}
console.log("-------------------------------")
forfor(var i = 0; i <= a.length; i++) {
console.log("index:", i, "value:", a[i]);
}
console.log("-------------------------------")
whilevar index = 0;
while (index <= a.length) {
console.log("index:", index, "value:", a[index]);
index++;
}
console.log("-------------------------------")
forEacha.forEach(function (value, key) {
console.log("key:", key, "value:", value);
})
数组空值
js 中的数组支持空值,出现空值时会占用该索引位,但是遍历的时候不会遍历该索引的值
var a = [1, 2, 3, , 5];
a.forEach(function (value, key) {
console.log("key", key, "value", value);
})
// key 0 value 1
// key 1 value 2
// key 2 value 3
// key 4 value 5
类数组对象
- 如果一个对象的所有键名都是正整数或者 0, 且有 length 属性,那么这个对象就是类数组对象
- 典型的类数组对象有 arguments 对象,字符串,以及大部分的 dom 元素集
- 数组的 slice 方法可以将类似数组的对象变成真正的数组
var arr = Array.prototype.slice.call(arrayLike);
- 除了将类数组对象转成真正的数组,还可以使用 call () 将数组的方法直接放到类数组对象上使用
Array.prototype.forEach.call(arrayLike, function(){});
数据类型转换
自动数据类型转换
其他类型转字符串
当 +
加号作为操作符,且操作数中含有字符串时,会自动将另一个操作数转为字符串;
规则如下:
- 字符串 + 基础数据类型:会直接将基础数据类型转为和字面量相同的字符串
- 字符串 + 复合数据类型:复合数据类型会先调用
valueOf()
方法,如果该方法返回基础数据类型则将其转为字符串,如果返回的是复合数据类型,则调用toString()
方法,如果返回的是基础数据类型则将其转为字符串,如果不是则报错;
// 基础类型
// 自动数据类型转换
// + 字符串
// 1. 字符串 + 基础数据类型: 会直接将基础数据类型转为和字面量相同的字符串
var tmp1 = "" + 3;
console.log("tmp1", tmp1); // tmp1 "3"
var tmp2 = "" + true;
console.log("tmp2", tmp2); // tmp2 "true"
var tmp3 = "" + undefined;
console.log("tmp3", tmp3); // tmp3 "undefined"
var tmp4 = "" + null;
console.log("tmp4", tmp4); // tmp4 "null"
// 字符串+复合数据类型: 复合数据类型会先调用 valueOf 方法, 如果此方法返回的是引用类型, 则再调用 toString()方法, 最后将返回值转为字符串类型
var tmp5 = [1, 2, 3] + "";
console.log("tmp5", tmp5); // tmp5 1,2,3
var tmp6 = {} + "";
console.log("tmp6", tmp6); // tmp6 [object Object]
// 重写 toString 方法
var o = {
toString: function () {
return 1;
}
}
var tmp7 = o + "";
console.log("tmp7", tmp7) // tmp7 "1"
// 重写 valueOf 方法
o.valueOf = function () {
return 2;
}
var tmp8 = "" + o;
console.log("tmp8", tmp8); // tmp8 2
var a = {
valueOf: function () {
return {}
},
toString: function () {
return "toString"
}
};
console.log("" + a); // toString
其他类型转布尔值
数值转布尔值
数值在逻辑判断条件下会自动转成布尔值;±0 和 NaN 为 false, 其他数值都是 true;
if (0) {
console.log("0 is true");
}else{
console.log("0 is false");
}
if (NaN) {
console.log("NaN is true");
}else{
console.log("NaN is false");
}
if (-0) {
console.log("-0 is true");
} else {
console.log("-0 is false")
}
// 0 is false
// NaN is false
// -0 is false
字符串转布尔值
空字符串”” 或者’’为 false, 其他都是 true
undefined 和 null 转布尔值
undefined 和 null 转为布尔值都是 false
对象转布尔值
只有对象为 null 或者 undefined 时,转为布尔值才是 false; 其他情况下 (包括空对象 {} 和空数组 []) 转为布尔值都是 true;
var o = {};
if(o){
console.log("{} is true");
}else{
console.log("[] is true");
}
// {} is true ; 空数组[] 同理
其他类型转为数值
一元操作符 +
和 -
都会触发其他类型转为数值;
数学运算符操作的两个操作数都不是字符串时,也会触发其他类型转为数值的操作;
转化规则如下:
- 字符串转数值:空字符串转为 0, 非空字符串转为对应的数字;不合法则返回 NaN;
- 布尔值转数值: true 为 1, false 为 0;
- null 转为数值为 0
- undefined 转数值为 NaN
- 对象转数值会先调用
valueOf()
方法,如果其返回值是基础数据类型,则将其转为数值返回,如果是复合数据类型,则会调用toString()
方法,再将其转为数值,如果不合法则返回 NaN;
强制类型转换
- Number (): 将其他类型转为数值;将对象转为数值时,会先调用对象的
valueOf()
方法,不满足条件再调用toString()
方法 - String (): 将其他类型转为字符串;将对象转为字符串时,会先调用对象的
toString()
方法,不满足条件时再调用对象的valueOf()
方法; - Boolean (): 将其他类型转为布尔值
异常处理
Error 对象
Error 对象通常包含常用的三个属性,且必须包含 message 属性;
- name: 异常名
- message: 异常提示信息
- stack: 异常调用栈信息
var e = new Error("自定义异常触发");
e.name = "自定义异常名称";
console.log(e.name);
console.log(e.message);
console.log(e.stack);
/*
自定义异常名称
自定义异常触发
自定义异常名称: 自定义异常触发
at Object.<anonymous> (/Users/zhangyang/codes/antiJs/js_learn/_03_基本数据类型(下)/tmp.js:201:9)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
at internal/main/run_main_module.js:17:47
*/
try catch finally
手动抛出异常并捕获
try {
var e = new Error("message 自定义异常");
e.name = "自定义异常名称";
throw e;
} catch (e) {
console.log("e.name", e.name, "e.message", e.message);
console.log("e.stack", e.stack);
}
throw
throw
语句用来抛出用户定义的异常,当前函数的执行将被停止,throw
之后的代码不会被执行;并且代码将会进入调用栈中第一个 catch
块中;如果没有 catch
块,程序将会终止;
try {
console.log("before throw error");
throw new Error("throw error");
console.log("after throw error");
} catch (e) {
console.log("catch error", e.message);
}
// before throw error
// catch error throw error
try/catch/finally
try 的三种声明形式:
- try…catch
- try…finally
- try…catch…finally
try/catch 主要用于捕获异常,try/catch 语句包含一个 try 块,和至少一个 catch 块或者一个 finally 块;
-
try: try 块中放入可能会产生异常的语句或者函数
-
catch: catch 块中包含要执行的语句,当 try 块中抛出异常时,catch 块会捕获这个异常,并执行 catch 块中的代码;如果 try 块中没有异常抛出,则 catch 块会被跳过;
-
finally: finally 块在 try 块和 catch 块之后执行,无论是否有异常产生,finally 块都会执行;当 finally 块中有异常产生时,会覆盖掉 try 块中的异常信息;
try { try { throw new Error("error1"); } finally { throw new Error("error2"); } } catch (e) { console.log("e.message", e.message); } // e.message error2
-
如果 finally 块中返回一个值,那么这个值将会作为整个 try/catch/finally 的返回值,无论在 try 和 catch 有没有 return, 都是返回 finally 中的值;
function test() { try { throw new Error("error1"); return 1 }catch (e) { throw new Error("error2"); return 2; }finally { return 3; } } console.log("test()", test()); // test() 3
对象详解和 hook
Object 静态方法和实例方法
- js 中所有其他对象都是继承自 Object ; 即所有对象都是 Object 的实例;
- Object 的原生方法分为两类: Object 本身的方法和 Object 的实例方法
- Object 本身的方法:直接定义在 Object 上的方法,相当于 java 的静态方法
- Object 的实例方法:定义在 Object 的原型对象 Object.prototype 上的方法;相当于 java 中的动态方法,可以被 Object 对象直接使用
Object 的静态方法
所谓静态方法,就是设置在 Object 类上的方法;
- Object.keys (): 遍历对象的属性,返回可枚举的属性名;这个方法只能查看对象自身的属性,继承来的属性无法查看
- Object.getOwnpeopertyNames (): 遍历对象的属性,可以返回不可枚举的属性名;
- Object.getOwnPropertyDescriptors (): 获取对象所有属性的描述对象;
- Object.defineProperty (): 通过描述对象,定义某个属性,可以重写 get 和 set 方法来进行 hook
- Object.defineProperties (): 通过描述对象,定义多个属性,可以重写 set 和 get 方法来进行 hook
- Object.getPrototypeOf (): 返回参数对象的原型,可以用于环