- Solve some problems through certain skills
- code: https://github.com/baixc1/csdn/tree/master/DesignPatterns/Skill
Chain mode (OperateOfResponsibility)
core
- Definition: returns the current object in the object method to realize the chain call to multiple methods of one object
- Key points: simplify interface calls (return this)
- Application: jQuery method chain call
Example 1: jQuery get element function
- Requirements: simply simulate the basic functions of jQuery to obtain elements
- js
// index.js var A = function (selector, context) { return new A.fn.init(selector, context); }; A.fn = A.prototype = { /** * // init Method, new. This method will report an error * init(){ * * } * All method definitions are not constructors, and TypeError will be thrown if you try to instantiate them. */ /** * * @param {string} selector selector * @param {dom|undefined} context context * @returns */ init: function (selector, context = document) { this.length = 0; if (~selector.indexOf("#")) { this[0] = document.getElementById(selector.slice(1)); this.length = 1; } else { var doms = context.getElementsByTagName(selector), i = 0, len = doms.length; for (; i < len; i++) { this[i] = doms[i]; } this.length = len; } this.context = context; this.selector = selector; return this; }, size() { return this.length; }, splice: [].splice, // Enhanced array properties }; A.fn.init.prototype = A.fn; // Prototype chain inheritance console.log(A("#d1")); console.log(A("#d1").size()); // 1 console.log(A("div", A("#d1")[0]).size()); // 3 if (typeof module !== "undefined") { module.exports = A; }
- html
<div id="d1"> <div>11</div> <div></div> <div></div> </div>
- effect
Example: jQuery method 2
- Requirement: implement the extend method of jQuery (internal / external object extension)
- js
// extend.js // nodejs environment if (typeof require === "function") { A = require("./index"); } /** * Extended object member method * Pass 2 or more parameters, similar to object Assign method (external object extension) * Pass a parameter to expand this(A or A.extend) (internal expansion) */ A.extend = A.fn.extend = function () { // The extension object is calculated from the second parameter let i = 1, len = arguments.length, //Source object target = arguments[0], // Traverse the pointer of the extension object j; // Only 1 parameter if (len === 1) { target = this; // The first parameter is the extension object i--; } // Traversal extension object for (; i < len; i++) { for (j in arguments[i]) { // Expand source object target[j] = arguments[i][j]; } } return target; }; // External expansion (similar to Object.assign) var obj = {}; console.log(A.extend(obj, { a: 1, b: 2 }, { a: 2, c: 3 }) === obj); // true console.log(obj); // { a: 2, b: 2, c: 3 } A.fn.extend(obj, { d: 4, b: 0 }); console.log(obj); // { a: 2, b: 0, c: 3, d: 4 } // Internal extension (jQuery framework extends classes and prototype methods) // Browser environment test if (typeof window === "object") { // Expand A.fn (prototype expansion, instance access) A.extend(A.fn, { version: "1.0" }); const d1 = A("#d1"); console.log(d1); // init [div#d1, context: document, selector: "#d1"] console.log(d1.version); // 1.0 A.fn.extend({ getVersion() { return this.version; }, }); console.log(d1.getVersion()); // 1.0 // Extension A (class / constructor extension) A.extend({ a: 11, b: 22 }); A.extend({ c: 33 }); const { a, b, c } = A; console.log(a, b, c); // 11 22 33 }
Example 3: jQuery operation DOM method
- Requirements: simulate jQuery, realize event binding, html / css / attr query and assignment
- js
A.extend({ // Convert - to hump camelCase(str) { return str.replace(/\-(\w)/g, function (all, letter) { console.log(all, letter); return letter.toUpperCase(); }); }, }); // Add method (event, attribute, class, html) A.fn.extend({ // Event, create functions in different environments, and reduce the verification when calling on: (function () { // Flag browser DOM2 level events if (document.addEventListener) { return function (type, fn) { for (var i = 0; i < this.length; i++) { this[i].addEventListener(type, fn, false); } return this; }; } // IEDOM2 level events else if (document.attachEvent) { return function (type, fn) { for (var i = 0; i < this.length; i++) { this[i].attachEvent(`on${type}`, fn); } return this; }; } // DOM2 level events are not supported else { return function (type, fn) { for (var i = 0; i < this.length; i++) { this[i][`on${type}`] = fn; } return this; }; } })(), /** * * @returns this */ css() { const args = arguments, len = args.length; if (this.length < 1) return this; // Only one parameter if (len === 1) { // Get style $ css('width) if (typeof args[0] === "string") { return getComputedStyle(this[0])[A.camelCase(args[0])]; } // Set $ css({width: '20px','background-color':'red'}) else if (typeof args[0] === "object" && args[0] !== null) { for (var i in args[0]) { for (var j = 0; j < this.length; j++) { this[j].style[A.camelCase(i)] = args[0][i]; } } } } // Two parameters $ css('width','30px') else if (len === 2) { for (var j = 0; j < this.length; j++) { this[j].style[args[0]] = args[1]; } } return this; }, attr() { const args = arguments, len = args.length; if (this.length < 1) return this; // Only one parameter if (len === 1) { // Get style $ attr('class') if (typeof args[0] === "string") { return getAttribute(this[0])[name]; } // Set $ attr({name: 'xx','id':'xx'}) else if (typeof args[0] === "object" && args[0] !== null) { for (var i in args[0]) { for (var j = 0; j < this.length; j++) { this[j].setAttribute(i, args[0][i]); } } } } // Two parameters $ attr('id','xx') else if (len === 2) { for (var j = 0; j < this.length; j++) { this[j].setAttribute(args[0], args[1]); } } return this; }, html() { const args = arguments, len = args.length; if (len === 0) { return this[0] && this[0].innerHTML; } else { // One parameter for (var i = 0; i < this.length; i++) { this[i].innerHTML = args[0]; } } return this; }, }); const input = A("#input1"); input .css({ border: "1px solid #ddd", "background-color": "red", width: "100px", }) .attr({ name: "input", class: "xx", }); console.log(input.css("background-color")); A("#div2") .html("<p>I am xxx</p>") .on("click", function (e) { console.log(e); });
- html
<body> <div id="d1"> <div>11</div> <div></div> <div></div> </div> <div> <input id="input1" /> <div id="div2"></div> </div> <script src="./index.js"></script> <script src="./extend.js"></script> </body>
- effect
Delegate mode (Entrust)
core
- Definition: multiple objects accept and process the same request and delegate to another object to process the request uniformly
- Key points: centralized processing of multiple event operations to optimize performance
- Application: DOM event delegation
Example 1: event delegation
- Requirement: delegate child element events to parent elements
- advantage
- Avoid performance problems caused by a large number of child element binding events
- Elements and their descendants have strong correlation business logic and can be handled in a unified manner
- js
// Event delegation var div = document.getElementById("d1"); let btn3; div.onclick = function (e) { const target = e.target; var type = target.dataset.type; if (type === "btn1") { target.style.backgroundColor = target.style.backgroundColor === "red" ? "" : "red"; } else if (type === "btn2" && !btn3) { btn3 = document.createElement("button"); btn3.innerText = "Button 3"; btn3.setAttribute("data-type", "btn3"); target.parentElement.appendChild(btn3); } else if (type === "btn3") { target.parentElement.removeChild(btn3); btn3 = null; } };
- html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #d1 { position: relative; border: 1px solid #ddd; width: 500px; padding: 30px; } div { margin-bottom: 10px; } span { position: absolute; right: 10px; top: 10px; } #div { margin: 20px; } </style> </head> <body> <div id="d1" data-type="main"> <span data-type="close">xx Icon</span> <p>Content content content content content content content content content content content content content content content content content content content content</p> <div> <input data-type="kw" /> </div> <div> <button data-type="btn1">Button 1</button> <button data-type="btn2">Button 2</button> </div> </div> <script src="./index.js"></script> </body> </html>
- effect
Data access object schema (dataaccessobject Dao)
core
- Definition: abstract and encapsulate the access and storage of data sources
- Application: cache read / write encapsulation, database read / write encapsulation
Example 1: html5 localStorage access encapsulation
- Requirement: encapsulate the addition, deletion, modification and query operations of localStorage API and return relevant data
- js
// index.js // Requirements: local storage encapsulation /** * Local storage class * @param {string} preId Prefix id * @param {string} timeSign Splice connector between timestamp and data */ var BaseLocalStorage = function (preId, timeSign) { this.preId = preId; this.timeSign = timeSign || "|-|"; }; BaseLocalStorage.prototype = { // Operation status status: { SUCCESS: 0, FAIL: 1, OVERFLOW: 2, TIMEOUT: 3, }, storage: localStorage, getKey(key) { return this.preId + key; }, // Add / modify data set(key, value, callback, time) { // Default state var status = this.status.SUCCESS, key = this.getKey(key); try { // time is the date object or timestamp time = new Date(time).getTime() || time.getTime(); } catch (e) { // The default time is 30 days time = new Date().getTime() + 1000 * 3600 * 24 * 30; } try { this.storage.setItem(key, time + this.timeSign + value); } catch (e) { // Overflow failed status = this.status.OVERFLOW; } // Callback callback && callback.call(this, status, key, value); }, // get data get(key, callback) { var status = this.status.SUCCESS, key = this.getKey(key), // Default value value = null, timeSignLen = this.timeSign.length, // The starting position of the splice in the data index, // time stamp time, // Final data result; try { value = this.storage.getItem(key); } catch (e) { console.log(this); result = { status: this.status.FAIL, value: null, }; callback && callback.call(this, result.status, result.value); return result; } if (value) { index = value.indexOf(this.timeSign); time = +value.slice(0, index); if (time > new Date().getTime()) { value = value.slice(index + timeSignLen); } else { value = null; // be overdue status = this.status.TIMEOUT; this.remove(key); } } else { status = this.status.FAIL; } result = { status, value, }; callback && callback.call(this, result.status, result.value); return result; }, // Delete data remove(key, callback) { var status = this.status.FAIL, key = this.getKey(key), value = null; try { value = this.storage.getItem(key); } catch (e) {} if (value) { try { this.storage.removeItem(key); status = this.status.SUCCESS; } catch (e) {} } // The operation is successful and the real data is returned callback && callback.call( this, status, status > 0 ? null : value.slice(value.indexOf(this.timeSign) + this.timeSign.length) ); }, }; var ls = new BaseLocalStorage("ls_", "---"); ls.set("a", "a de value", (...list) => { console.log(list); // [0, "ls_a", "a de value"] }); ls.get("a", function () { console.log(arguments); // Arguments(2) [0, "a de value", callee: ƒ, Symbol(Symbol.iterator): ƒ] }); ls.remove("a", function () { console.log(arguments); // Arguments(2) [0, "a de value", callee: ƒ, Symbol(Symbol.iterator): ƒ] }); ls.remove("a", function () { console.log(arguments); // Arguments(2) [1, null, callee: ƒ, Symbol(Symbol.iterator): ƒ] }); ls.get("a", function () { console.log(arguments); // Arguments(2) [1, null, callee: ƒ, Symbol(Symbol.iterator): ƒ] }); ls.set( "b", "b value", (...list) => { console.log(list); // [0, "ls_b", "b value"] }, Date.now() + 100 ); ls.get("b", (...list) => { console.log(list); // [0, "b value"] }); setTimeout(() => { ls.get("b", (...list) => { console.log(list); // [3, null] }); }, 200);
Throttle mode
core
-
Definition: performs throttling control on duplicate business logic, performs the last operation and cancels other operations to improve performance
-
Application: page scrolling, mouse click / slide, image loading, data batch upload
-
Restrictor
// Throttler: clear the function to be executed and delay the execution of the latest function var throttle = function () { // Callback function let fn; // The first argument is a Boolean value (, the second argument is a function) if (typeof arguments[0] === "boolean") { fn = arguments[1]; fn._timer && clearTimeout(fn._timer); } // The first parameter is the function, and the second parameter is the function execution parameter else { fn = arguments[0]; var p = Object.assign( { context: null, // Function execution scope args: [], time: 300, }, arguments[1] ); // Self executing function, clearing the previous timer arguments.callee(true, fn); // Delay execution with timer fn._timer = setTimeout(() => { fn.apply(p.context, p.args); }, p.time); } };
Example 1: return to the top
- Requirement: when page scrolling stops, add animation to the back to top button
- js
function moveScroll() { var top = $(document).scrollTop(); console.log(top); $("#back").animate({ top: top + 100 }, 400, "easeOutCubic"); } $(window).on("scroll", function () { // After the slide is completed, call moveScroll (within a certain time). throttle(moveScroll); });
- html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script> <script src="https://cdn.bootcdn.net/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js"></script> <style> .content { margin: 10px; height: 1500px; border: 1px solid #ddd; } #back { position: fixed; right: 40px; top: 40px; width: 40px; height: 40px; border: 1px solid #ddd; } </style> </head> <body> <div class="content"></div> <span id="back"></span> <script src="./index.js"></script> </body> </html>
- effect
Example 2: floating layer optimization
- Demand: optimize the floating layer. When the mouse moves to the container, the layer is displayed, and when it moves to li (icon), the displayed picture is switched
- js
// Demand: optimize the floating layer. When the mouse moves to the container, the layer is displayed, and when it moves to li (icon), the displayed picture is switched // Use throttling mode to optimize pop-up changes caused by careless move in and removal function $(id) { return document.getElementById(id); } function $tag(tag, container = document) { return container.getElementsByTagName(tag); } // Floating layer var Layer = function (id) { // container this.container = $(id); // Floating layer in vessel this.layer = $tag("div", this.container)[0]; this.lis = $tag("li", this.container); this.imgs = $tag("img", this.container); this.bindEvent(); }; Layer.prototype = { bindEvent() { var that = this; // Hidden floating layer function hide() { that.layer.className = ""; } // Show floating layer function show() { that.layer.className = "show"; } // Displayed when container enter s and not displayed when leave this.on(this.container, "mouseenter", function () { // Clear hidden floating layer method timer throttle(true, hide); // Delayed display floating layer method throttle(show); }).on(this.container, "mouseleave", function () { throttle(true, show); throttle(hide); }); for (var i = 0; i < this.lis.length; i++) { this.lis[i].index = i; // Hide all pictures first, and then show the pictures of hover this.on(this.lis[i], "mouseenter", function () { var index = this.index; for (var j = 0; j < that.imgs.length; j++) { that.imgs[j].className = ""; } that.imgs[index].className = "show"; }); } }, on(ele, type, fn) { // Abbreviation ele.addEventListener(type, fn, false); return this; }, };
- html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> img { display: none; width: 100px; height: 100px; border: 1px solid #ddd } li { border: 1px solid #ddd } #icon { border: 1px solid #ddd } #icon div { display: none; padding: 10px; border: 1px solid #ddd } .show { display: block !important; } </style> </head> <body> <div id="icon" class="icon"> <ul class="icon"> <li>hover I show wechat pictures</li> <li>hover I show microblog pictures</li> <li>hover I show other pictures</li> </ul> <div> <img class="show" src="img/1.png" alt="I'm wechat picture" /> <img src="img/2.png" alt="I'm a microblog picture" /> <img src="img/3.png" alt="I'm another picture" /> </div> </div> <script src="./index.js"></script> </body> </html>
- effect
Example 3: delayed loading of pictures
- Demand: delayed loading of pictures (excessive loading of pictures will affect the loading of pages. The pictures of viewports shall be loaded first)
- js
// Demand: delayed loading of pictures (excessive loading of pictures will affect the loading of pages. The pictures of viewports shall be loaded first) class LazyLoader { constructor(id) { this.container = document.getElementById(id); // Get all picture elements this.imgs = this.getImgs(); this.init(); } // initialization init() { this.update(); this.bindEvent(); } // Get delayed loaded pictures getImgs() { return Array.from(this.container.getElementsByTagName("img")); } // Load picture update() { if (!this.imgs.length) return; var i = this.imgs.length - 1; // Traverse from back to front to optimize deletion performance for (; i >= 0; i--) { if (this.shouldShow(i)) { // Picture format < img SRC = "display picture in loading" data SRC = "actual picture" / > this.imgs[i].src = this.imgs[i].getAttribute("data-src"); this.imgs.splice(i, 1); } } } // Judge whether the picture is in the viewport (at least one of the upper and lower edges is in the window) shouldShow(i) { var img = this.imgs[i], //Top height of visualization range scrollTop = document.documentElement.scrollTop || document.body.scrollTop, //Bottom height of visualization range scrollBtm = scrollTop + document.documentElement.clientHeight, // Picture top position imgTop = this.pageY(img), imgBtm = imgTop + img.offsetHeight; if ( (imgTop > scrollTop && imgTop < scrollBtm) || (imgBtm > scrollTop && imgBtm < scrollBtm) ) { return true; } return false; } // Get the element ordinate (recursion, accumulate the parent element offsetTop) pageY(ele) { if (ele.offsetParent) { return ele.offsetTop + this.pageY(ele.offsetParent); } else { return ele.offsetTop; } } // Binding event (simplified version) on(ele, type, fn) { ele.addEventListener(type, fn); } // Bind resize and scroll events bindEvent() { ["resize", "scroll"].forEach((event) => { this.on(window, event, () => { // context to this Bind this with the update method throttle(this.update, { context: this }); }); }); } } window.onload = function () { new LazyLoader("root"); };
- html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../throttle.js"></script> <script src="index.js"></script> <style> div img { display: block; height: 300px; } </style> </head> <body> <div id="root"> <img src="../imgs/loading.gif" data-src="../imgs/1.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/2.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/3.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/4.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/5.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/6.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/7.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/8.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/9.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/10.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/11.jpeg" /> <img src="../imgs/loading.gif" data-src="../imgs/12.jpeg" /> </div> </body> </html>
- effect
Example 4: batch upload statistics
- Requirements: optimize statistical packaging. Statistics are put into the array and uploaded in batches.
- js
// Requirements: optimize statistical packaging. Statistics are put into the array and uploaded in batches. // Package statistics object var LogPack = (function () { var data = [], // Request cache array MaxNum = 10, // Request cache maximum itemSplitStr = "|", //Key value pair and key value pair spacer keyValueSplitStr = "*", // Key and value spacer img = new Image(); // Send a get request through img's src and report the data // Send request function sendLog() { var log = "", sendData = data.splice(0, MaxNum); // Traversal data list for (let i = 0; i < sendData.length; i++) { log += `log${i}=`; // Traversing object key value pairs for (let j in sendData[i]) { log += j + keyValueSplitStr + sendData[i][j] + itemSplitStr; } // Remove the last one (write dead?), Use & to connect query parameters log = log.replace(/\|$/, "") + "&"; } log += "logLen=" + sendData.length; img.src = "xx.gif?" + log; } return function (param) { // No parameter call indicates that the data is reported directly if (!param) { sendLog(); return; } // Add data data.push(param); data.length >= MaxNum && sendLog(); }; })(); // Delegation mode document.getElementsByTagName("div")[0].onclick = function (e) { const { nodeName, innerHTML, dataset } = e.target; if (nodeName === "BUTTON") { LogPack({ id: dataset.id, context: innerHTML, type: "click", }); } }; // Direct transmission document.getElementById("btn").onclick = function () { LogPack(); };
- html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> div { margin-bottom: 10px; } </style> </head> <body> <div> <button class="btn" data-id="1">Statistics 1</button> <button class="btn" data-id="2">Statistics 2</button> <button class="btn" data-id="3">Statistics 3</button> <button class="btn" data-id="4">Statistics 4</button> </div> <button id="btn">send out</button> <script src="index.js"></script> </body> </html>
- effect