变量提升(这玩意简直就是编程里的奇葩)

function fn() {
    console.log(name);
    var name='hyj';
}
fn()

这样写代码,在其他语言中是绝对不允许的,但是在js中是合法的,还可以执行;因为在js执行的时候,它会首先检测你的代码,发现代码中有name的时候他就会自动改变至如下逻辑:

function fn() {
    var name='hyj';
    console.log(name);
}
fn()

所以实际运行的时候和我们写代码的逻辑可能不太一样,这种把变量提前到代码块第一部分运行的逻辑被称为变量提升。这在其他语言里是没有的,而且不是什么好事情。在新的ES6中,就明确了,这样使用变量是不完善的,ES6提出,使用let来声明变量,就不会出问题了。

function fn() {
    console.log(name);
    let name = "hyj";
}
fn()

如果这样写,就报错了,所以用let来声明变量是新版JS提倡的方案。

let的其他作用

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

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

function fn() {
    let name = "hyj";
    console.log(name);
    let name = "xsy";
    console.log(name);
}

注意, 报错是发生在代码检查阶段.。所以上述代码根本就执行不了;在同一个作用域内。let声明的变量只能声明一次, 其他使用上和var没有差别。

闭包函数

先看以下代码:

let name = "hyj";
function chi() {
    name = "eat";
}
chi();
console.log(name);

在函数内部想要修改外部的变量是十分容易的一件事,尤其是全局变量,这是非常危险的。试想,我写了一个函数要用到name,结果被别人写的某个函数给修改掉了,多难受。

接下来看下面的案例,准备两个工具人,分别是js01和js02。

var name = "hyj";

setTimeout(function(){
    console.log("first:" + name)
}, 5000);
```js
var name = "xsy"

console.log("second:" + name)

html:

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

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

function fn(){
    let dog = "wang"
}
fn()
console.log(dog)

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

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

(function(){
    var name = "hyj"
    setTimeout(function(){
        console.log("one" + name)
    }, 5000)
})()
```js
(function(){
    var name = "xsy"
    console.log("second" + name)
})()

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

let jiami = (function(){

    let key = "10086"

    let mi = function(data) {
        console.log("接下来,我要加密了哦,rsa哦,很厉害的哦");
        console.log("key:" + key);
        console.log("data:" + data)

        return "miwen"
    }

    return mi;
})();

在我们封装一个 js 包的时候,好像跟还需要准备解密的功能,而且不可能一个 js 包就一个功能。所以我们可以返回一个对象,对象里可以存放很多功能,而一些不希望外界触碰的功能,就可以很好的保护起来。

let jiami = (function(){

    let key = "10086"

    let res_jiami = function(data) {
        console.log("接下来,我要加密了哦,rsa哦,很厉害的哦")
        console.log("key:" + key)
        console.log("data:" + data)

        // 返回密文
        return "miwen"
    }

    // 该函数属于模块内部,外界无法访问
    let n = {
        abc:function () {
            console.log("我是abc,你叫我干嘛")
        }
    }

    return {
        rsa_jiami:function (data) {
            console.log("接下来,我要加密了哦,rsa哦,很厉害的哦");
            console.log("key:" + this.get_rsa_key())
            n.abc()
            console.log("data:" + data)

            return "miwen"
        },
        aes_jiami:function (data) {
            console.log("接下来,我要加密了哦,aes哦,很厉害的哦");
            console.log("key:" + this.get_aes_key())
            n.abc()
            console.log("data:" + data)

            return "miwen"
        },
        aes_jiami:function (data) {
            console.log("接下来,我要加密了哦,rsa哦,很厉害的哦")
        },
        get_rsa_key: function () {
            return this.rsa_key = "rsa_key"
        },
        get_aes_key: function () {
            return key.aes_key = "aes_key"
        }
    }

    return mi;
})();

html 中使用:

<script>
    miwen = jiami.rsa_jiami("ddddd")
    console.log(miwen)
</script>

JS 中的各种操作(非交互)

定时器

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

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

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

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

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

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

for(let i = 0; i <= 9999; i++)window.clearInterval(i); // 清理掉所有定时器

关于时间

var d = new Date(); // 获取系统时间
var d = new Date("2018-12-01 15:32:48"); // 得到一个具体时间

