逆向爬虫23 Javascript基础进阶

Javascript基础进阶

一. JS中的常用功能介绍(非交互)

1.1 定时器

在JS中,有两种设置定时器的方案

// 语法规则
t = setTimeout(函数, 时间)
// 经过xxx时间后, 执行xxx函数

// 5秒后打印我爱你
t = setTimeout(function(){
    console.log("我爱你")
}, 5000);

window.clearTimeout(t)  // 停止一个定时器
//语法规则
t = setInterval(函数, 时间)
// 每隔 xxx时间, 执行一次xxx函数

// 每隔5秒钟, 打印`我爱你`
t = setInterval(function(){
    console.log("我爱你")
}, 5000)

window.clearInterval(t)  // 停止一个定时器

关于第二种 setInterval 定时器来说,有一种很损的防爬措施,就是一旦浏览器检测到 F12 被按下后,就疯狂的执行下面这段代码

// 每隔1毫秒, 往缓存里写数据
t = setInterval(function(){
    // 往缓存里写数据
}, 1)

这样会导致你的浏览器奇卡无比,最后只能强行关掉它,解决它的方案是

window.clearInterval(t)  // 清理掉这个定时器

1.2 关于时间

// 自定义的getTime函数,把时间整理成人看起来舒服的样子
function getTime() {
    var now = new Date();   // 创建一个日期对象,默认情况就是系统时间
    // 2022-2-26 18:32:xx
    // var year = now.getYear();        // 从1900年开始计算
    var year = now.getFullYear();       // 正常的年份
    var month = now.getMonth() + 1;     // 月份
    var date = now.getDate();           // 日期

    var hour = now.getHours();          // 小时
    var minute = now.getMinutes();      // 分钟
    var second = now.getSeconds();      // 秒
    console.log(year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second);
}
setInterval(getTime, 1000);

// 一般前端,如果不需要给用户展示的话,没有必要去格式化这个时间
// 时间戳 (各个语言通用)
var now = new Date();
console.log(now.getTime());     // 获取时间戳 1970-1-1 0:0:0
// Javascript中时间戳的单位是毫秒,Python中时间戳的单位是秒

时间戳的功能:

  1. 告诉服务器HTTP请求是什么时候发送的。
  2. 让浏览器不要使用缓存里的数据,如果没有时间戳,浏览器会自动将缓存里的数据展示给用户看。

1.3 eval函数

eval本身在js里面正常情况下使用的并不多,但是很多网站会利用eval的特性来完成反爬操作,我们来看看eval是个什么鬼?

从功能上讲,eval非常简单,它和python里面的eval是一样的,它可以动态的把字符串当成js代码进行运行

s = "console.log('我爱你')"
eval(s);

也就是说,eval里面传递的应该是即将要执行的代码(字符串),那么在页面中如果看到了eval加密该如何是好?其实只要记住了一个事儿,它里面不论多复杂,一定是个字符串,比如

eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0.1("美得你");',62,2,'console|log'.split('|'),0,{}))

这一坨看起来,肯定很不爽,怎么变成我们看着很舒服的样子呢?记住,eval()里面是字符串,记住~!!

那我想看看这个字符串长什么样?就把eval()里面的东西拷贝出来,执行一下,最终一定会得到一个字符串,要不然eval()执行不了的,对不?于是就有了下面的操作

在这里插入图片描述

如果上述操作后得到的javascript代码依然很复杂,则需要考验大家js语法功底了。

1.4 prototype的作用

prototype是js里面给类增加功能扩展的一种模式

写个类看看

function People(name, age){
    this.name = name;
    this.age = age;
    this.run = function(){
        console.log(this.name+"在跑")
    }
}

p1 = new People("张三", 18);
p2 = new People("李四", 19);

p1.run();
p2.run();

我现在代码写完了,突然之间,我感觉好像少了个功能。人不应该就一个功能,光会跑是不够的,还得能够ooxx,怎么办?直接改代码?可以,但不够好,如果这个类不是我写的呢?随便改别人代码是很不礼貌的,也很容易出错,怎么办?我们可以在我们自己代码中对某个类型动态增加功能,此时就用到了prototype

function People(name, age){
    this.name = name;
    this.age = age;
    this.run = function(){
        console.log(this.name+"在跑")
    }
}

// 通过prototype可以给People增加功能
People.prototype.xxoo = function(){
    console.log(this.name+"还可以xxoo");
}

p1 = new People("张三", 18);
p2 = new People("李四", 19);

p1.run();
p2.run();

p1.xxoo();
p2.xxoo();

prototype是一种不改变类原始代码的条件下,给类增加新功能的方法,有一点像python中的装饰器,但装饰器是给函数增加新的功能。

继承也是一种给类增加新功能的方式,但prototype不是,它只是把功能添加进了原始的类中,并没有产生新的类。

