算法還原
白盒還原
- 直接扣算法, 或者是標准算法
- 理解對方 js 的意思, 能夠翻譯成其他語言, 可能會占用較長的分析時間
黑盒還原
- 直接整體調用(加密復雜, 更新頻繁, 整體關聯度高)
- 不需要關注算法邏輯, 需要模擬瀏覽器環境, 需要對抗環境檢測
rpc 調用
- 算法復雜度高, 瀏覽器環境難以模擬
- 找到算法位置, 暴露出來, 直接 rpc 調用, 需要保證瀏覽器狀態(內存泄漏, 保活)
瀏覽器自動化
- 無法逆向
- 接近真人, 但是有大量的自動化痕跡;
基本數據類型
- 數值 (Number): 整數和小數
- 字符串 (String): 文本
- 布爾值(Boolean): 布爾值, true 表示真, false 表示假
- undefined: 未定義, 或者不存在
- null: 表示空值
- 對象 (Object): 各種值組成的集合
原始類型
- 數值
- 字符串
- 布爾值
原始類型就是最基本的數據類型, 不能再進行細分;
undefined 和 null 一般看成是兩個特殊值;
合成類型
- 對象
- 狹義的對象 Object
- 數組 Array
- 函數 Function
一個對象往往是由多個類型的值組成, 可以看成是一個存放各種值的容器
查看類型
- typeof: 返回一個值的數據類型
- instanceof: 表示對象是否是某個構造函數的實例
- Object.prototype.toString
typeof
可以用來檢查一個未聲明的變量, 而不報錯;
JS
// 基本數據類型
var tmp1 = "字符串";
var tmp2 = 1;
var tmp3 = 1.1;
var tmp4 = true;
// 特殊類型
var tmp5= undefined;
var tmp6 = null; // null 是一個 object
// 對象
var tmp7 = {}; // 對象
var tmp8 = []; // 數組
var tmp9 = function(){}; // 函數
console.log("typeof(tmp1)", typeof (tmp1));
console.log("typeof(tmp2)", typeof (tmp2));
console.log("typeof(tmp3)", typeof (tmp3));
console.log("typeof(tmp4)", typeof (tmp4));
console.log("typeof(tmp5)", typeof (tmp5));
console.log("typeof(tmp6)", typeof (tmp6));
console.log("typeof(tmp7)", typeof (tmp7));
console.log("typeof(tmp8)", typeof (tmp8));
console.log("typeof(tmp9)", typeof (tmp9));
// function 因為有類object 的操作, 所以也屬於 object
console.log("tmp9.name", tmp9.name); // 獲取函數名
//typeof(tmp1) string
//typeof(tmp2) number
//typeof(tmp3) number
//typeof(tmp4) boolean
//typeof(tmp5) undefined
//typeof(tmp6) object
//typeof(tmp7) object
//typeof(tmp8) object
//typeof(tmp9) function
//tmp9.name tmp9
null undefined 和布爾值
null 和 undefined 的區別
- null 表示一個空對象, undefined 表示未定義;
- null 轉為數值的時候為 0, undefined 轉為數值的實收為 NaN;
boolean
布爾值表示真和假, true 表示真, false 表示假;
下列運算符會返回布爾值:
- ! (not)
- 相等運算符: =, , !, !=
- 比較運算符: >=, <=, <, >
表示 false 的值
在自動數據轉換中, 下列值會表示 false:
- undefined
- null
- false
- 0
- NaN
- “” 或者 ‘’ 空字符串
其他的值都會被當成 true;
空數組[]和空對象{}對應的布爾值都是 true;
數值
在 js 中, 所有的數值都是 64 位浮點數的形式進行存儲的, 也就是說在 js 底層, 沒有整數只有浮點數;
因為浮點數精度的問題, js 在進行浮點數運算的時候經常會出現問題:
JS
console.log(0.1 + 0.2);
// 0.30000000000000004
進制
- 十進制: 沒有前導, 直接用數值表示
- 八進制: 有前綴 0o 或者 0O
- 十六進制: 有前綴 0x 或者 0X
- 二進制: 有前綴 0b 或者 0B
默認情況下, js 內部會將八進制, 十六進制, 二進制轉為十進制;
NaN
NaN 是 js 中的特殊值, 表示非數字(Not a Number), 主要出現在字符串解析成數字出錯的時候;
- NaN 不是獨立的數據類型, 它是一個特殊值, 它的數據類型依然是 Number
- NaN 不等於任何值, 包括它本身 (不等於本身可以用來檢測某個值是否是 NaN)
- NaN 和任何數運算, 得到的結果都是 NaN;
Infinity
Infinity 用來表示無窮, 一般出現在兩種場景下:
- 正數的數值太大, 或者負數的數值太小;
- 非 0 的數除以 0, 得到 Infinity
- js 中數值正向溢出或者負向溢出或者被 0 除都不會報錯, 所以單純的數學運算幾乎沒有可能拋出異常
- Infinity 大於一切數值(除了 NaN); -Infinity 小於一切數值(除了 NaN)
- Infinity 和 NaN 比較, 總是返回 false;
全局 api
parseInt(string[,radix])
parseInt(string[,radix])
將字符串解析成數值; 如果入參非字符串, 則會調用入參的toString
方法轉換為字符串再進行轉換; 如果設置了第二個參數 radix, 則會將字符串按指定的 radix 進制轉換成十進制; 返回數值或者 NaN
JS
var a = '0xf';
console.log(a, 16); // 將 16 進制的 0xf 轉為十進制
// 15
parseFloat(string)
parseFloat(string)
將字符串入參解析成浮點數; 返回浮點數或者 NaN ;
JS
var a = "4.567";
console.log(parseFloat(a)); // 4.567
// 當入參有非法字符, 則只保留合法部分進行轉換
var b = "4.567abcd";
console.log(parseFloat(b)); // 4.567
// 第二個小數點忽略
var c = "1.2.3";
console.log(parseFloat(c)); // 1.2
// 起始為非法字符則直接返回 NaN
var d = "aaa1.2"; // NaN
console.log(parseFloat(d));
isNaN()
判斷某個入參是否是 NaN; 可以利用 NaN 的不等性來進行判斷
JS
var a = NaN;
if (a != a ){
console.log("it is NaN");
}
isFinite()
判斷某個入參是否是 Infinity;
字符串
- 字符串和數組相似, 都支持使用[]運算符來通過指定索引來獲取值;
- length: 可以獲取字符串的長度
- 字符串不能通過[]運算符和索引來修改字符串的值
字符集
js 中使用的字符集為 Unicode 字符集, 所有的字符串都使用 Unicode 表示;
JS
var f\u006F\u006F = 'abc';
console.log(f\u006F\u006F);
base64 轉碼
- 瀏覽器:
- btoa(): 任意值轉為 base64 編碼
- atob(): base64 值解碼
- 非 ASCII 碼(如中文)要轉碼之后再 base64;
encodeURIComponent
decodeURIComponent
- Nodejs
var base64encode = Buffer.from("js").toString("base64");
var base64decode = Buffer.from(base64encode,'base64').toString();
對象
- 對象就是一組鍵值對 key-value 的集合, 是一種無序的復合數據集合
- 對象的每個鍵名又稱為屬性 property; 它的值可以是任意數據類型;
- 如果一個對象的某個屬性的值是函數, 則通常將這個屬性稱為該對象的方法, 可以像函數一樣調用這個方法;
- 屬性可以動態創建, 不必在對象聲明的時候就全部定義;
對象引用
- 如果不同的變量名指向同一個對象, 那么他們都是對這個對象的引用; 也就是說這些變量都指向了同一個內存地址, 修改其中任意一個的值都會影響到其他的變量;
- 如果取消某一個變量對於原對象的引用, 不會影響到其他引用該對象的變量
- 這種引用只局限於對象, 如果兩個變量指向同一個原始類型的值, 這些變量都是對原始類型的值的拷貝, 改變任意變量都不會影響其他變量
屬性查看
Obejct.keys()
可以查看該對象自身的屬性, 繼承來的屬性無法查看
JS
var a = {
hello: function(){
consoel.log("hi");
},
table: [1, 2, 3],
name: "kevin",
age: 21,
married: false
}
console.log(Object.keys(a));
//[ 'hello', 'table', 'name', 'age', 'married' ]
屬性刪除
- delete 用於刪除對象的屬性, 刪除成功后返回 true
- 刪除一個不存在的屬性, delete 不會報錯, 而且返回 true
- 只有一種情況, delete 命令會返回 false; 那就是刪除一條存在的屬性, 但是這條屬性被定義成不能刪除; (定義該屬性不能刪除 defineProperty)
- 只能刪除對象自身的屬性, 繼承來的屬性不能刪除
JS
var a = {
hello: function(){
consoel.log("hi");
},
table: [1, 2, 3],
name: "kevin",
age: 21,
married: false
}
delete a.age, delete a.hello;
console.log(Object.keys(a));
// [ 'table', 'name', 'married' ]
屬性存在判斷
- in 運算符可以用於檢查對象是否包含某個屬性; 檢查的是鍵名, 存在這個屬性返回 true, 不存在則返回 false;
hasOwnProperty()
: 判斷某個屬性是否是該對象自身的屬性;
JSvar a = { hello: function(){ consoel.log("hi"); }, table: [1, 2, 3], name: "kevin", age: 21, married: false}console.log(a.hasOwnProperty('table'));if ("table" in a) { console.log("table is property of a");}
屬性遍歷
- for in 循環可以用於遍歷對象的全部屬性; 不僅可以遍歷對象自身的屬性, 還可以遍歷對象繼承來的屬性;
- 如果只想遍歷對象自身的屬性, 可以配合
hasOwnProperty()
進行篩選
JS
var a = {
hello: function(){
consoel.log("hi");
},
table: [1, 2, 3],
name: "kevin",
age: 21,
married: false
}
for (const aKey in a) {
// 使用 hasOwnProperty 進行篩選
if (a.hasOwnProperty(aKey)) {
console.log(aKey);
}
}
函數
函數聲明
js 中函數聲明有三種方式
- 使用 function 申明
function a(){}
- 函數表達式
var a = function(){}
- Function 構造函數
var a = Function("a","b", "return a+b")
或者var a = new Function("a","b", "return a+b")
這兩種方式效果一樣
函數是一等公民
js 將函數看成是一個值, 與其他數據類型一樣, 凡是可以使用其他數據類型的地方都可以使用函數, 例如:
- 可以將函數賦值給變量或者對象的屬性
- 可以將函數作為參數傳遞給其他函數
- 可以將函數作為其他函數的返回值
函數變量名提升
js 中全局變量名存在變量提升, 函數內部的局部變量也存在變量提升;
JSfunction outer(){ console.log(a); // undefined 說明全局變量存在變量提升 console.log(b); // undefined 說明局部變量存在變量提升 var b = 2;}outer();var a = 1;
js 中函數的聲明也存在變量提升, 可以先調用該方法, 再定義該方法
JSb();function b(){ console.log("b called");}
函數的屬性和方法
- name (屬性): 返回函數的名字
- length(屬性): 返回函數預期傳入的形參數量
- toString()方法: 返回函數的字符串源碼
函數作用域
- 作用域 scope 是指變量存在的范圍
- es5 中 js 只有兩個作用域
- 全局作用域: 變量在整個程序中一直存在, 所有地方都可以讀取到該變量
- 函數作用域: 變量只在函數內部存在
- es6 中新增了塊級作用域
- es5 中 js 只有兩個作用域
- 函數外部聲明的變量就是全局變量, 它可以在函數內部讀取到;
- 在函數內部聲明的變量就是局部變量, 函數外部無法讀取;
- 函數本身的作用域就是其聲明時所在的作用域, 與其運行時的作用域無關;
- 函數內部聲明的函數, 作用域綁定函數內部(閉包)
函數參數省略
js 中函數的參數不是必須的, 允許省略函數的參數;
函數的 length 屬性只和函數聲明時形參的個數有關, 和實際調用時傳入的參數個數無關;
參數傳遞方式
- 函數的參數如果是原始數據類型 (數值, 字符串, 布爾), 參數傳遞使用按值傳遞的方式, 在函數內部修改參數的值不會影響函數外部
- 如果函數的參數是復合數據類型(數組, 對象, 其他函數), 參數的傳遞方式是按址傳遞, 傳入的是引用的地址, 因此在函數內部修改參數, 會影響到原始值;
arguments 對象
- 因為 js 允許函數有不定數目的參數, 所以需要在函數體的內部可以讀取到所有參數, 這就是 arguments 對象的由來
- arguments 對象包含了函數運行時的所有參數, 這是一個類數組對象,
arguments[0]
就是第一個參數; - arguments 對象只能在函數內部使用
- arguments.length 可以獲取函數調用時入參的真正個數
- arguments.callee 屬性可以獲取對應的原函數
- arguments 對象是一個類數組對象, 如果要讓他使用真正的數組方法, 需要將 arguments 轉換成數組:
Array.prototype.slice.call(arguments)
- 新建數組, 遍歷 arguments 將元素 push 到新數組中;
閉包
- 要理解閉包首先要理解 js 的作用域; 前面提到的 js 在 es5 中只有兩種作用域:
- 全局作用域
- 函數作用域
- 在函數的內部可以全局作用域的變量
- js 中特有的鏈式作用域結構, 子級會向上一級一級尋找所有父級的變量, 父級的所有變量對於子級來說都是可見的, 反之不成立;
JSvar a = 1;var b = 2;function f(){ var b = 3; console.log(a,b); function f1(){ var b = 4; console.log(a,b); } f1();}f();// 1 3// 1 4
鏈式作用域查找
子級會優先使用自己的作用域, 如果變量存在則使用, 不存在則會依次向上尋找, 直至全局作用域;
閉包定義
- 閉包可以簡單理解成定義在一個函數內部的函數
- 閉包最大的特點就是它可以記住自己誕生的環境, 本質上, 閉包就是將函數內部和函數外部連接起來的橋梁;
- 閉包最大的用處有兩個
- 可以直接讀取到外層函數內部的變量
- 可以讓這些變量始終保存在內存中, 閉包讓自己誕生的環境一直存在
通過閉包實現簡單的計數器
JSfunction count(){ var count = 0; function f(){ count++; console.log("count", count); } return f;}f = count();f();f();f();
立即調用函數表達式
js 中有三種立即調用函數的方式
var f = function(){}();
(function(){}())
(function(){})()
通常情況下, 只對匿名函數使用這種立即執行的表達式, 這樣有兩個目的:
- 不必為函數命名, 避免污染全局環境
- 立即調用函數的內部會形成單獨的作用域, 可以封裝一些外部無法讀取的私有變量
eval 命令
- eval 可以接受一個字符串, 並將字符串當做代碼執行
- eval 沒有自己的作用域, 都是使用當前運行的作用域, 所以 eval 會修改當前作用域下的變量的值
- eval 的本質是在當前作用域中, 注入代碼, 經常用於混淆和反爬
eval 別名調用
eval 的別名調用在 nodejs 下無法跑通, 需要在瀏覽器下運行;
需要注意, 在 eval 通過別名調用的時候, 作用域永遠是全局作用域;
JSvar a = 1;var e = eval;(function(){ var a = 2; e("console.log(a);"); // eval 在別名調用的時候使用全局作用域}())
數組
- 數組 Array 是按次序排列的一組值
- 每個值的位置都有對應的索引
- 數組使用 [ ] 來表示
- 任何類型的數據都可以放入數組中
- 本質上, 數組是特殊的對象, typeof 查看數組的類型返回的是 object
- Object.keys() 可以返回數組的鍵名(索引)
數組的屬性
- length: 表示數組的元素個數, 這個屬性是可寫的, 可以直接修改數組的 length 屬性, 來實現清空數組或刪除數組中元素的效果
- 數組本質上是特殊的對象, 支持使用點操作符對數組添加屬性;
JSvar a = [1, 1.1, true, {}, [], "hello", null, undefined];console.log("a.length", a.length);a.name = "add a.name property";for (const aKey in a) { console.log(aKey, a[aKey]);}console.log("Object.keys(a)", Object.keys(a));a.length = 0;console.log("a", a);console.log("a['name']", a['name']);console.log("a.name", a.name);console.log("a[0]", a[0]);
數組循環
- for in 循環
- for 循環
- while 循環
- forEach : 只有數組才有該方法; 該方法接受一個回調函數, 回調函數入參為 value 和 key;
JSvar a = [1, 1.1, true, {}, [], "hello", null, undefined];// for infor (const aKey in a) { console.log("aKey:", aKey, "value:", a[aKey]);}console.log("-------------------------------")// forfor (var i = 0; i <= a.length; i++) { console.log("index:", i, "value:", a[i]);}console.log("-------------------------------")// whilevar index = 0;while (index <= a.length) { console.log("index:", index, "value:", a[index]); index++;}console.log("-------------------------------")// forEacha.forEach(function (value,key) { console.log("key:", key, "value:", value);})
數組空值
js 中的數組支持空值, 出現空值時會占用該索引位, 但是遍歷的時候不會遍歷該索引的值
JSvar a = [1, 2, 3, , 5];a.forEach(function (value,key) { console.log("key", key, "value", value);})// key 0 value 1// key 1 value 2// key 2 value 3// key 4 value 5
類數組對象
- 如果一個對象的所有鍵名都是正整數或者 0, 且有 length 屬性, 那么這個對象就是類數組對象
- 典型的類數組對象有 arguments 對象, 字符串, 以及大部分的 dom 元素集
- 數組的 slice 方法可以將類似數組的對象變成真正的數組
var arr = Array.prototype.slice.call(arrayLike);
- 除了將類數組對象轉成真正的數組, 還可以使用 call() 將數組的方法直接放到類數組對象上使用
Array.prototype.forEach.call(arrayLike, function(){});
數據類型轉換
自動數據類型轉換
其他類型轉字符串
當+
加號作為操作符, 且操作數中含有字符串時, 會自動將另一個操作數轉為字符串;
規則如下:
- 字符串+基礎數據類型: 會直接將基礎數據類型轉為和字面量相同的字符串
- 字符串+復合數據類型: 復合數據類型會先調用
valueOf()
方法, 如果該方法返回基礎數據類型則將其轉為字符串, 如果返回的是復合數據類型, 則調用toString()
方法, 如果返回的是基礎數據類型則將其轉為字符串, 如果不是則報錯;
JS
// 基礎類型
// 自動數據類型轉換
// + 字符串
// 1. 字符串 + 基礎數據類型: 會直接將基礎數據類型轉為和字面量相同的字符串
var tmp1 = "" + 3;
console.log("tmp1", tmp1); // tmp1 "3"
var tmp2 = "" + true;
console.log("tmp2", tmp2); // tmp2 "true"
var tmp3 = "" + undefined;
console.log("tmp3", tmp3); // tmp3 "undefined"
var tmp4 = "" + null;
console.log("tmp4", tmp4); // tmp4 "null"
// 字符串+復合數據類型: 復合數據類型會先調用 valueOf 方法, 如果此方法返回的是引用類型, 則再調用 toString()方法, 最后將返回值轉為字符串類型
var tmp5 = [1, 2, 3] + "";
console.log("tmp5", tmp5); // tmp5 1,2,3
var tmp6 = {} + "";
console.log("tmp6", tmp6); // tmp6 [object Object]
// 重寫 toString 方法
var o = {
toString: function () {
return 1;
}
}
var tmp7 = o + "";
console.log("tmp7", tmp7) // tmp7 "1"
// 重寫 valueOf 方法
o.valueOf = function () {
return 2;
}
var tmp8 = "" + o;
console.log("tmp8", tmp8); // tmp8 2
var a = {
valueOf: function () {
return {}
},
toString: function () {
return "toString"
}
};
console.log("" + a); // toString
其他類型轉布爾值
數值轉布爾值
數值在邏輯判斷條件下會自動轉成布爾值; +-0 和 NaN 為 false, 其他數值都是 true;
JS
if (0) {
console.log("0 is true");
}else{
console.log("0 is false");
}
if (NaN) {
console.log("NaN is true");
}else{
console.log("NaN is false");
}
if (-0) {
console.log("-0 is true");
} else {
console.log("-0 is false")
}
// 0 is false
// NaN is false
// -0 is false
字符串轉布爾值
空字符串””或者’’為 false, 其他都是 true
undefined 和 null 轉布爾值
undefined 和 null 轉為布爾值都是 false
對象轉布爾值
只有對象為 null 或者 undefined 時, 轉為布爾值才是 false; 其他情況下(包括空對象{}和空數組[])轉為布爾值都是 true;
JS
var o = {};
if(o){
console.log("{} is true");
}else{
console.log("[] is true");
}
// {} is true ; 空數組[] 同理
其他類型轉為數值
一元操作符+
和-
都會觸發其他類型轉為數值;
數學運算符操作的兩個操作數都不是字符串時, 也會觸發其他類型轉為數值的操作;
轉化規則如下:
- 字符串轉數值: 空字符串轉為 0, 非空字符串轉為對應的數字; 不合法則返回 NaN;
- 布爾值轉數值: true 為 1, false 為 0;
- null 轉為數值為 0
- undefined 轉數值為 NaN
- 對象轉數值會先調用
valueOf()
方法, 如果其返回值是基礎數據類型, 則將其轉為數值返回, 如果是復合數據類型, 則會調用toString()
方法, 再將其轉為數值, 如果不合法則返回 NaN;
強制類型轉換
- Number(): 將其他類型轉為數值; 將對象轉為數值時, 會先調用對象的
valueOf()
方法, 不滿足條件再調用toString()
方法 - String(): 將其他類型轉為字符串; 將對象轉為字符串時, 會先調用對象的
toString()
方法, 不滿足條件時再調用對象的valueOf()
方法; - Boolean(): 將其他類型轉為布爾值
異常處理
Error 對象
Error 對象通常包含常用的三個屬性, 且必須包含 message 屬性;
- name: 異常名
- message: 異常提示信息
- stack: 異常調用棧信息
JS
var e = new Error("自定義異常觸發");
e.name = "自定義異常名稱";
console.log(e.name);
console.log(e.message);
console.log(e.stack);
/*
自定義異常名稱
自定義異常觸發
自定義異常名稱: 自定義異常觸發
at Object.<anonymous> (/Users/zhangyang/codes/antiJs/js_learn/_03_基本數據類型(下)/tmp.js:201:9)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
at internal/main/run_main_module.js:17:47
*/
try catch finally
手動拋出異常並捕獲
JS
try {
var e = new Error("message 自定義異常");
e.name = "自定義異常名稱";
throw e;
} catch (e) {
console.log("e.name", e.name, "e.message", e.message);
console.log("e.stack", e.stack);
}
throw
throw
語句用來拋出用戶定義的異常, 當前函數的執行將被停止, throw
之后的代碼不會被執行; 並且代碼將會進入調用棧中第一個catch
塊中; 如果沒有catch
塊, 程序將會終止;
JStry{ console.log("before throw error"); throw new Error("throw error"); console.log("after throw error");}catch(e){ console.log("catch error", e.message);}// before throw error// catch error throw error
try/catch/finally
try 的三種聲明形式:
- try…catch
- try…finally
- try…catch…finally
try/catch 主要用於捕獲異常, try/catch 語句包含一個 try 塊, 和至少一個 catch 塊或者一個 finally 塊;
-
try: try 塊中放入可能會產生異常的語句或者函數
-
catch: catch 塊中包含要執行的語句, 當 try 塊中拋出異常時, catch 塊會捕獲這個異常, 並執行 catch 塊中的代碼; 如果 try 塊中沒有異常拋出, 則 catch 塊會被跳過;
-
finally: finally 塊在 try 塊和 catch 塊之后執行, 無論是否有異常產生, finally 塊都會執行; 當 finally 塊中有異常產生時, 會覆蓋掉 try 塊中的異常信息;
JS try { try { throw new Error("error1"); } finally { throw new Error("error2"); } } catch (e) { console.log("e.message", e.message); } // e.message error2
-
如果 finally 塊中返回一個值, 那么這個值將會作為整個 try/catch/finally 的返回值, 無論在 try 和 catch 有沒有 return, 都是返回 finally 中的值;
JS function test() { try { throw new Error("error1"); return 1 }catch (e) { throw new Error("error2"); return 2; }finally { return 3; } } console.log("test()", test()); // test() 3
對象詳解和 hook
Object 靜態方法和實例方法
- js 中所有其他對象都是繼承自 Object ; 即所有對象都是 Object 的實例;
- Object 的原生方法分為兩類: Object 本身的方法和 Object 的實例方法
- Object 本身的方法: 直接定義在 Object 上的方法, 相當於 java 的靜態方法
- Object 的實例方法: 定義在 Object 的原型對象 Object.prototype 上的方法; 相當於 java 中的動態方法, 可以被 Object 對象直接使用
Object 的靜態方法
所謂靜態方法, 就是設置在 Object 類上的方法;
- Object.keys(): 遍歷對象的屬性, 返回可枚舉的屬性名; 這個方法只能查看對象自身的屬性, 繼承來的屬性無法查看
- Object.getOwnpeopertyNames(): 遍歷對象的屬性, 可以返回不可枚舉的屬性名;
- Object.getOwnPropertyDescriptors(): 獲取對象所有屬性的描述對象;
- Object.defineProperty(): 通過描述對象, 定義某個屬性, 可以重寫 get 和 set 方法來進行 hook
- Object.defineProperties(): 通過描述對象, 定義多個屬性, 可以重寫 set 和 get 方法來進行 hook
- Object.getPrototypeOf(): 返回參數對象的原型, 可以用於環境監測;
- Object.setPrototypeOf(): 為參數對象設置原型, 返回該參數對象; 它接收兩個參數, 第一個是現有對象, 第二個是原型對象;
JS
function Test(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
};
var test = new Test(1, 2, 3);
Object.defineProperty(test,"d",{
configurable: false, // 是否可以刪除 默認 false 不可刪除
enumerable: false, // 是否是可迭代字段
value: "4",
writable: true,
// 使用Object.defineProperty() 定義對象屬性時,如已設置 set 或 get, 就不能設置 writable 和 value 中的任何一個了
// get: function(){
// return this.d;
// },
// set: function (value) {
// this.d = value;
// return this.d;
// }
})
// 遍歷對象的屬性, 返回可枚舉的屬性名, 僅可查看變量本身的屬性, 不能查看繼承來的屬性
console.log(Object.keys(test)); // [ 'a', 'b', 'c' ]
// 遍歷對象的屬性, 可以返回不可枚舉的類型
console.log(Object.getOwnPropertyNames(test)); // [ 'a', 'b', 'c', 'd' ]
// 獲取對象的所有屬性的描述對象
console.log(Object.getOwnPropertyDescriptors(test));
/*
* {
a: { value: 1, writable: true, enumerable: true, configurable: true },
b: { value: 2, writable: true, enumerable: true, configurable: true },
c: { value: 3, writable: true, enumerable: true, configurable: true },
d: {
value: '4',
writable: true,
enumerable: false,
configurable: false
}
}
*/
// 設置對象的原型
Object.setPrototypeOf(test, Test);
// 獲取對象的原型對象, 可以用於原型檢測
console.log(Object.getPrototypeOf(test));
Object 的實例方法
- Object.prototype.valueOf(): 返回當前對象對應的值
- Object.prototype.toString(): 返回當前對象對應的字符串形式
- 因為實例對象可能自定義了 toString 方法, 覆蓋了 Object.prototype.toString 方法; 所以為了得到類型字符串, 最好使用 Object.prototype.toString 方法, 通過 call 的方式可以在任意對象上調用這個方法, 幫助判斷;
- Object.prototype.hasOwnProperty(): 判斷某個屬性是否是當前對象自身的屬性, 可以區分某個屬性是對象自身的還是繼承來的;
- Object.prototype.isPrototypeOf(): 判斷當前對象是否是另一個對象的原型;
- Object.prototype.propertyIsEnumerable(): 判斷某個屬性是否是可枚舉的
構造函數
- 典型的面向對象的變成語言都有類的概念, 所謂類就是對象的模板, 對象就是類的實例;
- js 不是基於類的, 而是基於構造函數和原型鏈;
- 構造函數就是一個簡單的函數, 當函數和 new 關鍵字配合使用, 就可以返回一個實例對象;
JS
var init = function (a, b, c) {
this.a = a;
this.b = b;
this.c = c;
};
var initObj = new init(1, 2, 3);
console.log("initObj", initObj);
創建對象的方式
- 直接賦值,
var obj = {};
或者var obj=Object({});
(強制轉為對象) - new 關鍵字創建:
var obj = new Object();
- Object.create(arg, property): 以傳入的對象的構造函數作為模板, 可以生成實例對象; 一般用於在拿不到構造函數時, 直接通過現有的實例作為參數, 來構造新的實例;
這三種創建對象方式的區別:
- 字面量和 new 關鍵字創建的對象是 Object 的實例, 原型指向 Object.prototype; 繼承內置對象 Object
- Object.create(arg, property) 創建的對象的原型取決入參 arg, arg 為 null 時, 創建的是空對象; arg 為指定對象時, 則新對象的原型指向指定對象, 繼承指定對象;
JSvar init = function (a, b, c) { this.a = a; this.b = b; this.c = c;};var obj = new init(1, 2, 3);var obj2 = Object.create(obj,{ "p": { "value": "p1" }})console.log("obj2", obj2); // obj2 init {}console.log("obj2.p", obj2.p);// obj2.p p1
原型對象 Object.prototype
- js 的繼承機制的設計思想是: 原型對象上的所有屬性和方法, 都能被實例對象共享; 也就是說, 如果屬性和方法定義在原型對象上, 那么所有的實例都可以共享這些屬性和方法;
- js 規定, 每個對象都有一個
__proto__
指向原型對象 - js 規定, 每個函數除了有
__proto__
之外, 還有一個prototype
屬性;__proto__
指向函數原型 Function.prototype,prototype
指向一個原型對象; - 原型對象上定義的屬性和方法不是實例對象自身的, 所以修改原型對象上的屬性和方法, 這種變動會影響到所有實例屬性;
- 當實例本身沒有某個屬性或者方法的時候, 它會到原型對象上尋找該屬性和方法, 類似 java 的雙親委派機制;
prototype
和__proto__
區別
JS
function test(){
console.log("test");
}
console.log(test.prototype.__proto__.__proto__); // null
console.log(test.__proto__.__proto__.__proto__); // null
JS
function DTA(boss, employee) {
this.boss = boss;
this.employee = employee;
}
DTA.prototype.room = {
"roomNum": 1
}
DTA.prototype.name = "kevin";
DTA.prototype.dowork = function () {
return this.boss +" " + this.employee +" "+ "do work now";
}
var dta = new DTA("kevin", "rub");
var dta2 = new DTA("kevin2", "rub2");
// 修改 DTA.prototype 原型上的屬性, 會影響到所有的實例
DTA.prototype.name = "change";
console.log(dta2.__proto__ === DTA.prototype); // true
console.log(dta2.name); // change
dta2.room.roomNum = 2; // 從其他對象上修改原型對象的屬性, 也會影響其他對象
dta2.__proto__.name = "change2";
console.log(dta.name); // change2
console.log("dta.boss", dta.boss);
console.log("dta.employee", dta.employee);
console.log("dta.dowork()", dta.dowork());
console.log("dta.room.roomNum", dta.room.roomNum); // 2
原型鏈
- js 規定, 所有對象都有自己的原型對象 prototype;
- 任何一個對象都可以充當其他對象的原型;
- 由於原型也是一個對象, 所以它也有自己的原型對象, 最終形成一個原型鏈, 對象 –> 原型 –> 另一個原型…
constructor
- 原型對象 property 有一個 constructor 屬性, 這個屬性指向 prototype 對象所在的構造函數
- constructor 屬性的作用是: 可以得知某一個實例對象, 到底是由哪一個構造函數產生的
- 有了 constructor 屬性, 就可以找到構造函數, 可以通過一個實例對象來新建另一個實例
- constructor 還可以用來設置一些環境監測:
document.constructor === HTMLDocument
navigator.constructor === Navigator
- 經常和
toString()
配合進行檢測, 這樣在 node 中運行不會報錯,更難發現;navigator.constructor.toString() === "function Navigator() { [native code] }"
JS
// 函數的 .prototype.constructor 等於函數本身
function test() {};
console.log(test.prototype.constructor === test);
// 環境監測 document.constructor === HTMLDocument
function HTMLDocument() {};
var document = new HTMLDocument();
console.log(document.constructor === HTMLDocument);
var document2 = new document.__proto__.constructor();
console.log(document2.constructor === HTMLDocument);
// 環境監測 navigator.constructor === Navigator
function Navigator() {};
var navigator = new Navigator();
console.log(navigator.constructor === Navigator);
var navigator2 = new navigator.constructor();
console.log(navigator2.constructor === Navigator); // 不帶__proto__也可以, 默認會自動去原型鏈上查找
// 環境監測 配合 toString()進行檢測,node 下不會報錯, 更難發現
// navigator.constructor.toString() === "function Navigator() { [native code] }"
function Navigator() {}
Navigator.toString = function () { // Navigator.prototype.constructor.toString
return "function Navigator() { [native code] }"
}
var navigator = new Navigator();
console.log(navigator.constructor.toString());
console.log(navigator.constructor.toString() === "function Navigator() { [native code] }");
hook cookie
簡易版 hook cookie
JS
var cookie = document.cookie;
Object.defineProperty(document, 'cookie', {
get: function(){
console.log("getter:" + cookie);
return cookie;
},
set: function(value){
console.log("setter:" + cookie);
if (value.indexOf("targetCookie") > -1){
debugger;
}
cookie = value;
}
})
油猴 hook cookie
JS
// ==UserScript==
// @name Hook Cookie
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @include *
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
console.log("hook cookies start ...");
var cookie_cache = document.cookie;
Object.defineProperty(document, 'cookie', {
get: function () {
console.log("get cookie:" + cookie_cache);
return cookie_cache;
},
set: function (val) {
console.log('Setting cookie', val);
// 填寫cookie名
if (val.indexOf('w_token') != -1) {
debugger;
}
var cookie = val.split(";")[0];
var ncookie = cookie.split("=");
var flag = false;
var cache = cookie_cache.split("; ");
cache = cache.map(function (a) {
if (a.split("=")[0] === ncookie[0]) {
flag = true;
return cookie;
}
return a;
})
cookie_cache = cache.join("; ");
if (!flag) {
cookie_cache += cookie + "; ";
}
return cookie_cache;
}
});
})();
object copy
對象拷貝需要做到兩件事:
- 確保拷貝后的對象, 與原對象具有相同的原型
- 確保拷貝后的對象, 與原對象具有同樣的實例屬性
JS
function copyObj(obj){
return Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
}
function kevin(a){
this.a = a;
}
var kevinObj = new kevin(1);
var kevinObj2 = copyObj(kevinObj);
Array 對象
- isArray(): 返回一個布爾值, 表示參數是否是一個數組, 可以彌補 typeof 運算符的不足
- push(), pop()
- push: 數組的末端添加一個或者多個元素, 並返回添加新元素后的數組長度
- pop: 刪除數組的最后一個元素, 並返回該元素
- shift(), unshift()
- shift: 用於刪除數組的第一個元素, 並返回該元素
- unshift: 用於在數組的第一個位置添加元素, 並返回添加新元素后數組長度
- join(): 以指定的參數作為分隔符, 將數組中所有成員連接成一個字符串並返回, 如果不指定分隔符, 則默認使用逗號分隔
- concat(): 用於多個數組合並, 將新數組的成員添加到原數組成員的后面, 然后返回一個新數組, 原數組保持不變
- reverse(): 用於顛倒排列數組元素, 返回改變后的數組, 該方法將會改變原數組
- slice(): 提取目標數組的一部分, 返回一個新數組, 原數組保持不變
- splice(): 刪除原數組的一部分, 並可以在刪除的位置添加新的數組成員, 返回值是被刪除的元素, 該方法會改變原數組;
- sort(): 對數組成員進行排序, 默認是按照字典順序排序, 排序后原數組將改變
- map(): 將數組的所有成員依次傳入回調函數中, 然后把回調函數每一次執行結果組成一個新數組返回
- forEach(): 與 map()類似, 無返回值, 只是操作原數組
- filter(): 用於過濾數組成員, 滿足條件的成員組成一個新數組返回;
- indexOf(): 返回給定元素在數組中第一次出現的位置, 如果沒有出現則返回-1;
包裝對象
三種原始類型的值在一定條件下也會自動轉為對象, Boolean(), String()和 Number();
當使用 new 關鍵字的時候就會創建一個新的包裝對象, 當不使用 new 關鍵字時就是強制類型轉換;
Math
- Math.abs(): 返回入參的絕對值
- Math.ceil(): 返回入參向上取整之后的值
- Math.floor(): 返回入參向下取整之后的值
- Math.max(): 返回數組中的最大值
- Math.min(): 返回數組中的最小值
- Math.random(): 返回 0-1 的偽隨機值
- Math.round(): 返回入參四舍五入后的整數
Date
創建一個新的 Date 對象的唯一方式是通過 new 操作符;
JSnew Date();new Date(value);new Date(dateString);new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);
- Date.now(): 返回當前時間戳
- Date.parse(): 解析時間字符串, 返回時間戳
控制台 API
- inspect(obj): 打開相關面板並選中相應的元素, 展示它的細節
- getEventListeners(): 返回一個對象, 該對象的鍵為事件, 值為數組; 數組的成員為該事件的回調函數;
- keys(), values(): 返回一個數組, 包含 object 的所有鍵名和值
- monitorEvents(object,[,events]): 監聽特定對象上發生的特定事件; 事件發生時, 會返回一個 Event 對象; 包含該事件的相關信息; unmonitorEvents 方法用來停止監聽;
- monitorEvents: 允許監聽同一大類的事件, 所有事件可以分為四大類:
- mouse: mousedown, mouseup, click, dblclick, mousemove, mouseover, mouseout, mousewheel
- key: keydown, keyup, keypress, textInput
- touch: touchstart, touchmove, touchend, touchcancel
- control: resize, scroll, zoom, focus, blur, select, change, submit, reset
- monitorEvents: 允許監聽同一大類的事件, 所有事件可以分為四大類:
- copy(obj): 復制特定對象到剪貼板
- debugger: 斷點
this 關鍵字
- this 就是屬性或者方法當前所在的對象
- this 主要使用場景:
- 全局環境使用 this, 它指的就是頂層對象 window
- 構造函數中使用 this, 它指的就是實例對象
- 如果對象的方法包含 this, this 的指向就是方法運行時所在的對象; 該方法賦值給另一個對象, 就會改變 this 的指向;
綁定 this 的方法
- Function.prototype.call(thisValue, arg1, arg2 …) : 函數實例的 call 方法, 可以指定函數內部的 this 的指向(即函數執行時所在的作用域); 然后在指定作用域中調用該函數
- Function.prototype.apply(thisValue, [arg1, arg2. ..]): apply 和 call 方法類似, 都是可以改變 this 的指向; 唯一區別就是它接受一個數組作為函數執行時的參數;
- Function.prototype.bind(thisValue, arg1, arg2. ..): 將函數體內的 this 綁定到某個對象, 然后返回一個新函數;
JSvar d = new Date();console.log(d.getTime());var getTime = d.getTime;getTime(); // 直接調用 error, this is not a Date objectvar d_getTime = getTime.bind(d, []);console.log(d_getTime());var d = new Date();var getTime = d.getTime;var result = getTime.apply(d, []);console.log(result);var d = new Date();var getTime = d.getTime;var result = getTime.call(d);console.log(result);
es6 部分新特性
es6 是 js 語言的下一代標准, 在 2015 年 6 月正式發布; 它的目標是讓 js 語言可以用來編寫復雜的大型應用程序; es 和 js 的關系是: es 是 js 的規格, js 是 es 的實現;
塊級作用域
es6 中提出了兩個新的變量聲明命令: let 和 const; let 可以完全取代 var; var 命令存在變量提升的效果, let 命令則不存在這個問題; 所以盡量使用 let, 減少 var 的使用;
在全局環境中使用 var 聲明變量, 會直接將變量定義到全局環境中, 在全局環境中使用 let 和 const 是不會直接將變量定義到全局環境上的;
JS
// 在瀏覽器環境下運行
var a = 10;
let b = 20;
const c = 30;
console.log(window.a); // 10
console.log(window.b); // undefined
console.log(window.c); // undefined
在 es5 中, 只有函數作用域和全局作用域; 在 es6 中新增了塊級作用域; 比如典型的 for 循環; 使用 var 會導致變量泄漏到全局環境中, 使用 let 就不會存在該問題;
JS
for (var i = 0; i < 10; i++){
console.log(i);
}
console.log(i);
for (let a = 0; a < 10; a++){
console.log(a);
}
console.log(a); // undefined
屬性簡潔表達
JS
// es5
// var team = {company: "dta"}
// es6
const company = "dta"; // 先定義屬性
const team = {company}; // 再定義對象
console.log(team);
// es6
let boss = "Dta boss";
let Dta = {
[boss]: "roysue"
}
console.log("Dta['Dta boss']", Dta['Dta boss']);
對象方法簡潔表達
JS// es5var team = { dowork: function(){ return "work!"; }}// es6var team = { dowork(){ return "work"; }}
字符串模板
在 es6 中新增了動態的字符串模板, 使用反引號表示, 使用${}進行填充;
JS
var name = "kevin";
console.log(`hi, ${name}`);
解構賦值
數組解構
JS
const arr = [1,2,3,4];
const [first, second] = arr; // first = arr[0], second = arr[1];
console.log(first, second); // 1,2
對象解構
JS
var obj = {
firstName: "chen",
lastName: "guilin"
}
function getFullName(obj){
const {firstName, lastName} = obj;
console.log(firstName, lastName);
}
getFullName(obj); // chen guilin
快速復制對象
JS
let Dta = {
name: "kevin",
age: 29,
dowork(){
return "work";
}
}
let newDta = {...Dta}; // 用於取出參數對象的所有可遍歷屬性, 拷貝到當前對象中
console.log("newDta", newDta);
// 其實是調用了 Object.assign()方法
let roysue = {};
let r1ysue = Object.assign({}, roysue);
// 等同於
let r2ysue = {...roysue};
// 其他快速克隆對象的方法
var obj = {};
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj),
obj
);
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
數組創建對象
JS
function processInput(){
let [a,b,c,d] = [1,2,3,4];
return {a,b,c,d}
}
console.log(processInput()); // {a:1, b:2, c:3, d:4};
箭頭函數
立即執行函數可以寫成箭頭函數的形式:
JS
(() => {
console.log("welcome to the DTA");
})();
在那些使用匿名函數當做參數的場景下, 盡量使用箭頭函數作為替代, 因為使用箭頭函數更加簡潔, 而且箭頭函數在定義函數的時候就綁定了 this; 不需要在執行的時候考慮 this 的問題;
JS
let a = [1, 2, 3].map((x) => {
return x * x;
});
console.log("a", a); // [1,4,9]
function Test(){
this.s1 = 0;
this.s2 = 0;
setInterval(()=>{this.s1++;}, 1000);
setInterval(function () {
this.s2++;
}, 1000);
}
var test = new Test();
setInterval(() => {
console.log(test.s1);
}, 3000); // 2 5 ...
setInterval(() => {
console.log(test.s2);
}, 3000); // 0 0 ...
模塊的引入
JS
// commonJs 模塊引入 node 還是使用這個
let {stat, exists, readfile} = require("fs");
// 等同於
let _fs = require("fs");
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
// es6 模塊
import {stat, exists, readfile} from 'fs';
在 js 中引入其他文件中的函數或者對象
JS
// 當前目錄下新建 testImport.js 文件
var dta = {
name: "dta",
version : '1.0.0',
boss: "roysue",
dowork: function (name) {
console.log(`${this.name} is working`);
}
}
module.exports = dta; // 將當前對象導出
// 在其他文件中引入
let dta = require("./testImport");
console.log(dta);
dta.dowork();
// 在當前目錄下新建 testImport.js 文件
function add(x,y){
return x+y;
}
exports.add = add;
// 在其他文件中引入
let {add} = require("./testImport");
var result = add(1,2);
console.log("result",result);
鏈式判斷運算符
如果讀取對象內部的某個屬性, 往往需要判斷一下該對象是否存在這屬性; 比如 message.body.user.firstName
, 安全的寫法為:
JSconst firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default';
這樣寫相對比較麻煩, 在 es6 中可以使用鏈式判斷運算符進行判斷;
JS
let Dta = {
name: "kevin",
age: 29,
dowork(){
return "work";
},
room: {
r1: {
l: "l1"
}
}
}
console.log(Dta.room.r1.l); // l1
console.log(Dta?.room?.r1?.l?.f || "default"); // default
null 判斷運算符
在讀取對象屬性的時候, 如果該屬性是 null 或者 undefined, 可以通過 || 設置默認值const roysue = Dta.Boss.roysue || 'roysue'
但是這樣寫的話在, 當Dta.Boss.roysue
的值為空字符串或者 false 或者 0 的時候也會觸發默認值;
為了避免這個錯誤, es6 中新增了 null 判斷運算符 ??; 只有當運算符的左側值為 null 或者 undefined 時, 才會返回右側的默認值
JS
const roysue = Dta.Boss.roysue ?? 'roysue';
對象的新增方法
- Object.is(a,b) 用來比較兩個值是否是嚴格相等; 與嚴格比較符 === 基本一致;
- Object.assign(target, source1, source2): 用於對象的合並, 將原對象 source 的所有可枚舉屬性, 全部復制到 target 對象中;
- 注意點:
- 淺拷貝: 如果原對象某個屬性是對象, 那么目標對象拷貝得到的屬性時這個對象屬性的引用;
- 同名屬性替換: 一旦遇到同名的屬性, Object.assign()處理的方法是替換, 而不是添加
- 常見用途:
- 為對象添加屬性
- 為對象添加方法
- 克隆對象: 只能克隆對象自身的值, 不能克隆它繼承的值
- 合並多個對象
- 注意點:
- Object.getOwnPropertyDescriptors(): 返回指定對象的所有自身屬性的描述對象(非繼承來的屬性)
__proto__
: 該屬性可以用來讀取和設置當前對象的原型對象(prototype);
類的表示
在 es5 中, 類的表示通常是:
JS
function Dta(boss, employee) {
this.boss = boss;
this.employee = employee;
}
Dta.prototype.toString = function () {
return '(' + this.boss + ',' + this.employee + ')';
}
var dta = new Dta('kevin', 'rub');
console.log(dta.toString());
在 es6 中, 為了讓類的概念更接近 java 和 c++的類, 新增了 class 關鍵字;
JS
class Dta{
constructor(boss, employee) {
this.boss = boss;
this.employee = employee;
}
toString(){
return '(' + this.boss + ',' + this.employee + ')';
}
}
var dta = new Dta("kevin", "rub");
console.log(dta.toString());
Proxy 和 Reflect
- Proxy 可以理解成, 在目標對象之前架設一層攔截, 外界對該對象的訪問, 都必須先通過這一層攔截; 因此提供了一種機制, 可以對外界的訪問進行過濾和改寫; Proxy 這個詞的原意是代理, 在這里可以理解成 Proxy 為一個代理器;
- Proxy 支持的 13 中攔截操作
- get(target, propertyKey, receiver): 攔截對象屬性的讀取, 比如: proxy.foo 或者 proxy[‘foo’];
- set(target, propertyKey, propertyValue, receiver): 攔截對象屬性的設置, 比如 proxy.foo=1或者proxy[‘foo’]=1; 返回布爾值
- has(target, propertyKey): 攔截 propertyKey in proxy 的操作, 返回布爾值;
- deleteProperty(target, propertyKey): 攔截 delete proxy[propertyKey]的操作, 返回布爾值;
- ownKeys(target): 攔截 Object.getOwnPropertyNames(), Obejct.getOwnPropertySymbols(),Obejct.keys(),for..in 循環; 返回數組, 該方法返回目標對象所有自身的屬性的屬性名; 而 Object.keys()僅返回目標對象自身可遍歷的屬性名;
- getOwnPropertyDescriptor(target, propertyKey): 攔截 Object.getOwnPropertyDescriptor(); 返回屬性的描述對象
- defineProperty(target, propertyKey, propertyDesc): 攔截 Object.defineProperty()和 Object.defineProperties(), 返回布爾值;
- preventExtensions(target): 攔截 Object.preventExtensions()返回布爾值
- getPrototypeOf(target): 攔截 Object.getPrototypeOf(), 返回一個對象;
- isExtensible(target): 攔截 Object.isExtensible(), 返回布爾值;
- setPrototypeOf(target, proto): 攔截 Object.setPrototypeOf(), 返回布爾值;
- apply(target, object, args): 攔截 proxy 實例作為函數調用的操作; 比如 proxy(..args), proxy.call(obj, args), proxy.apply(…) ;
- construct: 攔截 proxy 實例作為構造函數調用的操作, 比如 new proxy(…args);
- Proxy 支持的 13 中攔截操作
- Reflect 對象和 Proxy 對象一樣, 是 es6 為了操作對象而提供的新 API; 大部分與 Object 對象的同名方法的作用都是相同的; 而且 Reflect 與 Proxy 對象的方法是一一對應的;
- Reflect 設計的目的:
- 將 Object 對象的一些明顯屬於語言內部的方法(Object.defineProperty)放在 Reflect 對象上; 現階段某些方法同時在 Object 和 Reflect 對象上部署, 未來新的方法只能部署在 Reflect 對象上; 也就是說 Reflect 可以拿到語言內部的方法
- 修改某些 Object 方法的返回結果, 讓其變得更合理
- 讓 Object 操作都變成函數行為
- Reflect 對象的方法和 Proxy 對象的方法一一對應;
- Reflect 靜態方法
- Reflect.get(target, propertyName, receiver): 查找並返回 target 對象的 name 屬性, receiver 綁定 this;
- Reflect.set(target, propertyName, propertyValue, receiver): 設置 target 對象的 propertyName 屬性等於 propertyValue
- Reflect.has(obj, propertyName): 判斷 propertyName 是否存在在對象中, 相當於 in
- Reflect.deleteProperty(obj, propertyName): 方法等同於 delete obj[propertyName]; 用於刪除對象的屬性
- Reflect.construct(target, args): 等同於 new target(args); 調用構造函數的方法
- Reflect.getPrototypeOf(obj): 讀取對象的
__proto__
屬性; 對應 Object.getPrototypeOf() - Reflect.setPrototypeOf(obj, newProto): 設置目標對象的原型, 對應 Object.setPrototypeOf();
- Reflect.apply(func, thisArg, args): 等同於 Function.prototype.apply.call(func, thisArg, args)
- Reflect.defineProperty(target, propertyKey, attributes): 等同於 Obejct.defineProperty();
- Reflect.getOwnPropertyDescriptor(target, propertyKey): 等同於 Object.getOwnPropertyDescriptor();
- Reflect.isExtensible(target): 對應 Object.isExtensible()表示當前對象是否可擴展;
- Reflect.preventExtensions(target): 對用 Object.preventExtensions()讓一個對象變為不可擴展
- Reflect.ownKeys(target): 返回對象的所有屬性, 可以返回 Symbol 對象;
- Reflect 設計的目的:
代理器案例
JS
// Proxy 和 Reflect
let obj = new Proxy({}, {
get(target, p, receiver) {
console.log(`get ${p}`);
return Reflect.get(target, p, receiver);
},
set(target, p, value, receiver) {
console.log(`set key: ${p}, value: ${value}`);
return Reflect.set(target, p, value, receiver);
}
});
obj.name = "kevin";
obj.name;
Proxy 作為其他對象的原型
Proxy 實例也可以作為其他對象的原型對象, 可以用來攔截原型鏈的訪問:
JS
var proxy = new Proxy({}, {
get: function(target, propertyKey, receiver){
return 35;
}
})
var obj = Object.create(proxy);
console.log(obj.time); // 35
Proxy 攔截函數調用
JS
// Proxy 攔截函數調用
function test(a, b) {
return a + b;
}
test = new Proxy(test, {
apply(target, thisArg, argArray) {
console.log("thisArg", thisArg);
console.log("target", target);
console.log("argArray", argArray);
let result = Reflect.apply(target, thisArg, argArray);
console.log("result", result);
return result;
}
})
test(1, 2);
/*
thisArg undefined
target [Function: test]
argArray [ 1, 2 ]
result 3
* */
Proxy 監控構造函數的調用
JS
// Proxy 監控構造函數
function Test(a,b) {
this.a = a;
this.b = b;
return a + b;
}
Test = new Proxy(Test, {
construct(target, argArray, newTarget) {
console.log("target", target);
console.log("argArray", argArray);
console.log("newTarget", newTarget);
var result = Reflect.construct(target, argArray, newTarget);
console.log("result", JSON.stringify(result));
return result
}
})
let test = new Test(1, 2);
console.log("test", test);
/*
target [Function: Test]
argArray [ 1, 2 ]
newTarget [Function: Test]
result {"a":1,"b":2}
test Test { a: 1, b: 2 }
*/
window 和 navigator Proxy 實戰
采用 Proxy 幫助補充環境
JS
let mywindow = {};
let mynavigator = {};
let rawstringify = JSON.stringify;
JSON.stringify = function (Object){
if((Object?.value ?? Object) === global){
return "global"
}else{
return rawstringify(Object)
}
}
function checkproxy(){
//Object.keys(window)
window.a = {
"b":{
"c":{
"d":123
}
}
}
window.a.b.c.d = 456
window.a.b
window.btoa("123")
window.atob.name
"c" in window.a
delete window.a.b
Object.defineProperty(window, "b",{
value:"bbb"
})
Object.getOwnPropertyDescriptor(window,"b")
Object.getPrototypeOf(window)
Object.setPrototypeOf(window,{"dta":"dta"})
// for (let windowKey in window) {
// windowKey
// }
Object.preventExtensions(window)
Object.isExtensible(window)
}
function getMethodHandler(WatchName){
let methodhandler = {
apply(target, thisArg, argArray) {
let result = Reflect.apply(target, thisArg, argArray)
console.log(`[${WatchName}] apply function name is [${target.name}], argArray is [${argArray}], result is [${result}].`)
return result
},
construct(target, argArray, newTarget) {
var result = Reflect.construct(target, argArray, newTarget)
console.log(`[${WatchName}] construct function name is [${target.name}], argArray is [${argArray}], result is [${JSON.stringify(result)}].`)
return result;
}
}
return methodhandler
}
function getObjhandler(WatchName){
let handler = {
get(target, propKey, receiver) {
let result = Reflect.get(target, propKey, receiver)
if (result instanceof Object){
if (typeof result === "function"){
console.log(`[${WatchName}] getting propKey is [${propKey}] , it is function`)
//return new Proxy(result,getMethodHandler(WatchName))
}else{
console.log(`[${WatchName}] getting propKey is [${propKey}], result is [${JSON.stringify(result)}]`);
}
return new Proxy(result,getObjhandler(`${WatchName}.${propKey}`))
}
console.log(`[${WatchName}] getting propKey is [${propKey}], result is [${result}]`);
return result;
},
set(target, propKey, value, receiver) {
if(value instanceof Object){
console.log(`[${WatchName}] setting propKey is [${propKey}], value is [${JSON.stringify(value)}]`);
}else{
console.log(`[${WatchName}] setting propKey is [${propKey}], value is [${value}]`);
}
return Reflect.set(target, propKey, value, receiver);
},
has(target, propKey){
var result = Reflect.has(target, propKey);
console.log(`[${WatchName}] has propKey [${propKey}], result is [${result}]`)
return result;
},
deleteProperty(target, propKey){
var result = Reflect.deleteProperty(target, propKey);
console.log(`[${WatchName}] delete propKey [${propKey}], result is [${result}]`)
return result;
},
getOwnPropertyDescriptor(target, propKey){
var result = Reflect.getOwnPropertyDescriptor(target, propKey);
console.log(`[${WatchName}] getOwnPropertyDescriptor propKey [${propKey}] result is [${JSON.stringify(result)}]`)
return result;
},
defineProperty(target, propKey, attributes){
var result = Reflect.defineProperty(target, propKey, attributes);
console.log(`[${WatchName}] defineProperty propKey [${propKey}] attributes is [${JSON.stringify(attributes)}], result is [${result}]`)
return result
},
getPrototypeOf(target){
var result = Reflect.getPrototypeOf(target)
console.log(`[${WatchName}] getPrototypeOf result is [${JSON.stringify(result)}]`)
return result;
},
setPrototypeOf(target, proto){
console.log(`[${WatchName}] setPrototypeOf proto is [${JSON.stringify(proto)}]`)
return Reflect.setPrototypeOf(target, proto);
},
preventExtensions(target){
console.log(`[${WatchName}] preventExtensions`)
return Reflect.preventExtensions(target);
},
isExtensible(target){
var result = Reflect.isExtensible(target)
console.log(`[${WatchName}] isExtensible, result is [${result}]`)
return result;
},
ownKeys(target){
var result = Reflect.ownKeys(target)
console.log(`[${WatchName}] invoke ownkeys, result is [${JSON.stringify(result)}]`)
return result
},
apply(target, thisArg, argArray) {
let result = Reflect.apply(target, thisArg, argArray)
console.log(`[${WatchName}] apply function name is [${target.name}], argArray is [${argArray}], result is [${result}].`)
return result
},
construct(target, argArray, newTarget) {
var result = Reflect.construct(target, argArray, newTarget)
console.log(`[${WatchName}] construct function name is [${target.name}], argArray is [${argArray}], result is [${JSON.stringify(result)}].`)
return result;
}
}
return handler;
}
const window = new Proxy(Object.assign(global,mywindow), getObjhandler("window"));
const navigator = new Proxy(Object.create(mynavigator), getObjhandler("navigator"));
checkproxy()
module.exports = {
window,
navigator
}
異步操作與 Ajax
單線程模型
- 單線程模型指的是: js 只在一個線程上運行, 也就是說, js 同時只能執行一個任務, 其他任務都必須在后面排隊等待
- js 只在一個線程上運行, 不代表 js 引擎只有一個線程, 實際上 js 引擎有多個線程, 單個腳本只能在一個線程上運行(主線程); 其他線程都在后台配合主線程;
同步任務和異步任務
- 同步任務是那些沒有被引擎掛起, 在主線程上排隊執行的任務; 只有前一個任務執行完畢, 才能執行后一個任務;
- 異步任務是那些被引擎放在一邊, 不進入主線程, 而是會進入任務隊列的任務; 只有引擎認為某些異步任務可以執行了(比如 Ajax 操作從服務器得到了響應), 該任務(采用回調函數的形式) 才會進入到主線程進行執行; 排在異步任務后面的代碼不用等待異步任務結束就可以馬上開始運行, 所以說異步任務不會阻塞其他任務代碼;
Ajax 操作可以被當做同步任務處理, 也可以當做異步任務處理, 由參數 async 控制; 如果是同步任務, 主線程就會一直等待 Ajax 操作返回結果, 才會繼續執行后面的代碼; 如果是異步任務, 那么主線程發出 Ajax 請求后會直接向下執行其他代碼, 等到 Ajax 操作有了結果, 引擎會其對應的回調函數放入主線程進行處理;
任務隊列和事件循環
- js 運行時, 除了正在運行的主線程, 引擎還提供了一個任務隊列(task queue); 任務隊列中是各種需要當前程序處理的異步任務;
- 首先, 主線程會執行所有的同步任務, 等到同步任務全部執行完成, 主線程才會去任務隊列中尋找異步任務;
- 如果異步任務滿足執行條件, 則異步任務重新進入到主線程中執行, 此時異步任務就變成了同步任務; 當這個異步任務執行完成, 下一個異步任務再進入主線程進行執行, 一旦任務隊列被清空, 程序就結束運行;
- 事件循環指的是只要同步任務執行完成, 引擎就會不斷去檢查那些掛起的異步任務是不是滿足進入主線程的條件; 這種循環機制就叫做事件循環, 是一種程序結構, 用於等待和發送消息和事件;
異步操作模式
- 回調函數
- 回調函數是異步操作最簡單的方法
- 回調函數的優點是簡單, 容易理解和實現; 缺點是不利於代碼的閱讀和維護; 多個部分之間高度耦合; 在多個回調函數相互嵌套的情況下, 會導致程序結構混亂, 難以閱讀和追蹤;
- 事件監聽
- 采用事件驅動模式, 異步任務的執行不取決於代碼的順序, 而是取決於某個事件是否發生;
- 優點是比較容易理解, 可以綁定多個事件, 每個事件可以指定多個回調函數; 可以解耦, 實現模塊化; 缺點是整個程序都要變成事件驅動型, 運行流程不清晰, 閱讀難以看出主流程;
- 發布/訂閱
- 這種方式的性質和事件監聽類似; 但是可以通過查看消息, 了解存在多少信號, 每個信號有多少個訂閱者, 從而監控程序的運行;
定時器
定時器是 js 中提供定時執行代碼的功能; 主要由setTimeout()
和setInterval()
這兩個函數來完成; 它們可以向任務隊列中添加定時任務;
運行機制
- setTimeout 和 setInterval 的運行機制, 是將指定的代碼移出本輪事件循環, 等到下一輪事件循環, 再檢測是否滿足指定時間, 如果滿足就執行對應的代碼, 如果不滿足則繼續等待;
- setTimeout 和 setInterval 指定的回調函數, 必須等到本輪事件循環的所有同步任務都執行完, 才會開始執行; 由於前面的任務到底需要多少時間執行是不確定的, 所以沒有辦法保證 setTimeout 和 setInterval 指定的任務一定會按照預定時間執行;
- setTimeout(f,0); 也不會立即執行, f 方法會被移出本輪事件循環;
setTimeout
function setTimeout(callback, ms, ...args)
- setTimeout 函數用於指定某個函數或者某段代碼在多少毫秒之后執行; 它返回一個整數, 該整數表示定時器的編號, 之后可以通過這個編號取消該定時器;
- 需要注意: node 中如果回調函數是對象的方法, 那么 setTimeout 會使方法內部的 this 關鍵字指向全局環境, 而不是定義這個方法時的那個對象;
- 為了防止該問題, 可以將對象的方法放在一個函數內
- 使用 bind 方法, 將對象的方法 bind 到對象上
JS
// setTimeout()
var x = "from window";
var obj = {
x: "from obj",
y: function () {
console.log(this.x);
}
}
setTimeout(obj.y, 1000); // node 環境下輸出 undefined, 瀏覽器下輸出 from window
// 將該方法放在一個函數中, 防止 this 指向全局環境
setTimeout(function () {
obj.y(); // node 和瀏覽器下都輸出 from obj;
}, 1000);
setTimeout(obj.y.bind(obj), 1000); // node 和 瀏覽器下都輸入 from obj
setTimeout 也可以實現指定間隔時間執行, 相當於下面的 setInterval 方法; 區別在於 setTimeout 會考慮自身的運行時間,可以保證嚴格的間隔時間;
JS
var i = 1;
function f() {
console.log(i);
i++;
setTimeout(f, 1000);
}
setTimeout(f, 1000);
setInterval
- setInterval 函數的用法和 setTimeout 完全一致, 區別僅僅是在以 setInterval 指定某個任務每隔一段時間就執行一次, 也就是無限次的定時執行;
- setInterval 指定的是開始執行的間隔時間, 並不考慮每次任務執行本身所消耗的時間, 所以兩次執行之間的間隔會小於指定的時間;
JS
var i = "from global";
var obj = {
a : 1,
i: "from obj",
f: function () {
console.log("this.i", this.i);
}
}
setInterval(obj.f, 1000); // node 下輸出為 undefined, 瀏覽器下輸出為 from global
setInterval(function () {
obj.f();
}, 1000) // from obj
setInterval(obj.f.bind(obj), 1000); // from obj
清除定時器
- 可以使用 clearTimeout()和 clearInterval()方法來清除定時器
- setTimeout()和 setInterval()都返回一個整數值, 該整數值表示定時器編號; 將這個編號傳入 clearTimeout()和 clearInterval()就可以取消對應的定時器;
- 需要注意, setTimeout 和 setInterval 返回的定時器編號的數值都是連續的且單調遞增的, 可以利用這一點取消所有的定時器 (過一部分檢測)
- 在 node 環境下, setTimeout 和 setInterval 返回的不是定時器編號, 而是一個定時器對象;
JS
// 清除所有定時器
(function(){
var gid = setInterval(clearAllTimeouts,0);
console.log(gid);
function clearAllTimeouts(){
var id = setTimeout(function(){},0);
while (id > 0){
if (id !== gid){
clearTimeout(id);
}
id--;
}
}
})()
Promise 對象
- Promise 對象是 js 的異步操作解決方案, 為異步操作提供了統一的接口; 它起到了代理作用, 充當異步操作與回調函數之間的中介; 讓異步操作具備同步操作的接口; Promise 可以讓異步操作寫起來就像在寫同步操作的流程, 而不必一層層嵌套回調函數;
- Promise 是一個對象, 也是一個構造函數;
JS
var objPromise = function (input) {
return new Promise(function (resolve, reject) {
var obj = {};
obj.name = input;
setTimeout(resolve, 1000, obj.name);
console.log("繼續執行");
});
}
function resolve(name) {
console.log("name", name);
return name;
}
function resolve2(name) {
console.log("name2", name);
return name;
}
objPromise("kevin").then(resolve).then(resolve2).catch((error)=>{
console.log("error", error);});
使用 Promise 的時候, 業務邏輯代碼都在 newPromise 和 then 的回調方法中;
Ajax 請求
- Ajax 是一種在無需重新加載整個頁面的情況下, 能夠更新部分網頁的技術;
- Ajax = 異步 js 和 xml
- 傳統的頁面如果需要更新內容, 必須要重新加載整個頁面; Ajax 可以使網頁實現異步更新, 在不加載整個頁面的情況下,對頁面的部分進行更新;
JS
// 創建 XMLHttpRequest 對象
var xmlhttp = new XMLHttpRequest();
// 和服務器建立連接
xmlhttp.open("method", "url", async);
/*
method: 請求的類型, GET POST
url: url 地址
async: true(異步), false(同步);
*/
// 發送請求
xmlhttp.send(string); // string 僅用於 post 請求
在異步 async 為 true 時, 需要設置 XMLHttpRequest 對象的onreadystatechange 一個回調函數; 或者設置 XMLHttpReuqest 對象的 onload 一個回調函數;
JS
xmlhttp.onreadystatechange = function(){
if (xmlhttp.readyState === 4) && (xmlhttp.status === 200){
console.log(xmlhttp.responseText);
}
}
// or
xmlhttp.onload = function(){
if (xmlhttp.readyState === 4) && (xmlhttp.status === 200){
console.log(xmlhttp.responseText);
}
}
簡單示例
python 服務端代碼
PYTHON
# -*- coding: UTF-8 -*-
__author__ = "Kevin"
__time__ = "2021/8/26"
__blog__ = "https://kevinspider.github.io/"
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
return {"message": "Hello World"}
html 請求頁面
HTML
<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Demo</title>
<script src="../snip/vue.js"></script>
</head>
<body>
<div id="root">
<button id="request" v-on:click="promiseOnloadRequest">發送請求</button>
</div>
</body>
<script>
var vue = new Vue({
el: "#root",
methods: {
request: function () {
// 發送請求
var xmlhttp = new XMLHttpRequest();
// 設置回調函數
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
console.log("success: ", xmlhttp.status);
console.log("xmlhttp.responseText: ", xmlhttp.responseText);
}
}
xmlhttp.open("GET", "http://127.0.0.1:8000", true);
xmlhttp.send()
},
promiseRequest: function () {
var promise = new Promise(function (resolve, reject) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if ((xmlhttp.readyState === 4) && (xmlhttp.status == 200)) {
resolve(xmlhttp.responseText);
}
}
xmlhttp.open("GET", "http://127.0.0.1:8000", true);
xmlhttp.send()
})
function f(res) {
console.log("use promiseRequest");
console.log("res", res);
}
promise.then(f).catch((error) => {
console.log(error);
})
},
promiseOnloadRequest: function () {
var promise = new Promise(function (resolve, reject) {
var xml = new XMLHttpRequest();
xml.open("GET", "http://127.0.0.1:8000", true);
xml.onload = resolve;
xml.send()
});
function parseResponse(res) {
console.log("promiseOnloadRequest", res);
}
function parseError(error) {
console.log("error", error);
}
promise.then(parseResponse).catch(parseError);
}
}
});
</script>
</html>
Dom 和瀏覽器模型
Dom 簡介
- Dom 是 js 操作網頁的接口, 全稱是文檔對象模型; 它的作用是將網頁轉為一個 js 對象, 從而可以通過用腳本進行各種操作(增刪改查)
- 瀏覽器會根據 Dom 模型, 將結構化的文檔(html 或 XML) 解析成一系列的節點, 再由這些節點組成一個樹狀結構; 所有的節點和最終的樹狀結構都有規范的對外接口;
- Dom 是一個接口規范, 可以用任何語言實現; 嚴格來說 Dom 不是 js 語法的一部分, 但是 Dom 操作是 js 最常見的操作, 離開了 Dom, js 就無法控制網頁; 另一方面, js 也是最常用於操作 Dom 的語言;
節點
- Dom 的最小組成單位是節點(node); 文檔的樹形結構就是由各種不同類型的節點組成;
- 節點一共有七種:
- Document: 整個文檔樹的頂層節點
- DocumentType: doctype 標簽
<! DOCTYPE html>
- Element: 網頁的各種 HTML 標簽, 比如
<body> <a>
等 - Attr: 網頁元素的屬性 比如
class="right"
- Text: 標簽之間或標簽包含的文本
- Comment: 注釋
- DocumentFragment: 文檔的片段
- 瀏覽器提供一個原生的節點對象 Node, 上面這七種節點都繼承了 Node, 因此具有一些相同的屬性和方法
事件
事件名稱參照: https://developer.mozilla.org/zh-CN/docs/Web/Events
- 事件的本質是程序各個組成部分之間的一種通信方式, 也是異步編程的一種實現; Dom 支持大量的事件
- Dom 的事件操作(監聽和觸發) 都定義在 EventTarget 接口; 所有節點對象都部署在這個接口, 其他一些需要事件通信的瀏覽器內置對象(比如 XMLHttpRequest, AudioNode, AudioContext等)也都部署了這個接口;
- EventTarget 接口主要提供了三個方法
- addEventListener(type, listener[,useCapture]): 綁定事件的監聽函數
- removeEventListener(type, listener[,useCapture]): 移除事件的監聽函數
- dispatchEvent(event): 觸發事件
事件傳播
- 一個事件發生后, 會在子元素和父元素之間傳播, 這種傳播分成三個階段:
- 第一個階段: 從 window 對象傳導到目標節點 (上層傳到底層), 稱為捕獲階段;
- 第二個階段: 在目標節點上觸發, 稱為目標階段
- 第三個階段: 從目標節點傳導回 window 對象 (從底層傳回上層); 稱為冒泡階段;
- 三種階段的傳播模型, 讓同一個事件會在多個節點上觸發;
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>節點</title>
</head>
<body>
<div id="div">
<p id="p">點擊觸發</p>
</div>
</body>
<script>
var phases = {
"1": "capture", // 捕獲階段
"2": "target", // 目標階段
"3": "bubble" // 冒泡階段
}
var div = document.getElementById("div");
var p = document.getElementById("p");
function callback(event) {
let tag = event.currentTarget.tagName;
let phase = phases[event.eventPhase];
console.log("tag", tag);
console.log("phase", phase);
}
// 當 addEventListener 第三個參數設置為 true 時, 會先觸發父節點的事件, phase 為 capture; 再觸發 target 目標, phase 為 target;
// div.addEventListener("click", callback, true);
// p.addEventListener("click", callback, true);
// 當 addEventListener 第三個參數設置為 false 時, 會先觸發子節點的事件, phase 為 target; 再觸發冒泡, 回傳到父節點, phase 為 bubble;
div.addEventListener("click", callback, false);
p.addEventListener("click", callback, false);
</script>
</html>
Event 對象
- 事件發生后, 會產生一個事件對象, 作為參數傳遞給監聽函數; 瀏覽器原生提供了一個 Event 對象, 所有的事件都是這個對象的實例, 或者說所有的事件都繼承了 Event.prototype 對象;
- Event 對象本身就是一個構造函數, 可以用來生成一個新的事件實例;
var event = new Event(type, options);
- Event 構造函數接受兩個參數, 第一個參數 type 是字符串, 表示事件的名稱; 第二個參數 options 是一個對象, 表示事件對象的配置; 該對象主要有兩個屬性:
- bubbles: 布爾值, 可選, 默認是 false; 表示事件對象是否冒泡;
- cancelable: 布爾值, 可選, 默認是 false; 表示事件是否可以被取消, 即能否用
Event.proventDefault()
取消這個事件; 一旦事件被取消, 就好像沒有發生過一樣, 不會觸發瀏覽器對該事件的默認行為;
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>節點</title>
</head>
<body>
<div id="div">
<p id="p">點擊觸發</p>
</div>
</body>
<script>
var phases = {
"1": "capture", // 捕獲階段
"2": "target", // 目標階段
"3": "bubble" // 冒泡階段
}
var div = document.getElementById("div");
var p = document.getElementById("p");
function callback(event) {
let tag = event.currentTarget.tagName;
let phase = phases[event.eventPhase];
console.log("tag", tag);
console.log("phase", phase);
}
var event = new Event("look", {
bubbles: true,
cancelable: true
});
p.addEventListener("click", callback, false);
p.dispatchEvent(event);
</script>
</html>
監聽函數
-
瀏覽器的事件模型, 就是通過監聽函數(listener)對事件作出反應; 事件發生后, 瀏覽器監聽到了這個事件, 就會執行對應的監聽函數;
-
三種方式為事件綁定監聽函數
-
HTML 的 on -屬性
HTML <div id="button" onclick="btnClick()">點擊觸發</div> <script> function btnClick(event) { console.log("event", event); console.log("button clicked"); } </script>
-
元素節點的事件屬性
HTML <div id="button">點擊觸發</div> <script> var button = document.getElementById("button"); button.onclick = function (event) { console.log("event", event); console.log("按鈕點擊"); } </script>
-
EventTarget.addEventListener()
HTML <div id="button">點擊觸發</div> <script> var button = document.getElementById("button"); function btnClick(event) { console.log("event", event); console.log("addEventListener", "clicked"); } button.addEventListener("click", btnClick); </script>
-
window
瀏覽器中, window 對象指的是當前的瀏覽器窗口; 它也是當前頁面的頂層對象, 它也是當前頁面的頂層對象, 即最高一層的對象, 所有其他對象都是它的下屬, 一個變量如果未聲明, 那么默認就是頂層對象的一個屬性;
Document
- document 節點對象代表整個文檔, 每個網頁都有自己的 document 對象; window.document 屬性就指向了這個對象; 只要瀏覽器開始載入 HTML 文檔, 該對象就存在了, 可以直接使用;
- document 對象有不同的辦法可以獲取
- 正常的網頁, 直接使用 document 或者 window.document 獲取
- iframe 框架里面的網頁, 使用 iframe 節點的 contentDocuemnt 屬性
- Ajax 操作返回的文檔, 使用 XMLHttpRequest 對象的 responseXML 屬性;
- 內部節點的 ownerDocument 屬性
- document 對象繼承了 EventTarget 接口和 Node 接口; 還 mixin 了 ParentNode 接口;
Document 常用方法
- document.open(): 清除當前文檔的所有內容, 讓文檔處於可寫狀態, 供 document.write()方法寫入內容
- document.write(): 寫入內容
- document.close(): 關閉文檔
- document.querySelector(): 接受一個 css 選擇器作為參數, 返回匹配到該選擇器的元素節點; 如果有多個節點滿足匹配條件, 則返回第一個匹配的節點; 如果沒有發現匹配的節點, 返回 null;
- document.querySelectorAll(): 接受一個 css 選擇器作為參數, 返回匹配到該選擇器的所有元素節點數組; 如果沒有發現匹配的節點, 返回 null;
- document.getElementsByClassName(): 返回一個類似數組的對象, 包括了所有 class 名字符合指定條件的元素;
- document.getElementsByName(): 返回一個類似數組的對象, 包含了所有 name 屬性滿足條件的元素;
- document.getElementById(): 返回匹配指定 id 的元素
navigator
navigator 對象的屬性:
- navigator.userAgent: 瀏覽器請求頭
- navigator.plugins: 返回一個類數組的對象, 成員是 plugin;
- navigator.platform: 返回用戶的操作系統信息
- navigator.onLine: 表示當前用戶在線還是離線
- navigator.languages: 表示當前瀏覽器語言
- navigator.language: 表示當前瀏覽器的首選語言
- navigator.geolocation: 返回一個 Geolocation 地理位置對象
- navigator.cookieEnabled: 表示瀏覽器的 Cookie功能是否開啟
navigator 對象的方法
- navigator.javaEnabled(): 表示瀏覽器是否能運行 Java Applet 小程序
- navigator.sendBeacon(): 用於向服務器異步發送數據
- navigator.deviceMemory(): 返回當前計算機的內存數量(單位為 G)
- navigator.connection(): 包含當前網絡連接的相關信息;
screen
- screen.height: 瀏覽器窗口所在的屏幕的高度(單位像素)
- screen.width: 瀏覽器窗口所在的屏幕的寬度(單位像素)
- screen.availHeight: 瀏覽器窗口可用的屏幕高度(單位像素); 因為部分空間可能不可以用, 比如系統的任務欄或者 mac 系統屏幕底部的 Dock 區;這個屬性等於 height 減去那些被系統占用的組件的高度;
- screen.availWidth: 瀏覽器窗口可用的屏幕寬度(單位像素);
- screen.pixelDepth: 整數, 表示屏幕的色彩位數, 比如 24 表示屏幕提供 24 位的色彩
- screen.colorDepth: screen.pixelDepth 的別名, 嚴格來說, colorDepth 表示應用程序的顏色深度, pixelDepth 表示屏幕的顏色深度, 絕大多數情況下, 它們都是相同的一件事;
- screen.orientation: 返回一個對象, 表示屏幕的方向;
cookie
- cookie 是服務器保存在瀏覽器的一小段文本信息, 一般大小不能超過 4KB; 瀏覽器每次向服務器發出請求, 會自動附上這段信息
- cookie 主要保存狀態信息, 一般用於:
- 對話 session 管理; 保存登錄, 購物車等需要記錄的信息
- 個性化信息: 保存用戶偏好, 比如網頁的字體大小, 背景色等;
- 追蹤用戶: 記錄和分析用戶的行為和偏好
cookie 的生成
- 服務器如果希望在瀏覽器保存 cookie, 就需要在 http 的 response 中設置 Set-Cookie 字段;
- document.cookie 用於讀寫當前頁面的 cookie; 讀取的時候, 會返回當前網頁中所有的不含 HttpOnly 屬性的 cookie 信息;
- document.cookie 屬性時可寫的; 可以通過對 document.cookie 設置值來添加 cookie;
document.cookie = "dta=dta; expires=Mon, Aug 30 2021 15:44:17 GMT; path=/; domain=www.dtasecurity.cn"
- 刪除現有的 cookie 的唯一方法, 就是設置它的 expires 屬性為一個過去的時間;
storage
- window.sessionStorage 和 window.localStorage 接口都實現了 Storage 接口;
- window.sessionStorage 保存的數據用於瀏覽器的一次會話; 當會話結束(通常是窗口關閉), 數據被清空;
- localStorage 保存的數據長期存在, 下一次訪問該網站的時候, 網頁可以直接讀取以前的數據
- 除了保存數據的時間長短不同, 這個兩個對象其他方面都基本一致
Storage 屬性
- Storage.length: 返回保存的數據個數
Storage 方法
- Storage.setItem(): 接受兩個參數, 一個是鍵名, 一個是要保存的數據; 寫入不一定要用這個方法, 也可以直接賦值
- Storage.getItem(): 用於讀取數據, 它只有一個參數, 就是鍵名;
- Storage.removeItem(): 用於清除某個鍵名對應的鍵值
- Storage.clear(): 用於清除所有保存的數據
- Storage.key(): 接受一個整數作為參數, 返回該位置對應的鍵名; 結合 Storage.length 屬性和 Storage.key()方法, 可以遍歷所有的鍵;
history
- window.history 屬性指向 History 對象, 它表示當前窗口的瀏覽歷史;
- History.length: 當前窗口訪問過的網址數量(包括當前頁面)
- History.state: History 堆棧最上層的狀態值, 通常是 undefined;
- History.back(): 移動到上一個網址, 等同於點擊瀏覽器的后退鍵
- History.forward(): 移動到下一個網址, 等同於點擊瀏覽器的前進鍵;
- History.go(): 接受一個整數作為參數, 以當前網址作為基准, 移動到參數指定的網址
- History.pushState(): 在歷史記錄中添加一條記錄
- History.replaceState(): 修改 History 對象的當前記錄;
location
location 對象是瀏覽器提供的原生對象, 提供 URL 相關的信息和操作方法; 通過 window.location 或者 document.location 都可以獲得這個對象;
location 屬性
- location.href: 整個 URL
- location.protocol: 當前 URL 的協議, 包括冒號(😃
- location.host: 主機, 如果端口不是協議默認的 80 和 443, 則還會返回冒號和端口號
- location.hostname: 主機名, 不包括端口號;
- location.port: 端口號
- location.pathname: URL 的路徑部分, 從根路徑/開始
- location.search: 查詢字符串部分, 從?開始;
- location.hash: 片段字符串部分, 從#開始
- location.username: 域名簽名的用戶名
- location.password: 域名前面的密碼;
- location.origin: URL 的協議, 主機名和端口
location 方法
-
location.assign(): 接受一個 URL 字符串參數, 讓瀏覽器立即跳轉到新的 URL;
-
location.replace(): 同上, 但是會在瀏覽器的歷史 history 里面刪除當前網址;
-
location.reload(): 使得瀏覽器重新加載當前網址, 相當於刷新
-
location.toString(): 返回整個 URL 字符串, 相當於讀取了 location.href 屬性;
-
location.encodeURI(): 會將除了元字符和語義字符之外的字符都進行轉義, 一般用於轉義整個 url;
JS var url = "https://www.baidu.com?q=你好"; encodeURI(url); // "https://www.baidu.com?q=%E4%BD%A0%E5%A5%BD"
-
location.encodeURIComponent(): 會將除了語義字符之外的所有字符都轉義, 一般用於轉義部分 url;
JS var url = "https://www.baidu.com?q=你好"; encodeURIComponent(url); // "https%3A%2F%2Fwww.baidu.com%3Fq%3D%E4%BD%A0%E5%A5%BD"
-
location.decodeURI(): encodeURI()方法的逆運算
-
location.decodeURIComponent(): encodeURIComponent()方法的逆運算
滑塊
hook 定位
比較常見的通過 Canvas 繪制滑塊驗證碼的 hook 點:
JS
(function() {
'use strict';
// Your code here...
console.log("hook CanvasRenderingContext2D");
var _drawImage = CanvasRenderingContext2D.prototype.drawImage;
var _putImageData = CanvasRenderingContext2D.prototype.putImageData;
CanvasRenderingContext2D.prototype.drawImage = function(){
console.log("CanvasRenderingContext2D.prototype.drawImage", arguments);
debugger;
return _drawImage.apply(this, arguments);
};
CanvasRenderingContext2D.prototype.putImageData = function(){
console.log("CanvasRenderingContext2D.prototype.putImageData", arguments);
debugger;
return _putImageData.apply(this, arguments);
}
})();
通過 hook 追蹤堆棧, 找到 js 中是如何對正序數組進行還原的;
hook 獲取軌跡
JS
// ==UserScript==
// @name hook trail
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @include *
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
let _addEventListener = EventTarget.prototype.addEventListener;
window.flag = false;
window.zedtail = [];
window.start_x;
window.start_y;
window.start_t;
EventTarget.prototype.addEventListener = function () {
let eventname = arguments[0];
let eventfunc = arguments[1];
let neweventfunc = function (events) {
if (events.type === "mousedown") {
window.zedtail = [];
window.start_x = events.clientX;
window.start_y = events.clientY;
window.start_t = +new Date;
window.flag = true;
} else if (events.type === "mousemove") {
if (window.flag) {
let movex = parseInt(events.clientX - window.start_x);
let movey = parseInt(events.clientY - window.start_y);
let movet = (new Date).getTime() - window.start_t;
console.log([movex, movey, movet]);
window.zedtail.push([movex, movey, movet]);
}
} else if (events.type === "mouseup") {
console.log(window.zedtail);
window.flag = false;
}
eventfunc(events);
};
console.log(eventname, eventfunc.toString());
return _addEventListener.apply(this, [eventname, neweventfunc]);
}
})();
完整代碼
通過正序數組將亂序圖片還原成正序圖片
PYTHON
# -*- coding: UTF-8 -*-
__author__ = "Kevin"
__time__ = "2021/9/1"
__blog__ = "https://kevinspider.github.io/"
"""
e.canvasCtx.drawImage(
e.img,
30 * a, sx可選 需要繪制到目標上下文中的,image的矩形(裁剪)選擇框的左上角 X 軸坐標。
0, sy可選 需要繪制到目標上下文中的,image的矩形(裁剪)選擇框的左上角 Y 軸坐標。
30, sWidth可選 需要繪制到目標上下文中的,image的矩形(裁剪)選擇框的寬度。如果不說明,整個矩形(裁剪)從坐標的sx和sy開始,到image的右下角結束。
400, sHeight可選 需要繪制到目標上下文中的,image的矩形(裁剪)選擇框的高度。
30 * t[a] / 1.5, dx image的左上角在目標canvas上 X 軸坐標。
0, dy image的左上角在目標canvas上 Y 軸坐標。
20, image 在目標canvas上繪制的寬度。 允許對繪制的image進行縮放。 如果不說明, 在繪制時image寬度不會縮放。
200) dHeight可選 image在目標canvas上繪制的高度。 允許對繪制的image進行縮放。 如果不說明, 在繪制時image高度不會縮放。
"""
import json
import math
import random
import requests
import base64
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
# 1.獲取滑塊圖片
def getImg():
headers = {
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
'Accept': 'application/json, text/plain, */*',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
'Origin': 'http://www.dtasecurity.cn:30080',
'Referer': 'http://www.dtasecurity.cn:30080/',
'Accept-Language': 'zh,zh-CN;q=0.9',
}
params = (
('w', '400'),
('h', '200'),
)
response = requests.get('http://www.dtasecurity.cn:35555/picture', headers=headers, params=params, verify=False)
data = json.loads(response.text)
# 驗證碼 id , 之后需要回傳進行驗證
sid = data['sid']
# 加密之后的正序數組, 需要解密
c = data['c']
p1_path = "./p1.jpeg"
p2_path = "./p2.jpeg"
# 亂序圖片 "data:image/jpeg;base64,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"
p1 = data['p1'].split(',')[-1]
# 滑塊圖片 "data:image/jpeg;base64,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"
p2 = data['p2'].split(',')[-1]
with open(p1_path, 'wb') as f:
f.write(base64.b64decode(p1))
with open(p2_path, 'wb') as f:
f.write(base64.b64decode(p2))
# 滑塊對應的 y 坐標
y = data['y']
return sid, c, p1_path, p2_path, y
# 2. python 還原 js 中獲取真實排序數組的方法
def decodeKey(input):
order_list = []
for each in input:
order_list.append(ord(each) ^ 66)
print(order_list)
return order_list
# 3. 將下載下來的亂序圖片還原
def getRightImg(path, order_list, offset):
right_path = "./right.jpg"
wrong_img = Image.open(path)
width, height = wrong_img.size
# 初始化正確順序的列表容器
right_order = []
for i in range(len(order_list)):
right_order.append(0)
# 遍歷傳入的數組, 按數組指定的順序將每個片段插入到列表容器中
for index, i in enumerate(order_list):
box = (index * offset, 0, (index + 1) * offset, height)
tmp_img = wrong_img.crop(box)
right_order[i] = tmp_img
# 將排序之后的片段重新寫入圖片
x = y = 0
right_img = Image.new("RGB", (width, height), 255)
for img in right_order:
right_img.paste(img, (x, y))
x += offset
# right_img.show()
right_img.save(right_path)
return right_path
# 4. 灰度 高斯濾波器(降噪) 邊緣檢測 調參, 模板匹配
def getDistance(right_path, p2_path):
# 模板匹配
def find_template(canny_img, canny_slide_img):
result = cv.matchTemplate(canny_img, canny_slide_img, cv.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result)
return min_val, max_val, min_loc, max_loc
# 獲取灰度圖片 cv.IMREAD_GRAYSCALE
gray_img = cv.imread(right_path, cv.IMREAD_GRAYSCALE)
gray_slide_img = cv.imread(p2_path, cv.IMREAD_GRAYSCALE)
# 去除噪聲 高斯模糊
gs_img = cv.GaussianBlur(gray_img, (5, 5), 0)
gs_slide_img = cv.GaussianBlur(gray_slide_img, (5, 5), 0)
# 邊緣檢測
canny_img = cv.Canny(gs_img, 25, 45)
canny_slide_img = cv.Canny(gs_slide_img, 25, 45)
result = find_template(canny_img, canny_slide_img)
print(result)
return result
# 通過 tracebar 進行參數調節 mac 上就沒成功過
# def tracebar(x):
# threshold1 = cv.getTrackbarPos("threshold1", "test")
# threshold2 = cv.getTrackbarPos("threshold2", "test")
# canny_img = cv.Canny(gs_img, threshold1, threshold2)
# cv.imshow("canny_img", canny_img)
# cv.namedWindow("test")
# cv.createTrackbar("threshold1", "test", 0, 255, tracebar)
# cv.createTrackbar("threshold2", "test", 0, 255, tracebar)
# 顯示圖片測試
# cv.imshow("gray_img", gray_img)
# cv.imshow("gs_img", gs_img)
# cv.waitKey()
# cv.destroyAllWindows()
# 5. 獲取軌跡(jshook打印 or 直接還原編碼后的軌跡信息)
# 油猴 hook 腳本 ./hookTrail.js
# 6. 畫出軌跡圖像 找緩動函數相同形狀
def show_plt():
for i in range(1, 6):
trail_list = eval(f"trail_{i}")
print(f"trail_{i}")
x_trail = []
y_trail = []
t_trail = []
for trail in trail_list:
x = trail[0]
y = trail[1]
t = trail[2]
x_trail.append(x)
y_trail.append(y)
t_trail.append(t)
print(np.diff(x_trail))
# 打印 t 軸
# print(t_trail)
plt.plot(t_trail, x_trail)
plt.show()
# 7. 繪制緩動函數 找到符合形狀的作用域
# 8. 根據圖片調整 np.linspace 的作用域, 最終調整為 -0.5 到 1;
# 9. 找到作用域內最大值 最小值 上下移動 *距離系數
# 10. 替換時間(t)軸為自己的
# 11. 高斯函數增加波動
# 緩動函數速查表 https://www.xuanfengge.com/easeing/easeing/
# 對應實現: https://github.com/gdsmith/jquery.easing
def show_easeOutQuint(distance):
def func(x):
return (1 - pow(1 - x, 5)) + 6.59375 # 6.59375 最小值, 整體上移
size = 400
# 作用域 -0.5 到 1, 400 個點
x = np.linspace(-0.5, 1, size)
# 獲取 y 的最大值和最小值, 修改 func 函數, 讓圖片整體向上移動到 x 軸上方
print(func(-0.5), func(1))
# y 的最大值為最終要滑動的距離, 所以 func(1)的值要等於傳入的滑動距離, func(1) * distance/y當前最大值
# y = [distance/7.59375 * func(i) for i in x]
y = [func(i) for i in x]
plt.plot(x, y)
plt.show()
# 將 x 軸換成我們的時間 t, 打印時間 t 查看大概的時間區間
t = np.linspace(200, 3000, size)
# 使用高斯函數對 y 增加噪點波動
delta_pt = abs(np.random.normal(scale=1.1, size=size))
for index in range(len(delta_pt)):
change_y = int(x[index] + delta_pt[index])
if (index + 1 < size and y[index + 1] > change_y):
y[index] += change_y
# y[index] = int(y[index])
print("np.diff(y)", np.diff(y))
plt.plot(t, y)
plt.show()
# 10. 細節修改 最終版本
def get_trail(move_distence, show=False):
def easeOutQuint(x):
return (1 - math.pow(1 - x, 5))
def __set_pt_time(_dist):
if _dist < 100:
__need_time = int(random.uniform(500, 1500))
else:
__need_time = int(random.uniform(1000, 2000))
__end_pt_time = []
__move_pt_time = []
__pos_z = []
total_move_time = __need_time * random.uniform(0.8, 0.9)
start_point_time = random.uniform(110, 200)
__start_pt_time = [int(start_point_time)]
sum_move_time = 0
_tmp_total_move_time = total_move_time
while True:
delta_time = random.uniform(15, 20)
if _tmp_total_move_time < delta_time:
break
sum_move_time += delta_time
_tmp_total_move_time -= delta_time
__move_pt_time.append(int(start_point_time + sum_move_time))
last_pt_time = __move_pt_time[-1]
__move_pt_time.append(int(last_pt_time + _tmp_total_move_time))
sum_end_time = start_point_time + total_move_time
other_point_time = __need_time - sum_end_time
end_first_ptime = other_point_time / 2
while True:
delta_time = random.uniform(110, 200)
if end_first_ptime - delta_time <= 0:
break
end_first_ptime -= delta_time
sum_end_time += delta_time
__end_pt_time.append(int(sum_end_time))
__end_pt_time.append(int(sum_end_time + (other_point_time / 2 + end_first_ptime)))
__pos_z.extend(__start_pt_time)
__pos_z.extend(__move_pt_time)
__pos_z.extend(__end_pt_time)
return __pos_z
def __get_pos_y(point_count):
_pos_y = []
start_y = random.randint(-1, 1)
end_y = random.randint(-13, -5)
x = np.linspace(start_y, end_y, point_count)
for _, val in enumerate(x):
_pos_y.append(int(val))
return _pos_y
time_list = __set_pt_time(move_distence)
trail_length = len(time_list)
t = np.linspace(-0.5, 1, trail_length) # t
# -6.59375 1.0 先進行向上平移, + 6.59375 => 平移后最大值變為 1.0 + 6.59375 = 7.59375
print(easeOutQuint(-0.5), easeOutQuint(1))
mult = move_distence / 7.59375
x = [int(mult * (easeOutQuint(i) + 6.59375)) for i in t]
y = __get_pos_y(trail_length)
# t=-0.5 x=-6.59375
# t=1 x=7.59375
delta_pt = abs(np.random.normal(scale=3, size=trail_length))
for index in range(len(delta_pt)):
change_x = int(x[index] + delta_pt[index])
if index + 1 < trail_length and x[index + 1] > change_x:
x[index] = change_x
if show:
delta_t = [i for i in range(trail_length)]
plt.plot(delta_t, delta_pt, color='green')
plt.plot(time_list, x, color='red')
plt.show()
result = []
print(x[-1] - x[0])
for idx in range(trail_length):
result.append([x[idx], y[idx], time_list[idx]])
return result
# 11. 提交數據加密
def encodeData(trail):
postStr = ""
for each in trail:
x = each[0]
y = each[1]
t = each[2]
tmp = f"{x},{y},{t}"
postStr += base64.b64encode(tmp.encode()).decode() + "*"
postStr = postStr.rstrip("*")
return postStr
# 12. 提交滑塊
def postData(postStr, sid):
headers = {
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
'Accept': 'application/json, text/plain, */*',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
'Content-Type': 'application/json;charset=UTF-8',
'Origin': 'http://www.dtasecurity.cn:30080',
'Referer': 'http://www.dtasecurity.cn:30080/',
'Accept-Language': 'zh,zh-CN;q=0.9',
}
data = {
"sid": sid,
"trail": postStr
}
response = requests.post('http://www.dtasecurity.cn:35555/slide',
headers=headers, json=data, verify=False)
print(response.text)
if __name__ == '__main__':
sid, c, p1_path, p2_path, y = getImg()
print(sid, c, p1_path, p2_path, y)
order_list = decodeKey(c)
right_path = getRightImg(p1_path, order_list, 30)
min_val, max_val, min_loc, max_loc = getDistance(right_path, p2_path)
distance = max_loc[0]
trail = get_trail(distance / (30 / 20), True) # 30 原始圖片截取寬度 20 寫入的時候的寬度
postStr = encodeData(trail)
postData(postStr,sid)
# show_plt()
# distance = 100
# show_easeOutQuint(100)
# result = get_trail(100, True)
# print(result)
文章作者: Kevin
文章鏈接: http://example.com/antijs/
版權聲明: 本博客所有文章除特別聲明外,均采用 CC BY-NC-SA 4.0 許可協議。轉載請注明來自 凡牆總是門!