JavaScript02: 進階
一. 變數宣告
1.1 變數提升
// 以下程式碼,或多或少會有些問題的
function fn(){
console.log(name);
var name = '大馬猴';
}
fn()
// 問題:
name變數先使用,再定義 這麼寫程式碼,在其他語言裡. 絕對是不允許的
但是在js裡,不但允許,還能執行,為什麼呢?
因為在js執行的時候,它會首先檢測整體程式碼,發現在程式碼中會有name使用
則執行時,就會自動變成這樣的邏輯:
// 變數提升的邏輯:
function fn(){
var name;
console.log(name);
name = '大馬猴';
}
fn()
console.log(a); // undefined
// 看到了麼,實際執行的時候和寫程式碼的順序可能會不一樣
這種 把變數 提升到程式碼塊第一部分執行 的邏輯,被稱為變數提升
1.2 let 宣告變數
結論一:用let宣告變數,是新版本javascript ES6 提倡的一種宣告變數的方案
// ES6提出 用let 宣告變數,防止變數提升的邏輯
function fn(){
console.log(name); // 直接報錯, let變數不可以變數提升
let name = '大馬猴';
}
fn()
結論二:在同一個作用域內. let宣告的變數只能宣告一次,其他使用上和var沒有差別
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 = "王力宏"; // 報錯,同一個作用域 let 宣告同一個變數,只能一次
console.log(name);
}
fn()
// 注意: 報錯是發生在程式碼檢查階段. 所以上述程式碼,根本就執行不了
二. 閉包函式
先看一段程式碼
let name = "周杰倫"
function chi(){
name = "吃掉"
}
chi();
console.log(name); // "吃掉"
// 發現沒有: 在函式內部修改,外部的變數是十分容易的一件事. 尤其是全域性變數. 這是非常危險的.
// 試想 寫了一個函式. 要用到name, 結果被別人寫的某個函式給修改掉了...
接下來,看一個案例:
準備兩個工具人. 來編寫程式碼. 分別是js01和js02.
// 1號工具人
var name = "alex"
// 定時器,5000秒後執行
setTimeout(function(){
console.log("一號工具人:" + name) // 一號工具人還以為是alex呢, 但是該變數是不安全的.
}, 5000);
// 2號工具人
var name = "周杰倫"
console.log("二號工具人", name);
html:
<script src="js01.js"></script>
<script src="js02.js"></script>
此時執行的結果:
很明顯, 雖然各自js在編寫時是分開的. 但是在執行時, 是在同一個空間內執行的. 他們擁有相同的作用域
此時的變數勢必是非常非常不安全的. 那麼如何來解決呢?
注意:在js裡 變數是有作用域的. 也就是說一個變數的宣告和使用是有範圍的,不是無限的
// 驗證: 變數是有作用域的
function fn(){
let love = "愛呀"
}
fn()
console.log(love)
// 直接就報錯了 也就是說. 在js裡 變數作用域是有全域性和區域性 的概念
直接宣告在最外層的變數,就是全域性變數。所有函式、所有程式碼塊都可以共享的
在函式內和程式碼塊內宣告的變數,尤其是函式內,宣告出來的變數它是一個區域性變數,外界是無法進行訪問的
我們就可以利用這一點,來給每個工具人建立一個區域性空間. 就像這樣:
// 1號工具人 都是自執行函式
(function(){
var name = "alex";
setTimeout(function(){
console.log("一號工具人:"+name)
}, 5000);
})()
// 二號工具人 都是自執行函式
!function(){
var name = "周杰倫"
console.log("二號工具人", name);
}()
執行結果
這樣,雖然解決了變數的衝突問題
但是想想. 如果在外面,需要函式內部的一些東西,來進行相關操作,怎麼辦?
比如 一號工具人要提供一個功能(加密),外界要呼叫, 怎麼辦?
// 1號工具人
// 區域性函式中,對外介面 方式一: return 返回到 全域性變數中 ===> 閉包函式
// 1.首先:全域性要使用,那js檔案中,就不能是自執行函式,要設定一個名字
let jiami = (function(){
let key = "10086" // 假裝我是秘鑰
// 我是一個加密函式
let mi = function(data){ // 資料
console.log("接下來, 我要加密了,rsa哦. 很厲害的")
console.log("秘鑰:"+key);
console.log("資料:"+data);
// 返回密文
return "我要返回密文";
}
// 2.其次:外面可能需要用到該功能. 故 需要該變數返回(暴露到全域性空間). 返回加密函式
return mi;
})();
// 區域性函式中,對外介面 方式二:藉助於window物件,將返回的變數 直接 賦值到全域性變數中
(function(){
let key = "10086" // 假裝我是秘鑰
// 我是一個加密函式
let mi = function(data){ // 資料
console.log("接下來, 我要加密了,rsa哦. 很厲害的")
console.log("秘鑰:"+key);
console.log("資料:"+data);
// 返回密文
return "我要返回密文";
}
// 對外的介面
window.mi = mi;
})();
注意:如果封裝一個加密js包的時候,就還得準備出解密的功能
並且, 不可能一個js包就一個功能吧。 那也太痛苦了(js檔案起名字),那怎麼辦?
可以返回一個物件,物件裡面可以存放好多個功能
而一些不希望外界觸碰的功能. 就可以很好的保護起來.
// 1號工具人.
let jiami = (function(){
let key = "10086" // 加裝我是秘鑰
// 該函式只屬於該模組內部,外界無法訪問. 就不返回
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. 至此. 何為閉包? 上面這個就是閉包
相信你百度一下就會知道,什麼內層函式使用外層函式變數、什麼讓一個變數常駐記憶體等等
其實細看,它之所以稱之為閉包,它是一個封閉的環境。在內部. 自己和自己玩兒
避免了對該模組內部的衝擊和改動. 避免的變數之間的衝突問題
閉包的特點:
- 內層函式對外層函式變數的使用
- 會讓變數常駐於記憶體
這倆玩意就不解釋了, 和python的閉包是一個意思。不懂沒關係,能看懂它的執行過程就好
三. JS中的各種操作(非互動)
3.1 定時器
在JS中, 有兩種設定定時器的方案
// 延時器:經過xxx時間後, 執行xxx函式
t = setTimeout(函式, 時間)
// 5000毫秒 5秒後列印我愛你
t = setTimeout(function(){
console.log("我愛你")
}, 5000);
clearTimeout(t) // 停止一個定時器
// window.clearTimeout(t)
// 定時器:每隔 xxx時間, 執行一次xxx函式
t = setInterval(函式, 時間)
// 每隔5秒鐘, 列印`我愛你`
t = setInterval(function(){
console.log("我愛你")
}, 5000)
window.clearInterval(t) // 停止一個定時器
for(let i = 0; i <= 9999; i++)window.clearInterval(i); // 清理掉所有定時器
// 定時器 關於js逆向,常遇到的:
// 1.心跳檢測 一般是用來監測使用者 是否掉線了,也可以用來監測使用者瀏覽器環境是否健康
// 這個就是 服務端 主動向 瀏覽器端 傳送請求檢測
http是被動響應的 伺服器端只能不停的間隔傳送檢測請求,才能時刻監測客戶端某個元素的狀態(是否點選 或 掃描二維碼)等
js逆向中:心跳檢測
就是正常瀏覽器是 不停的心跳檢測 且有資料返回,
js逆向程式碼時,可能只發一次,服務端就給做反爬 禁IP什麼的
// 2.無限debugger
// 無限debugger的核心
setInterval(function(){
debugger; // 設定斷點
}, 1000)
網頁頁面中,會正常顯示其他的html程式碼,但是一旦F12除錯,就會一直處理斷點中
// 解決原理:
在source原始碼中,setInderval 這一行(沒進入定時器體內之前),左鍵點選行號(設定斷點)
重新整理頁面,頁面會除錯暫停到 設定斷點這一行
再在控制檯(console)中,將定時器幹掉 (重置定時器為普通的空函式 setInterval=function(){}; )
3.2 關於時間
eg:http://www.baidu.com/s?word=jfdsaf&t=1640090719637 引數t就是時間戳
var d = new Date(); // 獲取當前系統時間
var d = new Date("2018-12-01 15:32:48"); // YYYY-MM-DD HH:mm:ss得到一個具體時間
// 時間格式化
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;
// 時間戳 表示從1970-1-1 00:00:00 到現在一共經過了多少毫秒
var d = new Date();
console.log(d.getTime())
// 注意1:python的時間戳 單位是秒
import time
print( int(time.time() * 1000) ) # 擴大一千倍,再取整
// 注意2: 有些時候,前端例項化日期物件時,會少一個呼叫括號
var d = new Date; // 坑人寫法,也可以
console.log(d.getTime())
3.3 eval函式(必須會)
http://tools.jb51.net/password/evalencode 一個線上JS處理eval的網站. 大多數的eval加密. 都可以搞定了.
// eval:
可以動態的把字串,當成js程式碼執行 // 從功能上講非常簡單,和python裡面的eval是一樣的
s = "console.log('我愛你')";
eval(s);
// 重點:eval加密
擴充使用 --> 前端利用eval的特性來完成反爬操作
// 解決核心:
eval函式,裡面傳遞的應該是 即將要執行的 程式碼(字串)
// eg:
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()裡面的東西複製出來
執行一下 // 注意:一般是個自執行函式,直接貼上會報錯 故加個括號 eg: (eval中的程式碼)
// 或 和下面截圖一樣 賦值給變數,再列印變數
最終一定會得到一個字串,要不然eval()執行不了的
3.4 prototype 原型物件
prototype 原型物件,作用是 給類增加功能擴充套件 的一種模式
寫個物件導向來看看.
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();
幾個重要概念
3.4.1 構造器
構造一個物件的函式. 叫構造器.
function People(){ // People 就是構造器 constractor
}
var p = new People(); // 呼叫構造器 ---> 物件
p.constractor == People; // true
3.4.2 原型物件
每一個js物件中,都有一個隱藏屬性__proto__
,指向該物件的 原型物件
在執行該物件的方法或者查詢屬性時,首先, 物件自己(構造器中宣告的)是否存在該屬性或者方法
如果存在, 就執行自己的. 如果自己不存在. 就去找 原型物件
function Friend(){
this.chi = function(){
console.log("我的朋友在吃");
}
};
// 指定Friend 的原型物件
Friend.prototype = {
chi: function(){
console.log("我的原型在吃")
}
};
// 或者這種寫法
Friend.prototype.chi = function(){
console.log("我的原型在吃")
};
f = new Friend();
f.chi(); // 執行結果: 我的朋友在吃
// 屬性查詢順序:
先查詢該物件(構造器中宣告的)中 是否有chi這個方法
再找,它的原型物件上 是否有chi這個方法
// 總結: !!!
Friend // 構造器
f // 物件
f.__proto__ <===> Friend.prototype // 構造器的prototype屬性 和 物件的 __proto__,都是指向f物件 的 原型物件
3.4.3 原型鏈
原型鏈(prototype chain):是屬性查詢的方式
當呼叫一個物件的屬性時,如果物件沒有該屬性,從物件的原型物件上去找該屬性,
如果原型上也沒有該屬性,那就去找原型的原型,直到最後返回null為止,null沒有原型。
// 前提:
每個物件身體裡. 都隱藏著 __proto__屬性 也就是它的 原型物件
同時 原型物件 也是物件, 也就是說 原型物件 也有 __proto__ 屬性
類似於.....這樣:
f.__proto__.__proto__ ===> Friend.prototype.__proto__ ===> Object.prototype // Object物件的原型
列印出來的效果是這樣的:
// 故:在執行 f.toString() 的時候不會報錯. 可以正常執行的原因,就在這裡
執行過程:
先找 f物件 中是否有 toString 沒有
找它的 原型物件,原型物件 中沒有
繼續找 原型物件的原型物件
直至找到Object的原型為止,如果還沒有,就報錯了.
f.hahahahahahah() // 報錯
// 綜上:
原型鏈是js 方法查詢的路徑指示標
3.4.4 原型鏈的延伸使用
用原型鏈能做什麼? 網站反爬(噁心): 有些頁面網站,透過原型鏈,讓你F12時,一直無限debug
看一段神奇的程式碼
(function(){debugger})(); // 這樣一段程式碼可以看到. 瀏覽器進入了debugger斷點.
// 這段程式碼的背後是什麼呢?
// 注意:
在js程式碼執行時,每一個function的物件,都是透過Function()來建立的
也就是說 函式是Function()的物件
// 校驗:
function fn(){}
console.log(fn.__proto__.constructor); // fn函式的原型 構造器是 ƒ Function() { [native code] }
// 所以:函式就是Function的物件. 那麼,我們也可以透過Function來構建一個函式.
new Function('debugger')(); // 效果一樣的.
OK. 這東西對我們來說有什麼用. 上程式碼
<!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 xxxx = Function.prototype.constructor; Function.prototype.constructor = function(code){ console.log("i love you"); if (code != 'debugger'){ return new xxxx(code); } else { return; } }
更加詳細的hook. 下節課會講.
3.5 window物件
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 中有很多功能物件,還可以控制頁面跳轉
window.location.herf = "新地址" // 當前視窗 跳轉到新地址url
綜上:全域性變數可以用window.xxx來表示
ok. 接下來注意看. 我要搞事情了
(function(){
let chi = function(){
console.log("我是吃")
}
window.chi = chi
})();
chi()
// 換一種寫法. 你還認識麼?
(function(w){
let chi = function(){
console.log("我是吃")
}
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("吃了麼");
3.6 call和apply
對於逆向工程師而言,並不需要深入的理解call和apply的本質作用.,只需要知道這玩意執行起來的邏輯順序是什麼即可
在執行時,正常的js呼叫:
function People(name, age){
this.name = name;
this.age = age;
this.chi = function(){
console.log(this.name, "在吃東西")
}
}
p1 = new People("alex", 18);
p2 = new People("wusir", 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, "在吃", what_1, what_2);
}
}
p1 = new People("alex", 18);
p2 = new People("wusir", 20);
p1.chi("饅頭", "大餅");
p2.chi("大米飯", "金坷垃");
function eat(what_1, what_2){
console.log(this.name, "在吃", what_1, what_2);
}
// call的語法是: 函式.call(物件, 引數1, 引數2, 引數3....)
// 執行邏輯是: 執行函式. 並把物件傳遞給函式中的this. 其他引數照常傳遞給函式
eat.call(p1, "查克拉", "元宇宙");
apply和他幾乎一模一樣. 區別是: apply傳遞引數要求是一個陣列
eat.apply(p1, ["苞米茬子", "大餅子"]);
3.7 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) => {}
3.8 ES6中的promise(難)
具體執行過程和推理過程. 請看影片. 這裡很饒騰.
function send(url){
return new Promise(function(resolve, reject){
console.log("我要傳送ajax了", url)
setTimeout(function(){
console.log("我傳送ajax回來了")
// 成功了, 要去處理返回值
resolve("資料", url);
}, 3000);
});
}
send("www.baidu.com").then(function(data){
console.log("我要處理資料了啊", data);
return send("www.google.com");
}).then(function(data, url){
console.log("我又來處理資料了", data);
});
3.9 逗號運算子
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:"alex", "a":a};
}
let r = s();
console.log(r); // {name: 'alex', a: 111}
3.10 三元運算子
// 三元運算子
條件?值1:值2 // 條件成立時,返回 ?後面的 反之,返回 :後面的
let a = 10;
let b = 20;
let d = a > b? a: b ;
console.log(d); // 20
看一個噁心的:
let a = 10;
let b = 20;
let d = 17;
let c = 5;
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);
3.11 JS hook
hook又稱鉤子,可以在呼叫系統函式之前,先執行我們的函式. 鉤子函式
例如:hook eval
eval_ = eval; // 先儲存系統的eval函式
eval = function(s){
console.log(s);
debugger;
return eval_(s);
}
eval()
eval.toString = function(){return 'function eval() { [native code] }'} // 可能會被檢測到, 用這種方案來進行
對Function的hook, 主要為了解決無限debugger
fnc_ = Function.prototype.constructor;
Function.prototype.constructor = function(){
if(arguments[0]==='debugger'){
return;
} else {
return fnc_.apply(this, arguments);
}
}
上面都是hook的系統函式. 但有時,需要hook某個屬性. 此時應該怎麼辦?
var v;
Object.defineProperty(document, "cookie", {
set: function(val) {
console.log("有人來存cookie了");
v = val;
debugger;
return val;
},
get() {
console.log("有人提取cookie了");
debugger;
return v;
}
});
剩下的,就不再贅述了.
在逆向時, 常用的主要有: hook eval 、hook Function 、hook JSON.stringify、JSON.parse 、hook cookie、hook window物件
四. JS和HTML互動(選修)
在HTML中,可以直接在標籤上給出一些事件的觸發
例如:頁面上的一個按鈕
<input type="button" value="點我就愛你"/>
我們能夠知道,該標籤在頁面中會產生一個按鈕,但是該按鈕無論如何進行點選. 都不會觸發任何事件
但此時, 人家其實觸發了. 只是你沒處理而已. 在點選該按鈕的時候. 瀏覽器其實收集到了點選事件.
但是由於我們沒有給出任何 發生了點選事件應該做什麼 的事情. 所以也就沒有了反應.
可以透過onclick
屬性. 來給點選事件新增上具體要做什麼
<input type='button' value="點我就愛你" onclick="fn()" />
當發生點選事件時去執行fn(). fn() 是什麼? fn就是我們javascript的一個函式.
完整程式碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function fn(){
alert("臭不要臉")
}
</script>
</head>
<body>
<input type="button" value="點我就愛你" onclick="fn()">
</body>
</html>
至此, 成功實現了 從HTML中呼叫JS
那麼在HTML中有多少種事件可以觸發呢? 非常多....記住幾個就好了
// html中的事件
click 點選事件
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的值 獲取控制代碼
// <form name='myform'><input type="myusername"/></form>
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>
<script>
// 在頁面載入的時候
window.onload = function(){
// let btnEle = document.getElementById('btn')
// btnEle.onclick = 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>