// 往Date类中增加一个格式化时间的功能
var now = new Date();
Date.prototype.format = function () {
    var year = this.getFullYear();       // 正常的年份
    var month = this.getMonth() + 1;     // 月份
    var date = this.getDate();           // 日期

    var hour = this.getHours();          // 小时
    var minute = this.getMinutes();      // 分钟
    var second = this.getSeconds();      // 秒
    return (year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second);
}
console.log(now.format());

二. 变量提升

看以下代码,或多或少会有些问题的

function fn(){
    console.log(name);
    var name = '大马猴';
}
fn()

发现问题了么,这么写代码,在其他语言里,绝对是不允许的,但是在js里,不但允许,还能执行,为什么呢? 因为在js执行的时候,它会首先检测你的代码,发现在代码中会有name使用,OK,运行时就会变成这样的逻辑:

function fn(){
    var name;
    console.log(name);
    name = '大马猴';
}
fn()
console.log(a);

看到了么,实际运行的时候和我们写代码的顺序可能会不一样…这种把变量提前到代码块第一部分运行的逻辑被称为变量提升。这在其他语言里是绝对没有的,并且也不是什么好事情,正常的逻辑不应该是这样的,那么怎么办?在新的ES6中,就明确了,这样使用变量是不完善的,es6提出,用let来声明变量,就不会出现该问题了。

function fn(){
    console.log(name);  // 直接报错, let变量不可以变量提升.
    let name = '大马猴'; 
}
fn()

结论一, 用let声明变量是新版本javascript提倡的一种声明变量的方案.

let还有哪些作用呢?

function fn(){
    // console.log(name);  // 直接报错, let变量不可以变量提升.
    // let name = '大马猴';
    var name = "周杰伦";
    var name = "王力宏";
    console.log(name);
}
fn()

显然一个变量被声明了两次,这样也是不合理的,var本意是声明变量,同一个东西,被声明两次,所以ES6规定,let声明的变量,在同一个作用域内,只能声明一次。

function fn(){
    // console.log(name);  // 直接报错, let变量不可以变量提升.
    // let name = '大马猴';
    let name = "周杰伦";
    console.log(name);
    let name = "王力宏";
    console.log(name);
}
fn()

注意,报错是发生在代码检查阶段,所以,上述代码根本就执行不了。

结论二, 在同一个作用域内. let声明的变量只能声明一次. 其他使用上和var没有差别

三. 闭包函数 (难点)

3.1 闭包推导

先看一段代码

let name = "周杰伦"
function chi(){
    name = "吃掉"
}
chi();
console.log(name);

发现没有,在函数内部想要修改外部的变量是十分容易的一件事。尤其是全局变量,这是非常危险的。

再看一个案例:

我准备两个工具人,来编写代码,分别是js01和js02

// 1号工具人.
var name = "alex"

setTimeout(function(){
    console.log("一号工具人:"+name) // 一号工具人还以为是alex呢, 但是该变量是不安全的.
}, 5000);
// 2号工具人
var name = "周杰伦"
console.log("二号工具人", name);

html:

<script src="js01.js">script>
<script src="js02.js">script>

此时运行的结果:

逆向爬虫23 Javascript基础进阶_第1张图片

很明显,虽然各自js在编写时是分开的,但是在运行时,是在同一个空间内执行的,他们拥有相同的作用域。此时的变量势必是非常非常不安全的,那么如何来解决呢?注意,在js里,变量是有作用域的,也就是说一个变量的声明和使用是有范围的,不是无限的,这一点,很容易验证

function fn(){
    let love = "爱呀"
}
fn()
console.log(love)

直接就报错了,也就是说,在js里是有全局和局部的概念的

直接声明在最外层的变量就是全局变量,所有函数,所有代码块都可以共享的,但是反过来就不是了,在函数内和代码块内声明的变量,尤其是函数内,声明出来的变量它是一个局部变量,外界是无法进行访问的,我们就可以利用这一点来给每个工具人创建一个局部空间,就像这样:

// 1号工具人.
(function(){
    var name = "alex"
    setTimeout(function(){
        console.log("一号工具人:"+name) // 一号工具人还以为是alex呢, 但是该变量是不安全的.
    }, 5000);
})()
// 二号工具人
(function(){
    var name = "周杰伦"
    console.log("二号工具人", name);
})()

运行结果

逆向爬虫23 Javascript基础进阶_第2张图片

这样虽然解决了变量的冲突问题,但是…我们想想。如果我需要函数内部的一些东西来帮我进行相关操作怎么办…比如,一号工具人要提供一个功能(加密),外界要调用,怎么办?

