├── .DS_Store ├── Domdiff ├── diff.js ├── render.js └── utils.js ├── LICENSE ├── README.md ├── ThoughWorks ├── 2018_SPRING_DEV .pdf ├── MIT.LICENSE ├── README.md ├── SpecRunner.html ├── lib │ └── jasmine-2.8.0 │ │ ├── boot.js │ │ ├── console.js │ │ ├── jasmine-html.js │ │ ├── jasmine.css │ │ ├── jasmine.js │ │ └── jasmine_favicon.png ├── plane(一).html ├── spec │ └── getLocation.js └── src │ └── getLocation.js ├── _config.yml ├── bubble_sort.js ├── counting_sort.js ├── doublyLinkedList.js ├── draw ├── draw.html └── js │ ├── index.js │ └── jquery-2.1.1.min.js ├── getMiddle.js ├── hash.js ├── index.html ├── insert.js ├── link.js ├── linkedList.js ├── main.js ├── map.js ├── merge.js ├── permutation.js ├── qsort.js ├── radix_sort.js ├── reverString.js ├── suanfa.js ├── this.js ├── tree.js ├── userMedia.html ├── webpack.js ├── xx.html ├── 函数节流.md ├── 别踩白块.html ├── 别踩白块文档.png ├── 只读属性.md ├── 数组对象去重.md ├── 有序数组.md ├── 深拷贝.md ├── 简易计算器 ├── calculator.css ├── calculator.js ├── calculator1.png ├── 简易计算器.md └── 计算器.html ├── 网页版拼图游戏 ├── puzzle.css ├── puzzle.js ├── puzzle.md ├── puzzle1.png ├── puzzle2.png └── 拼图游戏(puzzle).html └── 雪花特效.html /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunseekers/JavaScript/cbff2028cf7217b97a78844d0d903f32af495ca2/.DS_Store -------------------------------------------------------------------------------- /Domdiff/diff.js: -------------------------------------------------------------------------------- 1 | const REPLACE = 0 2 | const ATTRS = 1 3 | const TEXT = 2 4 | const REORDER = 3 5 | //diff 入口,比较新旧两棵树的差异 6 | function diff (oldTree,newTree){ 7 | let index = 0 8 | let patches = {}//用来记录每一个节点差异的补丁对象 9 | walk(oldTree,newTree,index,patches) 10 | return patches 11 | } 12 | /** 13 | * walk 遍历查找节点差异 14 | * @param { Object } oldNode 15 | * @param { Object } newNode 16 | * @param { Number } index - currentNodeIndex 17 | * @param { Object } patches - 记录节点差异的对象 18 | */ 19 | function walk(oldNode,newNode,index,patches){ 20 | let currenPatch = [] 21 | if (newNode === null || newNode === undefined) { 22 | // 先不做操作, 具体交给 list diff 处理 23 | }else if (_.isString(oldNode)&&_.isString(newNode)){ 24 | // 比较文本之间的不同 25 | if (newNode !== oldNode) currenPatch.push({type:TEXT,content:newNode}) 26 | }else if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key){ 27 | // 比较attrs的不同 28 | let attrsPatches = diffAttrs(oldNode,newNode) 29 | if(attrsPatches){ 30 | currentPatch.push({ type: ATTRS, attrs: attrsPatches }) 31 | } 32 | // 递归进行子节点的diff比较 33 | diffChildren(oldNode.children, newNode.children, index, patches) 34 | }else { 35 | currentPatch.push({ type: REPLACE, node: newNode}) 36 | } 37 | if(currenPatch.length){ 38 | patches[index] = currenPatch 39 | } 40 | } 41 | function diffAttrs(oldNode,newNode){ 42 | let count = 0 43 | let oldAttrs = oldNode.attrs 44 | let newAttrs = newNode.attrs 45 | let key,value 46 | let attrsPatches = {} 47 | // 如果存在不同的 attrs 48 | for( key in oldAttrs){ 49 | value = oldAttrs[key] 50 | // 如果 oldAttrs 移除掉一些 attrs, newAttrs[key] === undefined 51 | if(newAttrs[key]!==value){ 52 | count++ 53 | attrsPatches[key] = newAttrs[key] 54 | } 55 | } 56 | // 如果存在新的 attr 57 | for (key in newAttrs) { 58 | value = newAttrs[key] 59 | if (!oldAttrs.hasOwnProperty(key)) { 60 | count++ 61 | attrsPatches[key] = value 62 | } 63 | } 64 | if (count === 0) { 65 | return null 66 | } 67 | return attrsPatches 68 | } 69 | // 设置节点唯一标识 70 | let key_id = 0 71 | function diffChildren(oldChildren, newChildren, index, patches) { 72 | // 存放当前node的标识,初始化值为 0 73 | let currentNodeIndex = index 74 | oldChildren.forEach((child,i)=>{ 75 | key_id++ 76 | let newChild = newChildren[i] 77 | currentNodeIndex = key_id 78 | // 递归继续比较 79 | walk(child, newChild, currentNodeIndex, patches) 80 | }) 81 | } 82 | 83 | /** 84 | * Diff two list in O(N). 85 | * @param {Array} oldList - 原始列表 86 | * @param {Array} newList - 经过一些操作的得出的新列表 87 | * @return {Object} - {moves: } 88 | * - moves list操作记录的集合 89 | */ 90 | function diff(oldList,newList,key){ 91 | let oldMap = getKeyIndexAndFree(oldList,key) 92 | let newMap = getKeyIndexAndFree(newList,key) 93 | let newFree = newMap.free 94 | let oldKeyIndex = oldMap.keyIndex 95 | let newKeyIndex = newMap.keyIndex 96 | //记录所有的move操作 97 | let moves = [] 98 | // a simulate list 99 | let children = [] 100 | let i = 0 101 | let item 102 | let itemKey 103 | let freeIndex = 0 104 | // newList 向 oldList 的形式靠近进行操作 105 | while (i < oldList.length){ 106 | item = oldList[i] 107 | itemKey = getItemKey(item.key) 108 | if (itemKey) { 109 | if (!newKeyIndex.hasOwnProperty(itemKey)) { 110 | children.push(null) 111 | } else { 112 | let newItemIndex = newKeyIndex[itemKey] 113 | children.push(newList[newItemIndex]) 114 | } 115 | } 116 | i++ 117 | } 118 | let simulateList = children.slice(0) 119 | //移除列表中一些不存在的元素 120 | i = 0 121 | while (i new list 130 | // j => simulateList 131 | let j = i = 0 132 | while (i } 可以是Element对象,也可以只是字符串,即textNode 7 | */ 8 | class Element { 9 | constructor(tagName,attrs,children){ 10 | //如果只有两个参数 11 | if (_.isArray(attrs)) { 12 | children = attrs 13 | attrs = {} 14 | } 15 | this.tagName = tagName 16 | this.attrs = attrs || {} 17 | this.children = children 18 | //设置 this.key 属性,为后面的list diff 做准备 19 | this.key = attrs ? attrs.key : void 0 20 | } 21 | render () { 22 | let el = document.createElement(this.tagName) 23 | //设置节点属性 24 | for (let attrName in attrs){ 25 | let attrValue = attrs[attrName] 26 | _.setAttr(el,attrName,attrValue) 27 | } 28 | //设置子节点的内容,有可能要递归设置 29 | let children = this.children || [] 30 | children.forEach(child=>{ 31 | let childEl = child instanceof Element ? child.render() : document.createTextNode(child) 32 | el.appendChild(childEl) 33 | }) 34 | return el 35 | } 36 | } 37 | function el(tagName,attrs,children){ 38 | return new Element(tagName,attrs,children) 39 | } 40 | // module.exports = el 41 | -------------------------------------------------------------------------------- /Domdiff/utils.js: -------------------------------------------------------------------------------- 1 | const _ = exports 2 | _.setAttr = function setAttr (node,key,value){ 3 | switch(key){ 4 | case 'style': 5 | node.style.cssText = value// 通过js去设置样式,可以是多个属性集合的字符串,会覆盖之前的样式 6 | break; 7 | case 'value': 8 | let tagName = node.tagName||'' 9 | tagName = tagName.toLowerCase() 10 | if(tagName === 'input'||tagName === 'textarea'){ 11 | node.value = value 12 | } else { 13 | // 如果节点不是 input 或者 textarea, 则使用 `setAttribute` 去设置属性 14 | node.setAttribute(key,value)//设置指定元素上的某个属性值。如果属性已经存在,则更新该值;否则,使用指定的名称和值添加一个新的属性。 15 | } 16 | break; 17 | default: 18 | node.setAttribute(key,value) 19 | break; 20 | } 21 | } 22 | _.slice = function slice(arrayLick,index){ 23 | return Array.prototype.slice.call(arrayLick,index) 24 | } 25 | _.type = function type(obj){ 26 | return Object.prototype.toString.call(obj).replace(/\[object\s\]/g,'') 27 | } 28 | _.isArray = function isArray(list){ 29 | return _.type(list) === 'Array' 30 | } 31 | _.toArray = function toArray(listLike){ 32 | if(!listLike) return [] 33 | let list = [] 34 | for(let i = 0 , l = listLike,length;i 2 | 3 | 4 | 5 | Jasmine Spec Runner v2.8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ThoughWorks/lib/jasmine-2.8.0/boot.js: -------------------------------------------------------------------------------- 1 | /** 2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. 3 | 4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. 5 | 6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. 7 | 8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem 9 | */ 10 | 11 | (function() { 12 | 13 | /** 14 | * ## Require & Instantiate 15 | * 16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. 17 | */ 18 | window.jasmine = jasmineRequire.core(jasmineRequire); 19 | 20 | /** 21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. 22 | */ 23 | jasmineRequire.html(jasmine); 24 | 25 | /** 26 | * Create the Jasmine environment. This is used to run all specs in a project. 27 | */ 28 | var env = jasmine.getEnv(); 29 | 30 | /** 31 | * ## The Global Interface 32 | * 33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. 34 | */ 35 | var jasmineInterface = jasmineRequire.interface(jasmine, env); 36 | 37 | /** 38 | * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. 39 | */ 40 | extend(window, jasmineInterface); 41 | 42 | /** 43 | * ## Runner Parameters 44 | * 45 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. 46 | */ 47 | 48 | var queryString = new jasmine.QueryString({ 49 | getWindowLocation: function() { return window.location; } 50 | }); 51 | 52 | var filterSpecs = !!queryString.getParam("spec"); 53 | 54 | var catchingExceptions = queryString.getParam("catch"); 55 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 56 | 57 | var throwingExpectationFailures = queryString.getParam("throwFailures"); 58 | env.throwOnExpectationFailure(throwingExpectationFailures); 59 | 60 | var random = queryString.getParam("random"); 61 | env.randomizeTests(random); 62 | 63 | var seed = queryString.getParam("seed"); 64 | if (seed) { 65 | env.seed(seed); 66 | } 67 | 68 | /** 69 | * ## Reporters 70 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). 71 | */ 72 | var htmlReporter = new jasmine.HtmlReporter({ 73 | env: env, 74 | onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, 75 | onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, 76 | onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, 77 | addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, 78 | getContainer: function() { return document.body; }, 79 | createElement: function() { return document.createElement.apply(document, arguments); }, 80 | createTextNode: function() { return document.createTextNode.apply(document, arguments); }, 81 | timer: new jasmine.Timer(), 82 | filterSpecs: filterSpecs 83 | }); 84 | 85 | /** 86 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. 87 | */ 88 | env.addReporter(jasmineInterface.jsApiReporter); 89 | env.addReporter(htmlReporter); 90 | 91 | /** 92 | * Filter which specs will be run by matching the start of the full name against the `spec` query param. 93 | */ 94 | var specFilter = new jasmine.HtmlSpecFilter({ 95 | filterString: function() { return queryString.getParam("spec"); } 96 | }); 97 | 98 | env.specFilter = function(spec) { 99 | return specFilter.matches(spec.getFullName()); 100 | }; 101 | 102 | /** 103 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. 104 | */ 105 | window.setTimeout = window.setTimeout; 106 | window.setInterval = window.setInterval; 107 | window.clearTimeout = window.clearTimeout; 108 | window.clearInterval = window.clearInterval; 109 | 110 | /** 111 | * ## Execution 112 | * 113 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. 114 | */ 115 | var currentWindowOnload = window.onload; 116 | 117 | window.onload = function() { 118 | if (currentWindowOnload) { 119 | currentWindowOnload(); 120 | } 121 | htmlReporter.initialize(); 122 | env.execute(); 123 | }; 124 | 125 | /** 126 | * Helper function for readability above. 127 | */ 128 | function extend(destination, source) { 129 | for (var property in source) destination[property] = source[property]; 130 | return destination; 131 | } 132 | 133 | }()); 134 | -------------------------------------------------------------------------------- /ThoughWorks/lib/jasmine-2.8.0/console.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2017 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | function getJasmineRequireObj() { 24 | if (typeof module !== 'undefined' && module.exports) { 25 | return exports; 26 | } else { 27 | window.jasmineRequire = window.jasmineRequire || {}; 28 | return window.jasmineRequire; 29 | } 30 | } 31 | 32 | getJasmineRequireObj().console = function(jRequire, j$) { 33 | j$.ConsoleReporter = jRequire.ConsoleReporter(); 34 | }; 35 | 36 | getJasmineRequireObj().ConsoleReporter = function() { 37 | 38 | var noopTimer = { 39 | start: function(){}, 40 | elapsed: function(){ return 0; } 41 | }; 42 | 43 | function ConsoleReporter(options) { 44 | var print = options.print, 45 | showColors = options.showColors || false, 46 | onComplete = options.onComplete || function() {}, 47 | timer = options.timer || noopTimer, 48 | specCount, 49 | failureCount, 50 | failedSpecs = [], 51 | pendingCount, 52 | ansi = { 53 | green: '\x1B[32m', 54 | red: '\x1B[31m', 55 | yellow: '\x1B[33m', 56 | none: '\x1B[0m' 57 | }, 58 | failedSuites = []; 59 | 60 | print('ConsoleReporter is deprecated and will be removed in a future version.'); 61 | 62 | this.jasmineStarted = function() { 63 | specCount = 0; 64 | failureCount = 0; 65 | pendingCount = 0; 66 | print('Started'); 67 | printNewline(); 68 | timer.start(); 69 | }; 70 | 71 | this.jasmineDone = function() { 72 | printNewline(); 73 | for (var i = 0; i < failedSpecs.length; i++) { 74 | specFailureDetails(failedSpecs[i]); 75 | } 76 | 77 | if(specCount > 0) { 78 | printNewline(); 79 | 80 | var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + 81 | failureCount + ' ' + plural('failure', failureCount); 82 | 83 | if (pendingCount) { 84 | specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); 85 | } 86 | 87 | print(specCounts); 88 | } else { 89 | print('No specs found'); 90 | } 91 | 92 | printNewline(); 93 | var seconds = timer.elapsed() / 1000; 94 | print('Finished in ' + seconds + ' ' + plural('second', seconds)); 95 | printNewline(); 96 | 97 | for(i = 0; i < failedSuites.length; i++) { 98 | suiteFailureDetails(failedSuites[i]); 99 | } 100 | 101 | onComplete(failureCount === 0); 102 | }; 103 | 104 | this.specDone = function(result) { 105 | specCount++; 106 | 107 | if (result.status == 'pending') { 108 | pendingCount++; 109 | print(colored('yellow', '*')); 110 | return; 111 | } 112 | 113 | if (result.status == 'passed') { 114 | print(colored('green', '.')); 115 | return; 116 | } 117 | 118 | if (result.status == 'failed') { 119 | failureCount++; 120 | failedSpecs.push(result); 121 | print(colored('red', 'F')); 122 | } 123 | }; 124 | 125 | this.suiteDone = function(result) { 126 | if (result.failedExpectations && result.failedExpectations.length > 0) { 127 | failureCount++; 128 | failedSuites.push(result); 129 | } 130 | }; 131 | 132 | return this; 133 | 134 | function printNewline() { 135 | print('\n'); 136 | } 137 | 138 | function colored(color, str) { 139 | return showColors ? (ansi[color] + str + ansi.none) : str; 140 | } 141 | 142 | function plural(str, count) { 143 | return count == 1 ? str : str + 's'; 144 | } 145 | 146 | function repeat(thing, times) { 147 | var arr = []; 148 | for (var i = 0; i < times; i++) { 149 | arr.push(thing); 150 | } 151 | return arr; 152 | } 153 | 154 | function indent(str, spaces) { 155 | var lines = (str || '').split('\n'); 156 | var newArr = []; 157 | for (var i = 0; i < lines.length; i++) { 158 | newArr.push(repeat(' ', spaces).join('') + lines[i]); 159 | } 160 | return newArr.join('\n'); 161 | } 162 | 163 | function specFailureDetails(result) { 164 | printNewline(); 165 | print(result.fullName); 166 | 167 | for (var i = 0; i < result.failedExpectations.length; i++) { 168 | var failedExpectation = result.failedExpectations[i]; 169 | printNewline(); 170 | print(indent(failedExpectation.message, 2)); 171 | print(indent(failedExpectation.stack, 2)); 172 | } 173 | 174 | printNewline(); 175 | } 176 | 177 | function suiteFailureDetails(result) { 178 | for (var i = 0; i < result.failedExpectations.length; i++) { 179 | printNewline(); 180 | print(colored('red', 'An error was thrown in an afterAll')); 181 | printNewline(); 182 | print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); 183 | 184 | } 185 | printNewline(); 186 | } 187 | } 188 | 189 | return ConsoleReporter; 190 | }; 191 | -------------------------------------------------------------------------------- /ThoughWorks/lib/jasmine-2.8.0/jasmine-html.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2017 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | jasmineRequire.html = function(j$) { 24 | j$.ResultsNode = jasmineRequire.ResultsNode(); 25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); 26 | j$.QueryString = jasmineRequire.QueryString(); 27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); 28 | }; 29 | 30 | jasmineRequire.HtmlReporter = function(j$) { 31 | 32 | var noopTimer = { 33 | start: function() {}, 34 | elapsed: function() { return 0; } 35 | }; 36 | 37 | function HtmlReporter(options) { 38 | var env = options.env || {}, 39 | getContainer = options.getContainer, 40 | createElement = options.createElement, 41 | createTextNode = options.createTextNode, 42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, 43 | onThrowExpectationsClick = options.onThrowExpectationsClick || function() {}, 44 | onRandomClick = options.onRandomClick || function() {}, 45 | addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, 46 | filterSpecs = options.filterSpecs, 47 | timer = options.timer || noopTimer, 48 | results = [], 49 | specsExecuted = 0, 50 | failureCount = 0, 51 | pendingSpecCount = 0, 52 | htmlReporterMain, 53 | symbols, 54 | failedSuites = []; 55 | 56 | this.initialize = function() { 57 | clearPrior(); 58 | htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'}, 59 | createDom('div', {className: 'jasmine-banner'}, 60 | createDom('a', {className: 'jasmine-title', href: 'http://jasmine.github.io/', target: '_blank'}), 61 | createDom('span', {className: 'jasmine-version'}, j$.version) 62 | ), 63 | createDom('ul', {className: 'jasmine-symbol-summary'}), 64 | createDom('div', {className: 'jasmine-alert'}), 65 | createDom('div', {className: 'jasmine-results'}, 66 | createDom('div', {className: 'jasmine-failures'}) 67 | ) 68 | ); 69 | getContainer().appendChild(htmlReporterMain); 70 | }; 71 | 72 | var totalSpecsDefined; 73 | this.jasmineStarted = function(options) { 74 | totalSpecsDefined = options.totalSpecsDefined || 0; 75 | timer.start(); 76 | }; 77 | 78 | var summary = createDom('div', {className: 'jasmine-summary'}); 79 | 80 | var topResults = new j$.ResultsNode({}, '', null), 81 | currentParent = topResults; 82 | 83 | this.suiteStarted = function(result) { 84 | currentParent.addChild(result, 'suite'); 85 | currentParent = currentParent.last(); 86 | }; 87 | 88 | this.suiteDone = function(result) { 89 | if (result.status == 'failed') { 90 | failedSuites.push(result); 91 | } 92 | 93 | if (currentParent == topResults) { 94 | return; 95 | } 96 | 97 | currentParent = currentParent.parent; 98 | }; 99 | 100 | this.specStarted = function(result) { 101 | currentParent.addChild(result, 'spec'); 102 | }; 103 | 104 | var failures = []; 105 | this.specDone = function(result) { 106 | if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') { 107 | console.error('Spec \'' + result.fullName + '\' has no expectations.'); 108 | } 109 | 110 | if (result.status != 'disabled') { 111 | specsExecuted++; 112 | } 113 | 114 | if (!symbols){ 115 | symbols = find('.jasmine-symbol-summary'); 116 | } 117 | 118 | symbols.appendChild(createDom('li', { 119 | className: noExpectations(result) ? 'jasmine-empty' : 'jasmine-' + result.status, 120 | id: 'spec_' + result.id, 121 | title: result.fullName 122 | } 123 | )); 124 | 125 | if (result.status == 'failed') { 126 | failureCount++; 127 | 128 | var failure = 129 | createDom('div', {className: 'jasmine-spec-detail jasmine-failed'}, 130 | createDom('div', {className: 'jasmine-description'}, 131 | createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) 132 | ), 133 | createDom('div', {className: 'jasmine-messages'}) 134 | ); 135 | var messages = failure.childNodes[1]; 136 | 137 | for (var i = 0; i < result.failedExpectations.length; i++) { 138 | var expectation = result.failedExpectations[i]; 139 | messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message)); 140 | messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack)); 141 | } 142 | 143 | failures.push(failure); 144 | } 145 | 146 | if (result.status == 'pending') { 147 | pendingSpecCount++; 148 | } 149 | }; 150 | 151 | this.jasmineDone = function(doneResult) { 152 | var banner = find('.jasmine-banner'); 153 | var alert = find('.jasmine-alert'); 154 | var order = doneResult && doneResult.order; 155 | alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); 156 | 157 | banner.appendChild( 158 | createDom('div', { className: 'jasmine-run-options' }, 159 | createDom('span', { className: 'jasmine-trigger' }, 'Options'), 160 | createDom('div', { className: 'jasmine-payload' }, 161 | createDom('div', { className: 'jasmine-exceptions' }, 162 | createDom('input', { 163 | className: 'jasmine-raise', 164 | id: 'jasmine-raise-exceptions', 165 | type: 'checkbox' 166 | }), 167 | createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')), 168 | createDom('div', { className: 'jasmine-throw-failures' }, 169 | createDom('input', { 170 | className: 'jasmine-throw', 171 | id: 'jasmine-throw-failures', 172 | type: 'checkbox' 173 | }), 174 | createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')), 175 | createDom('div', { className: 'jasmine-random-order' }, 176 | createDom('input', { 177 | className: 'jasmine-random', 178 | id: 'jasmine-random-order', 179 | type: 'checkbox' 180 | }), 181 | createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order')) 182 | ) 183 | )); 184 | 185 | var raiseCheckbox = find('#jasmine-raise-exceptions'); 186 | 187 | raiseCheckbox.checked = !env.catchingExceptions(); 188 | raiseCheckbox.onclick = onRaiseExceptionsClick; 189 | 190 | var throwCheckbox = find('#jasmine-throw-failures'); 191 | throwCheckbox.checked = env.throwingExpectationFailures(); 192 | throwCheckbox.onclick = onThrowExpectationsClick; 193 | 194 | var randomCheckbox = find('#jasmine-random-order'); 195 | randomCheckbox.checked = env.randomTests(); 196 | randomCheckbox.onclick = onRandomClick; 197 | 198 | var optionsMenu = find('.jasmine-run-options'), 199 | optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'), 200 | optionsPayload = optionsMenu.querySelector('.jasmine-payload'), 201 | isOpen = /\bjasmine-open\b/; 202 | 203 | optionsTrigger.onclick = function() { 204 | if (isOpen.test(optionsPayload.className)) { 205 | optionsPayload.className = optionsPayload.className.replace(isOpen, ''); 206 | } else { 207 | optionsPayload.className += ' jasmine-open'; 208 | } 209 | }; 210 | 211 | if (specsExecuted < totalSpecsDefined) { 212 | var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; 213 | var skippedLink = addToExistingQueryString('spec', ''); 214 | alert.appendChild( 215 | createDom('span', {className: 'jasmine-bar jasmine-skipped'}, 216 | createDom('a', {href: skippedLink, title: 'Run all specs'}, skippedMessage) 217 | ) 218 | ); 219 | } 220 | var statusBarMessage = ''; 221 | var statusBarClassName = 'jasmine-bar '; 222 | 223 | if (totalSpecsDefined > 0) { 224 | statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); 225 | if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } 226 | statusBarClassName += (failureCount > 0) ? 'jasmine-failed' : 'jasmine-passed'; 227 | } else { 228 | statusBarClassName += 'jasmine-skipped'; 229 | statusBarMessage += 'No specs found'; 230 | } 231 | 232 | var seedBar; 233 | if (order && order.random) { 234 | seedBar = createDom('span', {className: 'jasmine-seed-bar'}, 235 | ', randomized with seed ', 236 | createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed) 237 | ); 238 | } 239 | 240 | alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar)); 241 | 242 | var errorBarClassName = 'jasmine-bar jasmine-errored'; 243 | var errorBarMessagePrefix = 'AfterAll '; 244 | 245 | for(var i = 0; i < failedSuites.length; i++) { 246 | var failedSuite = failedSuites[i]; 247 | for(var j = 0; j < failedSuite.failedExpectations.length; j++) { 248 | alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failedSuite.failedExpectations[j].message)); 249 | } 250 | } 251 | 252 | var globalFailures = (doneResult && doneResult.failedExpectations) || []; 253 | for(i = 0; i < globalFailures.length; i++) { 254 | var failure = globalFailures[i]; 255 | alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failure.message)); 256 | } 257 | 258 | var results = find('.jasmine-results'); 259 | results.appendChild(summary); 260 | 261 | summaryList(topResults, summary); 262 | 263 | function summaryList(resultsTree, domParent) { 264 | var specListNode; 265 | for (var i = 0; i < resultsTree.children.length; i++) { 266 | var resultNode = resultsTree.children[i]; 267 | if (filterSpecs && !hasActiveSpec(resultNode)) { 268 | continue; 269 | } 270 | if (resultNode.type == 'suite') { 271 | var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id}, 272 | createDom('li', {className: 'jasmine-suite-detail'}, 273 | createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) 274 | ) 275 | ); 276 | 277 | summaryList(resultNode, suiteListNode); 278 | domParent.appendChild(suiteListNode); 279 | } 280 | if (resultNode.type == 'spec') { 281 | if (domParent.getAttribute('class') != 'jasmine-specs') { 282 | specListNode = createDom('ul', {className: 'jasmine-specs'}); 283 | domParent.appendChild(specListNode); 284 | } 285 | var specDescription = resultNode.result.description; 286 | if(noExpectations(resultNode.result)) { 287 | specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; 288 | } 289 | if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') { 290 | specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason; 291 | } 292 | specListNode.appendChild( 293 | createDom('li', { 294 | className: 'jasmine-' + resultNode.result.status, 295 | id: 'spec-' + resultNode.result.id 296 | }, 297 | createDom('a', {href: specHref(resultNode.result)}, specDescription) 298 | ) 299 | ); 300 | } 301 | } 302 | } 303 | 304 | if (failures.length) { 305 | alert.appendChild( 306 | createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'}, 307 | createDom('span', {}, 'Spec List | '), 308 | createDom('a', {className: 'jasmine-failures-menu', href: '#'}, 'Failures'))); 309 | alert.appendChild( 310 | createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-failure-list'}, 311 | createDom('a', {className: 'jasmine-spec-list-menu', href: '#'}, 'Spec List'), 312 | createDom('span', {}, ' | Failures '))); 313 | 314 | find('.jasmine-failures-menu').onclick = function() { 315 | setMenuModeTo('jasmine-failure-list'); 316 | }; 317 | find('.jasmine-spec-list-menu').onclick = function() { 318 | setMenuModeTo('jasmine-spec-list'); 319 | }; 320 | 321 | setMenuModeTo('jasmine-failure-list'); 322 | 323 | var failureNode = find('.jasmine-failures'); 324 | for (i = 0; i < failures.length; i++) { 325 | failureNode.appendChild(failures[i]); 326 | } 327 | } 328 | }; 329 | 330 | return this; 331 | 332 | function find(selector) { 333 | return getContainer().querySelector('.jasmine_html-reporter ' + selector); 334 | } 335 | 336 | function clearPrior() { 337 | // return the reporter 338 | var oldReporter = find(''); 339 | 340 | if(oldReporter) { 341 | getContainer().removeChild(oldReporter); 342 | } 343 | } 344 | 345 | function createDom(type, attrs, childrenVarArgs) { 346 | var el = createElement(type); 347 | 348 | for (var i = 2; i < arguments.length; i++) { 349 | var child = arguments[i]; 350 | 351 | if (typeof child === 'string') { 352 | el.appendChild(createTextNode(child)); 353 | } else { 354 | if (child) { 355 | el.appendChild(child); 356 | } 357 | } 358 | } 359 | 360 | for (var attr in attrs) { 361 | if (attr == 'className') { 362 | el[attr] = attrs[attr]; 363 | } else { 364 | el.setAttribute(attr, attrs[attr]); 365 | } 366 | } 367 | 368 | return el; 369 | } 370 | 371 | function pluralize(singular, count) { 372 | var word = (count == 1 ? singular : singular + 's'); 373 | 374 | return '' + count + ' ' + word; 375 | } 376 | 377 | function specHref(result) { 378 | return addToExistingQueryString('spec', result.fullName); 379 | } 380 | 381 | function seedHref(seed) { 382 | return addToExistingQueryString('seed', seed); 383 | } 384 | 385 | function defaultQueryString(key, value) { 386 | return '?' + key + '=' + value; 387 | } 388 | 389 | function setMenuModeTo(mode) { 390 | htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode); 391 | } 392 | 393 | function noExpectations(result) { 394 | return (result.failedExpectations.length + result.passedExpectations.length) === 0 && 395 | result.status === 'passed'; 396 | } 397 | 398 | function hasActiveSpec(resultNode) { 399 | if (resultNode.type == 'spec' && resultNode.result.status != 'disabled') { 400 | return true; 401 | } 402 | 403 | if (resultNode.type == 'suite') { 404 | for (var i = 0, j = resultNode.children.length; i < j; i++) { 405 | if (hasActiveSpec(resultNode.children[i])) { 406 | return true; 407 | } 408 | } 409 | } 410 | } 411 | } 412 | 413 | return HtmlReporter; 414 | }; 415 | 416 | jasmineRequire.HtmlSpecFilter = function() { 417 | function HtmlSpecFilter(options) { 418 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); 419 | var filterPattern = new RegExp(filterString); 420 | 421 | this.matches = function(specName) { 422 | return filterPattern.test(specName); 423 | }; 424 | } 425 | 426 | return HtmlSpecFilter; 427 | }; 428 | 429 | jasmineRequire.ResultsNode = function() { 430 | function ResultsNode(result, type, parent) { 431 | this.result = result; 432 | this.type = type; 433 | this.parent = parent; 434 | 435 | this.children = []; 436 | 437 | this.addChild = function(result, type) { 438 | this.children.push(new ResultsNode(result, type, this)); 439 | }; 440 | 441 | this.last = function() { 442 | return this.children[this.children.length - 1]; 443 | }; 444 | } 445 | 446 | return ResultsNode; 447 | }; 448 | 449 | jasmineRequire.QueryString = function() { 450 | function QueryString(options) { 451 | 452 | this.navigateWithNewParam = function(key, value) { 453 | options.getWindowLocation().search = this.fullStringWithNewParam(key, value); 454 | }; 455 | 456 | this.fullStringWithNewParam = function(key, value) { 457 | var paramMap = queryStringToParamMap(); 458 | paramMap[key] = value; 459 | return toQueryString(paramMap); 460 | }; 461 | 462 | this.getParam = function(key) { 463 | return queryStringToParamMap()[key]; 464 | }; 465 | 466 | return this; 467 | 468 | function toQueryString(paramMap) { 469 | var qStrPairs = []; 470 | for (var prop in paramMap) { 471 | qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])); 472 | } 473 | return '?' + qStrPairs.join('&'); 474 | } 475 | 476 | function queryStringToParamMap() { 477 | var paramStr = options.getWindowLocation().search.substring(1), 478 | params = [], 479 | paramMap = {}; 480 | 481 | if (paramStr.length > 0) { 482 | params = paramStr.split('&'); 483 | for (var i = 0; i < params.length; i++) { 484 | var p = params[i].split('='); 485 | var value = decodeURIComponent(p[1]); 486 | if (value === 'true' || value === 'false') { 487 | value = JSON.parse(value); 488 | } 489 | paramMap[decodeURIComponent(p[0])] = value; 490 | } 491 | } 492 | 493 | return paramMap; 494 | } 495 | 496 | } 497 | 498 | return QueryString; 499 | }; 500 | -------------------------------------------------------------------------------- /ThoughWorks/lib/jasmine-2.8.0/jasmine.css: -------------------------------------------------------------------------------- 1 | body { overflow-y: scroll; } 2 | 3 | .jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; } 4 | .jasmine_html-reporter a { text-decoration: none; } 5 | .jasmine_html-reporter a:hover { text-decoration: underline; } 6 | .jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; } 7 | .jasmine_html-reporter .jasmine-banner, .jasmine_html-reporter .jasmine-symbol-summary, .jasmine_html-reporter .jasmine-summary, .jasmine_html-reporter .jasmine-result-message, .jasmine_html-reporter .jasmine-spec .jasmine-description, .jasmine_html-reporter .jasmine-spec-detail .jasmine-description, .jasmine_html-reporter .jasmine-alert .jasmine-bar, .jasmine_html-reporter .jasmine-stack-trace { padding-left: 9px; padding-right: 9px; } 8 | .jasmine_html-reporter .jasmine-banner { position: relative; } 9 | .jasmine_html-reporter .jasmine-banner .jasmine-title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; } 10 | .jasmine_html-reporter .jasmine-banner .jasmine-version { margin-left: 14px; position: relative; top: 6px; } 11 | .jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; } 12 | .jasmine_html-reporter .jasmine-version { color: #aaa; } 13 | .jasmine_html-reporter .jasmine-banner { margin-top: 14px; } 14 | .jasmine_html-reporter .jasmine-duration { color: #fff; float: right; line-height: 28px; padding-right: 9px; } 15 | .jasmine_html-reporter .jasmine-symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } 16 | .jasmine_html-reporter .jasmine-symbol-summary li { display: inline-block; height: 10px; width: 14px; font-size: 16px; } 17 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed { font-size: 14px; } 18 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed:before { color: #007069; content: "\02022"; } 19 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed { line-height: 9px; } 20 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; } 21 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled { font-size: 14px; } 22 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled:before { color: #bababa; content: "\02022"; } 23 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending { line-height: 17px; } 24 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending:before { color: #ba9d37; content: "*"; } 25 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty { font-size: 14px; } 26 | .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty:before { color: #ba9d37; content: "\02022"; } 27 | .jasmine_html-reporter .jasmine-run-options { float: right; margin-right: 5px; border: 1px solid #8a4182; color: #8a4182; position: relative; line-height: 20px; } 28 | .jasmine_html-reporter .jasmine-run-options .jasmine-trigger { cursor: pointer; padding: 8px 16px; } 29 | .jasmine_html-reporter .jasmine-run-options .jasmine-payload { position: absolute; display: none; right: -1px; border: 1px solid #8a4182; background-color: #eee; white-space: nowrap; padding: 4px 8px; } 30 | .jasmine_html-reporter .jasmine-run-options .jasmine-payload.jasmine-open { display: block; } 31 | .jasmine_html-reporter .jasmine-bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 32 | .jasmine_html-reporter .jasmine-bar.jasmine-failed { background-color: #ca3a11; } 33 | .jasmine_html-reporter .jasmine-bar.jasmine-passed { background-color: #007069; } 34 | .jasmine_html-reporter .jasmine-bar.jasmine-skipped { background-color: #bababa; } 35 | .jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; } 36 | .jasmine_html-reporter .jasmine-bar.jasmine-menu { background-color: #fff; color: #aaa; } 37 | .jasmine_html-reporter .jasmine-bar.jasmine-menu a { color: #333; } 38 | .jasmine_html-reporter .jasmine-bar a { color: white; } 39 | .jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list, .jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures { display: none; } 40 | .jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list, .jasmine_html-reporter.jasmine-failure-list .jasmine-summary { display: none; } 41 | .jasmine_html-reporter .jasmine-results { margin-top: 14px; } 42 | .jasmine_html-reporter .jasmine-summary { margin-top: 14px; } 43 | .jasmine_html-reporter .jasmine-summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } 44 | .jasmine_html-reporter .jasmine-summary ul.jasmine-suite { margin-top: 7px; margin-bottom: 7px; } 45 | .jasmine_html-reporter .jasmine-summary li.jasmine-passed a { color: #007069; } 46 | .jasmine_html-reporter .jasmine-summary li.jasmine-failed a { color: #ca3a11; } 47 | .jasmine_html-reporter .jasmine-summary li.jasmine-empty a { color: #ba9d37; } 48 | .jasmine_html-reporter .jasmine-summary li.jasmine-pending a { color: #ba9d37; } 49 | .jasmine_html-reporter .jasmine-summary li.jasmine-disabled a { color: #bababa; } 50 | .jasmine_html-reporter .jasmine-description + .jasmine-suite { margin-top: 0; } 51 | .jasmine_html-reporter .jasmine-suite { margin-top: 14px; } 52 | .jasmine_html-reporter .jasmine-suite a { color: #333; } 53 | .jasmine_html-reporter .jasmine-failures .jasmine-spec-detail { margin-bottom: 28px; } 54 | .jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description { background-color: #ca3a11; } 55 | .jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description a { color: white; } 56 | .jasmine_html-reporter .jasmine-result-message { padding-top: 14px; color: #333; white-space: pre; } 57 | .jasmine_html-reporter .jasmine-result-message span.jasmine-result { display: block; } 58 | .jasmine_html-reporter .jasmine-stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; } 59 | -------------------------------------------------------------------------------- /ThoughWorks/lib/jasmine-2.8.0/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunseekers/JavaScript/cbff2028cf7217b97a78844d0d903f32af495ca2/ThoughWorks/lib/jasmine-2.8.0/jasmine_favicon.png -------------------------------------------------------------------------------- /ThoughWorks/plane(一).html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 91 | 92 | -------------------------------------------------------------------------------- /ThoughWorks/spec/getLocation.js: -------------------------------------------------------------------------------- 1 | /* 2 | jasmine基本语法介绍: 3 | describe(string, function):可以理解为是一个测试集或者测试包(官方称之为suite),主要功能是用来划分单元测试的,describe是可以嵌套使用的 4 | 参数string:描述测试包的信息 5 | 参数function:测试集的具体实现,可包含任意代码 6 | 7 | it(string, function):测试用例(官方称之为spec) 8 | 参数string:描述测试用例的信息 9 | 参数function:测试用例的具体实现,可包含任意代码 10 | 11 | expect()的参数是需要测试的东西,toBe()是一种断言,相当于===,not相当于取非。 12 | 13 | 从以下例子可知: 14 | 1、每个测试文件中可以包含多个describe 15 | 2、每个describe中可以包含多个it 16 | 3、每个it中可以包含多个expect 17 | 4、describe可嵌套使用 18 | */ 19 | describe("当指定消息ID 2 时,应该输出",function(){ 20 | const location=` 21 | plane1 1 1 1 22 | plane1 1 1 1 1 2 3 23 | plane1 2 3 4 1 1 1 24 | plane1 3 4 5 25 | plane1 1 1 1 1 2 3`; 26 | const expectedLocation="plane1 2 3,4,5"; 27 | const locationMsg=getLocation(location,2); 28 | it(expectedLocation, function() { 29 | expect(locationMsg).toEqual(expectedLocation); 30 | }); 31 | }) 32 | describe("当指定消息ID 4 时,应该输出",function(){ 33 | const location=` 34 | plane1 1 1 1 35 | plane1 1 1 1 1 2 3 36 | plane1 2 3 4 1 1 1 37 | plane1 3 4 5 38 | plane1 1 1 1 1 2 3`; 39 | const expectedLocation="Error: 4"; 40 | const locationMsg=getLocation(location,4); 41 | it(expectedLocation, function() { 42 | expect(locationMsg).toEqual(expectedLocation); 43 | }); 44 | }) 45 | describe("当指定消息ID 100 时,应该输出",function(){ 46 | const location=` 47 | plane1 1 1 1 48 | plane1 1 1 1 1 2 3 49 | plane1 2 3 4 1 1 1 50 | plane1 3 4 5 51 | plane1 1 1 1 1 2 3`; 52 | const expectedLocation="Cannot found : 100"; 53 | const locationMsg=getLocation(location,100); 54 | it(expectedLocation, function() { 55 | expect(locationMsg).toEqual(expectedLocation); 56 | }); 57 | }) 58 | 59 | 60 | -------------------------------------------------------------------------------- /ThoughWorks/src/getLocation.js: -------------------------------------------------------------------------------- 1 | /* 2 | 1. 题目要求输入两个值,一个是文本内容,一个是消息序列,即两个参数 3 | 2. 判断无人机的ID和和坐标位置 4 | 3. 无人机飞行时传回来的坐标是否有故障 5 | 4. 消息测试 6 | 比如: plane 1 1 1 plane 1 1 1 1 2 3 7 | */ 8 | function getLocation(infomation,signalIndex){ 9 | let planeInfomation=infomation.trim(); 10 | let planeName=planeInfomation.split(' ')[0]; 11 | let regName=/^[A-Za-z0-9]+$/gi; 12 | let location=infomation.split(`${planeName}`);//此时是带有空格的数组里面包着字符串,为了后面计算坐标方便需要转换为数字数组 13 | //console.log(infomation); 14 | let locations=location.map(function(ele,index,array){ 15 | return ele.trim().split(' ').join(''); 16 | }).slice(1); 17 | location=location.map(function(ele,index,array){ 18 | return ele.trim().split(' '); 19 | }).slice(1) 20 | //判断坐标是不是数字 21 | let isNumber=locations.every(function(ele){ 22 | return (ele-0); 23 | }); 24 | if(regName.test(planeName)&&isNumber===true&&location[0].length==3){ 25 | //如果消息不存在 26 | if(location.length<(signalIndex+1)){ 27 | //alert(`Cannot found : ${signalIndex}`);有弹出框更方便看到效果 28 | console.log(`Cannot found : ${signalIndex}`); 29 | return `Cannot found : ${signalIndex}` 30 | }else{//消息存在,应该对坐标值进行计算 31 | //保存初始位置 32 | let a=location[0][0]; 33 | let b=location[0][1]; 34 | let c=location[0][2]; 35 | let arr=[a,b,c]; 36 | let locationArr=[];//用于保存所有的坐标值 37 | locationArr.push(arr); 38 | for(let i=0;i1){ 54 | //判断当前的坐标的起始位置是不是上次坐标的位置 55 | if(((location[i][0]==locationArr[i-1][0])==true)&&((location[i][1]==locationArr[i-1][1])==true)&&((location[i][2]==locationArr[i-1][2])==true)&&(location[i].length==6)){ 56 | //把坐标的起始位置加上偏移量,计算出现在坐标的位置作为下次坐标的起始位置,保存到数组 57 | a=location[i][0]-0+(location[i][3]-0); 58 | b=location[i][1]-0 + (location[i][4]-0); 59 | c=location[i][2]-0 + (location[i][5]-0); 60 | arr=[a,b,c]; 61 | locationArr.push(arr); 62 | }else{ 63 | //坐标有误 64 | let r = ["na", "na", "na"]; 65 | locationArr.push(r); 66 | } 67 | } 68 | } 69 | if(locationArr[signalIndex][0]=='na'){ 70 | console.log(`Error: ${signalIndex}`); 71 | //alert(`Error: ${signalIndex}`);有弹出框更方便看到效果 72 | return `Error: ${signalIndex}`; 73 | }else{ 74 | console.log(`${planeName} ${signalIndex} ${locationArr[signalIndex]}`); 75 | //alert(`${planeName} ${signalIndex} ${locationArr[signalIndex]}`);有弹出框更方便看到效果 76 | return `${planeName} ${signalIndex} ${locationArr[signalIndex]}`; 77 | } 78 | } 79 | }else{ 80 | console.log("无人机处于故障"); 81 | //alert("无人机处于故障"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /bubble_sort.js: -------------------------------------------------------------------------------- 1 | function bubble_sort(A){ 2 | for(let i=A.length-1;i>=1;i--){ 3 | for(let j =0 ;j<=i;j++){ 4 | A[j-1]>A[j]&&swap(A,j-1,j)//如果前面的数大就交换位置 5 | } 6 | } 7 | } 8 | function swap(A,i,j){ 9 | const t = A[i] 10 | A[i]=A[j] 11 | A[j]=t 12 | } 13 | // 执行时间大概是:(N^2-N)/2 14 | -------------------------------------------------------------------------------- /counting_sort.js: -------------------------------------------------------------------------------- 1 | //计数排序 2 | function counting_sort(A){ 3 | const max = Math.max(...A) 4 | //累计数组 5 | const B = Array(max+1).fill(0) 6 | //结果数组 7 | const C = Array(A.length) 8 | //累计位增加 9 | A.foreach((_,index)=>{ 10 | B[A[index]]++ 11 | }) 12 | //累计求和 13 | for(let i =1;i this.lengyj) return false 29 | if (position === 0) { 30 | // 第一个节点是否存在 31 | if (this.head === null) { 32 | this.head = this.tail = newNode 33 | } else { 34 | this.head.prev = newNode 35 | newNode.next = this.head 36 | this.head = newNode 37 | } 38 | } else if (position === this.length) { 39 | this.tail.next = newNode 40 | newNode.prev = this.tail 41 | this.tail = newNode 42 | } else { 43 | const currentNode = this.header 44 | const previousNode = null 45 | let index = 0 46 | while (index++ < position) { 47 | currentNode = currentNode.next 48 | previousNode = currentNode 49 | } 50 | previousNode.next = newNode 51 | newNode.next = currentNode 52 | newNode.prev = previousNode 53 | currentNode.prev = newNode 54 | } 55 | this.length++ 56 | return true 57 | } 58 | removeAt(position) { 59 | if (positin < 0 || position > this.lengyj) return false 60 | let currentNode = this.header 61 | if (position === 0) { 62 | if (this.length === 1) { 63 | this.header = this.tail = null 64 | } else { 65 | this.header = this.header.next 66 | this.header.pre = null 67 | } 68 | } else if (position === this.length - 1) { 69 | currentNode = this.tail 70 | this.tail.prev.next = null 71 | this.tail = this.tail.prev 72 | } else { 73 | let index = 0 74 | let previousNode = null 75 | while (index++ < position) { 76 | previousNode = currentNode 77 | currentNode = currentNode.next 78 | } 79 | previousNode.next = currentNode.next 80 | currentNode.next.prev = previousNode 81 | } 82 | this.length-- 83 | return currentNode.data 84 | } 85 | // 更新节点,思路先删除原来的节点,再再新的位置上面插入一个节点 86 | updata(position, data) { 87 | let result = this.removeAt(position) 88 | this.insert(opsition, data) 89 | return result 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /draw/draw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 随机抽奖 7 | 8 | 9 | 66 | 67 | 68 | 69 | 70 | 75 |
76 | 77 | 78 | 79 | 80 | 81 |
82 | 83 | -------------------------------------------------------------------------------- /draw/js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @authors Your Name (you@example.org) 4 | * @date 2017-10-30 11:13:20 5 | * @version $Id$ 6 | */ 7 | $(function(){ 8 | const color = ["#626262","#787878","rgba(0,0,0,0.5)","#DCC722","white","#FF4350"], 9 | info = ["谢谢参与"," 1000"," 10"," 500"," 100"," 4999"," 1"," 20"], 10 | info1 = ['再接再厉',' 元',' 元',' 淘金币',' 元',' 淘金币',' 元',' 淘金币']; 11 | let clickNum=5,//可抽奖次数 12 | rotateNum=0,//旋转次数 13 | angles,//旋转角度 14 | notice;//中奖公告 15 | $('#btn').on('click',function(){ 16 | if(clickNum>=1){ 17 | clickNum--; 18 | runCup(); 19 | //转盘旋转过程“开始抽奖”按钮无法点击 20 | $("#btn").attr('disabled',true); 21 | rotateNum++; 22 | //“开始抽奖”按钮无法点击恢复点击 23 | setTimeout(function(){ 24 | alert(notice); 25 | $('#btn').removeAttr("disabled", true); 26 | },6000); 27 | }else{ 28 | alert("亲,抽奖次数已用光!"); 29 | 30 | } 31 | }); 32 | //转盘旋转 33 | function runCup(){ 34 | probability(); 35 | const degValue = 'rotate('+angles+'deg'+')'; 36 | $("#canvas").css('transform',degValue); 37 | } 38 | //各奖项对应的旋转角度及中奖公告内容 39 | function probability(){ 40 | //获取随机数 41 | let num=parseInt(Math.random()*7); 42 | switch (num) { 43 | case 0: angles=2160*rotateNum+ 1800;notice = info[0] + info1[0]; 44 | break; 45 | case 1: angles=2160*rotateNum+ 1845;notice = info[7] + info1[7]; 46 | break; 47 | case 2: angles=2160*rotateNum+ 1890;notice = info[6] + info1[6]; 48 | break; 49 | case 3: angles=2160*rotateNum+ 1935;notice = info[5] + info1[5]; 50 | break; 51 | case 4: angles=2160*rotateNum+ 1980;notice = info[4] + info1[4]; 52 | break; 53 | case 5: angles=2160*rotateNum+ 2025;notice = info[3] + info1[3]; 54 | break; 55 | case 6: angle=2160*rotateNum+ 2070;notice = info[2] + info1[2]; 56 | break; 57 | case 7: angles=2160*rotateNum+ 2115;notice = info[1] + info1[1]; 58 | break; 59 | } 60 | } 61 | circle(); 62 | function circle(){ 63 | const ctx=$("#canvas")[0].getContext('2d'), 64 | ctx1=$("#canvas1")[0].getContext('2d'), 65 | ctx2=$("#canvas2")[0].getContext('2d'), 66 | ctx3=$("#canvas3")[0].getContext('2d'); 67 | console.log($("#canvas"));//$("#canvas")获取到的是所有id为canvas的元素,是一个集合 68 | createCricle(); 69 | createCirText(); 70 | initPoint(); 71 | //绘制外面的圆 72 | function createCricle(){ 73 | let startAngle=0;//扇形开始的角度 74 | let endAngle=0;//扇形结束的角度 75 | for(let i=0;i<8;i++){ 76 | startAngle=Math.PI*(i/4-1/8); 77 | endAngle=startAngle+Math.PI*(1/4); 78 | ctx.save(); 79 | ctx.beginPath(); 80 | ctx.arc(150,150,100,startAngle,endAngle,false); 81 | ctx.lineWidth=120; 82 | i%2==0?(ctx.strokeStyle=color[0]):(ctx.strokeStyle=color[1]); 83 | ctx.stroke(); 84 | ctx.restore(); 85 | } 86 | } 87 | function createCirText(){ 88 | ctx.textAlign='start'; 89 | ctx.textBaseline='middle'; 90 | ctx.fillStyle=color[3]; 91 | const step=Math.PI/4; 92 | for(let i=0;i<8;i++){ 93 | ctx.save(); 94 | ctx.beginPath(); 95 | ctx.translate(150,150); 96 | ctx.rotate(i*step); 97 | ctx.font = " 20px Microsoft YaHei"; 98 | ctx.fillStyle = color[3]; 99 | ctx.fillText(info[i],-30,-115,60); 100 | ctx.font = " 14px Microsoft YaHei"; 101 | ctx.fillText(info1[i],-30,-95,60); 102 | ctx.closePath(); 103 | ctx.restore(); 104 | } 105 | } 106 | //转盘的指针 107 | function initPoint(){ 108 | //箭头指针 109 | ctx1.beginPath(); 110 | ctx1.moveTo(100,24); 111 | ctx1.lineTo(90,62); 112 | ctx1.lineTo(110,62); 113 | ctx1.lineTo(100,24); 114 | ctx1.fillStyle = color[5]; 115 | ctx1.fill(); 116 | ctx1.closePath(); 117 | //中间小圆 118 | ctx3.beginPath(); 119 | ctx3.arc(100,100,40,0,Math.PI*2,false); 120 | ctx3.fillStyle = color[5]; 121 | ctx3.fill(); 122 | ctx3.closePath(); 123 | //小圆文字 124 | ctx3.font = "Bold 20px Microsoft YaHei"; 125 | ctx3.textAlign='start'; 126 | ctx3.textBaseline='middle'; 127 | ctx3.fillStyle = color[4]; 128 | ctx3.beginPath(); 129 | ctx3.fillText('开始',80,90,40); 130 | ctx3.fillText('抽奖',80,110,40); 131 | ctx3.fill(); 132 | ctx3.closePath(); 133 | //中间圆圈 134 | ctx2.beginPath(); 135 | ctx2.arc(75,75,75,0,Math.PI*2,false); 136 | ctx2.fillStyle = color[2]; 137 | ctx2.fill(); 138 | ctx2.closePath(); 139 | } 140 | } 141 | 142 | }) 143 | -------------------------------------------------------------------------------- /getMiddle.js: -------------------------------------------------------------------------------- 1 | //根据传入的字符串,如果长度为偶数,返回中间两个字母,如果为奇数则返回中间的字符 2 | //string.substr() 截取指定长度的字符串 3 | function getMiddle(str) { 4 | let numStyle = 0 5 | let len =str.length 6 | let index = 0 7 | len % 2 === 0 ? numStyle = 0 : numStyle = 1 8 | if (numStyle === 0 ){ 9 | index = len/2-1 10 | return str.substr(index,2) 11 | }else { 12 | index = parseInt(len/2) 13 | return str.substr(index,1) 14 | } 15 | } 16 | //简洁写法 Math.ceil() 函数返回大于或等于一个给定数字的最小整数(向上取整) 17 | function getMiddle(str){ 18 | return str.substr(Math.ceil(str.length/2 - 1),str.length % 2 ===0 ? 2 : 1) 19 | } -------------------------------------------------------------------------------- /hash.js: -------------------------------------------------------------------------------- 1 | export function hashFn(string, limit = 7) { 2 | const PRIME = 31 3 | let hashCode = 0 4 | for (let item of string) { 5 | hashCode = PRIME * hashCode + item.charCodeAt() 6 | } 7 | return hashCode % limit 8 | } 9 | export function isPrime(number) { 10 | if (number <= 1) return false 11 | let temp = Math.ceil(Math.sqrt(number)) 12 | for (let i = 2; i < temp; i++) { 13 | if (number % i === 0) { 14 | return false 15 | } 16 | } 17 | return true 18 | } 19 | 20 | export class HashTable { 21 | constructor() { 22 | this.storage = [] 23 | this.count = 0 //存储当前存放元素的个数 24 | this.limit = 7 // 哈希表的长度 25 | // 填充因子(已有个数/总个数 26 | this.loadFactor = 0.75 27 | this.minLoadFactor = 0.25 28 | } 29 | //根据number获取最临近的质数 30 | getPrime(number) { 31 | while (!isPrime(number)) { 32 | number++ 33 | } 34 | return number 35 | } 36 | // 往hash 里面添加数据(假设是一个三位数组) 37 | // 首先找到应该添加到的一维的下表,然后找到二位数组的地方,最后在添加 38 | put(key, value) { 39 | let index = this.storage[this.hashFn(key)] //1、根据 key 获取要映射到 storage 里面的 index(通过哈希函数获取) 40 | let bucket = this.storage[index] // 2、根据 index 取出对应的 bucket 41 | if (bucket === undefined) { //3、判断是否存在 bucket 42 | bucket = [] 43 | this.storage[index] = bucket 44 | } 45 | // 4、判断是插入数据操作还是修改数据操作 46 | for (let i = 0; i < bucket.length; i++) { 47 | let tuple = bucket[i] 48 | if (tuple[0] === key) { // 如果 key 相等,则修改数据 49 | tuple[1] === value // 修改完 tuple 里数据,return 终止,不再往下执行。 50 | return 51 | } 52 | } 53 | // 5、bucket 新增数据 54 | this.bucket.push([key, value]) 55 | this.count++ 56 | // 判断哈希表是否要扩容,若装填因子 > 0.75,则扩容 57 | if (this.count / this.limit > this.loadFactor) { 58 | this.resize(this.getPrime(this.limit * 2)) 59 | } 60 | } 61 | 62 | // 根据 get(key) 获取 value 63 | get(key) { 64 | let index = this.storage[this.hashFn(key)] //1、根据 key 获取要映射到 storage 里面的 index(通过哈希函数获取) 65 | let bucket = this.storage[index] // 2、根据 index 取出对应的 bucket 66 | if (bucket === undefined) { //3、判断是否存在 bucket 67 | return null 68 | } 69 | // 4、判断是插入数据操作还是修改数据操作 70 | for (let i = 0; i < bucket.length; i++) { 71 | let tuple = bucket[i] 72 | if (tuple[0] === key) { // 如果 key 相等,则修改数据 73 | return tuple[1] 74 | } 75 | } 76 | return null 77 | } 78 | 79 | // remove(key) 删除指定 key 的数据 80 | remove(key) { 81 | let index = this.storage[this.hashFn(key)] //1、根据 key 获取要映射到 storage 里面的 index(通过哈希函数获取) 82 | let bucket = this.storage[index] // 2、根据 index 取出对应的 bucket 83 | if (bucket === undefined) { //3、判断是否存在 bucket 84 | return null 85 | } 86 | // 4、判断是插入数据操作还是修改数据操作 87 | for (let i = 0; i < bucket.length; i++) { 88 | let tuple = bucket[i] 89 | if (tuple[0] === key) { // 如果 key 相等,则修改数据 90 | bucket.splice(i, 1) 91 | this.count--; 92 | // 根据装填因子的大小,判断是否要进行哈希表压缩 93 | if (this.limit > 7 && this.count / this.limit < this.minLoadFactor) { 94 | this.resize(this.getPrime(Math.floor(this.limit / 2))); 95 | } 96 | return tuple 97 | } 98 | } 99 | return null 100 | } 101 | isEmpty() { 102 | return this.count === 0 103 | } 104 | size() { 105 | return this.count 106 | } 107 | // 重新调整哈希表大小,扩容或压缩 108 | resize(newLimit) { 109 | const oldStorage = this.storage 110 | this.storage = [] 111 | this.count = 0 112 | this.limit = newLimit 113 | // 3、遍历 oldStorage,取出所有数据,重新 put 到 this.storage 114 | for (let bucket of oldStorage) { 115 | if (bucket) { 116 | for (const b of bucket) { 117 | this.put(b[0], b[1]) 118 | } 119 | } 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | debounce 8 | 13 | 14 | 15 | 16 |
17 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /insert.js: -------------------------------------------------------------------------------- 1 | //在一段有序数组中插入一个数字 2 | const array = [1,2,4,8,9] 3 | const b = 3 4 | const index = array.find(a=>a>b) 5 | if(index===undefined){ 6 | array.push(b) 7 | }else{ 8 | array.splice(index,0,b) 9 | } 10 | //简化版 11 | const index1 = array.indexOf(b) 12 | array.splice(index1===-1?array.length:index1,0,b) 13 | //原始实现方式 14 | function inster(A,x){ 15 | //p指向下一个要比较的元素 16 | //p+1指向空位 17 | let p = A.length-1 18 | while(p>=0&&A[p]>x){ 19 | A[p+1] = A[p] 20 | p-- 21 | } 22 | A[p+1]=x 23 | } 24 | //整个插入排序的过程 25 | //1.数组的第一个元素和后面一个元素比较 26 | function insertion_sort(A){ 27 | for(let i = 1;i=0&&A[p]>x){//执行时间不定,最好不走循环提,最坏数组是倒叙 34 | A[p+1]=A[p] 35 | p-- 36 | } 37 | A[p+1]=x 38 | } 39 | // 执行时间大概是:(N^2-N)/2 40 | -------------------------------------------------------------------------------- /link.js: -------------------------------------------------------------------------------- 1 | //单向列表 2 | class LinkedList{ 3 | constructor(){ 4 | this.head = null 5 | } 6 | insert(node){//O(1) 7 | if(this.head!==null){ 8 | node.next = this.head 9 | } 10 | this.head = node 11 | } 12 | find(node){//))O(n) 13 | let p = this.head 14 | while(p&&p!==node){ 15 | p = p.next() 16 | } 17 | return p 18 | } 19 | } 20 | //队列 21 | class Queue { 22 | enqueue(item){ 23 | if(this.size===this.max){ 24 | throw 'Queue Overflow' 25 | } 26 | this.data[this.p++] = item 27 | this.size++ 28 | if(this.p == this.max){ 29 | this.p = 0 30 | } 31 | } 32 | dequeue(){ 33 | if(this.size==0){ 34 | throw 'Queue Underflow' 35 | } 36 | const item = this.data[this.q++] 37 | this.size-- 38 | if(this.q===this.max){ 39 | this.q=0 40 | } 41 | return item 42 | } 43 | } 44 | 45 | //反转二叉树 46 | function reverseBTress(node){ 47 | if(!node){ 48 | return 49 | } 50 | const tmp = node.left 51 | node.left = node.right 52 | node.right = tmp 53 | reverseBTress(node.right) 54 | reverseBTress(node.left) 55 | } 56 | 57 | //a.name=ramroll&a.dress&x=1&y= 58 | 59 | function parse(str){ 60 | return str.split('&').reduce((o,kv)=>{ 61 | const [key,value] = kv.split('=') 62 | if(!value){ 63 | return o 64 | } 65 | // o[key] = value 66 | deep_set(o,kv.split(/[\[\]]/g).filter(x=>x),value) 67 | return o 68 | },{}) 69 | } 70 | function deep_set(o,path,value){ 71 | debugger 72 | let i = 0; 73 | for(;i0){ 103 | this.s2.push(this.s1.pop()) 104 | } 105 | if(this.s2.length>0){ 106 | return this.s2.pop() 107 | } 108 | } 109 | } 110 | 111 | // 112 | function f(w,h){ 113 | const dp = [] 114 | for(let y = 0;y<=h;y++){ 115 | 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /linkedList.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(data) { 3 | this.data = data 4 | this.next = null 5 | } 6 | } 7 | class linkedList { 8 | constructor() { 9 | this.length = 0 10 | this.head = null 11 | } 12 | append(data) { 13 | const newNode = new Node(data) 14 | 15 | if (this.length === 0) { 16 | this.head = newNode.data 17 | } else { 18 | // 当 currentNode.next 不为空时, 19 | // 循序依次找最后一个节点,即节点的 next 为 null 时 20 | let currentNode = this.head 21 | while (currentNode.next !== null) { 22 | currentNode = currentNode.next 23 | } 24 | currentNode.next = newNode 25 | this.next = newNode 26 | } 27 | this.length++ 28 | } 29 | insert(position, data) { 30 | if (position < 0 || position > this.length) return false 31 | const newNode = new Node(data); 32 | if (position === 0) { 33 | newNode.next = this.head 34 | this.head = newNode 35 | } else { 36 | let currentNode = this.head 37 | let previousNode = null //head 的 上一节点为 null 38 | let index = 0 39 | while (index++ < position) { 40 | previousNode = currentNode 41 | currentNode = currentNode.next 42 | } 43 | // 在当前节点和当前节点的上一节点之间插入新节点,即它们的改变指向 44 | newNode.next = currentNode 45 | previousNode.next = newNode 46 | } 47 | this.length++ 48 | } 49 | getData(position) { 50 | if (position < 0 || position > this.length) return false 51 | let currentNode = this.head 52 | let index = 0 53 | while (index++ < position) { 54 | currentNode = currentNode.next 55 | } 56 | return currentNode.data 57 | } 58 | indexOf(data) { 59 | let currentNode = this.head 60 | let index = 0 61 | while (currentNode) { 62 | if (currentNode.data === data) { 63 | return index 64 | } 65 | currentNode = currentNode.next 66 | index++ 67 | } 68 | } 69 | update(position, data) { 70 | if (position < 0 || position > this.length) return false 71 | let currentNode = this.head 72 | let index = 0 73 | while (index++ < position) { 74 | currentNode = currentNode.next 75 | } 76 | currentNode.data = data 77 | return currentNode 78 | } 79 | removeAt(position) { 80 | if (position < 0 || position > this.length) return null 81 | let currentNode = this.head 82 | if (position === 0) { 83 | this.head = currentNode.next 84 | } else { 85 | let previousNode = null 86 | let index = 0 87 | while (index++ < position) { 88 | previousNode = currentNode 89 | currentNode = currentNode.next 90 | } 91 | // 巧妙之处,让上一节点的 next 指向到当前的节点的 next,相当于删除了当前节点 92 | previousNode.next = currentNode.next 93 | } 94 | this.length-- 95 | return currentNode 96 | } 97 | // remove(data) 删除指定 data 的节点,并返回删除的那个节点 98 | remove(data) { 99 | return this.removeAt(this.indexOf(data)); 100 | } 101 | size() { 102 | return this.length 103 | } 104 | isEmpty() { 105 | return this.length === 0 106 | } 107 | toString() { 108 | let currentNode = this.head 109 | let result = '' 110 | while (currentNode) { 111 | result += currentNode.data 112 | currentNode = currentNode.next 113 | } 114 | return result 115 | } 116 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const videoElement = document.querySelector('video'); 4 | const videoSelect = document.querySelector('select#videoSource'); 5 | const selectors = [videoSelect]; 6 | 7 | function gotDevices(deviceInfos) { 8 | const values = selectors.map(select => select.value); 9 | selectors.forEach(select => { 10 | while (select.firstChild) { 11 | select.removeChild(select.firstChild); 12 | } 13 | }); 14 | for (let i = 0; i !== deviceInfos.length; ++i) { 15 | const deviceInfo = deviceInfos[i]; 16 | const option = document.createElement('option'); 17 | option.value = deviceInfo.deviceId; 18 | 19 | if (deviceInfo.kind === 'videoinput') { 20 | option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`; 21 | videoSelect.appendChild(option); 22 | } else { 23 | console.log('Some other kind of source/device: ', deviceInfo); 24 | } 25 | } 26 | selectors.forEach((select, selectorIndex) => { 27 | if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) { 28 | select.value = values[selectorIndex]; 29 | } 30 | }); 31 | } 32 | 33 | navigator.mediaDevices.enumerateDevices().then(gotDevices) 34 | 35 | 36 | 37 | 38 | function gotStream(stream) { 39 | window.stream = stream; // make stream available to console 40 | videoElement.srcObject = stream; 41 | return navigator.mediaDevices.enumerateDevices(); 42 | } 43 | 44 | function handleError(error) { 45 | console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name); 46 | } 47 | 48 | function start() { 49 | if (window.stream) { 50 | window.stream.getTracks().forEach(track => { 51 | track.stop(); 52 | }); 53 | } 54 | const videoSource = videoSelect.value; 55 | const constraints = { 56 | video: { 57 | deviceId: videoSource ? { 58 | exact: videoSource 59 | } : undefined 60 | } 61 | }; 62 | navigator.mediaDevices.getUserMedia(constraints).then(gotStream).then(gotDevices).catch(handleError); 63 | } 64 | 65 | 66 | videoSelect.onchange = start; 67 | 68 | start(); -------------------------------------------------------------------------------- /map.js: -------------------------------------------------------------------------------- 1 | //实现map方法 2 | const seletMap = function (fn,context){ 3 | 4 | } 5 | -------------------------------------------------------------------------------- /merge.js: -------------------------------------------------------------------------------- 1 | //合并两个有序数组 2 | // A数组 3 | // p左半边开始位置 4 | // q左半边结束位置 5 | // r右半边结束 6 | function merge(A,p,q,r){ 7 | let A1 = A.slice(p,q)//存放左半边的临时空间 8 | let A2 = A.slice(q,r)//存放右半边的临时空间,slice是一个开区间,刚好可以用 9 | //追加哨兵 10 | A1.push(Number.MAX_SAFE_INTEGER) 11 | A2.push(Number.MAX_SAFE_INTEGER) 12 | for(let k=p,i=0,j=0;ka+b) 12 | } 13 | return s.pop() 14 | } 15 | -------------------------------------------------------------------------------- /qsort.js: -------------------------------------------------------------------------------- 1 | function swap(A,i,j){ 2 | [A[i],A[j]]=[A[j],A[i]] 3 | } 4 | function partition(A,lo,hi){ 5 | const pivot = A[hi-1] 6 | let i = 0,j = hi -1 7 | //小于中心点的位置[lo,i) 8 | //大于中心点的位置[j,hi-1) 9 | //未确定的位置[i,j) 10 | while(i!==j){ 11 | if(A[i]>pivot){ 12 | swap(A,i,--j) 13 | }else{ 14 | i++ 15 | } 16 | } 17 | swap(A,j,hi-1) 18 | return j 19 | } 20 | function qsort(A,lo=0,hi=A.length){ 21 | if(hi-lo<=1)return 22 | const p = partition(A,lo,hi) 23 | qsort(A,lo,p) 24 | qsort(A,p,hi) 25 | } 26 | const A = [1,56,2,8,23,60] 27 | qsort(A) 28 | -------------------------------------------------------------------------------- /radix_sort.js: -------------------------------------------------------------------------------- 1 | //基数排序,先排个位数,再排十位数,依次类推 2 | //buckets[digit].push(number)表示digit是几,就在第几项加入数字 3 | function radix_sort(A){ 4 | const max = Math.max(...A) 5 | const buckets = Array.from({length:10},()=>[]) 6 | let m = 1 7 | while(m{ 9 | const digit = ~~ (number % (m*10)/m) //个位数排序,十位百位 10 | buckets[digit].push(number) 11 | }) 12 | let j = 0 13 | buckets.forEach(bucket=>{ 14 | while(bucket.length>0){ 15 | A[j++]=bucket.shift()//个位排序,十位排序 16 | } 17 | }) 18 | m*=10 19 | } 20 | } 21 | const A = [12,3,67,90,1,3433,222222] 22 | radix_sort(A) 23 | console.log(A) 24 | 25 | 26 | //桶排序 27 | function insertion_sort(A){ 28 | for(let i=1;i=0&&A[p]>x){ 32 | A[p+1] = A[p] 33 | p-- 34 | } 35 | A[p+1] = x 36 | } 37 | } 38 | function bucket_sort(A,k,s){ 39 | const buckets = Array.from({length:10},()=>[]) 40 | //放入桶中 41 | for(let i = 0;ix) r = guess -1 10 | else l = guess+1 11 | //循环不变式,1.新查找范围左,2.新查找范围右 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /this.js: -------------------------------------------------------------------------------- 1 | //实现一个bind函数 2 | Function.prototype.myBind = function (context) { 3 | if (typeof context !== 'function') { 4 | throw new TypeError('Error') 5 | } 6 | var _this = this 7 | var argus = [...arguments].slice(1) 8 | //返回一个函数 9 | return function F() { 10 | //因为返回了一个函数,我们可以new F() 所以需要判断 11 | if (this instanceof F) { 12 | return new _this(...arfus, ...arguments, ) 13 | } 14 | return _this.apply(context, args.concat(...arguments)) 15 | } 16 | } 17 | Function.prototype.call2 = function (context) { 18 | debugger 19 | // 首先要获取调用call的函数,用this可以获取 20 | context.fn = this; 21 | context.fn(); 22 | delete context.fn; 23 | } 24 | 25 | //实现一个call函数 26 | Function.prototype.myCall = function (context) { 27 | debugger 28 | var context = context || window 29 | context.fn = this 30 | var args = Array.from(arguments).slice(1) 31 | var result = context.fn(...args) 32 | delete context.fn 33 | return result 34 | } 35 | 36 | //实现一个apply函数 37 | Function.prototype.myApply = function (context) { 38 | debugger 39 | var context = context || window 40 | context.fn = this 41 | var result 42 | if (arguments[1]) { 43 | result = context.slice 44 | } 45 | return result 46 | } 47 | 48 | let handle = { 49 | get(target, name) { 50 | return name in target ? target[name] : 37 51 | } 52 | } 53 | let p = new Proxy({}, handler) 54 | p.a = 9 55 | p.b = undefined -------------------------------------------------------------------------------- /tree.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(key) { 3 | this.key = key 4 | this.left = null 5 | this.right = null 6 | } 7 | } 8 | export class BinarySearchTree { 9 | constructor() { 10 | this.root = null 11 | } 12 | insert(key) { 13 | const newNode = new Node(key) 14 | if (this.root == null) { 15 | this.root = newNode 16 | } else { 17 | this.insertNode(this.root, newNode) 18 | } 19 | } 20 | insertNode(root, node) { 21 | if (node.key < root.key) { //左边查找插入 22 | if (root.key === null) { 23 | root = node 24 | } else { 25 | this.insertNode(root.left, node) 26 | } 27 | } else { 28 | if (root.key === null) { 29 | root = node 30 | } else { 31 | this.insertNode(root.right, node) 32 | } 33 | } 34 | } 35 | // 二叉树遍历 36 | // 先序遍历 37 | preorderTraversal() { 38 | let result = [] 39 | this.preorderTraversalNode(this.root, result) 40 | return result 41 | } 42 | preorderTraversalNode(node, result) { 43 | if (node === null) { 44 | result.push(node) 45 | } 46 | result.push(node) 47 | this.preorderTraversalNode(node.left, result) 48 | this.preorderTraversalNode(node.right, result) 49 | } 50 | // 中序遍历(左根右 LDR) 51 | inorderTraversal() { 52 | const result = []; 53 | this.inorderTraversalNode(this.root, result); 54 | return result; 55 | } 56 | 57 | inorderTraversalNode(node, result) { 58 | if (node === null) return result; 59 | this.inorderTraversalNode(node.left, result); 60 | result.push(node.key); 61 | this.inorderTraversalNode(node.right, result); 62 | } 63 | 64 | // 后序遍历(左右根 LRD) 65 | postorderTraversal() { 66 | const result = []; 67 | this.postorderTraversalNode(this.root, result); 68 | return result; 69 | } 70 | 71 | postorderTraversalNode(node, result) { 72 | if (node === null) return result; 73 | this.postorderTraversalNode(node.left, result); 74 | this.postorderTraversalNode(node.right, result); 75 | result.push(node.key); 76 | } 77 | // 获取二叉搜索树最小的值 78 | min() { 79 | if (!this.root) return null 80 | let node = this.root 81 | while (node.left) { 82 | node = node.left 83 | } 84 | return node.key 85 | } 86 | // 获取二叉搜索树最大的值 87 | max() { 88 | if (!this.root) return null 89 | let node = this.root 90 | while (node.right) { 91 | node = node.right 92 | } 93 | return node.right 94 | } 95 | // search(key) 查找二叉搜索树中是否有相同的key,存在返回 true,否则返回 false 96 | search(key) { 97 | this.serachNode(this.root, key) 98 | } 99 | //递归实现 100 | serachNode(node, key) { 101 | if (node === null) return false 102 | if (node.key < key) { //右边找 103 | this.serachNode(node.right, key) 104 | } else if (node.key > key) { //左边找 105 | this.serachNode(node.left, key) 106 | } else { 107 | return true 108 | } 109 | } 110 | // 通过 while 循环实现 111 | search1(key) { 112 | let node = this.root 113 | while (node != null) { 114 | if (node.key > key) { 115 | node = node.left 116 | } else if (node.key < key) { 117 | node = node.right 118 | } else { 119 | return true 120 | } 121 | } 122 | return false 123 | } 124 | remove(key) { 125 | let current = this.root 126 | let parentNode = null 127 | let isLeftChild = true 128 | // 循环查找到要删除的节点 currentNode,以及它的 parentNode、isLeftChild 129 | while (current.key != key) { 130 | parentNode = current 131 | 132 | if (current.key > key) { 133 | current = current.left 134 | isLeftChild = true 135 | } else { 136 | current = current.right 137 | isLeftChild = false 138 | } 139 | if (current == null) { 140 | return false 141 | } 142 | } 143 | // 找到了要删除的节点 144 | // 如果是叶子节点 145 | if (current.left === null && current.right === null) { 146 | if (current === this.root) { 147 | this.root = null 148 | } else if (isLeftChild) { 149 | parentNode.left = null 150 | } else { 151 | parentNode.right = null 152 | } 153 | } else if (current.right === null) { // 如果叶子节点底下有一个节点 154 | if (current === this.root) { 155 | this.root = current.left 156 | } else if (isLeftChild) { 157 | parentNode.left = current.left 158 | } else { 159 | parentNode.right = current.left 160 | } 161 | } else if (current.left === null) { 162 | if (current === this.root) { 163 | this.root = current.left 164 | } else if (isLeftChild) { 165 | parentNode.left = current.right 166 | } else { 167 | parentNode.right = current.right 168 | } 169 | } else { 170 | // 3、删除的是有两个子节点的节点 171 | // 找到后续节点 172 | let successor = this.getSuccessor(current) 173 | if (current === this.root) { 174 | this.root = successor 175 | } else if (isLeftChild) { 176 | parentNode.left = successor 177 | } else { 178 | parentNode.right = successor 179 | 180 | } 181 | } 182 | 183 | } 184 | // 这里只考虑了一种情况,删除左子树 185 | getSuccessor(delNode) { 186 | // 定义变量,保存要找到的后续 187 | let successor = delNode 188 | let current = delNode.right 189 | let successorParent = delNode 190 | // 循环查找 current 的右子树节点 191 | while (current !== null) { 192 | successorParent = successor 193 | successor = current 194 | current = current.left 195 | } 196 | // 判断寻找到的后续节点是否直接就是要删除节点的 right 197 | if (successor !== delNode.right) { 198 | successorParent.left = successor.right 199 | successor.right = delNode.right 200 | } 201 | return successor 202 | } 203 | } -------------------------------------------------------------------------------- /userMedia.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 浏览器拍照 16 | 17 | 49 | 50 | 51 | 52 | 53 |
54 |
55 | 56 |
57 | 58 |
59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /webpack.js: -------------------------------------------------------------------------------- 1 | const path=require('path') 2 | const HappyPack = require('happypack')//把打包任务分解成多个子进程并发的去执行 3 | module.exports={ 4 | //entry表示入口,webpack执行构建的第一步将从Entry开始,可抽象成输入 5 | //类型可以是 string,object,array 6 | entry:'./app/entry',//只有一个入口,入口只有一个文件 7 | entry:['./app/entry1','./app/entry2'],//只有一个入口,入口有两个文件 8 | entry:{//与两个入口 9 | a:'./app/entry-a', 10 | b:['./app/entry-b1','./app/entry-b2'] 11 | }, 12 | //如何输出结果,在webpack经过一系类处理后,如何输出最终想要的代码 13 | output:{ 14 | //输出文件放的目录,必须是string类型的绝对路径 15 | path:path.resolve(__dirname,'dist'), 16 | //输出文件的名称 17 | filename:'bundle.js',//完整的名称 18 | filename:'[name].js',//在配置了多个entry时,通过名称模板为不同的entry生成不同的文件名称 19 | filename:'[chunk].[hash].js',//根据文件内容的Hash值生成文件的名称,浏览器长时间缓存文件 20 | //发布到线上的所有资源的URL前缀,为string 21 | publicPath:'./assets/',//放到指定目录下 22 | publicPath:'',//放到根目录下 23 | publicPath:'https://cdn/example.com',//放到CDN上 24 | //到出库的名称,为string,不填他时,默认的输出格式是匿名的立即执行函数 25 | library:'MyLibrary', 26 | //导出库的类型,为枚举类型,默认是var 27 | //numd,num2,commonjs2,commonjs,amd,this,var,assign,window,global,jsonp,可以是这些类型 28 | libraryTarget:'umd', 29 | //是否包含有用的文件路径信息到生成代码里,为booleanleix 30 | pathinfo:true, 31 | //附加chunk的文件名称 32 | chunkFilename:'[id].js', 33 | chunkFilename:'[chunkhash].js', 34 | //JSON异步加载资源时回调函数名称,需要和服务端搭配 35 | jsonpFunction:'myWebpackJsonp', 36 | //生成Source Map文件的名称 37 | sourceMapFilename:'[file].map', 38 | //浏览器开发者工具里面显示的资源模块名称 39 | devtoolModuleFilenameTemplate:'webpack:///[resouce-path]', 40 | //异步加载跨域的资源时使用的方式 41 | crossOriginLoading:'use-credentials', 42 | crossOriginLoading:'aninymous', 43 | crossOriginLoading:'false', 44 | }, 45 | optimization:{//提取公共代码 46 | splitChunks:{ 47 | cacheGroups:{ 48 | //不同页面之间的公共模块 49 | commons:{ 50 | chunks:'initial',//最开始的模块 51 | minChunks:2,//最少有两个复用 52 | }, 53 | //第三方模块 54 | vendor:{ 55 | chunks:'initial',//最开始的模块 56 | test:/node_modules/, 57 | name:"vendor" 58 | } 59 | } 60 | } 61 | } 62 | //配置模块相关 63 | module:{ 64 | rules:[//配置Loader 65 | { 66 | test:/\.jsx?$/,//正则匹配命中要使用Loader的文件 67 | include:[//只会命中这里面的文件 68 | path.resolve(__dirname,'app') 69 | ], 70 | exclude:[//忽略这里面的文件 71 | path.resolve(__dirname,'app/demo-files') 72 | ], 73 | use:[//使用哪些Loader,有先后次序,从后往前 74 | 'style-loader',//直接使用Loader的名称 75 | { 76 | loader:'css-loader', 77 | options:{ 78 | //向html-loader传一些参数 79 | presets:[//映射 80 | ['env',{modules:false}]//不编译成ES5语法 81 | ] 82 | } 83 | } 84 | ] 85 | }, 86 | { 87 | test:/\.css?$/, 88 | use:'happypack.loader?id=css',//使用多子进程编译 89 | include:path.resolve('./src'), 90 | exclude:/node_modules/ 91 | } 92 | ], 93 | noParse:[//不用接喜欢和处理的模块 94 | /special-library\.js$///用正则匹配 95 | ], 96 | plugins:[ 97 | new HappyPack({ 98 | id:'css', 99 | loader:['style-loader','css-loader'] 100 | }), 101 | new HappyPack({ 102 | id:'balel', 103 | loader:['balel-loader'] 104 | }), 105 | new WebpackParalleUglifyPlugin()//js文件的串行压缩变为开启多个子进程并行执行 106 | ],//配置插件 107 | //配置寻找模块的规则 108 | resolve:{ 109 | modules:[//寻找模块的根目录,为array类型,默认以node_modules为根目录 110 | 'node_modules', 111 | path.resolve(__dirname,'app') 112 | ], 113 | extensions:['.js','.json','.jsx','.css'],//模块的后缀名 114 | alias:{//模块别名配置,用于映射模块 115 | //将'module'映射成'new-module',同样'module/path/file'也会被映射成'new-module/path/file' 116 | 'module':'new-module', 117 | //使用结尾符$后,将'only-module'映射成'new-module', 118 | //但是不想上面说的'module/path/file'不也会被映射成'new-module/path/file' 119 | 'only-module$':'new-module', 120 | }, 121 | alias:[//alias还支持使用数组来更详细地进行配置 122 | { 123 | name:'module',//老模块 124 | alias:'new-module',//新模块 125 | //是否映射模块,如果是true,则只有'module'会被映射,如果是false,'module/inner/path'也会被映射 126 | onlyModule:true 127 | } 128 | ], 129 | symlinks:true,//是否跟随文件的软连接去搜索模块的路径 130 | descriptionFiles:['package.json'],//模块的描述文件 131 | mainFields:['main'],//模块的描述文件里面描述的入口文件的字段名 132 | enforeExtension:false,//是否强制导入语句写明后缀名 133 | }, 134 | //输出文件的性能检查配置 135 | performance:{ 136 | hints:'warning',//有性能问题时输出警告 137 | hints:'error',//有性能问题时输出错误 138 | hints:false,//关闭性能检查 139 | maxAssetSize:200000,//最大文件的大小(单位为bytes) 140 | maxEntrypointSize:400000,//最大入口文件的大小 141 | assetFilter:function(assetFilename){//过滤要检查的文件 142 | return assetFilename.endsWith('.css')||assetFilename.endsWith('.js') 143 | } 144 | }, 145 | devtool:'source-map',//配置source-map类型 146 | context:__dirname,//Webpack使用的根目录,string类型必须是绝对路径 147 | target:'web',//浏览器默认 148 | target:'webworker',//WebWorker 149 | target:'node',//Node.js,使用'require'语句加载Chunk代码 150 | target:'async-node',//nw.js 151 | target:'electron-main',//electron,主线程 152 | target:'electron-renderer',//electron,渲染线程 153 | externals:{//使用来自JavaScript运行环境提供的全局变量 154 | jquery:'jQuery' 155 | }, 156 | stats:{//控制台输出日志控制 157 | assets:true, 158 | colors:true, 159 | errors:true, 160 | errorDetails:true, 161 | hash:true 162 | }, 163 | devServer:{//DevServer相关的配置 164 | proxy:{//代理到后端服务接口 165 | '/api':'http://loaclhost:3000' 166 | }, 167 | //配置DevServer HTTP 服务器的文件根目录 168 | contentBase:path.join(__disname,'public'), 169 | compress:true,//是否启开Gzip压缩 170 | historyApiFallback:true,//是否开放HTML5 History API 网页 171 | hot:true,//启动模块热加载,需要引入webpack.HotModuleReplacementPlugin()插件 172 | https:false,//是否开启HTTPS模式 173 | inline:true,//在打包文件注入一个websocket客户端 174 | open:true,//打开浏览器 175 | }, 176 | profile:true,//是否捕捉Webpack构建的性能信息,用于分析说明原因导致构建性能不佳 177 | cache:false,//是否启用缓存来提升速度 178 | watch:true,//表示是否监控源文件的变化,当源文件发生变化之后,重新打包 179 | wathOpyions:{//监听模式xuanx 180 | //不监听的文件或者文件夹,支持正则匹配,默认为空 181 | ignored:/node_modules/, 182 | //监听到变化后等300ms在执行动作,截流防止文件更新太快导致重新编译频率太快 183 | aggregateTimeout:300, 184 | //不停的询问系统指定的文件有没有发生变化,默认每秒询问1000次 185 | poll:1000 186 | } 187 | } 188 | } 189 | 190 | //注册 server Workers 191 | if(navigator.serviceWorker){ 192 | window.addEventListener('DOMContentLoaded',function(){ 193 | //调用serviceWorker.register注册,参数/sw.js 为脚本所在的URL路径 194 | navigator.serviceWorker.register('/sw.js') 195 | }) 196 | } 197 | //缓存版本的唯一标识符,用当前事件代替 198 | let cacheKey=new Date().toISOString(); 199 | //当前缓存的白名单,在新脚本的 install 事件里将使用白名单里面的 key 200 | let cacheWhitelist=[cacheKey] 201 | //需要被缓存的文件的 URL 列表 202 | let cacheFileList=[ 203 | 'index.html', 204 | 'app.js', 205 | 'app.css' 206 | ]; 207 | //在Service Workers 安装成功之后会派发 install 事件 208 | self.addEventListener('install',function(event){ 209 | //等所有资源缓存完成时,才可以进行下一步 210 | event.waitUntil( 211 | caches.open(cacheKey).then(function(cache){ 212 | //要缓存文件的 URL 列表 213 | return cache.addAll(cacheFileList); 214 | }) 215 | ) 216 | }); 217 | //拦截网络请求 218 | self.addEventListener('fetch',function(event){ 219 | event.respondWith( 220 | //去缓存中查询对应的请求 221 | caches.match(event.request).then(function(response){ 222 | if(response){ 223 | return response 224 | }else{ 225 | return fetch(event.request) 226 | } 227 | }) 228 | ) 229 | }); 230 | //新的Service Workers 线程取得控制权后,将会触发 activate 事件 231 | self.addEventListener('activate',function(event){ 232 | event.waitUntil( 233 | caches.keys().then(function(cacheNames){ 234 | return Promise.all(cacheNames.map(function(cacheName){ 235 | //如果不在白名单中缓存全部清理掉 236 | if(cacheWhitelist.indexOf(cacheName)===-1){ 237 | //删除缓存 238 | return caches.delete(cacheName) 239 | } 240 | })) 241 | }) 242 | ) 243 | }) 244 | 245 | //优化webpack 246 | // 1. 侧重优化开发体验的文件 247 | const path = require('path') 248 | const CommonsChunkPlugin = require('webpack/lib/optimize/CommonChunkPlugin') 249 | const HappyPack = require('happypack')//一个任务分成多个子进程并行 250 | //自动寻找 pages 目录下的所有目录,将每一个目录看做一个单页面应用 251 | const autoWebPlugin = new autoWebPlugin('./src/pages',{ 252 | //HTML 模板文件所在的文件路径 253 | template: './template.html', 254 | //提取所有页面的公共代码 255 | commonsChunk:{ 256 | //提取公共代码 Chunk 的名称 257 | name: 'common' 258 | } 259 | }) 260 | module.exports={ 261 | //AutoWebPlugin 会为寻找到的所有单页面应用生成对应的入口配置 262 | //通过autoWebPlugin.entry方法可以获取生成入口的配置 263 | entry:autoWebPlgin.entry({ 264 | //这里我们需要引入额外需要的 Chunk 入口 265 | base:'./src/base.js' 266 | }), 267 | output:{ 268 | filename:'[name].js' 269 | }, 270 | resolve:{ 271 | //使用绝对路径指明第三方模块存放的位置,以减少搜索步骤 272 | //其中__dirname 表示当前工作目录,也就是项目的根目录 273 | modules:[path.resolve(__disname,'node_modules')], 274 | //针对Npm中的第三方模块,优先采用 jsnext:main 中指向的 ES6模块化语法的文件,使用 Tree Shaking 优化 275 | //只采用 main 字段作为入口文件描述字段,以减少搜索步骤 276 | mainFileds:['jsnext:main','main'], 277 | }, 278 | module:{ 279 | rules:[ 280 | {//如果项目源码中只有 js 文件,就不要写成 /\.jsx?$/,以提升正则表达式的性能 281 | test:/\.js$/, 282 | //使用 HappyPack 加速构建 283 | use:['happypack/loader?id=babel'], 284 | //只对根目录底下的 src 目录文件采用 babel-loader 285 | include:path.resolve(__dirname,'src'), 286 | }, 287 | { 288 | //增加对css文件的支持 289 | test:/\.css/, 290 | use:['happypack/loader?id=css'] 291 | 292 | } 293 | ] 294 | }, 295 | plugins:[ 296 | autoWebPlugin, 297 | //使用 HappyPack 加速构建 298 | new HappyPack({ 299 | id:'babel', 300 | //babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启 301 | loaders:['babel-loader?cacheDirectory'] 302 | }), 303 | new HappyPack({ 304 | //UI 组件加载拆分 305 | id:'ui-component', 306 | loaders:[{ 307 | loader:'ui-component-loader', 308 | options:{ 309 | lib:'antd', 310 | style:'style/index.css', 311 | camel2:'-' 312 | } 313 | }] 314 | }), 315 | new HappyPack({ 316 | id:'css', 317 | //如何处理.css文件,用法和 Loader 配置中的一样 318 | loaders:['style-loader','css-loader'] 319 | }), 320 | //提取公共代码 321 | new CommonsChunkPlugin({ 322 | chunks:['common','base'], 323 | //将公共代码放到 base 中 324 | name:'base' 325 | }) 326 | ], 327 | watchOptions:{ 328 | //使用自动刷新,不监听的node_modules 目录下的文件 329 | ignored:/node_modules/ 330 | } 331 | } 332 | //2.配置侧重优化输出质量的文件 333 | const path = require('path') 334 | const DefinePlugin = require('webpack/lib/DefinePlugin') 335 | const ModuleConcatenationPlugin=require('webpack/lib/optimize/CommonsChunkPlugin') 336 | //自动寻找 pages 目录下的所有目录,将每一个目录看做一个单页面应用 337 | const autoWebPlugin = new autoWebPlugin('./src/pages',{ 338 | //HTML 模板文件所在的文件路径 339 | template:'./template.html', 340 | //提取所有页面的公共代码 341 | commonsChunk:{ 342 | //提取公共代码CHunk的名称 343 | name:'common', 344 | }, 345 | //指定存放CSS 文件的CDN目录URL 346 | stylePublicPath:'//css.cdn.com/id/' 347 | }); 348 | //自动寻找 pages 目录下的所有目录,将每一个目录看做一个单页面应用 349 | const autoWebPlugin = new autoWebPlugin('./src/pages',{ 350 | //HTML 模板文件所在的文件路径 351 | template: './template.html', 352 | //提取所有页面的公共代码 353 | commonsChunk:{ 354 | //提取公共代码 Chunk 的名称 355 | name: 'common' 356 | } 357 | }) 358 | module.exports={ 359 | //AutoWebPlugin 会为寻找到的所有单页面应用生成对应的入口配置 360 | //通过autoWebPlugin.entry方法可以获取生成入口的配置 361 | entry:autoWebPlgin.entry({ 362 | //这里我们需要引入额外需要的 Chunk 入口 363 | base:'./src/base.js' 364 | }), 365 | output:{ 366 | //为输出的文件名加上 Hash 值 367 | filename:'[name]_[chunkhash:8].js', 368 | path:path.resolve(__dirname,'./dist'), 369 | //指定存放JavaScript文件的CDN目录URL 370 | publicPath:('//js.cdn.com/id/') 371 | }, 372 | resolve:{ 373 | //使用绝对路径指明第三方模块存放的位置,以减少搜索步骤 374 | //其中__dirname 表示当前工作目录,也就是项目的根目录。当node_modules找不到时,会继续找数组中的下一项(下一项存在的情况下) 375 | // modules:[path.resolve(__disname,'node_modules'),path.resolve(__disname,'bin')], 376 | modules:[path.resolve(__disname,'node_modules')], 377 | //针对Npm中的第三方模块,优先采用 jsnext:main 中指向的 ES6模块化语法的文件,使用 Tree Shaking 优化 378 | //只采用 main 字段作为入口文件描述字段,以减少搜索步骤,到对应的package.json里面去找,有没有那个字段 379 | mainFileds:['jsnext:main','main'], 380 | //别名配置,当模块加载react模块的时候,直接加载后面的绝对路径,快捷方式或者路径 381 | alias:{ 382 | react:path.resolve('.xxxxx') 383 | } 384 | }, 385 | module:{ 386 | noParse:[],//不需要递归去加载或者解析此文件 387 | rules:[ 388 | {//如果项目源码中只有 js 文件,就不要写成 /\.jsx?$/,以提升正则表达式的性能 389 | test:/\.js$/, 390 | //使用 HappyPack 加速构建 391 | use:['happypack/loader?id=babel'], 392 | //只对根目录底下的 src 目录文件采用 babel-loader 393 | include:path.resolve(__dirname,'src'), 394 | }, 395 | { 396 | //增加对css文件的支持 397 | test:/\.css/, 398 | //提取Chunk中的css代码到单独的文件中 399 | use:ExtractTextPlugin.extract({ 400 | use: ['happypack/loader?id=css'], 401 | //指定存放css中导入只有(例如图片)的CDN目录URL 402 | publicPath:'//img.cdb.com/id/' 403 | }) 404 | } 405 | ] 406 | }, 407 | plugins:[ 408 | autoWebPlugin, 409 | //开启ScopeHoisting 410 | new ModuleConcatenationPlugin(), 411 | new HappyPack({ 412 | id:'babel', 413 | //babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启 414 | loaders:['babel-loader?cacheDirectory'] 415 | }), 416 | new HappyPack({ 417 | //UI 组件加载拆分 418 | id:'ui-component', 419 | loaders:[{ 420 | loader:'ui-component-loader', 421 | options:{ 422 | lib:'antd', 423 | style:'style/index.css', 424 | camel2:'-' 425 | } 426 | }] 427 | }), 428 | new HappyPack({ 429 | id:'css', 430 | //如何处理.css文件,用法和 Loader 配置中的一样 431 | //通过minnimize 选项压缩css代码 432 | loaders:['css-loader?minimize'] 433 | }), 434 | new ExtractTextPlugin({ 435 | filename:`[name]_[contenthash:8].css` 436 | }), 437 | //提取公共代码 438 | new CommonsChunkPlugin({ 439 | //从 common 和base两个现成的chunk中提取公共代码部分 440 | chunks:['common','base'], 441 | //将公共代码放到 base 中 442 | name:'base' 443 | }), 444 | new DefinePlugin({ 445 | //定义 NODE_ENV 环境变量为 production ,去除react 代码中开发时才需要的部分 446 | 'process.env':{ 447 | NODE_ENV:JSON.stringify('priduction') 448 | } 449 | }), 450 | //使用ParallelUglifyPlugin 并行压缩输出的JavaScript代码 451 | new ParallelUglifyPlugin({ 452 | uglifyJS:{ 453 | output:{ 454 | //最紧凑的输出 455 | beautify:false, 456 | //删除所有注释 457 | comments:false, 458 | }, 459 | compress:{ 460 | //在 UglifyJS 删除没有用到的代码时不输出警告 461 | warnings:false, 462 | //删除所有console语句,可以兼容IE浏览器 463 | drop_console:true, 464 | //内嵌已定义但是只用到一次的变量 465 | collapse_vars:true, 466 | //提取出现多次但没有定义变量去引用的静态值 467 | reducr_vars:true 468 | } 469 | } 470 | }) 471 | ] 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | -------------------------------------------------------------------------------- /xx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 摄像头画面web前端实时输出功能 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /函数节流.md: -------------------------------------------------------------------------------- 1 | 当在注册,添加,重新调整浏览器窗口大小,浏览器页面滚动,鼠标移动,绑定了事件处理方法的时候,用户不断触发事件,事件就会不断被执行。过于频繁的操作浏览器还没有反应过来,又触发一次,可能会出现卡顿,性能浪费的情况,那么我们要怎么处理这样的情况呢? 2 | 3 | 函数节流 4 | 5 | ``` 6 | let throttle = function (fn,delay) { 7 | let timer = null 8 | return function (){ 9 | clearTimeout(timer) 10 | timer = setTimeout(()=>{ 11 | fn() 12 | },delay) 13 | } 14 | } 15 | ``` 16 | 采用闭包为了产生不必要的全局变量 17 | 18 | 有时候我们想的是在某一段时间内至少出触发一次 19 | ``` 20 | let throttle = function (fn,delay,atleast) { 21 | let timer = null 22 | let previous = null 23 | return function (){ 24 | let now = +new Date()//转换为事件戳 25 | if (!previous) previous = now 26 | if (now - previous > atleast) { 27 | fn () 28 | previous = now//重置上一次开始时间为本次结束时间 29 | }else { 30 | clearTimeout(timer) 31 | timer = setTimeout(()=>{ 32 | fn() 33 | },delay) 34 | } 35 | } 36 | } 37 | ``` -------------------------------------------------------------------------------- /别踩白块.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 别踩白块 6 | 47 | 192 | 193 | 194 |

分数

195 |

0

196 |
197 |
198 |
199 | 200 | 201 | -------------------------------------------------------------------------------- /别踩白块文档.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunseekers/JavaScript/cbff2028cf7217b97a78844d0d903f32af495ca2/别踩白块文档.png -------------------------------------------------------------------------------- /只读属性.md: -------------------------------------------------------------------------------- 1 | 有时候我们为了保护某些属性,让其无法被其他人意外更改,我们通常会把他们设置为常量,让其没法更改 2 | 方法一: 3 | `js` 的 `get` 语法可以设置对象的属性,一旦设置好了之后,我们还能继续设置,但是没有用 4 | ``` 5 | var obj = { 6 | log: ['a', 'b', 'c'], 7 | get latest() { 8 | if (this.log.length == 0) { 9 | return undefined; 10 | } 11 | return this.log[this.log.length - 1]; 12 | } 13 | } 14 | obj.latest = 90//可以设置,但是最后的结果还是没有改变 15 | ``` 16 | 方法二:`ES6` 的`const`,一旦`const` 设置为常量之后,再次浏览器会抛出异常信息 17 | 18 | 方法三:利用函数闭包保护变量,只是读取这个属性的时候需要调用一个方法,有点繁琐 19 | ``` 20 | var config = (function(){ 21 | var private = { 22 | 'myname':'zm', 23 | 'age':23 24 | } 25 | return { 26 | getMyname(name){ 27 | return private[name] 28 | } 29 | } 30 | }) 31 | ``` 32 | 从一个程序员的角度来说,属性分为可通过`JS`调用的的和不可通过`JS`调用的。不可调用的叫做内部属性,那么可调用的我们对应着叫外部属性吧。内部属性是`JS`解释器实现各种接口的时候使用的算法中需要调用的属性,举个栗子,有个内部属性叫`[[Put]]`,这是一个内部方法,传入属性名和值,它的作用就是为属性赋值。所以当我们使用`a.age = 18`的时候,实际就调用到了这个内部属性。而外部属性又分为两种,一种是数据属性,一种是访问器属性 33 | 34 | 35 | setTimeout(()=>{ 36 | console.log(0) 37 | }) 38 | new Promise(res=>{ 39 | res(1) 40 | Promise.res().then(t=>{ 41 | console.log(2) 42 | }) 43 | console.log(3) 44 | }).then(t=>{ 45 | console.log(t) 46 | }) 47 | console.log(4) 48 | debugger 49 | setTimeout(() => { console.log(0) }) 50 | new Promise(resolve => { 51 | resolve(1) 52 | Promise.resolve().then(t => { 53 | console.log(2) 54 | }) 55 | console.log(3) 56 | }) .then(t => { 57 | console.log(t) 58 | })console.log(4) 59 | -------------------------------------------------------------------------------- /数组对象去重.md: -------------------------------------------------------------------------------- 1 | 2 | 有一天有一个朋友给我发来消息 “数组对象根据对象中指定的属性去重?让我写写看”,看到这个的时候我有点懵逼,好像不太会。 3 | 4 | 我只能咬着牙硬着头皮死磕,差点从入门到放弃,在朋友一步一步指导下面终于写好了,朋友总结好了发了我一份,本着自愿共享的精神。过来两天我就把这个给忘了 5 | 6 | 在项目中刚好遇到这个东西,那时候脑子仅剩一点点的思路,努力回想时却早已记忆模糊。很无奈呀,怎么办?项目开发需要,硬着头皮又找朋友要了一份哈哈。现在我决定我要建立一个自己的 `js` 小仓库,里面就放我不懂的,有事没事去翻翻,温故 7 | 8 | 喜欢和朋友交流,偶尔会抛一些技术问题给我,我不会,每次都在指导下慢慢的一步一步明白懂了,顺便学到了新知识。哈哈一起学习进步,欢迎技术交流 9 | 10 | 11 | 问题:数组对象根据对象中指定的属性去重? 12 | 13 | > 方法一: `reduce` 方法 14 | ``` 15 | function unique(arr,u_key){ 16 | let obj = {} 17 | arr.reduce((prev,next)=>{ 18 | obj[next[u_key]+typeof next[u_key]] ? '' : 19 | obj[next[u_key]+typeof next[u_key]] = true && prev.push(next) 20 | return prev 21 | },[]) 22 | } 23 | ``` 24 | `push` 方法是返回新数组的长度,&& 返回的是后面那个值,而我们需要的是一个第一次执行的数组对象,所以另写了一行 `return prev` 25 | 26 | > 方法二: 计数器原理 27 | ``` 28 | function unique(arr,u_key){ 29 | let result = [] 30 | result[0] = arr[0] 31 | arr.forEach((meta_item,i)=>{ 32 | //声明计数变量,如果源数组中的一个对象和result结果数组中的所有对象不同,就push 33 | let num = 0 34 | result.forEach((r_item,j)=>{ 35 | if (meta_item[u_key]!==r_item[u_key]) { 36 | num++ 37 | } 38 | if (num === result.length) { 39 | result.push(meta_item) 40 | } 41 | }) 42 | }) 43 | return result 44 | } 45 | ``` 46 | 47 | > 方法三 : 简单粗暴循环,利用原理是对象的同名属性会被覆盖(和第一种方法有点像) 48 | ``` 49 | function unique(arr,u_key) { 50 | let obj = {} 51 | let result = [] 52 | arr.forEach(item=>{ 53 | let typeof_key = typeof item[u_key] + item[u_key] 54 | obj[typeof_key] = item 55 | }) 56 | for (let key in obj) { 57 | result.push(obj[key]) 58 | } 59 | return result 60 | } 61 | ``` 62 | 测试案例 63 | ``` 64 | let arrayList = [{ 65 | id:'1', 66 | name:'one' 67 | },{ 68 | id:'2', 69 | name:'tow', 70 | },{ 71 | id:'3', 72 | name:'three' 73 | },{ 74 | id:'1', 75 | name:'one' 76 | },{ 77 | id:2, 78 | name:'tow', 79 | },{ 80 | id:'3', 81 | name:'three' 82 | }] 83 | unique(arrayList,'id') 84 | ``` 85 | 方法四:`ES6` 的 `Map` 86 | ``` 87 | function unique(arr,u_key) { 88 | let map = new Map() 89 | arr.forEach((item,index)=>{ 90 | if (!map.has(item[u_key])){ 91 | map.set(item[u_key],item) 92 | } 93 | }) 94 | return [...map.values()] 95 | } 96 | ``` 97 | 这个方法是我 `Java` 同事看了我掘金以后给出的答案,或许这就是大佬吧,对于 `ES6` 的`map`,`set`,我只知道有这个东西,未曾深入了解过。自愧不如,`Java` 玩 `ES6` 玩的比我还好,我要一头扎入学习的海洋,到达他的高度 98 | 99 | -------------------------------------------------------------------------------- /有序数组.md: -------------------------------------------------------------------------------- 1 | 问题:给定一个从小到大的有序数组,从数组里面找到两个数字,可以等于 `taregt`,并返回索引 2 | 3 | 方法一: 既然数组是有序,可以采用二分法,从两端向中间靠拢,不知道要循环多少次,我们用 `while` 4 | ``` 5 | function towSum(arr,target) { 6 | if (arr == '' || arr.length == 0) return false 7 | let left = 0 8 | let right = arr.length - 1 9 | while(left < right ) { 10 | if (arr[left] + arr[right] < target) { 11 | left ++ 12 | } else if (arr[left] + arr[right] > target) { 13 | right -- 14 | } else { 15 | return [left,right] 16 | } 17 | } 18 | } 19 | ``` 20 | 利用已经有的排序,时间复杂度为 `O(NlogN)`,一次循环,空间复杂度为`O(1)` 21 | 22 | 方法二 :利用对象的数据结构(无论是否排序都可用) 23 | ``` 24 | function towSum (arr,target) { 25 | let obj = {} 26 | for (let i=0;i 0) { 29 | return [obj[item],i] 30 | }else { 31 | obj[target - item] = i 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | 方法二:如果题目给出的是没有排序的数组,我们可采用暴力破解,给出排序也可以用时间复杂度为 `O(n*n)` 38 | ``` 39 | function towSum (arr,target) { 40 | if (arr == '' || arr.length == 0) return false 41 | for (let i=0; i{ 57 | if (arr.findIndex(target-ele) > 0) { 58 | result = [index,arr.findIndex(ele)] 59 | } 60 | }) 61 | return result 62 | } 63 | ``` 64 | 到底是用哪一种方法,我们要根据具体情况具体分析,没有最好的,只要最适合的,哈哈 65 | -------------------------------------------------------------------------------- /深拷贝.md: -------------------------------------------------------------------------------- 1 | 曾在项目中遇到过使用深拷贝,萌新的我,居然傻傻把地址引用当做深拷贝,南辕北辙。在朋友的帮助下知道了深拷贝可以简单粗暴一句话解决 2 | 3 | ``` 4 | JSON.parse(JSON.stringify(source)); 5 | ``` 6 | `js` 中的引用类型和基本类型复制傻傻分不清的原因吧,堆内存,栈内存也不知道,一定是个假前端。 7 | 8 | 强烈推荐一篇文章 [深拷贝的终极探索(90%的人都不知道)](https://juejin.im/post/5bc1ae9be51d450e8b140b0c),原有一天我们成为那10%。哈哈 9 | 10 | > 深拷贝和浅拷贝 11 | 12 | 深拷贝和浅拷贝都是针对的引用类型,`JS` 中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝,而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据 13 | ``` 14 | let arr1 = [{ 15 | label: '一级 1', 16 | children: [{ 17 | label: '二级 1-1', 18 | children: [{ 19 | label: '三级 1-1-1' 20 | }] 21 | }] 22 | }, { 23 | label: '一级 2', 24 | children: [{ 25 | label: '二级 2-1', 26 | children: [{ 27 | label: '三级 2-1-1' 28 | }] 29 | }, { 30 | label: '二级 2-2', 31 | children: [{ 32 | label: '三级 2-2-1' 33 | }] 34 | }] 35 | }, { 36 | label: '一级 3', 37 | children: [{ 38 | label: '二级 3-1', 39 | children: [{ 40 | label: '三级 3-1-1' 41 | }] 42 | }, { 43 | label: '二级 3-2', 44 | children: [{ 45 | label: '三级 3-2-1' 46 | }] 47 | }] 48 | }] 49 | let arr2 = arr1//arr1 和arr2指向了同一个地址 50 | 51 | arr1[1].children[0].children[0].label = "我被修改了" 52 | 53 | arr1 === arr2 // true arr1 和 arr2 复制的是地址,而不是是值 54 | 55 | arr1 = []//arr1 指向一个新的地址 56 | 57 | arr1 == arr2 //false 58 | 59 | ``` 60 | 61 | > 引用类型的深层拷贝,深层拷贝是拷贝创建另一空间 62 | ``` 63 | let arr3 = JSON.parse(JSON.stringify(arr1)) 64 | arr1 === arr3 //false,他们虽然长的一模一样,但是不是同一个地址 65 | 66 | arr1[1].children[0].children[0].label = "我又被修改了" // arr1和arr3 现在长得也不一样了,他们互相之前没有任何关系 67 | ``` 68 | 69 | 推荐文章中提到了另外两种实现深拷贝方法,我就直接在这敲一遍 70 | ``` 71 | function clone (source) { 72 | let target = {} 73 | for (let i in source) { 74 | if (source.hasOwnProperty(i)) { 75 | console.log(i) 76 | if( typeof source[i] === 'object'){ 77 | target[i] = clone(source[i])//注意这里,typeof 不能区分数组和对象,instanceof 可区分数组和对象 78 | }else { 79 | target[i] = source[i] 80 | } 81 | } 82 | } 83 | return target; 84 | } 85 | let object1 = { 86 | label: '一级 3', 87 | children: [{ 88 | label: '二级 3-1', 89 | children: [{ 90 | label: '三级 3-1-1' 91 | }] 92 | }] 93 | } 94 | clone(object1) 95 | ``` 96 | 期待被改善的: 97 | 98 | 1.没有对参数做检验 99 | 100 | 2.判断是否对象的逻辑不够严谨 101 | 102 | 3.没有考虑数组的兼容 103 | 104 | > 生成指定深度和每层广度的代码 105 | 106 | 这段代码的 `temp = temp['data'] = {}` 我硬是看了好久才明白,原来又是引用类型,挽狂澜于既倒,扶大厦之将倾 107 | 108 | ``` 109 | function createData(deep,breadth) { 110 | let data = {} 111 | let temp = data 112 | for (let i = 0; i < deep; i++) { 113 | temp = temp['data'] = {}//注意看后面注释 114 | for (let j = 0; j < breadth; j++) { 115 | temp[j] = j 116 | } 117 | } 118 | return data 119 | } 120 | createData(1, 3);// 1层深度,每层有3个数据 {data: {0: 0, 1: 1, 2: 2}} 121 | let data = {} 122 | let temp = data//此时data和temp共同指向同一个地址 123 | temp = temp['data'] = {} 124 | //temp和data指向同一个地址,temp添加了一个data属性,那么data也添加了这个属性即data=temp={data:{}} 125 | //,temp=temp['data']={},temp重新指向了一个新的地址并且和data.data指向同一个地址,只要temp发生改变,data.data 也会发生改变 126 | ``` 127 | 128 | 在函数传值的过程中,函数的形参和实参指向同一个对象,当参数内部改变形参的时候,函数外面的实参也被改变了 -------------------------------------------------------------------------------- /简易计算器/calculator.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | table { 6 | /* 7 | * 为表格设置合并边框模型border-collapse: collapse; 8 | */ 9 | border-collapse: collapse; 10 | margin: 0 auto; 11 | border-spacing: 0px; 12 | } 13 | td { 14 | width: 150px; 15 | height: 100px; 16 | } 17 | input { 18 | width: 150px; 19 | height: 100px; 20 | } 21 | .btn { 22 | width: 150px; 23 | font-size: 12px; 24 | } 25 | .btn_click { 26 | width: 302px; 27 | font-size: x-large; 28 | } 29 | .txt { 30 | width: 600px; 31 | font-size: x-large; 32 | text-align: right; 33 | } 34 | -------------------------------------------------------------------------------- /简易计算器/calculator.js: -------------------------------------------------------------------------------- 1 | window.onload=function () { 2 | function fun(){ 3 | //定义数组 来接收用户按的数字和计算符号 4 | var arr=[], 5 | //获取屏幕元素 6 | txt=document.getElementsByClassName('txt')[0]; 7 | //事件监听兼容浏览器 8 | function addEvent(element,type,handle){ 9 | if(element.addEventListener){ 10 | element.addEventListener(type,handle,false); 11 | }else if(element.attachEvent){ 12 | element.attachEvent("on"+type,handle); 13 | }else{ 14 | element['on'+type]=handle; 15 | } 16 | } 17 | //AC和DEL的修改功能 18 | function change(){ 19 | if(this.value=="AC"){ 20 | arr=[]; 21 | txt.value=''; 22 | }else{ 23 | /* slice() 截断字符串 1.从那个位置开始 2.截取多少长度*/ 24 | txt.value=txt.value.slice(0,txt.value.length-1); 25 | } 26 | } 27 | //数组计算函数 28 | function calculator(){ 29 | /* 30 | * this指代的是当前事件的执行对象 31 | * 按完键将值传给屏幕 32 | * 判断是否为数字 33 | */ 34 | if(txt.value==''&&this.value=="."){ 35 | txt.value="0."; 36 | }else{ 37 | if(!isNaN(this.value)||this.value=="."){ 38 | /*用户输入的是数字或者点的情况*/ 39 | /*indexOf() 用来查找字符 如果有返回当前位置 如果没有返回-1*/ 40 | if(txt.value.indexOf('.')!=-1){ 41 | if(this.value!="."){ 42 | txt.value+=this.value; 43 | } 44 | }else{ 45 | txt.value+=this.value; 46 | } 47 | }else{ 48 | /*是符号的情况*/ 49 | //先存值 在清屏 50 | if(this.value!="="){ 51 | /*是符号但不为等号的情况*/ 52 | arr[arr.length] = txt.value; 53 | //存符号 54 | arr[arr.length] = this.value; 55 | //清屏 56 | txt.value = ""; 57 | }else{ 58 | arr[arr.length]=txt.value; 59 | txt.value=eval(arr.join('')); 60 | arr=[]; 61 | } 62 | } 63 | } 64 | } 65 | return { 66 | addEvent:addEvent, 67 | change:change, 68 | calculator:calculator 69 | } 70 | } 71 | var fun=fun(), 72 | btn_back=document.getElementsByClassName('btn_click'), 73 | btn_txt=document.getElementsByClassName('btn'); 74 | 75 | //给AC,DEL添加点击事件 76 | for(var i=0;i* 1.1 实验内容 5 | 6 | 我们在生活中经常会用到计算器,今天我们就来做一个网页版的,先上一张游戏效果图: 7 | 8 | 9 | 10 | 网页版需要我们点击按钮。为了使代码尽量简单,逻辑清晰,只保留了实现这个小计算器最重要的部分代码,让初学者也能很快看懂。 11 | 12 | >* 1.2 实验知识点 13 | 14 | HTML+CSS+JavaScript 15 | 16 | >* 1.3 适合人群 17 | 18 | 本实验难度较为简单,非常适合刚学完 HTML, CSS 和 JavaScript 的人拿来练手。 19 | 20 | ## 二、实验原理 21 | 22 | 在开始编程之前,让我们先来分析下整个计算机的流程:这个是非常简易的计算器,可以完成的功能有加减乘除和AC(清屏),DEL(退格)等基本运算,所以代码也不复杂 23 | 24 | 25 | ## 三、开发准备 26 | 27 | 本实验所有代码都写在 index.html 一个文件中,实验过程中有不明白的地方可以参考后面给出的完整代码。 28 | 29 | ## 四、实验步骤 30 | 31 | >* 4.1 页面布局 32 | 33 | 可以用 div+css 布局来实现计算机的静态效果展示,直接上 HTML 代码,我来简要说下 HTML 思路,整个框架我利用搭建的,需要注意的是,由于计算器屏幕不可输入,我设置为了disabled,我们这里现在是静态页面就自己确定了,然后通过 css 控制样式。 34 | ```python 35 | 36 | 37 | 38 | 39 | 40 | 简易计算器calculator 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 | 73 | 74 | 75 | ``` 76 | >* 4.2 添加样式 77 | 78 | 下面是css代码。 79 | ```python 80 | 81 | * { 82 | padding: 0; 83 | margin: 0; 84 | } 85 | table { 86 | /* 87 | * 为表格设置合并边框模型border-collapse: collapse; 88 | */ 89 | border-collapse: collapse; 90 | margin: 0 auto; 91 | border-spacing: 0px; 92 | } 93 | td { 94 | width: 150px; 95 | height: 100px; 96 | } 97 | input { 98 | width: 150px; 99 | height: 100px; 100 | } 101 | .btn { 102 | width: 150px; 103 | font-size: 12px; 104 | } 105 | .btn_click { 106 | width: 302px; 107 | font-size: x-large; 108 | } 109 | .txt { 110 | width: 600px; 111 | font-size: x-large; 112 | text-align: right; 113 | } 114 | 115 | ``` 116 | 如果以上部分你都能够理解并且对应着代码实现的话,那么你现在应该会出现这样的效果: 117 | 118 | 119 | 120 | 是不是很像计算器的界面了呢,我们已经成功了一大步,然后就是通过 js 来实现一些事件以及响应啦,就算完成啦。 121 | 122 | >* 4.3 思路讲解 123 | 124 | 第一部分:获取值到屏幕上 125 | 126 | 127 | ```python 128 | //获取并保存你想操作的元素 129 | var btn_txt = document.getElementsByClassName("btn"); 130 | var txt = document.getElementsByClassName("txt")[0]; 131 | //对他们添加操作: 132 | for (var i = 0; i < btn_txt.length; i++) { 133 | btn_txt[i].onclick = function () { 134 | } 135 | 136 | ``` 137 | 在进行操作的之前请等一下,请我们思考一下,btn_txt数组里存放着0,1,2,3,4,5,6,7,8,9," . "," + "," - "," * "," \ "," = "等一系列东西,我们当然要对数字和计算符号进行分开操作,所以我们用If……else……来判断一下 138 | 139 | ```python 140 | if (!isNaN(this.value) || this.value == ".") { 141 | /*用户输入的是数字或者点的情况*/ 142 | /* this 指代的是当前事件的执行对象*/ 143 | } 144 | else{ 145 | /*用户输入的是符号的情况*/ 146 | } 147 | ``` 148 | 149 | 接下来先考虑用户输入的是数字或者点的情况,数字可以连续输入到屏幕里,但是小数点不应该能连续输入到屏幕里,小数点应该只有一个才对,所以我们应该先加一个判断条件:屏幕里是否有小数点存在?如果屏幕里已经有小数点存在,那么我只允许你再输入数字,否则屏幕值不会接收,即是如下代码: 150 | ```python 151 | if (!isNaN(this.value) || this.value == ".") { 152 | /*用户输入的是数字或者点的情况*/ 153 | /* this 指代的是当前事件的执行对象*/ 154 | 155 | /*有点存在的情况*/ 156 | if (txt.value.indexOf(".") != -1) { 157 | /*indexOf() 用来查找字符 如果有 返回当前位置 如果没有 返回-1*/ 158 | if (this.value != ".") { 159 | /*当前按得不是点,进行拼接*/ 160 | txt.value += this.value; 161 | /*这里的txt.value += this.value;意思是 162 | 将当前用户所按的数字添加到屏幕上去*/ 163 | } 164 | } 165 | else { 166 | /*没点存在直接拼接*/ 167 | txt.value += this.value; 168 | } 169 | } 170 | 171 | else{ 172 | /*用户输入的是符号的情况接下来再考虑*/ 173 | } 174 | ``` 175 | 用户输入的是数字或者点的情况已经考虑结束了,现在考虑用户输入的是运算符号的情况!这种情况也分两部分,一种是用户按了等号,一种是按了除等号之外的其他加减乘除运算符号,因为等号比较特殊一点,按了就直接应该得出结果了,所以要区用if……else……分开。 176 | 还有一个事情我们要考虑的是,我希望在我按下加减乘除运算符号时可以清屏,这样我就可以继续键入下一数字了(举例:我按下数字“8”,再按下运算符“ + ”,按下瞬间屏幕清屏,然后我再键入数字“4”,最后按下“ = ”,最后得出结果“12”),但是清屏我并不想让我的数据丢失,所以此时我先新建一个数组来保存这些数据(这里的“数据”指数字和运算符,也叫“表达式”),然后再清屏!具体代码如下: 177 | ```python 178 | var arr = []; 179 | /*先自定义一个数组 来接收用户按的数字和计算符号 180 | 注意这里定义要在开头定义,而不是在函数内部*/ 181 | 182 | if (!isNaN(this.value) || this.value == ".") { 183 | /*用户输入的是数字或者点的情况*/ 184 | /*上面已经讨论过了,这里我省点地方*/ 185 | } 186 | 187 | else{ 188 | /*用户输入的是符号的情况*/ 189 | //先存值 在清屏 190 | if (this.value != "=") { 191 | /*是符号但不为等号的情况*/ 192 | arr[arr.length] = txt.value; 193 | //存符号 194 | arr[arr.length] = this.value; 195 | //清屏 196 | txt.value = ""; 197 | } 198 | else{ 199 | /*为等号的情况下面讨论*/ 200 | } 201 | } 202 | ``` 203 | 204 | 第二部分:计算屏幕上的表达式的值 205 | 206 | 好了下面我们讨论用户按下等号键的情况,这种比较简单,直接对表达式(表达式就是我们之前输入的数字与符号组合)进行计算就可以啦,需要注意的是计算完成之后要把保存表达式的数组arr清空,因为本次运算完满结束了,如果不清空里面的数据会影响下一次正常计算; 207 | ```python 208 | else { 209 | /*为等号的情况即是计算屏幕上表达式的值*/ 210 | 211 | //先将屏幕上所有值保存到数组里 212 | arr[arr.length] = txt.value; 213 | //eval()方法 专门用来计算表达式的值 214 | //join()方法 将数组的每位拼接成字符串 215 | txt.value = eval(arr.join("")); 216 | //计算完成之后将数组清空 217 | arr = []; 218 | } 219 | ``` 220 | 第三部分:添加AC,DEL功能 221 | 222 | 首先,获取清空按钮和退格按钮,然后把它们保存在btn_back变量下; 223 | 然后就遍历进行添加功能,这里同样需要一个if……else……语句来判断用户按的是AC按钮还是DEL按钮 224 | ```python 225 | var btn_back = document.getElementsByClassName("btn_click"); 226 | //获取清空按钮和退格按钮 227 | for (var i = 0; i < btn_back.length; i++) { 228 | btn_back[i].onclick = function () { 229 | //判断按钮 230 | if (this.value == "AC") { 231 | //如果是AC按钮,那么清空屏幕和数组里的一切值 232 | arr = []; 233 | txt.value = ""; 234 | } 235 | else { 236 | //不是AC,那必然是DEL,我们把屏幕的最后一位抹去再重新赋给屏幕 237 | 238 | /* slice(参数1,参数2) 截断字符串 239 | */ 240 | txt.value = txt.value.slice(0, txt.value.length - 1); 241 | } 242 | } 243 | } 244 | ``` 245 | 到这里为止,所有功能基本上全部添加完毕 246 | 247 | 其实程序写到这里,几个核心的功能点都已经实现了,是不是感觉很简单呢,剩下来的就是将这些方法组合起来,组成完整的逻辑关系,在我给出的源码里有添加一个记分器记录用户分数的功能,同时设置加速方法,使黑块的移动越来越快等等,有兴趣的的同学可以尝试着添加事件按钮,使这个游戏更接近 APP 版本。 248 | 249 | >* 4.6 js完整代码 250 | ```python 251 | window.onload=function () { 252 | //定义数组 来接收用户按的数字和计算符号 253 | var arr=[], 254 | //获取按钮对象 255 | btn_txt=document.getElementsByClassName('btn'), 256 | //获取屏幕元素 257 | txt=document.getElementsByClassName('txt')[0], 258 | //获取清空按钮和退格按钮 259 | btn_back=document.getElementsByClassName('btn_click'); 260 | //事件监听兼容浏览器 261 | function addEvent(element,type,handle){ 262 | if(element.addEventListener){ 263 | element.addEventListener(type,handle,false); 264 | }else if(element.attachEvent){ 265 | element.attachEvent("on"+type,handle); 266 | }else{ 267 | element['on'+type]=handle; 268 | } 269 | } 270 | //AC和DEL的修改功能 271 | function change(){ 272 | if(this.value=="AC"){ 273 | arr=[]; 274 | txt.value=''; 275 | }else{ 276 | /* slice() 截断字符串 */ 277 | txt.value=txt.value.slice(0,txt.value.length-1); 278 | } 279 | } 280 | //数组计算函数 281 | function calculator(){ 282 | /* 283 | * this指代的是当前事件的执行对象 284 | * 按完键将值传给屏幕 285 | * 判断是否为数字 286 | */ 287 | if(txt.value==''&&this.value=="."){ 288 | txt.value="0."; 289 | }else{ 290 | if(!isNaN(this.value)||this.value=="."){ 291 | /*用户输入的是数字或者点的情况*/ 292 | /*indexOf() 用来查找字符 如果有返回当前位置 如果没有返回-1*/ 293 | if(txt.value.indexOf('.')!=-1){ 294 | if(this.value!="."){ 295 | txt.value+=this.value; 296 | } 297 | }else{ 298 | txt.value+=this.value; 299 | } 300 | }else{ 301 | /*是符号的情况*/ 302 | //先存值 在清屏 303 | if(this.value!="="){ 304 | /*是符号但不为等号的情况*/ 305 | arr[arr.length] = txt.value; 306 | //存符号 307 | arr[arr.length] = this.value; 308 | //清屏 309 | txt.value = ""; 310 | }else{ 311 | arr[arr.length]=txt.value; 312 | txt.value=eval(arr.join('')); 313 | arr=[]; 314 | } 315 | } 316 | } 317 | } 318 | //给AC,DEL添加点击事件 319 | for(var i=0;i 2 | 3 | 4 | 5 | 简易计算器calculator 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /网页版拼图游戏/puzzle.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | 5 | } 6 | /* 7 | * 给body设置100%的高度和宽度,这样就会根据浏览器屏幕大小自动适配 8 | */ 9 | body { 10 | width: 100%; 11 | height: 100%; 12 | } 13 | #contain { 14 | width: 620px; 15 | height: 450px; 16 | margin: 0 auto; 17 | margin:100px; 18 | border-radius: 1px; 19 | position: relative; 20 | } 21 | /* 22 | * 游戏区的DIV,这个大小是计算出来的,取决于小方块的大小 23 | * 这里设置小方块的大小为50px*50px; 24 | */ 25 | #game { 26 | width: 450px; 27 | height: 450px; 28 | border-radius: 5px; 29 | display: inline-block; 30 | background: #ffe171; 31 | box-shadow: 0 0 10px #ffe171; 32 | position: absolute; 33 | } 34 | /* 35 | * 小方块的大小,定位为绝对定位,这样改变位置不会影响其他元素的位置, 36 | * 宽高都是149px。注意了,我们还设置了box-shadow:1px 1px 2px #777 ; 37 | * 所以总宽度等于150px;transition:.3s设置过渡时间,是css3属性; 38 | */ 39 | #game div { 40 | width: 149px; 41 | height:149px; 42 | box-shadow: 1px 1px 2px #777; 43 | background: #20a6fa; 44 | color: #fff; 45 | text-align: center; 46 | font-size: 150px; 47 | line-height: 150px; 48 | cursor: pointer; 49 | position: absolute; 50 | -webkit-transition: .3s;/*浏览器前缀,兼容其他浏览器 chrome*/ 51 | -moz-transition: .3s;/*firefox*/ 52 | -ms-transition: .3s;/*ie*/ 53 | -o-transition: .3s;/*opera*/ 54 | transition: .3s; 55 | } 56 | #game div:hover { color: #ffe171;} 57 | #control { 58 | width: 150px; 59 | height: 450px; 60 | display: inline-block; 61 | float: right; 62 | } 63 | /* 64 | * 设置控制区域的共同样式 65 | */ 66 | #control span, 67 | #control input { 68 | height: 35px; 69 | font-size: 20px; 70 | color: #222; 71 | margin-top:10px; 72 | text-align: center; 73 | } 74 | input:nth-child(3) { 75 | width: 100px; 76 | line-height: 35px; 77 | font-size: 28px; 78 | background: #20a6fa; 79 | color: #ffe171; 80 | text-shadow: 1px 1px 2px #ffe171; 81 | border-radius: 5px; 82 | box-shadow: 2px 2px 5px #4c98f5; 83 | 84 | cursor: pointer; 85 | display: inline-block; 86 | } 87 | input:nth-child(4) { 88 | width: 100px; 89 | line-height: 35px; 90 | background: #20a6fa; 91 | color: #ffe171; 92 | text-shadow: 1px 1px 2px #ffe171;/*字体阴影*/ 93 | box-shadow: 2px 2px 5px #4c98f5;/*盒子阴影*/ 94 | border-radius: 5px; 95 | cursor: pointer; 96 | } 97 | /*给Reset按钮设置属性*/ 98 | #d1{ 99 | left: 0px; 100 | } 101 | #d2{ 102 | left: 150px; 103 | } 104 | #d3{ 105 | left: 300px; 106 | } 107 | #d4{ 108 | top: 150px; 109 | } 110 | #d5{ 111 | top: 150px; 112 | left: 150px; 113 | } 114 | #d6{ 115 | top: 150px; 116 | left: 300px; 117 | } 118 | #d7{ 119 | top: 300px; 120 | } 121 | #d8{ 122 | left: 150px; 123 | top: 300px; 124 | } 125 | /*这是预先给每个小方块按照顺序排好位置*/ -------------------------------------------------------------------------------- /网页版拼图游戏/puzzle.js: -------------------------------------------------------------------------------- 1 | function $(id){ 2 | return document.getElementById(id); 3 | } 4 | 5 | function puzzle(){ 6 | var time=0, 7 | pause=true, 8 | set_timer=null, 9 | //保存大div当前装的小div的编号 10 | d_direct=[ 11 | [0],//为了逻辑更简单,第一个元素我们不用,我们从下标1开始使用 12 | [2,4],//大DIV编号为1的DIV可以去的位置,比如第一块可以去2,4号位置 13 | [1,3,5], 14 | [2,6], 15 | [1,5,7], 16 | [2,4,6,8], 17 | [3,5,9], 18 | [4,8], 19 | [5,7,9], 20 | [6,8] 21 | ], 22 | //保存DIV编号的可移动位置编号 23 | d_posXY=[ 24 | [0],//同样,我们不使用第一个元素 25 | [0,0],//第一个表示left,第二个表示top,比如第一块的位置为let:0px,top:0px 26 | [150,0], 27 | [300,0], 28 | [0,150], 29 | [150,150], 30 | [300,150], 31 | [0,300], 32 | [150,300], 33 | [300,300] 34 | ], 35 | //默认按照顺序排好,大DIV第九块没有,所以为0,我们用0表示空白块 36 | d=[0,1,2,3,4,5,6,7,8,0]; 37 | function move(id){ 38 | var i=1; 39 | //保存小DIV可以去的编号,0表示不能移动 40 | var target_d=0; 41 | //这个for循环是找出小DIV在大DIV中的位置 42 | for(i=1;i<10;++i){ 43 | if(d[i]==id)break; 44 | } 45 | //用来找出小DIV可以去的位置,如果返回0,表示不能移动,如果可以移动,则返回可以去的位置编号 46 | target_d=puzzle.whereCanTo(i); 47 | if(target_d!=0){ 48 | //把当前的大DIV编号设置为0,因为当前小DIV已经移走了,所以当前大DIV就没有装小DIV了 49 | d[i]=0; 50 | d[target_d]=id; 51 | $('d'+id).style.left=d_posXY[target_d][0]+"px"; 52 | $('d'+id).style.top=d_posXY[target_d][1]+"px"; 53 | //最后设置被点击的小DIV的位置,把它移到目标大DIV的位置 54 | } 55 | //如果target_d不为0,则表示可以移动,且target_d就是小DIV要去的大DIV的位置编号 56 | var finish_flag=true; 57 | //设置游戏是否完成标志,true表示完成 58 | for (var k=1;k<9;++k){ 59 | if(d[k]!=k){ 60 | finish_flag=false; 61 | break; 62 | //如果大DIV保存的编号和它本身的编号不同,则表示还不是全部按照顺序排的,那么设置为false,跳出循环,后面不用再判断了,因为只要一个不符,就没完成游戏 63 | } 64 | } 65 | //从1开始,把每个大DIV保存的编号遍历一下,判断是否完成 66 | if(finish_flag==true){ 67 | if(!pause){ 68 | puzzle.start(); 69 | alert('congratulation'); 70 | //如果为true,则表示游戏完成,如果当前没有暂停,则调用暂停韩式,并且弹出提示框,完成游戏。 71 | //start()这个函数是开始,暂停一起的函数,如果暂停,调用后会开始,如果开始,则调用后会暂停 72 | } 73 | } 74 | } 75 | //判断是否可移动函数,参数是大DIV的编号,不是小DIV的编号,因为小DIV编号跟可以去哪没关系,小DIV是会动的 76 | function whereCanTo(cur_div){ 77 | var j=0, 78 | move_flag=false; 79 | for(j=0;j1;--i){ 127 | var to=parseInt(Math.random()*(i-1)+1);//产生随机数,范围为1到i 128 | if(d[i]!=0){ 129 | $('d'+d[i]).style.left=d_posXY[to][0]+'px'; 130 | $('d'+d[i]).style.top=d_posXY[to][1]+'px'; 131 | } 132 | //把随机产生的div位置设置为当前的div的位置 133 | if(d[to]!=0){ 134 | $("d"+d[to]).style.left=d_posXY[i][0]+"px"; 135 | $("d"+d[to]).style.top=d_posXY[i][1]+"px"; 136 | } 137 | //然后把她们的div保存的编号对调一下 138 | d[i]+=d[to]; 139 | d[to]=d[i]-d[to]; 140 | d[i]-=d[to]; 141 | 142 | } 143 | } 144 | return { 145 | move:move, 146 | whereCanTo:whereCanTo, 147 | timer:timer, 148 | start:start, 149 | reset:reset, 150 | random_d:random_d 151 | }; 152 | } 153 | var puzzle=puzzle(); 154 | //初始化函数,页面加载的时候调用重置函数,重新开始 155 | window.onload=function(){ 156 | 157 | puzzle.reset(); 158 | } 159 | -------------------------------------------------------------------------------- /网页版拼图游戏/puzzle.md: -------------------------------------------------------------------------------- 1 | # 网页版拼图游戏 2 | ## 一、实验介绍 3 | 4 | ### 1.1 实验内容 5 | 6 | 本课程基于 HTML+CSS+JavaScript 实现网页版的拼图游戏。实现过程中将用到 HTML5,CSS3 及 JavaScript 相关知识。完成这个项目,可以进一步扎实前端基础知识。 7 | 8 | 九宫格拼图相信大家都玩过了,看似简单的小游戏,但实现起来其实并不那么简单。在以前,写程序是程序员的专利,只有他们才能做出一个软件来。但是现在不同了。科技的进步和经济的发展,使得每个人都可以使用计算机。特别是 HTML5 和 CSS3 的流行,使得制作一个基本的游戏变得简单。 9 | 10 | 下面我们就来做一个九宫格拼图。它的玩法是移动空格块旁边的方块,使得它们按照方块上面标的数字顺序排好。最终的效果: 11 | 12 | 13 | 14 | ### 1.2 实验知识点 15 | 16 | 本实验涉及以下知识点: 17 | 18 | HTML5 19 | CSS3 20 | JavaScript 21 | 22 | ### 1.3 适合人群 23 | 24 | 本课程难度一般,适合刚学完前端基础(HTML+CSS+JavaScript)的同学作为练手项目。 25 | 26 | ### 1.5 代码参考 27 | 28 | 本课程中的源码仅供参考可以通过以下方式获取: 29 | 30 | https://github.com/sunseekers/JavaScript-30day/tree/master/%E7%BD%91%E9%A1%B5%E7%89%88%E6%8B%BC%E5%9B%BE%E6%B8%B8%E6%88%8F 31 | ## 二、实验原理 32 | 33 | 根据下面的效果图来观察思考,我们要做的就是设置一个大 DIV 用来包裹里面的小 DIV,然后在里面设置 8 个小 DIV,从 1 开始给他们编号。右边设置两个按钮,点击开始的时候开始计时,完成拼图后停止计时,并弹出一个框,提示完成了。重来按钮是当用户觉得当前有难度的时候,点击重来可以重新开始一个新的拼图,把所有方块打乱顺序,然后开始计时。 34 | 35 | 36 | 37 | 我们的重点就是当鼠标点击其中一个方块时,要判断当前方块是否可移动,如果可移动,则移动到相应的位置,如不可移动,则不做任何事。当移动完一块后,要判断是否完成拼图。 38 | 39 | 我们把那个大 DIV 想象成一个盒子,它有九个位置,从 1 开始,到9编号,他们的位置和编号都是不会变的。把里面的 8 个小 DIV 想象成 8 个小盒子,给他们设置 top 和 left 就可以控制他们的位置。每个小 DIV 从 1 开始到 8 编号。他们的位置是可以随意改变的。所以当小 DIV 的编号和大 DIV 的编号全部重合时,就完成了拼图。 40 | 41 | 所以重点就只有一个了。那就是如何判断是否可移动。这个也简单。我们设置一个一维数组变量,用来保存大 DIV 它里面装的小 DIV 的编号。如果大 DIV 没有小方块,也就表面它是空白块,那么就设为 0。如果当前大 DIV 有小 DIV,那就设置为小 DIV 的编号。然后再设置一个二维数组变量,用来保存大 DIV 的可移动编号。也就是保存这个大 DIV 它所有的可去的位置。比如大 DIV 编号为 2 的,它只能向 1号,3号,5号这三个方向移动。又比如 5,它能向 2、4、6、8 这四个方向移动。我们循环遍历这个变量,如果对应的方向它 42 | 43 | 没有方块,也就是值为 0,那么它就可以往这个方向移动了。 44 | 45 | 46 | ## 三、实验步骤 47 | 48 | ## 3.1 编写布局 49 | 50 | 在puzzle.html中我没有下面的代码: 51 | ```python 52 | 53 | 54 | 55 | 56 | 拼图游戏 57 | 58 | 59 | 60 | 61 |
62 |
63 |
1
64 |
2
65 |
3
66 |
4
67 |
5
68 |
6
69 |
7
70 |
8
71 |
72 |
73 | 总用时 74 | 75 | 76 | 77 |
78 |
79 | 80 | 81 | ``` 82 | 布局文件就写完了。这里为了简化逻辑,更易编写代码,我们把所有操作都封装了。只要执行move(2),就是点击了编号为2的小方块,后面的一系列操作都完成了。 83 | 84 | ### 3.2 编写样式 - CSS 85 | 86 | 布局写完了,现在我们为游戏编写样式,使得它更漂亮。在这一步,大家就可以自己自由发挥了,你可以写出自己的风格,让游戏更漂亮。也可以添加更多的元素来装饰你的游戏。但是注意了,游戏 DIV 的大小如果改变了,一定要记得修改 js 代码,稍后我们会详细讲解。 87 | 88 | puzzle.css 89 | ```python 90 | * { 91 | padding: 0; 92 | margin: 0; 93 | 94 | } 95 | /* 96 | * 给body设置100%的高度和宽度,这样就会根据浏览器屏幕大小自动适配 97 | */ 98 | body { 99 | width: 100%; 100 | height: 100%; 101 | } 102 | #contain { 103 | width: 620px; 104 | height: 450px; 105 | margin: 0 auto; 106 | margin:100px; 107 | border-radius: 1px; 108 | position: relative; 109 | } 110 | /* 111 | * 游戏区的DIV,这个大小是计算出来的,取决于小方块的大小 112 | * 这里设置小方块的大小为50px*50px; 113 | */ 114 | #game { 115 | width: 450px; 116 | height: 450px; 117 | border-radius: 5px; 118 | display: inline-block; 119 | background: #ffe171; 120 | box-shadow: 0 0 10px #ffe171; 121 | position: absolute; 122 | } 123 | /* 124 | * 小方块的大小,定位为绝对定位,这样改变位置不会影响其他元素的位置, 125 | * 宽高都是149px。注意了,我们还设置了box-shadow:1px 1px 2px #777 ; 126 | * 所以总宽度等于150px;transition:.3s设置过渡时间,是css3属性; 127 | */ 128 | #game div { 129 | width: 149px; 130 | height:149px; 131 | box-shadow: 1px 1px 2px #777; 132 | background: #20a6fa; 133 | color: #fff; 134 | text-align: center; 135 | font-size: 150px; 136 | line-height: 150px; 137 | cursor: pointer; 138 | position: absolute; 139 | -webkit-transition: .3s;/*浏览器前缀,兼容其他浏览器 chrome*/ 140 | -moz-transition: .3s;/*firefox*/ 141 | -ms-transition: .3s;/*ie*/ 142 | -o-transition: .3s;/*opera*/ 143 | transition: .3s; 144 | } 145 | #game div:hover { color: #ffe171;} 146 | #control { 147 | width: 150px; 148 | height: 450px; 149 | display: inline-block; 150 | float: right; 151 | } 152 | /* 153 | * 设置控制区域的共同样式 154 | */ 155 | #control span, 156 | #control input { 157 | height: 35px; 158 | font-size: 20px; 159 | color: #222; 160 | margin-top:10px; 161 | text-align: center; 162 | } 163 | input:nth-child(3) { 164 | width: 100px; 165 | line-height: 35px; 166 | font-size: 28px; 167 | background: #20a6fa; 168 | color: #ffe171; 169 | text-shadow: 1px 1px 2px #ffe171; 170 | border-radius: 5px; 171 | box-shadow: 2px 2px 5px #4c98f5; 172 | 173 | cursor: pointer; 174 | display: inline-block; 175 | } 176 | input:nth-child(4) { 177 | width: 100px; 178 | line-height: 35px; 179 | background: #20a6fa; 180 | color: #ffe171; 181 | text-shadow: 1px 1px 2px #ffe171;/*字体阴影*/ 182 | box-shadow: 2px 2px 5px #4c98f5;/*盒子阴影*/ 183 | border-radius: 5px; 184 | cursor: pointer; 185 | } 186 | /*给Reset按钮设置属性*/ 187 | #d1{ 188 | left: 0px; 189 | } 190 | #d2{ 191 | left: 150px; 192 | } 193 | #d3{ 194 | left: 300px; 195 | } 196 | #d4{ 197 | top: 150px; 198 | } 199 | #d5{ 200 | top: 150px; 201 | left: 150px; 202 | } 203 | #d6{ 204 | top: 150px; 205 | left: 300px; 206 | } 207 | #d7{ 208 | top: 300px; 209 | } 210 | #d8{ 211 | left: 150px; 212 | top: 300px; 213 | } 214 | /*这是预先给每个小方块按照顺序排好位置*/ 215 | ``` 216 | /*这是预先给每个小方块按照顺序排好位置*/ 217 | 好了,样式也编写好了。最后再编写一个 js 控制代码,我们的拼图就可以用了。编写样式的时候大家还是先根据我这里的来,等完成了整个游戏,了解游戏逻辑的时候你们再自己发挥想象力去更改样式,不然可能会出现未知的错误。 218 | 219 | 完成这步,打开 puzzle.html 应该能看到下面的效果了: 220 | 221 | 222 | 223 | ### 3.3 控制代码 - JavaScript 224 | 225 | puzzle.js 226 | ```python 227 | var time=0, 228 | pause=true, 229 | set_timer=null, 230 | //保存大div当前装的小div的编号 231 | d_direct=[ 232 | [0],//为了逻辑更简单,第一个元素我们不用,我们从下标1开始使用 233 | [2,4],//大DIV编号为1的DIV可以去的位置,比如第一块可以去2,4号位置 234 | [1,3,5], 235 | [2,6], 236 | [1,5,7], 237 | [2,4,6,8], 238 | [3,5,9], 239 | [4,8], 240 | [5,7,9], 241 | [6,8] 242 | ], 243 | //保存DIV编号的可移动位置编号 244 | d_posXY=[ 245 | [0],//同样,我们不使用第一个元素 246 | [0,0],//第一个表示left,第二个表示top,比如第一块的位置为let:0px,top:0px 247 | [150,0], 248 | [300,0], 249 | [0,150], 250 | [150,150], 251 | [300,150], 252 | [0,300], 253 | [150,300], 254 | [300,300] 255 | ]; 256 | //默认按照顺序排好,大DIV第九块没有,所以为0,我们用0表示空白块 257 | d=[0,1,2,3,4,5,6,7,8,0]; 258 | function move(id){ 259 | var i=1; 260 | //保存小DIV可以去的编号,0表示不能移动 261 | var target_d=0; 262 | //这个for循环是找出小DIV在大DIV中的位置 263 | for(i=1;i<10;++i){ 264 | if(d[i]==id)break; 265 | } 266 | //用来找出小DIV可以去的位置,如果返回0,表示不能移动,如果可以移动,则返回可以去的位置编号 267 | target_d=whereCanTo(i); 268 | if(target_d!=0){ 269 | //把当前的大DIV编号设置为0,因为当前小DIV已经移走了,所以当前大DIV就没有装小DIV了 270 | d[i]=0; 271 | d[target_d]=id; 272 | document.getElementById('d'+id).style.left=d_posXY[target_d][0]+"px"; 273 | document.getElementById('d'+id).style.top=d_posXY[target_d][1]+"px"; 274 | //最后设置被点击的小DIV的位置,把它移到目标大DIV的位置 275 | } 276 | //如果target_d不为0,则表示可以移动,且target_d就是小DIV要去的大DIV的位置编号 277 | var finish_flag=true; 278 | //设置游戏是否完成标志,true表示完成 279 | for (var k=1;k<9;++k){ 280 | if(d[k]!=k){ 281 | finish_flag=false; 282 | break; 283 | //如果大DIV保存的编号和它本身的编号不同,则表示还不是全部按照顺序排的,那么设置为false,跳出循环,后面不用再判断了,因为只要一个不符,就没完成游戏 284 | } 285 | } 286 | //从1开始,把每个大DIV保存的编号遍历一下,判断是否完成 287 | if(finish_flag==true){ 288 | if(!pause){ 289 | start(); 290 | alert('congratulation'); 291 | //如果为true,则表示游戏完成,如果当前没有暂停,则调用暂停韩式,并且弹出提示框,完成游戏。 292 | //start()这个函数是开始,暂停一起的函数,如果暂停,调用后会开始,如果开始,则调用后会暂停 293 | } 294 | } 295 | } 296 | //判断是否可移动函数,参数是大DIV的编号,不是小DIV的编号,因为小DIV编号跟可以去哪没关系,小DIV是会动的 297 | function whereCanTo(cur_div){ 298 | var j=0, 299 | move_flag=false; 300 | for(j=0;j1;--i){ 347 | var to=parseInt(Math.random()*(i-1)+1);//产生随机数,范围为1到i 348 | if(d[i]!=0){ 349 | document.getElementById('d'+d[i]).style.left=d_posXY[to][0]+'px'; 350 | document.getElementById('d'+d[i]).style.top=d_posXY[to][1]+'px'; 351 | } 352 | if(d[to]!=0){ 353 | document.getElementById("d"+d[to]).style.left=d_posXY[i][0]+"px"; 354 | document.getElementById("d"+d[to]).style.top=d_posXY[i][1]+"px"; 355 | } 356 | //把随机产生的div位置设置为当前的div的位置 357 | var tem = d[to]; 358 | d[to]=d[i]; 359 | d[i]=tem; 360 | //然后把她们的div保存的编号对调一下 361 | } 362 | } 363 | //初始化函数,页面加载的时候调用重置函数,重新开始 364 | window.onload=function(){ 365 | reset(); 366 | } 367 | 368 | ``` 369 | 好了,所有代码都已经编写完成了。现在点击桌面上的 puzzle.html 文件,右键用火狐浏览器打开,就能看到效果了。点击上面的方块就可以移动。 370 | 371 | # 四、实验总结 372 | 373 | 通过本次实验,我们利用 HTML5 + CSS3 + JavaScript,实现了一个简单的拼图游戏。 374 | 375 | 这里说明一下,因为实验中使用的随机打乱方块的算法非常简单,但是存在 bug,有 50% 的概率生成的顺序是无法复原的,这个时候就只能点击重新开始。 376 | 377 | 因此思考如何实现更健全的算法,让其可以完全复原。 378 | 379 | 提供一种思路:按照人的方式随机去移动方块,而移动方块的代码已经写好了,直接调用就可以,所以只要自己编写一个函数,让方块移动几十步就可以打乱。而这种方式是一定可以拼回去的。 380 | 381 | 大家可以回去自己尝试。 382 | 383 | # 五、课后习题 384 | 385 | 当前的实现是基于鼠标点击的,添加键盘监听,让游戏使用方向键也能玩 386 | 思考如何实现更健全的算法,让其可以完全复原 -------------------------------------------------------------------------------- /网页版拼图游戏/puzzle1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunseekers/JavaScript/cbff2028cf7217b97a78844d0d903f32af495ca2/网页版拼图游戏/puzzle1.png -------------------------------------------------------------------------------- /网页版拼图游戏/puzzle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunseekers/JavaScript/cbff2028cf7217b97a78844d0d903f32af495ca2/网页版拼图游戏/puzzle2.png -------------------------------------------------------------------------------- /网页版拼图游戏/拼图游戏(puzzle).html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 拼图游戏 6 | 7 | 8 | 9 | 10 |
11 |
12 |
1
13 |
2
14 |
3
15 |
4
16 |
5
17 |
6
18 |
7
19 |
8
20 |
21 |
22 | 总用时 23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /雪花特效.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | js原生效果雪花特技 6 | 19 | 78 | 79 | 80 | 85 | 86 | 87 | --------------------------------------------------------------------------------