// 时间格式化
year = d.getFullYear();  // 拿到年份
month = d.getMonth() + 1; // 拿到月份. 注意月份从0开始
date = d.getDate();   // 拿到日期
hour = d.getHours();   // 拿到小时
minute = d.getMinutes();  // 分钟
seconds = d.getSeconds();  //秒

format_date = year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + seconds;
```js
d.getTime()  // 时间戳. 表示从1970-1-1 00:00:00 到现在一共经过了多少毫秒

eval 函数(必须会)

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

s = "console.log('I love you')"
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 加密都可以搞定。

prototype(很重要)

prototype 是 js 里面给累添加功能扩展的一种模式:

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

p1 = new People("san", 18)  
p2 = new People("si", 19)  

p1.run()  
p2.run()

现在代码写完了,可是我突然想加一个功能。直接改代码可以但是不够好,如果这个代码不是我写的改比人代码显然不太好。我们可以在我们自己代码中的某个类型动态增加功能,此时就可以使用 prototype。

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


People.prototype.xxoo = function () {  
    console.log(this.name, "xxooing")  
}  

p1 = new People("san", 18)  
p2 = new People("si", 19)  

p1.run()  
p2.run()  

p1.xxoo()  
p2.xxoo()

构造器

构造一个对象的函数,叫构造器

function People() { // 这就是构造器 constractor
}

var p = new People() // 调用构造器
p.constractor == People // true

原型对象

每个 js 对象中,都有一个隱藏属性 __proto__ 指向该对象的原型对象。在执行该对象的方法或者查找属性时,首先对象自己是否存在该属性或方法。如果存在,就执行自己的,如果自己不存在,就去找原型对象。

function Friend(){
    this.chi = function(){
        console.log("我的朋友在吃");
    }
}
Friend.prototype.chi = function(){
    console.log("我的原型在吃")
}

f = new Friend();
f.chi(); // 此时. 该对象中. 有chi这个方法.  同时, 它的原型对象上, 也有chi这个方法.
// 运行结果:
// 我的朋友在吃

_proto_和 prototype

在js中. 构造器的prototype属性和对象的 __proto__ 是一个东西,都是指向这个 原型对象

f.__proto__ === Friend.prototype   // true

原型链

每个对象身体里,都隐藏着 __proto__ 也就是他的 原型对象;原型对象也是对象,也就是说原型对象也有 __proto__ 属性。类似于下面:

f.__proto__.__proto__

打印出来效果如下:

此时,又出现一堆看不懂的东西,其实就是 Object 的原型。

f.__proto__.__proto__ === object.prototype

所以,我们在执行 f.toString() 的时候不会报错,反而可以正常运行,原因就在这里。执行过程:先找 f对象 中是否有 toString。没有,他就找 原型对象.原型对象 中有没有;没有,就继续找 原型对象.原型对象.原型对象。知道找到 Object 的原型为止。如果还没有找到,就报错。

f.nitamazhenniubi() // 报错

原型链的作用(很恶心)

下面有一段神奇的代码:

(function() {debugger})();

上面的代码可以看到,浏览器进入了 debugger 断点。那么这段代码后面是什么呢?在 js 代码执行时,每一个 function 的对象都是通过 Function() 来创建的,也就是说,函数时 Function() 对象。

function fn() {}
console.log(fn.__proto__.constructor);

函数就是 Function() 的对象,那么,我们可以通过 Function 来构建一个函数。

new Function('debugger')();

函数就是 Function 的对象,那么我们可以通过 Function 来构建一个函数。

new Function('debugger')();

跑起来的效果一样。
那么这玩意有啥用呢?代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="haha.js"></script>
  <script>
    txsdefwsw();
  </script>
</head>
<body>
    有内鬼. 终止交易
</body>
</html>

haha.js 中的内容如下:

function txsdefwsw() {
    var r = "V", n = "5", e = "8";

    function o(r) {
        if (!r) return "";
        for (var t = "", n = 44106, e = 0; e < r.length; e++) {
            var o = r.charCodeAt(e) ^ n;
            n = n * e % 256 + 2333, t += String.fromCharCode(o)
        }
        return t
    }

    try {
        var a = ["r", o("갯"), "g", o("갭"), function (t) {
            if (!t) return "";
            for (var o = "", a = r + n + e + "7", c = 45860, f = 0; f < t.length; f++) {
                var i = t.charCodeAt(f);
                c = (c + 1) % a.length, i ^= a.charCodeAt(c), o += String.fromCharCode(i)
            }
            return o
        }("@"), "b", "e", "d"].reverse().join("");
        !function c(r) {
            (1 !== ("" + r / r).length || 0 === r) && function () {
            }.constructor(a)(), c(++r)
        }(0)
    } catch (a) {
        setTimeout(txsdefwsw, 100);
    }
}

页面跑起来没什么问题,但是会无限 debugger;解决方案如下:

  1. 找到断点出. 右键-> never pause here;
  2. 写 js hook 代码;
var x = Function;
Function = function(arg) {
    arg = arg.replace("debugger", "");
    return new x(arg);
}

var function_constructor = (function[]{}).__proto__.constructor;
(function(){}).__proto__.constructor = function(arg) {
    console.log("ddd");
    if (arg === 'debugger') {
        return function(){}
    }
    else {
        return new function_constructor(arg);
    }
}

image-20221028222412104.png

image-20230410224207377.png

神奇的 windows

window 对象是一个很神奇的东西,你可以把这东西理解成 JavaScript 的全局。如果我们默认不用任何东西访问一个标识,那么默认认为是在用 window 对象。

例如:

eval === window.eval    // true
setInterval === window.setInterval  // true
var a = 10; 
a === window.a  // true
function fn(){}
fn === window.fn  // true

window.mm = "爱你"

console.log(mm); //"爱你"

综上,我们可以得出一个结论,全局变量可以用 window.xxx 表示。

注意:看下面代码:

(function({
    let chi = function() {
        console.log("eat")
    }
}));

chi()

(function(w){
    let chi = function(){
        console.log("eat")
    }
    w.chi = chi
})(window);

(function(w){
    let tools = {
        b64: function(){
            console.log("b64");
            return "b64";
        },
        md5: function(){
            console.log("md5");
            return "md5"
        }
    }

    w.jiami = {
        AES: function(msg){
            return tools.b64(),
                tools.md5(),
                'god like';
        },
        DES: function(){
            console.log("des");
        },
        RSA: function(){
            console.log("rsa");
        }
    }
})(window)

jiami.AES("eat?")

window 是整个浏览器的全局作用域。

call 和 apply

对于做爬虫的逆天工程师而言,不需要理解 callapply 的本质作用,只需要知道这玩意执行起来的逻辑顺序就可以了。

在运行时,正常的 js 调用:

function People(name, age) {
    this.name = name;
    this.age = age;
    this.chi = function() {
        console.log(this.name, "eatting")
    }
}
p1 = new People("lenn", 18);
p2 = new People("louis", 20);
p1.chi()
p2.chi()

接下来,我们可以使用 call 和 apply 也完成同样的函数调用

function People(name, age) {  
    this.name = name;  
    this.age = age;  
    this.chi = function (what_1, what_2) {  
        console.log(this.name, "eatting", what_1, what_2);  
    }  
}  

p1 = new People("lenn", 18);  
p2 = new People("louis", 20);  
p1.chi("apple", "banana");  
p2.chi("rice", "beef");  

function eat(what_1, what_2) {  
    console.log(this.name, "eatting", what_1, what_2);  
}  

eat.call(p1, "water", "milk");

apple 和他几乎一模一样,区别是 apple传递参数要求是一个数组

eat.apple(p1, ["water", "milk"]);

ES6 中的箭头函数

在 ES6 简化了函数的声明语法:

var fn = function(){};
var fn = () => {};

var fn = function(name){}
var fn = name => {}
var fn = (name) => {}

var fn = function(name, age){}
var fn = (name, age) => {}

ES6 中的 promise(难)

都好运算符

function s() {  
    console.log(1), console.log(2), console.log(3); // 从前向后执行 ,1,2,3    let s = (1, 2, 3); // 整体进行赋值的时候. 取的是最后一个值 3    console.log(s);  

    var a;  
    return a=10,  
        a++,  
        a+=100,  
        {name:"lenn", "a":a};  

}  

let r = s();  
console.log(r);

image.png

三元运算符(折磨)

let a = 10;  
let b = 20;  
let c = a > b ? a : b  
console.log(c)  

let a = 10;  
let b = 20;  
let c = 5;  
let d = 17;  

let e;  
let m;  

e = (e = a > 3 ? b : c, m = e < b++ ? c-- : a = 3 > b % d ? 27: 37, m++);  

console.log(e);  
console.log(c);  
console.log(m);

JS hook

hook 又称钩子,可以在调用系统函数之前,先执行我们的函数,例如,hook eval:

eval_ = eval;
eval = function(s) {
    console.log(s);
    debugger;
    return eval_(s);
}
eval()
eval.toString = function(){
    return 'function eval(){[native code]}'
} // 可能会被检测到,用这种方案进行

localStorage 和 sessionStorage

本地存储,存储在浏览器端的数据,可以理解成一个小型的非关系型数据库。
这两个东西使用上是一样的,区别在于一个永久存储一个临时存储。

  • localStorage:永久存储;
  • sessionStorage:临时存储,浏览器关闭后,数据就没了;
  • document.cookie:本地存储;

cookie 本质上是一个超大的字符串,有失效的时间。

都继承自 Storage 常见的操作:

setItem(key, value);
removeItem(key);
getItem(key);

JS 和 HTML 交互

在 HTML 中可以直接标签上给出一些事件的触发,例如,页面上的按钮:

<input type="button" value-"click me"/>

我们能够知道此时在页面中会产生一个按钮. 但是该按钮无论如何进行点击. 都不会触发任何事件. 但, 此时我要告诉你, 人家其实触发了. 只是你没处理而已. 在我们点击该按钮的时候. 浏览器其实收集到了点击事件. 但是由于我们没有给出任何 发生了点击事件应该做什么 的事情. 所以也就没有了反应. 我们可以通过 onclick 属性. 来给点击事件添加上具体要做什么。

<input type="button" value-"click me" onclick="fn()" />

多一个 onclick 其含义是当发生点击事件时,去执行 fn() 即我们编写的 JS 中的函数。
完整代码:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
    <script> function fn() {  
            alert("fuck you")  
        }  
    </script>  
</head>  
<body>  
    <input type="button" value="click" onclick="fn()">  
</body>  
</html>

在 HTML 中有多少种事件可以触发呢,很多很多,以下是一些常用的:

focus       获取焦点
blur        失去焦点
submit      提交表单
change      更换选项
scroll      滚动条滚动
mouseover   鼠标滑过
mouseout    鼠标滑出
mousemove   鼠标滑动

上述是一种绑定事件的方案,可以直接在 html 标签中使用 onxxx 系列属性来完成事件的绑定,同时 js 还提供以下事件绑定方案:

<input type="button" id="btn" value="别点我了">

<script>
    // 注意, 必须等到页面加载完毕了. 才可以这样
    document.querySelector("#btn").addEventListener("click", function(){
        console.log("你点我干什么?? ")
    })
</script>

document.querySelector() 给出一个 css 选择器,就可以得到一个 html 页面上标签元素的句柄,获取句柄的方式很有多,常见的有:

document.getElementById(); // 根据id的值获取句柄
document.getElementsByClassName(); // 根据class的值获取句柄

document.form的name.表单元素的name; //  document.myform.myusername;

那么,我们现在相当于可以从 html 中转到 js 了,并且在 js 中可以捕获到 html 中的内容。此时,对应的表单验证也可以完成了。

<form action="服务器地址" id="login_form">
    <label for="username">用户名:</label><input type="text" name="username" id="username"><span id="username_info"></span><br/>
    <label for="password">密码:</label><input type="text" name="password" id="password"><span id="password_info"></span><br/>
    <input type="button" id="btn" value="点我登录">
</form>
```js
<script>
    // 在页面加载的时候
    window.onload = function(){
        document.getElementById('btn').addEventListener("click", function(){

            // 清空提示信息
            document.getElementById('username_info').innerText = ""; 
            document.getElementById('password_info').innerText = "";

            let username = document.getElementById('username').value;  // 获取username标签中的value属性
            let password = document.getElementById('password').value;  // 获取密码
            let flag = true;  // 最终是否可以提交表单?
            if(!username){
                document.getElementById('username_info').innerText = "用户名不能为空";
                flag = false;
            }

            if(!password){
                document.getElementById('password_info').innerText = "密码不能为空";
                flag = false;
            }

            if (flag){
                document.getElementById('login_form').submit();
            }
        })
    }
</script>