// 1号工具人.
let jiami = (function(){

    let key = "10086" // 加装我是秘钥
    // 我是一个加密函数
    let mi = function(data){  // 数据
        console.log("接下来, 我要加密了,rsa哦. 很厉害的")
        console.log("秘钥:"+key);
        console.log("数据:"+data);
        // 返回密文
        return "我要返回密文";
    }
    // 外面需要用到这个功能啊. 你得把这个东东返回啊. 返回加密函数
    return mi;
})();

好像有点儿复杂了哈,别着急,注意了。我们如果封装一个加密js包的时候,好像还得准备出解密的功能,并且, 不可能一个js包就一个功能吧…那也太痛苦了(起名字). 那怎么办?我们可以返回一个对象,对象里面可以存放好多个功能,而一些不希望外界触碰的功能,就可以很好的保护起来。

// 1号工具人.
let jiami = (function(){

    let key = "10086" // 加装我是秘钥
    // 我是一个加密函数
    let rsa_jiami = function(data){  // 数据
        console.log("接下来, 我要加密了,rsa哦. 很厉害的")
        console.log("秘钥:"+key);
        console.log("数据:"+data);
        // 返回密文
        return "我要返回密文";
    }
	// 该函数只属于该模块内部. 外界无法访问.
    let n = {
        abc:function(){
            console.log("我是abc. 你叫我干什么?")
        }
    }

    // 外面需要用到的功能.进行返回.
    return {
        rsa_jiami: function(data){
            console.log("接下来, 我要加密了,rsa哦. 很厉害的")
            console.log("秘钥:"+this.get_rsa_key() + key);
            n.abc();
            console.log("数据:"+data);
            return "我要返回密文";
        },
        aes_jiami: function(data){
            console.log("接下来, 我要加密了,aes哦. 很厉害的")
            console.log("秘钥:"+this.get_aes_key());
            n.abc();
            console.log("秘钥:"+key);
            console.log("数据:"+data);
            return "我要返回密文";
        },
        get_rsa_key: function() {
            return this.rsa_key = "rsa的key", this.rsa_key
        },
        get_aes_key: function() {
            return this.rsa_key = "aes的key", this.rsa_key
        }
    }
})();

html里面使用时:

<script>
    miwen = jiami.rsa_jiami("吃你的糖葫芦把");
    console.log(miwen);
script>

OK,至此,何为闭包? 上面这个就是闭包,相信你百度一下就会知道,什么内层函数使用外层函数变量,什么让一个变量常驻内存,等等。其实你细看,它之所以称之为闭包~,它是一个封闭的环境,在内部,自己和自己玩儿,避免了对该模块内部的冲击和改动,避免的变量之间的冲突问题。

闭包的特点:

  1. 内层函数对外层函数变量的使用。
  2. 会让变量常驻与内存。

这俩玩意就不解释了,和python的闭包是一个意思,不懂没关系,能看懂他的执行过程就好。

3.2 我的理解

  1. 什么问题导致了需要有闭包?

    在html中,引入许多js文件,这些js文件中定义在最外层的变量和函数都属于全局作用域,因此相互之间会存在干扰,由于不同的文件很有可能是不同的程序员开发的,因此当变量名字或函数名字产生冲突时,会相互覆盖而出问题,因此需要一种机制让每个程序员写的功能都独立起来,关起门来自己玩自己的。因此产生了闭包的概念。

  2. 如何解决变量名冲突问题?(闭包)

    在C语言中,不同的C文件中的最外层变量和函数也都是全局作用域的,也会产生类似的问题,但是C语言在相同作用域声明两次同名函数或变量,编译器会直接报错,因此C语言通过在全局标识符前添加 static 关键字,把不同文件里的全局变量变成文件内部的静态全局变量,将全局作用域缩小到以文件为范围。

    闭包要解决的问题和C语言中的 static 关键字类似,缩小全局变量的作用域。

    由于Javascript中没有 staitc 关键字机制,但原理上只要缩小作用域就可以了,因此考虑到几乎任何编程语言都有的函数局部作用域的概念。将不希望被干扰到的变量名都打包进一个函数中,在函数中声明并使用,这样就相当于将全局作用域变成了函数局部作用域,和C语言中的变成文件作用域类似。

  3. 打包 (闭包) 之后,外部如何使用闭包内部的功能?

    将打包函数内部需要被外部调用的功能,定义到一个 {} 对象中,并返回出去,由于打包这些功能的函数是全局作用域,因此调用一下该打包函数,并用一个变量来接收它内部返回的 {} 对象,通过 变量.功能() 即可调用闭包函数内部的功能了。

  4. 闭包特性第二点:让变量常驻与内存。

    这也让我想到了C语言中,函数内部的 static 静态变量,C语言可以在函数内部通过定义静态变量的方式,让变量常驻内存。因此我觉得这里的闭包和C语言中的 staitc 关键字的引用有较深的联系。

你可能感兴趣的:(爬虫学习,javascript,爬虫,前端)