变量提升(这玩意简直就是编程里的奇葩)
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;解决方案如下:
- 找到断点出. 右键-> never pause here;
- 写 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);
}
}
神奇的 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
对于做爬虫的逆天工程师而言,不需要理解 call
和 apply
的本质作用,只需要知道这玩意执行起来的逻辑顺序就可以了。
在运行时,正常的 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);
三元运算符(折磨)
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>
评论