摘要:單體模式有以下優點用來劃分命名空間,減少全局變量數量。通常我們使用操作符創建單體模式的三種選擇,讓構造函數總返回最初的對象使用全局對象來存儲該實例不推薦,容易全局污染。實現該工廠模式并不困難,主要是要找到能夠穿件所需類型對象的構造函數。
介紹
最近開始給自己每周訂個學習任務,學習結果反饋為一篇文章的輸出,做好學習記錄。
這一周(02.25-03.03)我定的目標是《JavaScript 模式》的第七章學習一遍,學習結果的反饋就是本篇文章啦。
由于內容實在太長,我將本文分為兩部分:
《JavaScript 模式》知識點整理(上)
《JavaScript 模式》知識點整理(下)
本文內容中主要參考《JavaScript 模式》,其中也有些案例是來自網上資料,有備注出處啦,如造成不便,請聯系我刪改。
過兩天我會把這篇文章收錄到我整理的知識庫 【Cute-JavaScript】 中,并已經同步到 【github】上面。
一、單體模式(Singleton Pattern) 1.概念介紹單體模式(Singleton Pattern)的思想在于保證一個特定類僅有一個實例,即不管使用這個類創建多少個新對象,都會得到與第一次創建的對象完全相同。
它讓我們能將代碼組織成一個邏輯單元,并可以通過單一變量進行訪問。
單體模式有以下優點:
用來劃分命名空間,減少全局變量數量。
使代碼組織的更一致,提高代碼閱讀性和維護性。
只能被實例化一次。
但在JavaScript中沒有類,只有對象。當我們創建一個新對象,它都是個新的單體,因為JavaScript中永遠不會有完全相等的對象,除非它們是同一個對象。
因此,我們每次使用對象字面量創建對象的時候,實際上就是在創建一個單例。
let a1 = { name : "leo" }; let a2 = { name : "leo" }; a1 === a2; // false a1 == a2; // false
這里需要注意,單體模式有個條件,是該對象能被實例化,比如下面這樣就不是單體模式,因為它不能被實例化:
let a1 = { b1: 1, b2: 2, m1: function(){ return this.b1; }, m2: function(){ return this.b2; } } new a1(); // Uncaught TypeError: a1 is not a constructor
下面展示一個單體模式的基本結構:
let Singleton = function (name){ this.name = name; this.obj = null; } Singleton.prototype.getName = function(){ return this.name; } function getObj(name){ return this.obj || (this.obj = new Singleton(name)); } let g1 = getObj("leo"); let g2 = getObj("pingan"); g1 === g2; // true g1 == g2; // true g1.getName(); // "leo" g2.getName(); // "leo"
從這里可以看出,單體模式只能實例化一次,后面再調用的話,都是使用第一次實例化的結果。
2.應用場景單例模式只允許實例化一次,能提高對象訪問速度并且節約內存,通常被用于下面場景:
需要頻繁創建再銷毀的對象,或頻繁使用的對象:如:彈窗,文件;
常用的工具類對象;
常用的資源消耗大的對象;
3.實現彈框案例這里我們要用單體模式,創建一個彈框,大概需要實現:元素值創建一次,使用的時候直接調用。
因此我們這么做:
let create = (() => { let div; return () => { if(!div){ div = document.createElement("div"); div.innderHTML = "我是leo創建的彈框"; div.style.display = "none"; div.setAttribute("id", "leo"); document.body.appendChild(div); } return div; } })(); // 觸發事件 document.getElementById("otherBtn").onclick = () => { let first = create(); first.style.display = "block"; }4.使用new操作符
由于JavaScript中沒有類,但JavaScript有new語法來用構造函數創建對象,并可以使用這種方法實現單體模式。
當使用同一個構造函數以new操作符創建多個對象,獲得的是指向完全相同的對象的新指針。
通常我們使用new操作符創建單體模式的三種選擇,讓構造函數總返回最初的對象:
使用全局對象來存儲該實例(不推薦,容易全局污染)。
使用靜態屬性存儲該實例,無法保證該靜態屬性的私有性。
function Leo(name){ if(typeof Leo.obj === "object"){ return Leo.obj; } this.name = name; Leo.obj = this; return this; } let a1 = new Leo("leo"); let a2 = new Leo("pingan"); a1 === a2 ; // true a1 == a2 ; // true
唯一的缺點就是obj屬性是公開的,容易被修改。
使用閉包將該實例包裹,保證實例是私有性并不會被外界修改。
我們這通過重寫上面的方法,加入閉包:
function Leo(name){ let obj; this.name = name; obj = this; // 1.存儲第一次創建的對象 Leo = function(){ // 2.修改原來的構造函數 return obj; } } let a1 = new Leo("leo"); let a2 = new Leo("pingan"); a1 === a2 ; // true a1 == a2 ; // true
當我們第一次調用構造函數,像往常一樣返回this,而后面再調用的話,都將重寫構造函數,并訪問私有變量obj并返回。
二、工廠模式(Factory Pattern) 1.概念介紹工廠模式的目的在于創建對象,實現下列目標:
可重復執行,來創建相似對象;
當編譯時位置具體類型(類)時,為調用者提供一種創建對象的接口;
通過工廠方法(或類)創建的對象,都繼承父對象,下面一個簡單工廠方法理解:
function Person(name, age, sex){ let p = {}; // 或 let p = new Object(); 創建一個初始對象 p.name = name; p.age = age; p.sex = sex; p.ask = function(){ return "my name is" + this.name; } return p; } let leo = new Person("leo", 18, "boy"); let pingan = new Person("pingan", 18, "boy"); console.log(leo.name, leo.age, leo.sex); // "leo", 18, "boy" console.log(pingan.name, pingan.age, pingan.sex); // "pingan", 18, "boy"
通過調用Person構造函數,我們可以像工廠那樣,生產出無數個包含三個屬性和一個方法的對象。
可以看出,工廠模式可以解決創建多個類似對象的問題。
一個調用者想創建一個對象,只要知道其名稱就可以了。
擴展性高,如果想增加一個產品,只要擴展一個工廠類就可以。
屏蔽產品的具體實現,調用者只關心產品的接口。
2.2缺點每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的復雜度,同時也增加了系統具體類的依賴。這并不是什么好事。
3.實現復雜工廠模式在復雜工廠模式中,我們將其成員對象的實列化推遲到子類中,子類可以重寫父類接口方法以便創建的時候指定自己的對象類型。
父類類似一個公共函數,只處理創建過程中的問題,并且這些處理將被子類繼承,然后在子類實現專門功能。
比如這里我們需要實現這么一個實例:
需要一個公共父函數CarMaker;
父函數CarMaker有個factor靜態方法,用于創建car對象;
定義三個靜態屬性,值為三個函數,用于繼承父函數CarMaker;
然后我們希望這么使用這個函數:
let c1 = CarMaker.factory("Car1"); let c2 = CarMaker.factory("Car2"); let c3 = CarMaker.factory("Car3"); c1.drirve(); // "我的編號是6" c2.drirve(); // "我的編號是3" c3.drirve(); // "我的編號是12"
可以看出,調用時接收以字符串形式指定類型,并返回請求類型的對象,并且這樣使用是不需要用new操作符。
下面看代碼實現:
// 創建父構造函數 function CarMaker(){}; CarMaker.prototype.drive = function(){ return `我的編號是${this.id}`; } // 添加靜態工廠方法 CarMaker.factory = function (type){ let types = type, newcar; // 若構造函數不存在 則發生錯誤 if(typeof CarMaker[types] !== "function"){ throw{ name: "Error", message: `${types}不存在`}; } // 若構造函數存在,則讓原型繼承父類,但僅繼承一次 if(CarMaker[types].prototype.drive !== "function"){ CarMaker[types].prototype = new CarMaker(); } // 創建新實例,并返回 newcar = new CarMaker[types](); return newcar; } // 調用 CarMaker.c1 = function(){ this.id = 6; } CarMaker.c2 = function(){ this.id = 3; } CarMaker.c3 = function(){ this.id = 12; }
定義完成后,我們再執行前面的代碼:
let c1 = CarMaker.factory("Car1"); let c2 = CarMaker.factory("Car2"); let c3 = CarMaker.factory("Car3"); c1.drirve(); // "我的編號是6" c2.drirve(); // "我的編號是3" c3.drirve(); // "我的編號是12"
就能正常打印結果了。
實現該工廠模式并不困難,主要是要找到能夠穿件所需類型對象的構造函數。
這里使用簡單的映射來創建該對象的構造函數。
內置的對象工廠,就像全局的Object()構造函數,也是工廠模式的行為,根據輸入類型創建不同對象。
如傳入一個原始數字,返回一個Number()構造函數創建一個對象,傳入一個字符串或布爾值也成立。
對于傳入任何其他值,包括無輸入的值,都會創建一個常規的對象。
無論是否使用new操作符,都可以調用Object(),我們這么測試:
let a = new Object(), b = new Object(1), c = Object("1"), d = Object(true); a.constructor === Object; // true b.constructor === Number; // true c.constructor === String; // true d.constructor === Boolean; // true
事實上,Object()用途不大,這里列出來是因為它是我們比較常見的工廠模式。
三、迭代器模式(Iterator Pattern) 1.概念介紹迭代器模式(Iterator Pattern)是提供一種方法,順序訪問一個聚合對象中每個元素,并且不暴露該對象內部。
這種模式屬于行為型模式,有以下幾個特點:
訪問一個聚合對象的內容,而無需暴露它的內部表示。
提供統一接口來遍歷不同結構的數據集合。
遍歷的同事更改迭代器所在的集合結構可能會導致問題。
在迭代器模式中,通常包含有一個包含某種數據集合的對象,需要提供一種簡單的方法來訪問每個元素。
這里對象需要提供一個next()方法,每次調用都必須返回下一個連續的元素。
這里假設創建一個對象leo,我們通過調用它的next()方法訪問下一個連續的元素:
let obj; while(obj = leo.next()){ // do something console.log(obj); }
另外迭代器模式中,聚合對象還會提供一個更為漸變的hasNext()方法,來檢查是否已經到達數據末尾,我們這么修改前面的代碼:
while(leo.hasNext()){ // do something console.log(obj); }2.優缺點和應用場景 2.1優點
它簡化了聚合類,并支持以不同的方式遍歷一個聚合對象。
在同一個聚合上可以有多個遍歷。
在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
2.2缺點由于迭代器模式將存儲數據和遍歷數據的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的復雜性。
2.3應用場景訪問一個聚合對象的內容而無須暴露它的內部表示。
需要為聚合對象提供多種遍歷方式。
為遍歷不同的聚合結構提供一個統一的接口。
3.簡單案例根據上面的介紹,我們這里實現一個簡單案例,將設我們數據只是普通數組,然后每次檢索,返回的是間隔一個的數組元素(即不是連續返回):
let leo = (function(){ let index = 0, data = [1, 2, 3, 4, 5], len = data.length; return { next: function(){ let obj; if(!this.hasNext()){ return null; }; obj = data[index]; index = index + 2; return obj; }, hasNext: function(){ return index < len; } } })()
然后我們還要給它提供更簡單的訪問方式和多次迭代數據的能力,我們需要添加下面兩個方法:
rewind() 重置指針到初始位置;
current() 返回當前元素,因為當指針步前進時無法使用next()操作;
代碼變成這樣:
let leo = (function(){ //.. return { // .. rewind: function(){ index = 0; }, current: function(){ return data[index]; } } })();
這樣這個案例就完整了,接下來我們來測試:
// 讀取記錄 while(leo.hasNext()){ console.log(leo.next()); }; // 打印 1 3 5 // 回退 leo.rewind(); // 獲取當前 console.log(leo.current()); // 回到初始位置,打印14.應用場景
迭代器模式通常用于:對于集合內部結果常常變化各異,我們不想暴露其內部結構的話,但又響讓客戶代碼透明底訪問其中的元素,這種情況下我們可以使用迭代器模式。
簡單理解:遍歷一個聚合對象。
jQuery應用例子:
jQuery中的$.each()方法,可以讓我們傳入一個方法,實現對所有項的迭代操作:
$.each([1,2,3,4,5],function(index, value){ console.log(`${index}: ${value}`) })
使用迭代器模式實現each()方法
let myEach = function(arr, callback){ for(var i = 0; i< arr.length; i++){ callback(i, arr[i]); } }4.小結
迭代器模式是一種相對簡單的模式,目前絕大多數語言都內置了迭代器。而且迭代器模式也是非常常用,有時候不經意就是用了。
四、裝飾者模式(Decorator Pattern) 1.概念介紹裝飾者模式(Decorator Pattern):在不改變原類和繼承情況下,動態添加功能到對象中,通過包裝一個對象實現一個新的具有原對象相同接口的新對象。
裝飾者模式有以下特點:
添加功能時不改變原對象結構。
裝飾對象和原對象提供的接口相同,方便按照源對象的接口來使用裝飾對象。
裝飾對象中包含原對象的引用。即裝飾對象是真正的原對象包裝后的對象。
實際上,裝飾著模式的一個比較方便的特征在于其預期行為的可定制和可配置特性。從只有基本功能的普通對象開始,不斷增強對象的一些功能,并按照順序進行裝飾。
2.優缺點和應用場景 2.1優點裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能。
2.2缺點多層裝飾比較復雜。
2.3應用場景擴展一個類的功能。
動態增加功能,動態撤銷。
3.基本案例我們這里實現一個基本對象sale,可以通過sale對象獲取不同項目的價格,并通過調用sale.getPrice()方法返回對應價格。并且在不同情況下,用額外的功能來裝飾它,會得到不同情況下的價格。
3.1創建對象這里我們假設客戶需要支付國家稅和省級稅。按照裝飾者模式,我們就需要使用國家稅和省級稅兩個裝飾者來裝飾這個sale對象,然后在對使用價格格式化功能的裝飾者裝飾。實際看起來是這樣:
let sale = new Sale(100); sale = sale.decorate("country"); sale = sale.decorate("privince"); sale = sale.decorate("money"); sale.getPrice();
使用裝飾者模式后,每個裝飾都非常靈活,主要根據其裝飾者順序,于是如果客戶不需要上繳國家稅,代碼就可以這么實現:
let sale = new Sale(100); sale = sale.decorate("privince"); sale = sale.decorate("money"); sale.getPrice();3.2實現對象
接下來我們需要考慮的是如何實現Sale對象了。
實現裝飾者模式的其中一個方法是使得每個裝飾者成為一個對象,并且該對象包含了應該被重載的方法。每個裝飾者實際上繼承了目前已經被前一個裝飾者進行裝飾后的對象,每個裝飾方法在uber(繼承的對象)上調用同樣的方法并獲取值,此外還繼續執行一些操作。
uber關鍵字類似Java的super,它可以讓某個方法調用父類的方法,uber屬性指向父類原型。
即:當我們調用sale.getPrice()方法時,會調用money裝飾者的方法,然后每個裝飾方法都會先調用父對象的方法,因此一直往上調用,直到開始的Sale構造函數實現的未被裝飾的getPrice()方法。理解如下圖:
我們這里可以先實現構造函數Sale()和原型方法getPrice():
function Sale (price){ this.price = price || 100; } Sale.prototype.getPrice = function (){ return this.price; }
并且裝飾者對象都將以構造函數的屬性來實現:
Sale.decorators = {};
接下來實現country這個裝飾者并實現它的getPrice(),改方法首先從父對象的方法獲取值再做修改:
Sale.decorators.country = { getPrice: function(){ let price = this.uber.getPrice(); // 獲取父對象的值 price += price * 5 / 100; return price; } }
按照相同方法,實現其他裝飾者:
Sale.decorators.privince = { getPrice: function(){ let price = this.uber.getPrice(); price += price * 7 / 100; return price; } } Sale.decorators.money = { getPrice: function(){ return "¥" + this.uber.getPrice().toFixed(2); } }
最后我們還需要實現前面的decorate()方法,它將我們所有裝飾者拼接一起,并且做了下面的事情:
創建了個新對象newobj,繼承目前我們所擁有的對象(Sale),無論是原始對象還是最后裝飾后的對象,這里就是對象this,并設置newobj的uber屬性,便于子對象訪問父對象,然后將所有裝飾者的額外屬性復制到newobj中,返回newobj,即成為更新的sale對象:
Sale.prototype.decorate = function(decorator){ let F = function(){}, newobj, overrides = this.constructor.decorators[decorator]; F.prototype = this; newobj = new F(); newobj.user = F.prototype; for(let k in overrides){ if(overrides.hasOwnProperty(k)){ newobj[k] = overrides[k]; } } return newobj; }4.改造基本案例
這里我們使用列表實現相同功能,這個方法利用JavaScript語言的動態性質,并且不需要使用繼承,也不需要讓每個裝飾方法調用鏈中前面的方法,可以簡單的將前面方法的結果作為參數傳遞給下一個方法。
這樣實現也有個好處,支持反裝飾或撤銷裝飾,我們還是實現以下功能:
let sale = new Sale(100); sale = sale.decorate("country"); sale = sale.decorate("privince"); sale = sale.decorate("money"); sale.getPrice();
現在的Sale()構造函數中多了個裝飾者列表的屬性:
function Sale(price){ this.price = (price > 0) || 100; this.decorators_list = []; }
然后還是需要實現Sale.decorators,這里的getPrice()將變得更簡單,也沒有去調用父對象的getPrice(),而是將結果作為參數傳遞:
Sale.decorators = {}; Sale.decorators.country = { getPrice: function(price){ return price + price * 5 / 100; } } Sale.decorators.privince = { getPrice: function(price){ return price + price * 7 / 100; } } Sale.decorators.money = { getPrice: function(price){ return "¥" + this.uber.getPrice().toFixed(2); } }
而這時候父對象的decorate()和getPrice()變得復雜,decorate()用于追加裝飾者列表,getPrice()需要完成包括遍歷當前添加的裝飾者一級調用每個裝飾者的getPrice()方法、傳遞從前一個方法獲得的結果:
Sale.prototype.decorate = function(decorators){ this.decorators_list.push(decorators); } Sale.propotype.getPrice = function(){ let price = this.price, name; for(let i = 0 ;i< this.decorators_list.length; i++){ name = this.decorators_list[i]; price = Sale.decorators[name].getPrice(price); } return price; }5.對比兩個方法
很顯然,第二種列表實現方法會更簡單,不用設計繼承,并且裝飾方法也簡單。
案例中getPrice()是唯一可以裝飾的方法,如果想實現更多可以被裝飾的方法,我們可以抽一個方法,來將每個額外的裝飾方法重復遍歷裝飾者列表中的這塊代碼,通過它來接收方法并使其成為“可裝飾”的方法。這樣實現,sale的decorators_list屬性會成為一個對象,且該對象每個屬性都是以裝飾者對象數組中的方法和值命名。
策略模式(Strategy Pattern):封裝一系列算法,支持我們在運行時,使用相同接口,選擇不同算法。它的目的是為了將算法的使用與算法的實現分離開來。
策略模式通常會有兩部分組成,一部分是策略類,它負責實現通用的算法,另一部分是環境類,它用戶接收客戶端請求并委托給策略類。
2.優缺點 2.1優點有效地避免多重條件選擇語句;
支持開閉原則,將算法獨立封裝,使得更加便于切換、理解和擴展;
更加便于代碼復用;
2.2缺點策略類會增多;
所有策略類都需要對外暴露;
3.基本案例我們可以很簡單的將策略和算法直接做映射:
let add = { "add3" : (num) => num + 3, "add5" : (num) => num + 5, "add10": (num) => num + 10, } let demo = (type, num) => add[type](num); console.log(demo("add3", 10)); // 13 console.log(demo("add10", 12)); // 22
然后我們再把每個策略的算法抽出來:
let fun3 = (num) => num + 3; let fun5 = (num) => num + 5; let fun10 = (num) => num + 10; let add = { "add3" : (num) => fun3(num), "add5" : (num) => fun5(num), "add10": (num) => fun10(num), } let demo = (type, num) => add[type](num); console.log(demo("add3", 10)); // 13 console.log(demo("add10", 12)); // 224.表單驗證案例
我們需要使用策略模式,實現一個處理表單驗證的方法,無論表單的具體類型是什么都會調用驗證方法。我們需要讓驗證器能選擇最佳的策略來處理任務,并將具體的驗證數據委托給適當算法。
我們假設需要驗證下面的表單數據的有效性:
let data = { name : "pingan", age : "unknown", nickname: "leo", }
這里需要先配置驗證器,對表單數據中不同的數據使用不同的算法:
validator.config = { name : "isNonEmpty", age : "isNumber", nickname: "isAlphaNum", }
并且我們需要將驗證的錯誤信息打印到控制臺:
validator.validate(data); if(validator.hasErrors()){ console.log(validator.msg.join(" ")); }
接下來我們才要實現validator中具體的驗證算法,他們都有一個相同接口validator.types,提供validate()方法和instructions幫助信息:
// 非空值檢查 validator.types.isNonEmpty = { validate: function(value){ return value !== ""; } instructions: "該值不能為空" } // 數值類型檢查 validator.types.isNumber = { validate: function(value){ return !isNaN(value); } instructions: "該值只能是數字" } // 檢查是否只包含數字和字母 validator.types.isAlphaNum = { validate: function(value){ return !/[^a-z0-9]/i.test(value); } instructions: "該值只能包含數字和字母,且不包含特殊字符" }
最后就是要實現最核心的validator對象:
let validator = { types: {}, // 所有可用的檢查 msg:[], // 當前驗證的錯誤信息 config:{}, // 驗證配置 validate: function(data){ // 接口方法 let type, checker, result; this.msg = []; // 清空錯誤信息 for(let k in data){ if(data.hasOwnProperty(k)){ type = this.config[k]; checker = this.types[type]; if(!type) continue; // 不存在類型 則 不需要驗證 if(!checker){ throw { name: "驗證失敗", msg: `不能驗證類型:${type}` } } result = checker.validate(data[k]); if(!result){ this.msg.push(`無效的值:${k},${checker.instructions}`); } } } return this.hasErrors(); } hasErrors: function(){ return this.msg.length != 0; } }
總結這個案例,我們可以看出validator對象是通用的,需要增強validator對象的方法只需添加更多的類型檢查,后續針對每個新的用例,只需配置驗證器和運行validator()方法就可以。
5.小結日常開發的時候,還是需要根據實際情況來選擇設計模式,而不能為了設計模式而去設計模式。通過上面的學習,我們使用策略模式來避免多重條件判斷,并且通過開閉原則來封裝方法。我們應該多在開發中,逐漸積累自己的開發工具庫,便于以后使用。
參考資料《JavaScript Patterns》
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787... |
JS小冊 | js.pingan8787.com |
微信公眾號 | 前端自習課 |
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102339.html
摘要:缺點不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。預防低水平人員帶來的風險。開閉原則,高拓展性。這里的訂閱者稱為觀察者,而被觀察者稱為發布者,當一個事件發生,發布者會發布通知所有訂閱者,并常常以事件對象形式傳遞消息。 介紹 最近開始給自己每周訂個學習任務,學習結果反饋為一篇文章的輸出,做好學習記錄。 這一周(02.25-03.03)我定的目標是《JavaScript 模式》...
摘要:介紹這周開始學習老姚大佬的正則表達式迷你書,然后習慣性的看完一遍后,整理一下知識點,便于以后自己重新復習。感謝原書作者老姚,本文無意抄襲,只是作為自己知識點的整理,后續也會整理到自己的知識庫網站中。等價于,表示出現次。 showImg(https://segmentfault.com/img/remote/1460000018530584?w=919&h=449); 介紹 這周開始學習...
摘要:部分如上,四個區塊,四大名著,嘎嘎代碼如上,寫的很爛,輕噴用來控制元素的顯示和隱藏,實際上是為了實現動畫效果此處有裝逼的嫌疑,因為會阻礙,而不會,另外也可以用來代替。 說明 又是一個練手的小玩意兒,本身沒什么技術含量,就是幾個不常用的CSS3特性的結合而已。 要點 Label標簽的for屬性 單選框的:checked偽類 CSS的加號[+]選擇器 效果圖 showImg(https...
摘要:部分如上,四個區塊,四大名著,嘎嘎代碼如上,寫的很爛,輕噴用來控制元素的顯示和隱藏,實際上是為了實現動畫效果此處有裝逼的嫌疑,因為會阻礙,而不會,另外也可以用來代替。 說明 又是一個練手的小玩意兒,本身沒什么技術含量,就是幾個不常用的CSS3特性的結合而已。 要點 Label標簽的for屬性 單選框的:checked偽類 CSS的加號[+]選擇器 效果圖 showImg(https...
摘要:第二安裝設置這里老蔣有直接下載客戶端后,打開需要設置賬戶。總結,老蔣個人覺得如果用二次密碼驗證比用短信二次驗證好很多,因為短信有網絡的問題海外商家無法推送短信驗證碼,而是比較普遍的方法。隨著互聯網普遍于我們的生活中,我們任何的賬戶是不是都是數字化的。如果稍有不慎密碼泄露是不是可能導致賬戶安全遭受影響。比如我們有很多站長朋友擁有價值連城的域名,重要服務器賬戶數據的,如果賬戶出現安全問題,那損失...
閱讀 3450·2019-08-30 10:54
閱讀 3147·2019-08-29 16:38
閱讀 2165·2019-08-26 14:06
閱讀 1512·2019-08-23 15:39
閱讀 3034·2019-08-23 15:37
閱讀 2882·2019-08-23 13:50
閱讀 3189·2019-08-22 17:14
閱讀 2374·2019-08-22 15:44