Javascript基础入门
js基础语法大全 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference
JavaScript, 是一门能够运行在浏览器上的脚本语言. 简称JS. 首先, Javascript这个名字的由来就很有意思, 不少人认为Javascript和Java貌似很像. 容易想象成Java的脚本. 但其实不然, 两者之间没有任何关系. 纯粹是商业碰瓷.
那么既然JS是可以运行在浏览器上的脚本. 并且, 我们知道本质上, 浏览器是执行HTML程序的. 那么如何在HTML中引入JS呢?
标签中引入编写js代码

方案二, 将js代码写在js文件中, 然后通过script标签的src属性进行引入

两种方式运行出的效果是一致的. 但是需要各位注意一点, HTML程序在执行的时候是从上到下进行渲染的.
那么如果我把脚本放在下面和放在上面是有一些不同的.


在js中使用 // 来表示单行注释。使用 /* */ 表示多行注释。
; 表示一行代码的结束一句话。对 ; 的检测是不严格的,推荐大家都加上。
知识点:当js代码中出现native code,大概率是js的原生逻辑,是built-in 函数。

这个知识点在逆向非常有用,当出现native code的时候,我们就可以大胆的应用这个函数了,而不用再看这个函数是怎么来的。
比如。btoa 这个函数是自带的。

Pycharm小知识点:
只有专业版的才有

