算法相关
各种排序实现
相关数据
// 冒泡排序: 比较两个相邻的项,如果第一个大于第二个则交换他们的位置,元素项向上移动至正确的顺序,就好像气泡往上冒一样 冒泡demo: function bubbleSort(arr) { let len = arr.length; for (let i = 0; i < len; i++) { for (let j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j+1]) { //相邻元素两两对比 [arr[j + 1], arr[j]] = [arr[j], arr[j + 1]]; } } } return arr; } // 1) 首先,在数组中选择一个中间项作为主元 // 2) 创建两个指针,左边的指向数组第一个项,右边的指向最后一个项,移动左指针,直到找到一个比主元大的项,接着,移动右边的指针,直到找到一个比主元小的项,然后交换它们。重复这个过程,直到 // 左侧的指针超过了右侧的指针。这个使比主元小的都在左侧,比主元大的都在右侧。这一步叫划分操作 // 3) 接着,算法对划分后的小数组(较主元小的值组成的的小数组, 以及较主元大的值组成的小数组)重复之前的两个步骤,直到排序完成 快排demo: function quickSort(arr, left, right) { let len = arr.length; let partitionIndex; left = typeof left !== 'number' ? 0 : left; right = typeof right !== 'number' ? len - 1 : right; if (left < right) { partitionIndex = partition(arr, left, right); quickSort(arr, left, partitionIndex - 1); quickSort(arr, partitionIndex + 1, right); } return arr; } function partition(arr, left, right) { //分区操作 let pivot = left; //设定基准值(pivot) let index = pivot + 1; for (let i = index; i <= right; i++) { if (arr[i] < arr[pivot]) { [arr[i], arr[index]] = [arr[index], arr[i]]; index++; } } [arr[pivot], arr[index - 1]] = [arr[index - 1], arr[pivot]]; return index - 1; } // 选择排序:大概思路是找到最小的放在第一位,找到第二小的放在第二位,以此类推 算法复杂度O(n^2) 选择demo: function selectionSort(arr) { let len = arr.length; let minIndex; for (let i = 0; i < len - 1; i++) { minIndex = i; for (let j = i + 1; j < len; j++) { if (arr[j] < arr[minIndex]) { //寻找最小的数 minIndex = j; //将最小数的索引保存 } } [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; } return arr; } // 插入排序:每次排一个数组项,假设数组的第一项已经排序,接着,把第二项与第一项进行对比,第二项是该插入到第一项之前还是之后,第三项是该插入到第一项之前还是第一项之后还是第三项 插入demo: function insertionSort(arr) { let len = arr.length; let preIndex, current; for (let i = 1; i < len; i++) { preIndex = i - 1; current = arr[i]; while (preIndex >= 0 && arr[preIndex] > current) { arr[preIndex + 1] = arr[preIndex]; preIndex--; } arr[preIndex + 1] = current; } return arr; } // 归并排序:Mozilla Firefox 使用归并排序作为Array.prototype.sort的实现,而chrome使用快速排序的一个变体实现的,前面三种算法性能不好,但归并排序性能不错 算法复杂度O(nlog^n) // 归并排序是一种分治算法。本质上就是把一个原始数组切分成较小的数组,直到每个小数组只有一个位置,接着把小数组归并成较大的数组,在归并过程中也会完成排序,直到最后只有一个排序完毕的大数组 归并demo: function mergeSort(arr) { //采用自上而下的递归方法 let len = arr.length; if(len < 2) { return arr; } let middle = Math.floor(len / 2), left = arr.slice(0, middle), right = arr.slice(middle); return merge(mergeSort(left), mergeSort(right)); } function merge(left, right){ let result = []; while (left.length && right.length) { if (left[0] <= right[0]) { result.push(left.shift()); } else { result.push(right.shift()); } } result.push(...left); result.push(...right); return result; } //堆排序:堆排序把数组当中二叉树来排序而得名。 // 1)索引0是树的根节点;2)除根节点为,任意节点N的父节点是N/2;3)节点L的左子节点是2*L;4)节点R的右子节点为2*R + 1 // 本质上就是先构建二叉树,然后把根节点与最后一个进行交换,然后对剩下对元素进行二叉树构建,进行交换,直到剩下最后一个 堆demo: var len; //因为声明的多个函数都需要数据长度,所以把len设置成为全局变量 function buildMaxHeap(arr) { //建立大顶堆 len = arr.length; for (let i = Math.floor(len / 2); i >= 0; i--) { heapify(arr, i); } } function heapify(arr, i) { //堆调整 let left = 2 * i + 1; let right = 2 * i + 2; let largest = i; if (left < len && arr[left] > arr[largest]) { largest = left; } if (right < len && arr[right] > arr[largest]) { largest = right; } if (largest !== i) { [arr[i], arr[largest]] = [arr[largest], arr[i]]; heapify(arr, largest); } } function heapSort(arr) { buildMaxHeap(arr); for (let i = arr.length - 1; i > 0; i--) { [arr[0],arr[i]]=[arr[i],arr[0]]; len--; heapify(arr, 0); } return arr; }
二分查找
思路 (1)首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。 (2)如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。 (3)如果某一步数组为空,则表示找不到目标元素。
// 非递归算法 function binary_search(arr, key) { let low = 0; let high = arr.length - 1; while(low <= high){ let mid = parseInt((high + low) / 2); if(key === arr[mid]){ return mid; }else if(key > arr[mid]){ low = mid + 1; }else if(key < arr[mid]){ high = mid -1; }else{ return -1; } } } // 递归算法 function binary_search(arr,low, high, key) { if (low > high){ return -1; } let mid = parseInt((high + low) / 2); if(arr[mid] === key){ return mid; }else if (arr[mid] > key){ high = mid - 1; return binary_search(arr, low, high, key); }else if (arr[mid] < key){ low = mid + 1; return binary_search(arr, low, high, key); } };
二叉树相关
创建 function Node(data,left,right){ this.data = data;//数值 this.left = left;//左节点 this.right = right;//右节点 }; 插入二叉树 function insert(node,data){ //创建一个新的节点 let newNode = new Node(data,null,null); //判断是否存在根节点,没有将新节点存入 if(node == null){ node = newNode; }else{ //获取根节点 let current = node; let parent; while(true){ //将当前节点保存为父节点 parent = current; //将小的数据放在左节点 if(data < current.data){ //获取当前节点的左节点 //判断当前节点下的左节点是否有数据 current = current.left; if(current == null){ //如果没有数据将新节点存入当前节点下的左节点 parent.left = newNode; break; } }else{ current = current.right; if(current == null){ parent.right = newNode; break; } } } } } 翻转二叉树 function invertTree(node) { if (node !== null) { node.left, node.right = node.left, node.right; invertTree(node.left); invertTree(node.right); } return node; } 查找链表中倒数第k个结点 2个思路 1:先遍历出长度,然后查找长度-k+1的值 2:2个指针,一个指针先走k-1,然后两个一起走到底部,后者就是结果
网络安全相关
XSS CSRF
XSS(跨站脚本攻击),恶意的注入html代码,其他用户访问时,会被执行 特点:能注入恶意的HTML/JavaScript代码到用户浏览的网页上,从而达到Cookie资料窃取、会话劫持、钓鱼欺骗等攻击 防御手段:
- 浏览器禁止页面的JS访问带有HttpOnly属性的Cookie
- 两端进行输入格式检查
- 通过编码转义的方式进行输出检查 CSRF(攻击跨站请求伪造) 特点:重要操作的所有参数都是可以被攻击者猜测到的。攻击者预测出URL的所有参数与参数值,才能成功地构造一个伪造的请求。 防御手段:
- token验证机制,比如请求数据字段中添加一个token,响应请求时校验其有效性
- 用户操作限制,比如验证码(繁琐,用户体验差)
- 请求来源限制,比如限制HTTP Referer才能完成操作(防御效果相比较差) 实践中常用第一种
webpack相关
打包体积
优化思路
- 提取第三方库或通过引用外部文件的方式引入第三方库
- 代码压缩插件UglifyJsPlugin
- 服务器启用gzip压缩
- 按需加载资源文件 require.ensure
- 优化devtool中的source-map
- 剥离css文件,单独打包
- 去除不必要插件,通常就是开发环境与生产环境用同一套配置文件导致
打包效率
- 开发环境采用增量构建,启用热更新
- 开发环境不做无意义的工作如提取css计算文件hash等
- 配置devtool
- 选择合适的loader
- 个别loader开启cache 如babel-loader
- 第三方库采用引入方式
- 提取公共代码
- 优化构建时的搜索路径 指明需要构建目录及不需要构建目录
- 模块化引入需要的部分
Loader
编写一个loader
loader就是一个node模块,它输出了一个函数。当某种资源需要用这个loader转换时,这个函数会被调用。并且,这个函数可以通过提供给它的this上下文访问Loader API。 reverse-txt-loader 定义 module.exports = function(src) { //src是原文件内容(abcde),下面对内容进行处理,这里是反转 var result = src.split('').reverse().join(''); //返回JavaScript源码,必须是String或者Buffer return `module.exports = '${result}'`; } 使用 { test: /\.txt$/, use: [ { './path/reverse-txt-loader' } ] },
plugins
使用范围更广,通常只需要require()然后添加到plugins数组中,且需要new一个
其他
URL到界面显示发生了什么
- DNS解析 先本地缓存找,在一层层找 将常见的地址解析成唯一对应的ip地址基本顺序为:本地域名服务器->根域名服务器->com顶级域名服务器依次类推下去,找到后记录并缓存下来如www.google.com为
. -> .com -> google.com. -> www.google.com. - TCP连接 三次握手,只要没收到确认消息就要重新发
- 主机向服务器发送一个建立连接的请求(您好,我想认识您);
- 服务器接到请求后发送同意连接的信号(好的,很高兴认识您);
- 主机接到同意连接的信号后,再次向服务器发送了确认信号(我也很高兴认识您),自此,主机与服务器两者建立了连接。
- 发送HTTP请求 浏览器会分析这个url,并设置好请求报文发出。请求报文中包括请求行、请求头、空行、请求主体。https默认请求端口443, http默认80。 常见的http请求如下
POST / HTTP1.1 Host:www.wrox.com User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022) Content-Type:application/x-www-form-urlencoded Content-Length:40 Connection: Keep-Alive name=Professional%20Ajax&publisher=Wiley 第一部分:请求行,第一行说明是post请求,以及http1.1版本。 第二部分:请求头部,第二行至第六行。 第三部分:空行,第七行的空行。 第四部分:请求数据,第八行。 4. 服务器处理请求并返回HTTP报文 后端处理返回http报文如下 HTTP/1.1 200 OK Date: Fri, 22 May 2009 06:07:21 GMT Content-Type: text/html; charset=UTF-8 <html> <head></head> <body> <!--body goes here--> </body> </html> 第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok) 第二行和第三行为消息报头, Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8 第三部分:空行,消息报头后面的空行是必须的 第四部分:响应正文,服务器返回给客户端的文本信息。 空行后面的html部分为响应正文。
- 浏览器解析渲染页面
- 通过HTML解析器解析HTML文档,构建一个DOM Tree,同时通过CSS解析器解析HTML中存在的CSS,构建Style Rules,两者结合形成一个Attachment。
- 通过Attachment构造出一个呈现树(Render Tree)
- Render Tree构建完毕,进入到布局阶段(layout/reflow),将会为每个阶段分配一个应出现在屏幕上的确切坐标。
- 最后将全部的节点遍历绘制出来后,一个页面就展现出来了。 遇到script会停下来执行,所以通常把script放在底部
- 连接结束
组件封装
目的:为了重用,提高开发效率和代码质量 注意:低耦合,单一职责,可复用性,可维护性 常用操作:
- 分析布局
- 初步开发
- 化繁为简
- 组件抽象
JS异步加载
- 动态生成script标签
- 添加h5的async defer属性,前者乱序不适合依赖性加载
- async 是“下载完就执行”, defer 是“渲染完再执行”
css与js动画差异
- css性能好
- css代码逻辑相对简单
- js动画控制好
- js兼容性好
- js可实现的动画多
- js可以添加事件
负载均衡
多台服务器共同协作,不让其中某一台或几台超额工作,发挥服务器的最大作用
- http重定向负载均衡:调度者根据策略选择服务器以302响应请求,缺点只有第一次有效果,后续操作维持在该服务器
- dns负载均衡:解析域名时,访问多个ip服务器中的一个(可监控性较弱)
- 反向代理负载均衡:访问统一的服务器,由服务器进行调度访问实际的某个服务器,对统一的服务器要求大,性能受到 服务器群的数量
CDN
内容分发网络,基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
内存泄漏
定义:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题 js中可能出现的内存泄漏情况 结果:变慢,崩溃,延迟大等 原因:
- 全局变量
- dom清空时,还存在引用
- ie中使用闭包
- 定时器未清理
- 子元素存在引起的内存泄露
避免策略:
- 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
- 注意程序逻辑,避免“死循环”之类的 ;
- 避免创建过多的对象 原则:不用了的东西要及时归还。
- 减少层级过多的引用
babel原理
ES6、7代码输入 -> babylon进行解析 -> 得到AST(抽象语法树)-> plugin用babel-traverse对AST树进行遍历转译 ->得到新的AST树->用babel-generator通过AST树生成ES5代码、
promise
特性:Promise 对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,也即是说,错误总会被下一个catch语句捕获
js自定义事件
三要素:
document.createEvent()
event.initEvent()
element.dispatchEvent()
demo: (en:自定义事件名称,fn:事件处理函数,addEvent:为DOM元素添加自定义事件,triggerEvent:触发自定义事件) window.onload = function(){ var demo = document.getElementById("demo"); demo.addEvent("test",function(){console.log("handler1")}); demo.addEvent("test",function(){console.log("handler2")}); demo.onclick = function(){ this.triggerEvent("test"); } } Element.prototype.addEvent = function(en,fn){ this.pools = this.pools || {}; if(en in this.pools){ this.pools[en].push(fn); }else{ this.pools[en] = []; this.pools[en].push(fn); } } Element.prototype.triggerEvent = function(en){ if(en in this.pools){ var fns = this.pools[en]; for(var i=0,il=fns.length;i<il;i++){ fns[i](); } }else{ return; } }
es6模块 commonjs amd cmd
- CommonJS 的规范中,每个 JavaScript 文件就是一个独立的模块上下文(module context),在这个上下文中默认创建的属性都是私有的。也就是说,在一个文件定义的变量(还包括函数和类),都是私有的,对其他文件是不可见的。
- CommonJS是同步加载模块,在浏览器中会出现堵塞情况,所以不适用
- AMD 异步,需要定义回调define方式
- es6 一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量
- es6还可以导出类、方法,自动适用严格模式
前后端路由差别
1.后端每次路由请求都是重新访问服务器 2.前端路由实际上只是JS根据URL来操作DOM元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合。