Javascript基本数据类型
JS虽然是一个脚本语言. 麻雀虽小, 五脏俱全. 在js中也是可以像其他编程语言一样. 声明变量, 条件判断, 流程控制等等. 我们先看一下JS中的数据类型
在js中主要有这么几种数据类型(基本)
number 数字, 不论是整数还是小数, 数据类型都是number
string 字符串, 这个没啥可聊的. 就是很单纯的字符串
boolean 布尔值, 只有两个, true和false. 注意不是大写T和F.
object 对象, 这个比较特殊. 你可以理解为所有被new出来的东西都是对象
undefined, 这个表示未定义. 所有没有被定义过的东西默认都是该类型 类似像空一样的东西
在js中声明变量用var来声明
var 变量名; // 创建变量, 此时该变量除了被赋值啥也干不了.
var 变量名 = 值; // 创建一个变量, 并且有值.
var 变量名 = 值1, 变量名2 = 值2, 变量名3 = 值3.....; // 一次创建多个变量.并都有值
var 变量名1, 变量名2, 变量名3 = 值3; // 创建多个变量. 并且只有变量3有值
var a, b, c=3, d, e = 5; // 从左到右,声明5个变量, 其中, c=3, e=5
关于null和undefined. 这两个会很容易混. 你可以这样来记. null就是空对象. undefined就是空变量. 两者都可以表示空. 啥也没有. 本质其实是一样的. 都啥也干不了. 两者都可以当做false来看待就好了。
// 两个特殊值
var x; // undefined 不占用内存空间
console.log(typeof x); // object类型
// typeof 函数可以查看数据类型,但是只能看基础数据类型。
var x = null; // 空.... 是python的None,这种类型需要占用堆内存的
console.log(typeof x); // object类型
var x = function(){}; // 函数可以复制给一个变量
console.log(typeof x); // 所有的函数的数据类型都是function
&& 并且,左右两端必须同时为真,结果才能是真。
|| 或者,左右两端,有一个是真,结果就是真。
! 非/不,非真即假,非假既真。
var a = 10, b = 20, c = 30 ;
console.log(a > b && b > c); // false
console.log(!(a > b)) // 注意括号
x() && y()
该逻辑叫短路,如果前面的表达式已经得到结果了,后面就不运行了,
如果x()返回真,则运行y()。如果x()返回假,则不运行y(),直接结束判断了。
相同的逻辑还有 ||
== 和 ===
== 只是判断值是否一致
=== 会判断数据类型和数据是否都一致.
var a = "123";
var b = 123;
console,log(a == b); // true
console.log(a === b); // false
数据类型转换:
在js中:所有的数据和字符串相加,结果都是字符串拼接(重点)
// string -> number : parseInt(字符串)
var a = "10086";
a = parseInt(a); // 变成整数
console.log(a + 10); // 10096
// number -> string : 数字.toString() 或者 数字 + ""
var a = 100;
var b = a.toString();
var c = a + "";
console.log(b);
console.log(c);
// number -> string: 数字转化成16进制的字符串
var m = 122;
var n = m.toString(16);
console.log(n);
// 进制转换
var a = 10;
// 16进制的数字是多少
var x = a.toString(16); // a
// AB的十进制是多少
var d = parseInt("AB", 16); // 171
// + 左右两端如果出现了字符串,就是进行了字符串的拼接,在里面隐藏着类型转换(其他数据类型都会转换成字符串)。
var a = 10;
console.log(a + "蓝天白云");
// 结果是: 10蓝天白云
// 一般的程序员不用toString()的,因为写起来麻烦,一般用下面这种方法:
// 当逆向经常出现如下情况,目的就是为了把a转换成字符串
var a = 123;
var b = a + ""; // 在逆向的过程中. 这种写法是最多的,因为简单
console.log(b+333); // "123333" 字符串
关于++
// 在python中是没有++操作的. 但是在js中是有的.
a++; // 翻译一下就是a = a + 1
++a; // 翻译一下就是a = a + 1
a--; // 翻译一下就是a = a - 1
--a; // 翻译一下就是a = a - 1
//困扰无数初学者的疑惑, a++ 和 ++a有什么区别
// 两句话,
// 1. 不论是a++还是++a. 目的都是让a自增1。最终结果,都会+1.
// 2.
// a++ 先赋值, 后运算
// ++a 先计算, 后赋值
// b = a++ // 这个表达式整体运算出来的结果是 a,计算(++)之前的a
// c = ++a // 这个表达式整体运算出来的结果是 a + 1,计算(++)之后的a。
// ++在前. 先算+, ++在后, 先赋值. 可能会有坑. 大概率遇不到这个坑.
/*
var a = 10;
var b = a++;
var c = ++a;
console.log(a); // 12
console.log(b); // 10
console.log(c); // 12
*/
字符串常见操作:
s.split() 字符串切割,切出来的结果是数组
s.substr(start, len) 字符串切割, 从start开始切, 切len个字符
s.substring(start, end) 字符串切割, 从start切割到end
s.length 字符串长度
s.charAt(i) 第i索引位置的字符 等于 s[i]
s.indexOf('xxx') 返回xxx的索引位置, 如果没有xxx. 则返回-1
s.lastIndexOf("xxx") 返回xxx的最后一次出现的索引位置,如果没有xxx. 则返回-1
s.toUpperCase() 转换成大写字母
s.startsWith("xxx") 判断是否以xxx开头
s.trim 去掉左右两端的空白
s.concat 字符串拼接
s.includes() 是否包含,返回 true 或者 false
var s = "123456789";
console.log(s.split("45")); // 切出来的结果是数组.
// 输出结果: [ '123', '6789' ]
console.log(s.substr(3, 4)); // 34组合 从3开始, 截取4个内容
// 输出结果: 4567
console.log(s.substring(3, 4)); // 34组合 从3开始截取, 截取到4.
// 输出结果: 4
console.log(s.length);
// 输出结果: 9
console.log(s.charAt(3)); // 取索引是3的位置的字符
// 输出结果: 4
console.log(s[3]); // 取索引是3的位置的字符
// 输出结果: 4
console.log(s.indexOf("8")); // `美男`在字符串s中出现的索引位置,如果`美男`没有出现在s中. 此时返回-1
// 输出结果: 7
console.log(s.lastIndexOf("8")); // 获取到最后一个}出现的位置.
// 输出结果: 7
console.log("aBc".toUpperCase());// 转化成大写
// 输出结果: ABC
console.log("aBc".toLowerCase());// 转化成小写
// 输出结果: abc
console.log(s.startsWith("0")); // "0" 是否是字符串s的开头
// 输出结果: false
var s1 = "abc" // 去掉左右两端的空白
console.log(s1.trim());
// 输出结果: abc
s2 = s1.concat("胡辣汤").concat(123).concat("哈哈哈").concat(678); // 字符串拼接. 相当于+
console.log(s2);
// 输出结果: abc胡辣汤123哈哈哈678
var s3 = "123456"
console.log(s3.includes("45"));
// 输出结果: true
一定要搞懂、很重要的两个函数:
s.charCodeAt(index) 返回字符串第一个字符的字符的编码(unicode) // 加密
String.fromCharCode() 将 Unicode 编码转为一个字符 // 解密
console.log('A'.charCodeAt(0))
// 输出结果: 65
console.log('a'.charCodeAt(0))
// 输出结果: 97
var s = "Aa"
console.log(s.charCodeAt(0))
// 输出结果:65
console.log(s.charCodeAt(1))
// 输出结果: 97
console.log(String.fromCharCode(65))
// 输出结果: A
console.log(String.fromCharCode(97))
// 输出结果: a
混淆密码小案例(实际逆向中一般都比这个复杂)
// // 万恶之源....
var s = "我叫樵夫"; // 明文
var a = s.charCodeAt(0) + 10086 * 1; // 把字母转化成数字
var b = s.charCodeAt(1) + 10086 * 2; // 把字母转化成数字
var c = s.charCodeAt(2) + 10086 * 3; // 把字母转化成数字
var d = s.charCodeAt(3) + 10086 * 4; // 把字母转化成数字
var mi = ""+a+"|"+b+"|"+c+"|"+d;
console.log(mi); // 35191|41655|57447|63171
// 上面这个东西如果是服务器返回的
var s = "35191|41655|57447|63171";
console.log("mi", s);
// 在它的js里面百分之百要有一个功能. 该功能负责把上述字符串还原成正常的字符串
var arr = s.split("|");
var a = parseInt(arr[0]) - 10086 * 1;
var b = parseInt(arr[1]) - 10086 * 2;
var c = parseInt(arr[2]) - 10086 * 3;
var d = parseInt(arr[3]) - 10086 * 4;
console.log(a, b, c, d);
// 把数字, 转化回 字符
a = String.fromCharCode(a);
b = String.fromCharCode(b);
c = String.fromCharCode(c);
d = String.fromCharCode(d);
console.log("明文:", a+b+c+d);
JS条件分支
除了HTML以外. 几乎所有的编程语言都有条件判断的功能. 比如, python, 我们用if语句来做条件判断. 到了javascript中也是一样的, 也使用javascript来做条件上的判断.
// 语法
if(条件1){
代码块1
}
// 解读: 当`条件1`成立时, 执行代`码块1`中的内容, 如果`条件1`不成立. 则不执行该`代码块1`中的内容
// 注, 如果代`码块1`中的内容只有一行. 则可以省略外面的大括号(一些逆向工程里会有)
// 边界是以{}为准的.
// 坑: 在js中. 如果没有{}当没有大括号的时候. 它会运行后面的第一句话(用分号)
// 非严格模式
if (a > b)
console.log("a比b大"); // 这句话执行还是不执行,得看条件成立还是不成立。
console.log("不知道"); // 和上面的条件没关系,不管前面条件成立还是不成立,这句话都会执行。
else console.log("啦啦啦");
// 如果改成下面这样,分号改成了逗号,那么if 后面的两个console就属于一句话。是否属于一体的,关键要看第一个分号。
if (a > b)
console.log("a比b大"), console.log("不知道"); // 和上面的条件就是一体的, 这一行属于一体的。
else console.log("啦啦啦");
// 语法
if(条件1){
代码块1
} else {
代码块2
}
// 解读: 当`条件1`成立时, 执行`代码块1`中的内容, 如果`条件1`不成立. 则执行`代码块2`中的内容
// 语法
if(条件1){
代码块1
} else if(条件2) {
代码块2
} else if(条件3) {
代码块3
} ... {
代码块n
} else {
代码块else
}
// 解读: 当`条件1`成立时, 执行`代码块1`中的内容, 如果`条件2`不成立. 则执行`代码块2`中的内容...如果都不成立, 最终执行`代码块else`中的内容.
switch语句. 该语句是python中不存在的. 但是在Java和C, 以及JS中依然会有使用
switch(变量){
case 值1:
代码块1
break // 可选
case 值2:
代码块2
break // 可选
case 值3:
代码块3
break // 可选
default: // 可选
default代码块
}
/*
解读:
执行时,
switch会判断变量的值是否是`值1`,
如果是, 则执行代码块1以及代码块1中的break,
如果不是, 则继续判断`值2`...
如果前面的`值`都没有和`变量`相等的.则执行`default代码块`.
*/
case穿透现象:
每一个`case`中都可以选择`break`, 也可以不选择`break`, 需要注意的是, 如果不写`break`。
那么就会形成`case穿透`现象,后续的case将不在进行条件判断,而是直接运行case后面的代码。
某一个case匹配成功,那么后面的case就不判断了, 直接被执行。
例:`变量`的值如果和`值1` 相等,并且case1中没有写`break`,则在执行的时候。会执行完`case1`中的代码,然后会自动穿透到`case2`中去执行里面的代码, 而不经过case2中的数据的验证。
它是现象,不是bug。人家语法就是这么设计的。
// case穿透现象
var week = 3;
switch(week){
case 1:
console.log("星期一");
case 2:
console.log("星期二");
case 3: // 某一个case匹配成功. 那么后面的case就不判断了, 直接被执行.
console.log("星期三");
case 4:
console.log("星期四");
case 5:
console.log("星期五");
case 6:
console.log("星期六");
case 7:
console.log("星期天");
default:
console.log("啥也不是!");
}
可以使用break来解决穿透现象,跳过后面的代码,离开switch。
// 用break来解决穿透现象
var week = 3;
switch(week){
case 1:
console.log("星期一");
break;
case 2:
console.log("星期二");
break;
case 3: // 某一个case匹配成功. 那么后面的case就不判断了, 直接被执行.
console.log("星期三");
break;
case 4:
console.log("星期四");
break;
case 5:
console.log("星期五");
break;
case 6:
console.log("星期六");
break;
case 7:
console.log("星期天");
break;
default:
console.log("啥也不是!");
break;
}
case的妙用
// case穿透使用场景:输入月份,输出第几个季度
var month = 12;
switch (month){
case 1:
case 2:
case 3:
console.log("第一季度");
break;
case 4:
case 5:
case 6:
console.log("第二季度");
break;
case 7:
case 8:
case 9:
console.log("第三季度");
break;
case 10:
case 11:
case 12:
console.log("第四季度");
break;
// }
JS中的循环语句
在js中有三种循环语句. 首先是while循环. 它的逻辑和咱们python中的while几乎一模一样, 就是符号上有些许的区别.
while循环
// 语法
while(条件){
循环体 -> 里面可以有break和continue等关键字
}
/*
判断`条件`是否为真, 如果`真`, 则执行`循环体`.执行完`循环体`, 会再次判断`条件`....
并且在循环中也可以使用`break`和`continue`等关键字来控制循环的走向.
*/
var a = 0; // 循环变量. 用来记录循环次数的一个变量...
while(a < 100){ // 判断循环是否正常进行...
console.log("我爱你"); // 业务需求...循环内容...
a ++; // 改变循环变量. 必须放在业务代码执行之后.
}
var a = true;
while(a){ // 和if的变化逻辑是一样的. 炸你浏览器的效果.
console.log("lalalala");
}
// 上面代码在爬虫中有一个作用:用来做判断. 判断你是否打开了F12, 如果打开了自动运行,来处理哪些正在调试我网站的人.
// 反爬虫如何避免上面的代码:反调试 -> 反反调试(可以用工具删掉这段代码)
do while循环
和while的区别:由于do.while是先执行循环体,然后判断条件,所以do while循环至少会执行一次。
// 语法
do{
循环体
} while(条件);
var i = 0;
do {
console.log(i);
i ++;
} while(false);
/*
解读:
先执行`循环体`, 然后判断`条件`是否成立, 如果成立.在来一次.
注意, 由于do..while是先执行的`循环体`. 所以, 不论条件如何, 至少执行一次`循环体`
*/
for循环
// 语法: for的第一种语法
for(表达式1; 表达式2; 表达式3){
循环体
}
/*
解读:
for循环和我们python中的循环是完全不一样的. 解读起来会有点儿麻烦.
首先, 在执行的时候, 先执行`表达式1`,
然后, 判断`表达式2`得到的结果是否真, 如果`真`, 则执行循环体,
再然后, 执行`表达式3`,
再然后, 判断`表达式2`执行的结果是否为`真`, 如果`真`, 则执行`循环体`
再然后, 执行`表达式3`
.....
直到, `表达式2`得到的结果是`假`, 则跳出循环
*/
// 看起来很绕. 我们用for循环来跑一个1~99
for(var i = 1; i < 100; i++){
console.log(i);
}
/*
首先, i = 1,
然后, 判断 i < 100 成立
打印i
在然后, i++, i变成2
再然后, 判断 i < 100 还是成立
打印i
再然后, i++, i变成3
再然后, 判断 i< 100 还是成立
打印3....
....
当i = 100了. i < 100不成立. 程序结束
*/
// for循环的固定逻辑也就这样了
for(变量声明; 条件判断; 改变变量){
循环体
}
// for的第二种用法
var arr = [11,22,33,44,55,66]
for(let i in arr){
console.log(arr[i])
}
var arr = [11,22,33,44,55,66]
for(var i =0 ; i < arr.length; i++){
console.log(arr[i]);
}
// 这种写法非常类似python中的for循环. 但是要注意. 这里的`i`拿到的仅仅是 `数组a`的索引信息.
// 如果需要数据 a[i]
for循环改造小案例:
for (var f, g, h, i, j, k, l, c = new Array, d = b.length, e = 0; d > e; )
c[e] = b.charCodeAt(e),
e++;
代码解析:
先找哪里是循环体的内容,找for 后面的第一个分号,是 e++; 故加上 {} 就是:
for (var f, g, h, i, j, k, l, c = new Array, d = b.length, e = 0; d > e; ){
c[e] = b.charCodeAt(e),
e++;
}
这句话 var f, g, h, i, j, k, l, c = new Array, d = b.length, e = 0;
初始化其实就是 e = 0 ,前面那一坨其实是变量声明而已,只不过放在这里了。
所以最终可以改为:
var f, g, h, i, j, k, l;
var c = new Array;
for (var e = 0; e < b.length; e++){
c[e] = b.charCodeAt(e);
}
for循环改造小案例:
for (C = S[R--],
z = v(b, O),
A = "",
P = i.q[z][0]; P < i.q[z][1]; P++)
A += String.fromCharCode(r ^ i.p[P]);
// 分析:
// 下面这一段代码属于初始化,相当于 for(var i = 0; i < 10; i++) 中的var i = 0
C = S[R--],
z = v(b, O),
A = "",
P = i.q[z][0]
// 但是只有P被改变了,所以初始化代码只有:P = i.q[z][0]
// 故最后代码可以改为:
for (var P = i.q[z][0]; P < i.q[z][1]; P++){
A += String.fromCharCode(r ^ i.p[P]);
}
爬虫逆向常遇到的逻辑:流程平坦化(平坦流)
这种逻辑在OB混淆中经常能遇到。
需要一个数组来描述代码的正确执行顺序。那为啥别人会这样写代码么?因为不想让你那么容易看懂代码,打乱人为的肉眼代码顺序,想让你不能那么容易看出正确的执行顺序,但是呢,代码执行的顺序没问题。
var arr = [5,1,3,2,4];
var flag = true;
var a = 0;
while(flag){
switch (arr[a++]){
case 1:
console.log("2. 挽起袖子");
break;
case 2:
console.log("4. 兄弟们上");
break;
case 3:
console.log("3. 大吼一声");
break;
case 4:
console.log("5. 上车. 跑路");
flag = false;
break;
case 5:
console.log("1. 怒目而视");
break;
}
}
// 上述逻辑执行的真正的过程是:
// console.log("1. 怒目而视");
// console.log("2. 挽起袖子");
// console.log("3. 大吼一声");
// console.log("4. 兄弟们上");
// console.log("5. 上车. 跑路");
平坦流的另外一种方式:(瑞数级别)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
var arr = [8,1,3,5,4,10,2,7,6,9];
var i = 0;
var flag = true;
while(flag){
var m = arr[i++];
if(m > 5){
if(m > 7){
if (m > 9){
console.log("6. 上车. 跑路");
} else {
if (m > 8){
console.log("10. 上车. 跑路");
flag=false;
} else {
console.log("1. 怒目而视");
}
}
} else {
if (m > 6){
console.log("8. 上车. 跑路");
} else {
console.log("9. 上车. 跑路");
}
}
} else {
if (m > 3){
if(m > 4){
console.log("4. 兄弟们上");
} else {
console.log("5. 上车. 跑路");
}
} else{
if (m > 2){
console.log("3. 大吼一声");
} else {
if (m > 1){
console.log("7. 上车. 跑路");
} else {
console.log("2. 挽起袖子");
}
}
}
}
}
// console.log("1. 怒目而视");
// console.log("2. 挽起袖子");
// console.log("3. 大吼一声");
// console.log("4. 兄弟们上");
// console.log("5. 上车. 跑路");
// console.log("6. 上车. 跑路");
// console.log("7. 上车. 跑路");
</script>
</head>
<body>
</body>
</html>
JS中的数组
在JS中创建数组非常简单. 直接[ ]即可. 也可以用正规军的new Array(). 不过效果都是一样的.
var as = [11,22,33,44,55];
var bs = new Array(11,22,33,44,55);
var arr3 = new Array; // 不标准的写法,严格模式下会报错
数组的常用操作
arr.length; // 数组长度
// push 和 pop这两个操作组合在一起. 就是数据结构 => 栈
arr.push(data); // 添加数据,在数组的末尾添加数据
arr.pop(); // 删除数据, 从后面删除, 并返回被删除的内容
arr.shift() // 删除数据, 从前面删除, 并返回被删除的内容
arr.unshift // 也是添加数据, 在数组的前面插入数据
arr.join("连接符"); // 使用`连接符`将arr中的每一项拼接起来. 和python中的 "".join()雷同
var arr = [111,22,33, "aaa", "bbb"];
console.log(arr.join("_")); // 把数组转化成字符串
var x = arr.splice(start, count, items); // 删除某个位置的元素. 可以进行替换
// 从start开始删, 删除count个, 可以进行替换, 替换的元素是items
// 第一个参数,是起始位置。第二个参数,截取的数量。后面的其他参数, 当成元素插入到被删除的位置
var x = arr.splice(1, 2, "aaa", "bbb", "ccc"); // 指定删除1位置的内容
console.log(arr);
console.log(x);
// 降维
var arr = [11,22,33, [22,33, [77, 33, 11],44], [44, 55, 66]];
console.log(arr.flat().flat());
// 循环和遍历数组
// arr中的每一项循环出来. 分别去调用function函数, 会自动的将`数据`传递给函数的第一个参数
arr.forEach(function(e, i){ // 第二个参数是可选的
console.log(i+"__"+e);
});
var arr = [11, 22, 33, 44];
// 第一个参数是元素, 第二个参数是索引。对数组中的每一个元素去执行一个函数
arr.forEach(function(e, i){
console.log(i, e);
});
// 循环和遍历数组,方案一
var arr = [11,22,33,44,55]; // 数组有索引的, 通过索引进行循环
for(var i = 0; i < arr.length; i++){
console.log(arr[i]);
}
// 循环和遍历数组,方案二.
var arr = [11,22,33,44,55];
// 类似python的写法
for(var x in arr){ // 从arr中获取到每一个元素的索引
console.log(x, arr[x]); // 从列表中根据索引的位置, 提取元素
}
// 循环和遍历数组,方案三.
for (var x of arr){ // 此时拿到的是数组的元素(value)
console.log(x);
}
// map: 映射
// 数组也像python中的map一样对数组中的元素进行分别计算
// map和forEach的区别: map可以回收每一次函数调用的结果,最终组合成一个新的数组。
var arr = [11, 22, 33];
var result = arr.map(function(e){
console.log(e);
return e + 1000;
});
JS中的对象
在JS中创建一个对象非常容易. 和python中的字典几乎一样{ }
var p = {
name: "汪峰",
age: 18,
wife: "章子怡",
chi: function(){
console.log("吃饭")
}
};
// 使用对象
p.name
p.age
p['wife']
p.chi()
p['chi']()
// 上述内容和python的字典是没有差别的.
// js特有的,key可以不加引号,不管加不加引号,key都是字符串。
var p = {
name: "aaa"
}
var p1 = {
"name": "aaa"
}
// js的对象在使用的时候. 既可以通过`[]`来获取属性的值. 也可以通过`.`来获取属性的值
// 区别: []里面必须放字符串.
// .后面放的必须是属性名(不可以是字符串)
console.log(p['name']);
console.log(p.name); // 直接用对象.属性
// 两种写法在爬虫中的应用:
// 如果程序有重度混淆, 一般用的p['字符串'],这种写法比较容易伪装
// 如果程序是轻度混淆,或者不混淆, 一般用 p.属性,这种写法比较难伪装
从上述内容中几乎可以看到. JS对象的使用几乎是没有门槛的. 十分灵活
for(var n in p){
if(typeof(p[n]) != 'function'){
console.log(p[n])
}
}
JSON.stringify() 将一个 JavaScript 对象或值转换成一个 JSON 字符串。
JSON.parse() 将一个 JSON 字符串转换为对象。
var p = {
name: "汪峰",
age: 18,
wife: "章子怡",
chi: function(){
console.log("吃饭")
}
};
console.log(JSON.stringify(p));
console.log(JSON.parse('{"name":"汪峰","age":18,"wife":"章子怡"}'));
// 从网页向服务器发送请求
// 1. 准备参数( 对象 )
// 2_1. 在加密之前, 需要把对象(参数)转化成字符串, JSON.stringify -> 字符串 (未来可以作为切入点)
// 2_2. 对参数字符串进行加密(aes, des, rsa), 得到密文
// 3. 发送请求到服务器......
// 通过以上步骤我们知道,一般 JSON.stringify 函数是加密之前最后一个操作,所以在爬虫逆向中,一般期望找到加密函数,就去找 JSON.stringify函数之后的操作。
JS中的函数(重点)
在JS中声明函数和python差不多. 也要有一个关键字顶在前面. python是def, 到了JS里换成了function, 只不过在JS中没有像python那么死板, 必须def后面必须跟上函数名. 这也为我们未来做逆向提供了第一个超大的伏笔.
// 语法
// 声明函数
function 函数名(形参1, 形参2, 形参3....){
函数体
return 返回值
}
// 调用函数
函数名(实参1, 实参2, 实参3....)
// 函数的参数
function func(a, b) {
console.log(a, b);
}
// 参数可以少传,也可以多传
func(1); // 1 undefined
func(1,2,3,4); // 1 2
// 在前端arguments 可以获取到所有的参数。每个函数都有,默认的。
function func(a, b) {
console.log(arguments);
}
func(1,2,3,4); // [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }
// 返回值
// return多个值,只返回最后一个值。
// 默认没有返回值的时候. 得到的是undefined
function fn(){
console.log(1,2); // 打印
return 3, 4; // 返回4
return xxx(), 4; // 如果前面一个是函数,先计算函数,再返回4
}
var ret = fn();
console.log(ret); // 返回4
// 来看一个返回值爬虫逆向常见的例子
function d(d, e, f, g) {
var h = {}
, i = "haha";
return h.encText = 'hehe',
h.encText = "houhou",
h.encSecKey = "love",
h
}
// 期中 h.encText 等价于 h["encText"] ,h.encSecKey 等价于h["encSecKey"]
// 上面的返回值是 h = {encText: "houhou", encSecKey: "love"}
// 函数作为参数, 函数作为返回值。
// 函数名字只是一个标识符,和变量没什么差别
function fn(){
console.log("我是一个函数");
}
var mm = fn;
console.log(mm === fn);
// 函数名就是个变量,随便进行赋值
// 看看下面的函数返回结果是啥
function fn(na, mn){
console.log("我是fn");
var r = na(mn);
console.log("我是fn");
console.log(r);
}
function gn(mn){
var ret = mn()
console.log("我是gn");
return ret + 1;
}
function mn(){
return 123;
}
fn(gn, mn); // 结果: 124
// 函数前面有一些特殊的符号. 就是一个自运行的函数....仅此而已
// 后续的代码中不可能重新运行它
(function(a, b){
console.log("我是函数", a, b)
})(1, 2) // 自执行函数
!function(a, b){
console.log("我是函数", a, b)
}(33, 44); // 自执行函数
+function(a, b){
console.log("我是函数", a, b)
}(55, 66); // 自执行函数
~function(a, b){
console.log("我是函数", a, b)
}(77, 88); // 自执行函数
-function(a, b){
console.log("我是函数", a, b)
}(9, 999); // 自执行函数
简单来个案例看看
function an(a, b){
return a + b
}
ret = an(1, 2)
console.log(ret); // 3
很简单不是么? 那为什么我们在网页上看到一些网站的JS是如此的复杂呢?
注意了, JS其实没有一个非常非常严格的语法规则. 只要能够形成 xxx() 并且xxx是一个函数的话就可以执行.
例如:
var an = function(a, b){
return a + b
}
an(1, 2) // an虽然是var声明的, 但是它的指向是一个函数. 那就可以执行
var $ = function(a, b){
}
$(1, 2) // $没有被JS使用. 我就可以用啊. _也OK
// 这个也很过分. 这个东东要拆开来看 第一个括号里面放的就是一个函数啊. 所以依然可以执行.
(function(a, b){
return a + b
})(1, 2)
c = (function(){
var m = {
name:'alex',
age:'18',
xijiao: function(a){
console.log(a+"来帮我洗脚");
}
}
return m
})
// 还有最后一个问题. 未来我们也会遇到的. 就是它这个return
var an = function(){
return console.log("我爱你"), console.log("爱你妹"), "哈哈"
}
// 注意我们发现js会把return后的每一个,都执行一次. 但是最终真正的返回值其实是最后的那个"哈哈"
浙公网安备 33010602011771号