├── CSS ├── BFC.md ├── CSS.md ├── CSS3.md ├── CSS模块化.md ├── Flex.md ├── 垂直居中.md ├── 布局.md ├── 水平居中.md ├── 清除浮动的方法.md ├── 用7种方式实现三栏布局.md └── 自适应布局方式.md ├── HTML ├── HTML.md └── HTML5的新特点.md ├── Javascript ├── Bom.md ├── CallBack.md ├── DOM与事件.md ├── ES6.md ├── EventLoop.md ├── Form.md ├── JS基础.md ├── JS常用方法原理.md ├── Promise.md ├── async和await详解.md ├── 前后端数据交互.md ├── 回调地狱的解决方式总结.md ├── 垃圾回收机制.md ├── 字符串操作API.md ├── 定时器.md ├── 异步.md ├── 数组API.md ├── 正则表达.md ├── 深入理解Generator.md └── 防抖节流.md ├── MVVM └── MVVM.md ├── README.md ├── _config.yml ├── img ├── BFC_01.png ├── BFC_02.png ├── BFC_Box.webp ├── BFC_float.webp ├── BFC_rule03.webp ├── BFC_wordsfloat.webp ├── CROS_01.png ├── CROS_02.png ├── Cache_01.jpg ├── Effective_01.jpg ├── EventLoop_01.gif ├── EventLoop_02.jpg ├── EventLoop_03.jpg ├── FontandBack_01.jpg ├── FontandBack_02.jpg ├── FontandBack_03.jpg ├── JSBase_01.jpg ├── MVVM_01.png ├── MVVM_02.png ├── MVVM_03.png ├── MVVM_04.webp ├── Promise_01.png ├── Render_01.jpg ├── Sort_heapSort.gif ├── Sort_mergeSort.gif ├── Sort_shellSort.gif ├── callback_01.png ├── callback_02.png ├── css_01.png ├── css_02.png ├── css_03.jpg ├── css_04.jpg ├── css_05.jpg ├── http_01.png ├── solution_01.png └── sort_01.png ├── 一些思考 ├── Class中箭头函数和普通函数是咋回事?.md ├── 两个数组去重有多少种方法?性能如何?.md ├── 关于获取DOM节点的思考.md └── 虚拟DOM真的能提升性能吗?.md ├── 准备 ├── CSS面试准备.md ├── HTML面试准备.md ├── JS面试准备.md ├── 复习.md ├── 安全问题的准备.md ├── 对简历的准备.md ├── 性能优化面试准备.md ├── 浏览器相关问题的准备.md └── 跨域面试准备.md ├── 安全 └── 前端的安全防范.md ├── 性能优化 └── 性能优化.md ├── 手撕代码 ├── Javascript原生实现.md ├── Javascript原生实现JS版 │ ├── Ajax.js │ ├── Apply.js │ ├── Bind.js │ ├── Call.js │ ├── Copy.js │ ├── Create.js │ ├── DeepCopy.js │ ├── DoubleBind.js │ ├── EventBus.js │ ├── Instanceof.js │ ├── New.js │ ├── Promise.js │ ├── rem的实现原理.js │ ├── setInterval.js │ ├── 懒加载.js │ ├── 拖拽.js │ ├── 继承.js │ ├── 节流.js │ ├── 路由.js │ └── 防抖.js ├── 剑指offer题目.md └── 面试笔试中遇到的题.md ├── 浏览器 ├── 浏览器存储.md ├── 浏览器渲染.md └── 跨域CORS.md ├── 算法 ├── 十大排序算法.md └── 树.md ├── 网络 ├── Internet Base.md ├── UDP and TCP.md ├── http.md └── 输入URL到页面渲染的整个流程.md └── 面经 └── 面经.md /CSS/BFC.md: -------------------------------------------------------------------------------- 1 | # BFC 2 | 3 | Block Formatting Context, 块级格式化上下文,它是一个独立的渲染区域,只有Block-level Box参与,它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。 4 | 5 | 文档流分定位流、浮动流和 普通流 三种。而普通流其实就是指 BFC 中的 FC。 6 | 7 | FC直译过来是格式化上下文,它是**页面中的一块渲染区域**,有一套渲染规则,决定了其**子元素如何布局,以及和其他元素之间的关系和作用**。 8 | 9 | BFC 对布局的影响主要体现在对 **float** 和 **margin** 两个属性的处理。BFC 让 float 和 margin 这两个属性的表现更加符合我们的直觉。 10 | 11 | 根据 BFC 对其内部元素和外部元素的表现特性,将 BFC 的特性总结为 **对内部元素的包裹性** 及 **对外部元素的独立性**。 12 | 13 | ## 如何触发 14 | 15 | 满足下列条件之一就可触发 BFC。 16 | 17 | - 根元素,即 HTML 元素 18 | - `float` 的值不为 `none` 19 | - `overflow` 的值不为 `visible` 20 | - `display` 的值为 `inline-block`、`table-cell`、`table-caption` 21 | - `position` 的值为 `absolute` 或 `fixed` 22 | 23 | ## BFC 有哪些作用? 24 | 25 | BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素,反之亦然。它与普通的块框类似,但不同之处在于: 26 | 27 | 1. 自适应两栏布局 28 | 2. 可以阻止元素被浮动元素覆盖 29 | 3. 可以包含浮动元素——清除内部浮动 30 | 4. 分属于不同的 BFC 时可以阻止 margin 重叠 31 | 32 | ## BFC 布局规则 33 | 34 | 1. 内部的 Box 会在垂直方向,一个接一个地放置。 35 | 2. Box 垂直方向的距离由 margin 决定。**属于同一个 BFC 的两个相邻 Box** 的 margin 会发生重叠。 36 | 3. 每个元素的 margin box 的左边,与包含块 border box 的左边相接触(对于从左向右的格式化,否则相反)。即使存在浮动也是如此。 37 | 4. BFC 的区域不会与 float box 重叠。 38 | 5. BFC 就是页面上一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。 39 | 6. 计算 BFC 的高度时,浮动元素参与计算。 40 | 41 | 接下来分别去试一下: 42 | 43 | ### BFC 布局规则1:内部的 Box 会在垂直方向, 一个接一个地放置 44 | 45 | 布局规则1就是我们**平常div一行一行块级放置的样式**。 46 | 47 | ```html 48 |
content
` 79 | 80 | **行级元素**:与其他行内元素并排,没有宽高,默认宽度文字宽度 81 | 文本级标签(span,a,b,i,u,em)除了`
`都是 82 | 83 | 可以通过display属性互换 84 | display:inline 85 | 行内元素(可并排;不能设置高宽) 86 | 87 | display:block 88 | 块级元素(可以设高宽、独占一行、不设置宽度占满父级) 89 | 90 | ## 什么是Shadow DOM? 91 | 92 | Shadow DOM是浏览器的一种功能,能自动添加子元素,比如radio元素的controls,这些相关元素由浏览器生成。 93 | 94 | ## src和href的区别 95 | 96 | href 指向网络资源位置,建立当前文档和资源的连接,一般用于超链接 97 | 98 | src将资源嵌入到当前文档中,在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也是如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部。 99 | 100 | ## iframe的优缺点 101 | 优点 102 | 103 | - 可以用来加载速度较慢的第三方资源,例如广告、图标等 104 | - 可以用作安全沙箱 105 | - 可以并行下载脚本 106 | - 可以解决跨域问题 107 | 108 | 缺点 109 | 110 | - 加载代价昂贵,即使是空的页面也需要时间 111 | - 会阻塞页面的onload事件,iframe加载完后,父页面才会触发load 112 | - 没有语义 113 | - 增加http请求数 114 | - 搜索引擎爬虫不能很好的处理iframe的内容,不利于搜索引擎优化 115 | -------------------------------------------------------------------------------- /HTML/HTML5的新特点.md: -------------------------------------------------------------------------------- 1 | ## HTML5的新特点 2 | 3 | - 很多语义化的标签 4 | 5 | - canvas js绘图 6 | 7 | - draggable属性 可拖动 8 | 9 | - geolocationAPI 获取用户地理位置 10 | 11 | - audio/video 音频 视频元素 12 | 13 | - input类型增多,color、date、datetime、datetime-local、email、month、range、search、tel、time、url、week 14 | 15 | - 表单元素增强 16 | 17 | datalist 与input配合使用规定输入域的选项列表;keygen密钥;output定义不同类的输出。 18 | 19 | - Web存储,sessionStorge针对一个session进行数据存储,关闭浏览器创港口后清除,localStorage没有事件限制,不过它可能会因为本地时间修改失效。不过大量复杂数据结构一般用indexDB 20 | 21 | - Web worker 页面中执行脚本时,页面状态不可响应,直到脚本完成。在后台运行,独立于其他脚本,不会影响页面的性能。(相当于多线程并发) 22 | 23 | - SSE server-sent-event 网页自动获取来自服务器的更新。用于接收服务器发送时间通知 24 | 25 | - WebSocket 在单个TCP连接上进行全双工通讯的协议。只需要握手一次,形成快速通道,传输数据。客户端和服务器可以直接通过TCP交换数据。获取连接之后,可以用send发送数据,用onmessage接收服务器返回的数据。 26 | 27 | - 新API 28 | 29 | History、Command、Application cache …… -------------------------------------------------------------------------------- /Javascript/Bom.md: -------------------------------------------------------------------------------- 1 | ## Bom 2 | 3 | ## 1.window 4 | 5 | ### 窗口位置 6 | 7 | 窗口相对于屏幕左边和上面的位置 8 | 9 | FireFox:ScreenX、ScreenY 10 | 11 | Safari、Opera、Chrome: ScreenLeft、ScreenTop 12 | 13 | ```js 14 | var leftPos=window.ScreenLeft||window.ScreenX; 15 | var topPos=window.ScreenTop||window.screenY; 16 | ``` 17 | 18 | ### 页面视口大小 19 | 20 | ```js 21 | var pageWidth=window.innerWidth; 22 | var pageHeight=window.innerHeight; 23 | if(typeof pageWidth!='number'){ 24 | if(document.compatMode=="CSS1Compat"){ 25 | //标准模式 26 | pageWidth.document.documentElement.clientWidth; 27 | pageHeight.document.documentElement.clientHeight; 28 | }else{ 29 | //兼容模式 30 | pageWidth=document.body.clientWidth; 31 | pageHeight=docunment.body.clientHeight; 32 | } 33 | } 34 | ``` 35 | 36 | ## 2.Location 37 | 38 | 提供了当前窗口中加载文档有关的信息,还提供了一些导航功能。 39 | 40 | location对象既是window对象属性,又是document对象属性。window.location===document.location 41 | 42 | ### 访问片段 43 | 44 | Location可以将URL解析为独立的片段,访问片段的方法: 45 | 46 | - href 返回或设置当前文档的url 47 | - hostname 返回不带端口号的主域名 48 | - host 域名加端口 49 | - port 端口 50 | - search 返回url中查询字符串的部分 ?及之后的内容 51 | - hash 返回url中#后面的内容 52 | - protocol 使用的协议http 和 https 53 | - pathname url中的目录或者文件名 54 | 55 | 56 | 57 | ### 查询字符串参数实例 58 | 59 | 解析查询字符串,返回一个包含所有参数的对象 60 | 61 | ```js 62 | function getQuerySstringArgs(){ 63 | let qs=(location.search.length>0 ? loacation.seach:""), 64 | arg={}; 65 | 66 | itemArr=qs.length ? qs.split('&'):[], 67 | item=null,name=null,value=null,len=itemArr.length; 68 | 69 | for(let i=0;i{ 10 | console.log(index+1+"."+item) 11 | }) 12 | ``` 13 | 14 | 回调函数式可以多重的,比如ajax就是典型的栗子 15 | 16 | ```javascript 17 | function successCallback(){ 18 | //在发送之前做点什么 19 | } 20 | 21 | function successCallback(){ 22 | //在信息被成功接收之后做点什么 23 | } 24 | 25 | function completeCallback(){ 26 | //在完成之后做点什么 27 | } 28 | 29 | function errorCallback(){ 30 | //当错误发生时做点什么 31 | } 32 | $.ajax({ 33 | url:"http://fiddle.jshell.net/favicon.png", 34 | success:successCallback, 35 | complete:completeCallback, 36 | error:errorCallback 37 | 38 | }); 39 | ``` 40 | 41 | ## 回调函数的this 42 | 43 | 当回调函数是一个this对象的方法时,我们必须**改变执行回调函数**的方法来保证this对象的上下文。否则如果回调函数被传递给一个全局函数,this对象要么指向全局window对象,要么指向包含方法的对象。 44 | 45 | 举个复杂的栗子: 46 | 47 | #### 箭头函数plus回调函数中的this 48 | 49 | ```javascript 50 | let clientData = { 51 | id: '11111', 52 | fullName:"Not Set", 53 | setUserName: (firstName, lastName)=>{ 54 | this.fullName = firstName + " " + lastName; 55 | } 56 | } 57 | let getUserInput=(firstName,lastName,callback)=>{ 58 | //准备调用并存储fullName 59 | callback(firstName, lastName); 60 | } 61 | //调用 62 | getUserInput("Chitty","Zhang",clientData.setUserName); 63 | ``` 64 | 65 | 在这段代码里,箭头函数this是继承自父执行上下文,比如这里的箭头函数this.fullName,箭头函数本身所在的对象是clientData,而clientData的父执行上下文就是window,所以箭头函数的指向是window。 66 | 67 | 所以,结果并不是我们想的那样,而是: 68 | 69 | ```javascript 70 | > clientData.fullName 71 | "Not Set" 72 | > window.fullName 73 | "Chitty Zhang" 74 | ``` 75 | 76 | 似乎这都是箭头函数带来的问题,跟回调函数似乎没什么关联。或许有人会问,会不会是因为你用的ES6**箭头函数**导致的呢? 77 | 78 | #### 回调函数中的this 79 | 80 | 好的,那么用ES5再敲一遍…… 81 | 82 | ```javascript 83 | var clientData = { 84 | id: '11111', 85 | fullName:"Not Set", 86 | setUserName: function(firstName, lastName){ 87 | this.fullName = firstName + " " + lastName; 88 | } 89 | } 90 | function getUserInput(firstName,lastName,callback){ 91 | callback(firstName, lastName); 92 | } 93 | getUserInput("Chitty","Zhang",clientData.setUserName); 94 | ``` 95 | 96 | 结果,好像并没有什么变化…… 97 | 98 | ```javascript 99 | > clientData.fullName 100 | "Not Set" 101 | ``` 102 | 103 | 实际上,getUserInput是全局函数,在 clientData.setUserName 执行时,this对象指向windows对象,所以在进一步的执行下,this一直是window。 104 | 105 | 106 | 107 | **请注意!不要被结果误导!**这两种情况并不一样! 108 | 109 | 观察一下getUserInput的的this 110 | 111 | ES5 的结果是:this:window 112 | 113 |  114 | 115 | 但ES6的结果是:this:undefined 116 | 117 |  118 | 119 | 虽然最终结果一样,但请记住,他们是有本质区别的! 120 | 121 | 122 | 123 | #### 修复 124 | 125 | 我们可以用Call或者Apply函数来修复这个问题。 126 | 127 | ```javascript 128 | //新传一个回调的对象callbackObj 129 | let getUserInputUseApply=(firstname,lastname,callback,callbackObj)=>{ 130 | callback.apply(callbackObj, [firstname, lastname]); 131 | } 132 | //调用 133 | //clientData对象会被Apply方法使用来设置为this对象 134 | getUserInputUseApply("Chitty","Zhang",clientData.setUserName,clientData); 135 | ``` 136 | 137 | 现在这个问题已经被修复了✿✿ヽ(°▽°)ノ✿ 138 | 139 | ```javascript 140 | > clientData.fullName 141 | "Chitty Zhang" 142 | ``` 143 | 144 | 145 | 146 | ## 体验一下回调函数的好处 147 | 148 | 在下面的过程中,实现以下功能:读取用户信息,用用户数据创建一个通用的句子,并且欢迎用户 149 | 150 | ```javascript 151 | //通用句子,作为回调对象 152 | let genericSentence=(name,gender)=>{ 153 | let words=`${name} is the most funny ${gender} in the world.`; 154 | console.log(words); 155 | } 156 | //读取 157 | let getUserInput=(firstName,lastName,gender,callback)=>{ 158 | let fullName=`${firstName} ${lastName}`; 159 | if (typeof callback ==="function"){ 160 | callback(fullName,gender); 161 | } 162 | } 163 | //调用 164 | > getUserInput("Sarrans","Zhou","female",genericSentence) 165 | Sarrans Zhou is the most funny female in the world. 166 | ``` 167 | 168 | 因为getUserInput 函数仅仅只负责提取数据,我们可以把其他回调函数传递给它。 169 | 170 | 比如说我要实现"Hello,Mr./Ms. XXX",也可以使用它 171 | 172 | ```javascript 173 | //先写个欢迎用户的 174 | let welcome=(fullname,sex)=>{ 175 | let male=sex&&sex==="man"?"Mr.":"Ms."; 176 | console.log(`Hello,${male} ${fullname}! Nice to meet you :)`); 177 | } 178 | //调用 179 | > getUserInput("George","Chu","man",welcome) 180 | Hello,Mr. George Chu! Nice to meet you :) 181 | ``` 182 | 183 | ## 回调地狱 184 | 185 | 在执行异步代码时,无论以什么顺序简单的执行代码,经常情况会变成许多层级的回调函数堆积。它的根本问题在于: 186 | 187 | - 嵌套函数存在耦合性,动一发则动全身 188 | - 嵌套函数多了,难以处理错误 189 | 190 | 如何解决?思路有两: 191 | 192 | 1. 给你的函数命名并传递它们的名字作为回调函数,而不是主函数的参数中定义匿名函数。 193 | 2. 模块化L将你的代码分隔到模块中,这样你就可以到处一块代码来完成特定的工作。然后你可以在你的巨型应用中导入模块 -------------------------------------------------------------------------------- /Javascript/DOM与事件.md: -------------------------------------------------------------------------------- 1 | ## DOM与事件 2 | 3 | ### DOM如何创建元素 4 | 5 | - 创建 6 | 7 | createHTML('div'); 8 | 9 | - 添加 10 | 11 | appendChild(element) 12 | 13 | insertBefore(insertdom.chosendom) 14 | 15 | ### DOM获取元素的方式 16 | 17 | #### 通过元素类型获取 18 | 19 | 1. document.getElementById();//id名,在实际开发中较少使用,选择器中多用class id一般只用在顶级层存在 不能太过依赖id 20 | 2. document.getElementsByTagName();//标签名 21 | 3. document.getElementsByClassName();//类名 22 | 4. document.getElementsByName();//name属性值,一般不用 23 | 5. document.querySelector();//css选择符模式,返回与该模式匹配的第一个元素,结果为一个元素;如果没找到匹配的元素,则返回null 24 | 6. document.querySelectorAll()//css选择符模式,返回与该模式匹配的所有元素,结果为一个类数组 25 | 26 | #### 根据关系树来选择 27 | 28 | 1. parentNode//获取所选节点的父节点,最顶层的节点为#document 29 | 2. childNodes //获取所选节点的子节点们 30 | 3. firstChild //获取所选节点的第一个子节点 31 | 4. lastChild //获取所选节点的最后一个子节点 32 | 5. nextSibling //获取所选节点的后一个兄弟节点 列表中最后一个节点的nextSibling属性值为null 33 | 6. previousSibling //获取所选节点的前一兄弟节点 列表中第一个节点的previousSibling属性值为null 34 | 35 | #### 根据元素节点树来选择 36 | 37 | 1. parentElement //返回当前元素的父元素节点(IE9以下不兼容) 38 | 2. children // 返回当前元素的元素子节点 39 | 3. firstElementChild //返回的是第一个元素子节点(IE9以下不兼容) 40 | 4. lastElementChild //返回的是最后一个元素子节点(IE9以下不兼容) 41 | 5. nextElementSibling //返回的是后一个兄弟元素节点(IE9以下不兼容) 42 | 6. previousElementSibling //返回的是前一个兄弟元素节点(IE9以下不兼容) 43 | 44 | ### 如何给元素添加事件 45 | 46 | - 在HTML元素中绑定事件 onclick=show() 47 | - 获取dom,dom.onclick 48 | - addEventListener(click,show,1/0) 49 | 50 | ### addEventListener三个参数,取值意思 51 | 52 | 第一个参数是事件类型,第二个是事件发生的回调函数,第三个是个布尔值,默认是false,false是冒泡阶段执行,true是捕获阶段。 53 | 54 | ### 节点属性中children和childNodes有什么区别? 55 | 56 | - childNodes返回的是节点的子节点集合(NodeLists),包括元素节点、文本节点还有属性节点。 57 | - children返回的只是节点的元素节点集合(HTMLCollection) 58 | 59 | ### HTMLCollection和NodeList的比较 60 | 61 | - 共同点 62 | 63 | - 都是类数组对象,都有length属性 64 | 65 | - 都有共同的方法:item,可以通过item(index)获取返回结果的元素 66 | 67 | - 都是实时变动的,document上面的更改会反映到相关的对象上 68 | 69 | 注:querySeletorAll返回的NodeList是个浅拷贝的类数组对象,在节点数目上是非实时的,不过对节点属性进行修改,还是实时反映的。 70 | 71 | - 区别 72 | 73 | - NodeList可以包含任何节点类型,HTMLCollection只包含元素节点。elementNode就是HTML中的标签。 74 | - HTMLCollection比NodeList多一个方法:nameitem(),除了可以用id,还可以用name来获取节点信息。 75 | 76 | ### 事件冒泡与事件捕获 77 | 78 | 事件是先捕获,后冒泡 79 | 80 | 捕获阶段是外部元素先触发然后触发内部元素 81 | 82 | 冒泡阶段是内部元素先触发然后触发外部元素 83 | 84 | ### 如何阻止事件冒泡?如何取消默认事件?如何阻止事件的默认行为? 85 | 86 | - 阻止事件冒泡: 87 | 88 | W3C: stopPropagation(); 89 | 90 | IE: e.cancelBubble=true; 91 | 92 | 写法 : 93 | 94 | window.event ? window.event.cancelBubble=true:e.stop(Propagation) 95 | 96 | - 取消默认事件 97 | 98 | W3C:preventDefault() 99 | 100 | IE: e.returnValue:false; 101 | 102 | - 阻止默认行为: 103 | 104 | return false 105 | 106 | 原生的js会阻止默认行为,但会继续冒泡; 107 | 108 | jquery会阻止默认行为,并停止冒泡。 109 | 110 | ### 获取DOM节点get系列和query系列哪种性能好? 111 | 112 | 1.从性能上说get系列的性能都比query系列好,get系列里面各有差异,这些差异可以结合算法如何遍历搜索去理解,都解释得通。 113 | 114 | 2.`getElementsByTagName`比`querySelectorAll`快的原因在于:`getElementsByTagName`创建的过程不需要做任何操作,只需要返回一个指针即可。而`querySelectorAll`会循环遍历所有的的结果,然后创建一个新的NodeList。 115 | 116 | 3.实际在用的过程中**取决于要获取的是什么**,再进行选择。 117 | 118 | 具体的可以参考看看探索篇-[对获取DOM的N种方法性能的思考](https://github.com/YiiChitty/FrontEndLearning/blob/master/一些思考/关于获取DOM节点的思考.md) 119 | 120 | -------------------------------------------------------------------------------- /Javascript/JS常用方法原理.md: -------------------------------------------------------------------------------- 1 | # JS常用方法原理 2 | 3 | 这一部分试图研究一些常用方法的原理,将尝试用原生的js去实现这些方法,一方面加深对这些方法的理解,另一方面是为面试手写源码做的准备。 4 | 5 | ## call、apply、bind 6 | 7 | 以上这三种方法都是Function.prototype下的方法,均用于改变函数运行时上下文(this的指向)。 8 | 9 | call和apply作用都是相同的,只是传参的方式不同。除了第一个参数外,call可以接收一个参数列表,apply只接受一个参数数组。 10 | 11 | bind和其他两个方法作用也是一致的,只是该方法会返回一个函数。 12 | 13 | ### 实现call 14 | 15 | 1.不传入第一个参数时,默认为window 16 | 17 | 2.改变了 this 指向,让新的对象可以执行该函数(给新对象添加一个函数,执行完之后删除) 18 | 19 | ```javascript 20 | Function.prototype.myCall=function(context){ 21 | if(typeof this !=='function'){ 22 | throw new TypeError('error'); 23 | } 24 | var context=context||window; 25 | context.fn=this; 26 | var args=[...arguments].slice(1); 27 | //getValue.call(a,'xxx','xxx')=>a.fn('xxx','xxx') 28 | var result=context.fn(...args); 29 | delete context.fn; 30 | return dalete; 31 | } 32 | ``` 33 | 34 | ### 实现Apply 35 | 36 | ```javascript 37 | //思路一致 38 | Function.prototype.apply()=function(context){ 39 | if(typeof this !=='function'){ 40 | throw new TypeError('error'); 41 | } 42 | context=context||window; 43 | context.fn=this; 44 | let result; 45 | //参数处理有区别,第二个参数是一个数组 46 | if(arguments[1]){ 47 | result=context.fn(...arguments[1]); 48 | }else{ 49 | result=context.fn(); 50 | } 51 | delete context.fn; 52 | return result; 53 | } 54 | ``` 55 | 56 | ### 实现Bind 57 | 58 | 因为bind返回的是一个函数,所以需要判断边界,复杂一些。 59 | 60 | ```javascript 61 | Function.prototype.myBind=function(context){ 62 | if (typeof this !== 'function') { 63 | throw new TypeError('error'); 64 | } 65 | const _this=this; 66 | const args=[...arguments].slice(1); 67 | return function F(){ 68 | // new 函数,不动this 69 | if (this instanceof F){ 70 | return new _this(...args,...arguments); 71 | } 72 | return _this.apply(context,args.concat(...arguments)); 73 | } 74 | } 75 | ``` 76 | 77 | - bind返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new的方式,我们先来说直接调用的方式 78 | - 对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind可以实现类似这样的代码 `f.bind(obj, 1)(2)`,所以我们需要将两边的参数拼接起来,于是就有了这样的实现 `args.concat(...arguments)` 79 | - 对于 `new` 的情况来说,不会被任何方式改变 `this`,所以对于这种情况我们需要忽略传入的 `this` 80 | 81 | ## new 82 | 83 | 首先,先聊一下关于new的作用: 84 | 85 | 写了一个例子 86 | 87 | ```javascript 88 | function Test(name){ 89 | this.name=name; 90 | } 91 | Test.prototype.sayName=function(){ 92 | console.log(this.name) 93 | } 94 | const t=new Test('Chitty') 95 | 96 | //控制台输出 97 | t.name 98 | > 'Chitty' 99 | t.sayName(); 100 | > 'Chitty' 101 | ``` 102 | 103 | - **new 通过构造函数 Test 创建出来的实例可以访问到构造函数中的属性** 104 | - **new 通过构造函数 Test 创建出来的实例可以访问到构造函数原型链中的属性,也就是说通过 new操作符,实例与构造函数通过原型链连接了起来** 105 | 106 | 但是这个构造函数Test并没有返回任何值,试一下返回值会怎么样: 107 | 108 | ```javascript 109 | function Test(name){ 110 | this.name=name; 111 | return 1; 112 | } 113 | const t=new Test('Chitty') 114 | 115 | //控制台输出 116 | t.name 117 | > 'Chitty' 118 | ``` 119 | 120 | - **构造函数如果返回原始值,那么这个返回值毫无意义** 121 | 122 | 如果返回对象呢? 123 | 124 | ```javascript 125 | function Test(name){ 126 | this.name=name; 127 | return {name:'zy'} 128 | } 129 | const t=new Test('Chitty') 130 | 131 | //控制台输出 132 | t 133 | > {name:'zy'} 134 | t.name 135 | > 'zy' 136 | ``` 137 | 138 | 虽然构造函数内部的 `this` 还是依旧正常工作的,但是当返回值为对象时,这个返回值就会被正常的返回出去。 139 | 140 | - **构造函数如果返回值为对象,那么这个返回值会被正常使用** 141 | 142 | **tips:**所以,在使用构造函数的时候,尽量不要写返回值!原始值不生效,返回对象那new就没有作用了。 143 | 144 | ### 实现new 145 | 146 | 在调用new的时候会发生这些事情: 147 | 148 | 1.内部新生成一个对象 149 | 150 | 2.链接到原型 151 | 152 | 3.绑定this(对象=构造函数的this) 153 | 154 | 4.返回新对象(原始值忽略,但对象要正常处理) 155 | 156 | 试着实现一下: 157 | 158 | ```javascript 159 | function create(con,...args){ 160 | let obj={}; 161 | obj._proto_=con.prototype; 162 | //绑定this并执行构造函数 163 | let result=con.apply(obj,args); 164 | return result instanceof Object? result:obj 165 | } 166 | ``` 167 | 168 | 一步步剖析一下过程: 169 | 170 | 1. 首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用 171 | 2. 然后内部创建一个空对象 `obj` 172 | 3. 因为 `obj` 对象需要访问到构造函数原型链上的属性,所以需要把两者联系起来。 173 | 4. 将 `obj` 绑定到构造函数上,并且传入剩余的参数 174 | 5. 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 `obj`,这样就实现了忽略构造函数返回的原始值 175 | 176 | ## instanceof原理 177 | 178 | instanceof可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。 179 | 180 | 尝试实现一下: 181 | 182 | ```javascript 183 | function myInstanceof(left,right){ 184 | let prototype=right.prototype; 185 | left=left._proto_; 186 | while(true){ 187 | if(left===null||left===undefined) return false; 188 | if(prototype===left) return true; 189 | left=left._proto_; 190 | } 191 | } 192 | ``` 193 | 194 | 步骤和思路: 195 | 196 | - 首先获取类型的原型 197 | - 然后获得对象的原型 198 | - 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 `null`,因为原型链最终为 `null` 199 | 200 | ## 多参数柯里化 201 | 202 | ```js 203 | function progressCurrying(fn,args){ 204 | var _this=this; 205 | var len=fn.length; 206 | var args=args||[]; 207 | return function (){ 208 | var _args=Array.prototype.slice.call(arguments).concat(args); 209 | //参数个数小于最初的fn.length,则继续递归调用 210 | if(_args.length: "try async"} 13 | ``` 14 | 15 | 所以,async函数返回的是一个Promise对象 16 | 17 | 如果是一个直接量,saync会把这个直接量通过Promise.resolve()封装成Promise对象,如果没有返回值,那它会返回Promise.resolve(undefined) 18 | 19 | 20 | 21 | 所以,最外层不能用await获取的返回值的情况下,应该用原来的方式then()来处理这个对象 22 | 23 | ```javascript 24 | testAsync().then(v=>{ 25 | console.log(v) 26 | }) 27 | //控制台输出 28 | try async 29 | ``` 30 | 31 | Promise是无等待的,所以在没有使用await的情况下,执行async函数,它会立即执行,返回一个promise对象,且不会阻塞后面的语句。这个普通返回Promise的函数没有什么区别 32 | 33 | ### await 34 | 35 | #### await在等待什么 36 | 37 | 一般认为await是在等待一个async函数完成,不过按照语法说明,await等待的是一个表达式,这个表达式的计算结果是Promise对象或者其他值。 38 | 39 | async函数返回一个Promise对象,所以await用于等待一个async函数的返回值(不仅仅是Promise对象,还可以是任意表达式的结果)。 40 | 41 | ```javascript 42 | function getValue(){ 43 | return "hi"; 44 | } 45 | 46 | async function testAsync(){ 47 | return Promise.resolve("hello async"); 48 | } 49 | 50 | async function test(){ 51 | const v1=await getValue(); 52 | const v2=await testAsync(); 53 | console.log(v1,v2); 54 | } 55 | test(); 56 | 57 | //控制台输出 58 | hi hello async 59 | Promise {: undefined} 60 | ``` 61 | 62 | #### 等到了要等的,然后做什么 63 | 64 | await函数等到了一个Promise或者一个表达式的结果。 65 | 66 | 它其实是一个运算符,用于组成表达式,其运算结果取决于它等的内容: 67 | 68 | 如果不是一个Promise对象,运算结果就是它等到的东西 69 | 70 | 如果是一个Promise对象,await会阻塞后面的代码,等着Promise对象resolve,然后得到它的值作为await的结果。 71 | 72 | > 这个就是await为何要用在async函数中的原因,async函数调用不会阻塞,它内部所有阻塞都被封装在一个Promise对象中异步执行 73 | 74 | ### async/await怎么用 75 | 76 | 用setTimeout模拟耗时的异步操作: 77 | 78 | ```javascript 79 | function takeLongTime(){ 80 | return new Promise(resolve=>{ 81 | setTimeout(()=>resolve("long_time_value"),1000); 82 | }) 83 | } 84 | takeLongTime().then(v=>{ 85 | console.log("got",v) 86 | }) 87 | ``` 88 | 89 | 如果改用async/await: 90 | 91 | ```javascript 92 | function takeLongTime(){ 93 | return new Promise(resolve=>{ 94 | setTimeout(()=>resolve("long_time_value"),1000); 95 | }) 96 | } 97 | async function test(){ 98 | const v=await takeLongTime(); 99 | console.log(v); 100 | } 101 | test(); 102 | ``` 103 | 104 | takeLongTime本身返回的就是Promise对象,加不加async都一样 105 | 106 | 不过看上去似乎差别不明显 107 | 108 | ### async/await优势在于处理then链 109 | 110 | 单一的 Promise 链并不能发现 async/await 的优势,但如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了。 111 | 112 | > Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它 113 | 114 | 假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。 115 | 116 | ```javascript 117 | //传入参数n,表示函数执行时间,执行结果是n+200,这个值用于下一步骤 118 | function takeLongTime(n){ 119 | return new Promise(resolve=>{ 120 | setTimeout(()=>resolve(n+200),n); 121 | }); 122 | } 123 | function step1(n){ 124 | console.log(`step1 with ${n}`); 125 | return takeLongTime(n); 126 | } 127 | function step2(n){ 128 | console.log(`step2 with ${n}`); 129 | return takeLongTime(n); 130 | } 131 | function step3(n){ 132 | console.log(`step3 with ${n}`); 133 | return takeLongTime(n); 134 | } 135 | ``` 136 | 137 | 现在用Promise方式来实现三个步骤的处理 138 | 139 | ```javascript 140 | function doIt(){ 141 | console.time("doIt"); 142 | const time1=300; 143 | step1(time1).then(time2=>step2(time2)) 144 | .then(time3=>step3(time3)) 145 | .then(result=>{ 146 | console.log(`result is ${result}`) 147 | console.timeEnd("doIt") 148 | }); 149 | } 150 | doIt(); 151 | 152 | //控制台输出 153 | > step1 with 300 154 | > step2 with 500 155 | > step3 with 700 156 | > result is 900 157 | > doIt: 1501.7548828125ms 158 | ``` 159 | 160 | 如果用async/await来实现: 161 | 162 | ```javascript 163 | function doIt()=>{ 164 | console.time("doIt"); 165 | const time1=300; 166 | const time2=await step1(time1); 167 | const time3=await step2(time2); 168 | const result=await step3(time3); 169 | console.log(`result is ${result}`); 170 | console.timeEnd("doIt") 171 | } 172 | doIt(); 173 | 174 | //控制台输出 175 | step1 with 300 176 | step2 with 500 177 | step3 with 700 178 | result is 900 179 | doIt: 1501.7548828125ms 180 | ``` 181 | 182 | 和之前实现结果是一样的,但这个代码看起来更清晰。 183 | 184 | 还是刚才的题目。如果要求,三个步骤每个都需要返回之前所有步骤的结果。 185 | 186 | ```javascript 187 | function takeLongTime(n){ 188 | return new Promise(resolve=>{ 189 | setTimeout(()=>resolve(n+200),n); 190 | }); 191 | } 192 | function step1(n){ 193 | console.log(`step1 with ${n}`); 194 | return takeLongTime(n); 195 | } 196 | function step2(m,n){ 197 | console.log(`step2 with ${m} and ${n}`); 198 | return takeLongTime(m+n); 199 | } 200 | function step3(k,m,n){ 201 | console.log(`step3 with ${k} , ${m} and ${n}`); 202 | return takeLongTime(k+m+n); 203 | } 204 | ``` 205 | 206 | 先用async/await写 207 | 208 | ```java 209 | async function doIt(){ 210 | console.time("doIt"); 211 | const time1=300; 212 | const time2=await step1(time1); 213 | const time3=await step2(time1,time2); 214 | const result=await step3(time1,time2,time3); 215 | console.log(`result is ${result}`); 216 | console.timeEnd("doIt"); 217 | } 218 | ``` 219 | 220 | 如果把它写成 Promise 方式: 221 | 222 | ```javascript 223 | function doIt() { 224 | console.time("doIt"); 225 | const time1 = 300; 226 | step1(time1) 227 | .then(time2 => { 228 | return step2(time1, time2) 229 | .then(time3 => [time1, time2, time3]); 230 | }) 231 | .then(times => { 232 | const [time1, time2, time3] = times; 233 | return step3(time1, time2, time3); 234 | }) 235 | .then(result => { 236 | console.log(`result is ${result}`); 237 | console.timeEnd("doIt"); 238 | }); 239 | } 240 | doIt(); 241 | ``` 242 | 243 | 看着就眼晕,那一堆参数处理,就是 Promise 方案的死穴—— 参数传递太麻烦了! 244 | 245 | -------------------------------------------------------------------------------- /Javascript/回调地狱的解决方式总结.md: -------------------------------------------------------------------------------- 1 | # 回调地狱的解决方式总结 2 | 3 | 先写一个回调地狱的经典例子: 4 | 5 | ```javascript 6 | ajax('http://url-1', data1, function (err, result) { 7 | if (err) { 8 | return handle(err); 9 | } 10 | ajax('http://url-2', data2, function (err, result) { 11 | if (err) { 12 | return handle(err); 13 | } 14 | ajax('http://url-3', data3, function (err, result) { 15 | if (err) { 16 | return handle(err); 17 | } 18 | return success(result); 19 | }); 20 | }); 21 | }); 22 | ``` 23 | 24 | 以上代码看上去非常不利于阅读和维护 25 | 26 | ### 思路1:拆解function 27 | 28 | 将各步拆解为单个的function 29 | 30 | ```javascript 31 | function firstAjax() { 32 | ajax('http://url-1',data1, (err, result) => { 33 | if (err) { 34 | return handle(err); 35 | } 36 | secondAjax(); 37 | }) 38 | } 39 | function secondAjax() { 40 | ajax('http://url-2', data2, (err, result) => { 41 | if (err) { 42 | return handle(err); 43 | } 44 | thirdAjax(); 45 | }) 46 | } 47 | function thirdAjax(){ 48 | ajax('http://url-3', data3, (err, result)=>{ 49 | if (err) { 50 | return handle(err); 51 | } 52 | return success(result); 53 | }) 54 | } 55 | 56 | //执行 57 | ajax(url, () => { 58 | // 处理逻辑 59 | firstAjax() 60 | }) 61 | ``` 62 | 63 | 但是以上的代码虽然看上去利于阅读,但是还没有解决根本问题。 64 | 65 | 回调地狱的根本问题就是: 66 | 67 | - 嵌套函数存在耦合性,一旦有所改动,牵一发动全身 68 | - 嵌套函数一多,就很难处理错误 69 | 70 | ### 思路2:事件发布/监听模式 71 | 72 | 一方面,监听某一事件,当事件发生时,进行相应回调操作;另一方面,当某些操作完成后,通过发布事件触发回调,这样就可以将原本捆绑在一起的代码解耦。 73 | 74 | 还没有看这个部分,先占个坑 75 | 76 | 77 | 78 | ### 思路3:Generator 79 | 80 | 关于Generator的理解可以参考之前的文章:[链接]([https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Generator.md](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/深入理解Generator.md)) 81 | 82 | ```javascript 83 | try { 84 | r1 = yield ajax('http://url-1', data1); 85 | r2 = yield ajax('http://url-2', data2); 86 | r3 = yield ajax('http://url-3', data3); 87 | success(r3); 88 | } 89 | catch (err) { 90 | handle(err); 91 | } 92 | ``` 93 | 94 | ### 思路4:Promise 95 | 96 | 之前那个例子可能不太合适,换个实际应用的场景吧。 97 | 98 | 在手机上浏览电影网站,其中有个海报墙页面,里面有海量的电影节目,如下图所示。考虑到性能和用户体验,启动后,我们需要串行的加载10页数据(每页9张海报),即第一页加载完成后,启动第二页的加载,以此类推。 99 | 100 |  101 | 102 | ```javascript 103 | $(document).ready(function(){ 104 | //获取第一页数据 105 | $.getJSON("json/poster.json?page=1",function(result){ 106 | attachPoster(result); 107 | //获取第二页数据 108 | $.getJSON("json/poster.json?page=2",function(result){ 109 | attachPoster(result); 110 | //获取第三页数据 111 | $.getJSON("json/poster.json?page=3",function(result){ 112 | attachPoster(result); 113 | ... 114 | }); 115 | }); 116 | }); 117 | }); 118 | ``` 119 | 120 | 用Pomise写: 121 | 122 | ```javascript 123 | //封装页码的Promise 124 | function getPoster(page){ 125 | const promise = new Promise(function(resolve,reject){ 126 | $.getJSON("json/poster.json?page="+page,function(result){ 127 | resolve(result); 128 | }) 129 | }); 130 | return promise; 131 | } 132 | getPoster(1).then(function(result){ 133 | //获取第一页 134 | attachPoster(result); 135 | return getPoster(2); 136 | }).then(function(result){ 137 | //获取二页 138 | attachPoster(result); 139 | return getPoster(3); 140 | }).then(funciton(result){ 141 | //获取第三页 ... 142 | }) 143 | ``` 144 | 145 | ### 思路5: async/await 146 | 147 | ```javascript 148 | async function test(){ 149 | try 150 | { 151 | //每步都返回一个Pormise对象 152 | let r1=await ajax('http://url-1', data1); 153 | let r2=await ajax('http://url-2', data2); 154 | let r3=await ajax('http://url-3', data3); 155 | success(r3); 156 | } 157 | catch (err) { 158 | handle(err); 159 | } 160 | test(); 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /Javascript/垃圾回收机制.md: -------------------------------------------------------------------------------- 1 | ## 垃圾回收机制 2 | 3 | V8将内存(堆)分为新生代和老生代两部分。 4 | 5 | 新生代对象一般存活时间短,其内存空间分为两个部分,From空间和To空间,在这两个空间中,有一个是使用的,一个是空闲的。新分配的对象放入From空间中,当Form占满时,新生代GC启动,检查Form对象存活对象复制到To空间,失活就销毁。复制完成后Form空间和To空间互换。 6 | 7 | 老生代对象一般存活时间长,数量多,用了两个算法:标记清楚算法和标记压缩法。当新生代对象已经经历过一次GC算法了,将对象放入老生代空间;或者To空间对象占比超过25%,为不影响内存分配,会将对象放入老生代空间。 8 | 9 | 当某个空间没有分块、空间中被对象超过一定限制、空间中不能保证新生代对象移动到老生代中,会启动标记清除的算法。遍历所有对象,标记活的对象,销毁未标记的。清除对象后会造成内存出现碎片的情况,超过一定限制启动压缩算法。将活的对象一端移动,直到所有对象都移动完成,清理掉不需要的内存。 10 | 11 | 12 | 13 | 14 | 15 | 时间不够,后面再来好好了解这一块的内容。 16 | 17 | 关于它的详解可以看这篇[文章](https://www.jianshu.com/p/455d0b9ef0a8)。 18 | 19 | 补充一下: 20 | 21 | 其实,标记有很多性能上的问题。为了解决这个问题,2011 年,V8 从 stop-the-world 标记切换到增量标志。在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。但在 2018 年,GC 技术又有了一个重大突破,这项技术名为并发标记。该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行。具体的内容看V8的研究成果,戳[这个连接](https://v8project.blogspot.com/2018/06/concurrent-marking.html)。 22 | 23 | -------------------------------------------------------------------------------- /Javascript/字符串操作API.md: -------------------------------------------------------------------------------- 1 | ## 字符串操作API 2 | 3 | 今天网易笔试之前,扫了一下18年的题目,里面有一道题让实现汉字转拼音的,用到了stringObject.localeCompare()方法。 4 | 5 | 好吧,又是一个新的盲区。 6 | 7 | 于是就连夜整理了字符串操作的API以加强记忆。 8 | 9 | ### 操作原字符串 10 | 11 | - **replace** 12 | 13 | 用来查找匹配一个正则表达式的字符串,然后使用新字符串代替匹配的字符串。 14 | 15 | 16 | 17 | ### 对原字符串无影响 18 | 19 | - **concat** 20 | 21 | 将两个或者多个字符文本组合,返回一个新的字符串 22 | 23 | - **substring ** 24 | 25 | 参数:起始位置和结束位置(不包括),返回字符串的一个子串 26 | 27 | - **substr** 28 | 29 | 参数:起始位置和长度,返回字符串的子串 30 | 31 | - **match** 32 | 33 | 找到一个或多个正则表达式的匹配。字符串匹配一个正则表达式内容,如果没有返回 null 34 | 35 | - **slice** 36 | 37 | 参数:起始位置,结束位置(不包括)。提取字符串的一部分,并返回一个新字符串(与 substring 相同)。 38 | 39 | 40 | 41 | ### 查找类 42 | 43 | - **indexOf** 44 | 45 | 检索字符串。返回字符串中一个子串第一处出现的索引,没有返回-1 46 | 47 | - **lastIndexOf** 48 | 49 | 从后向前搜索字符串。返回字符串中一个子串最后一处出现的索引,没有返回-1 50 | 51 | - **charAt** 52 | 53 | 返回在指定位置的字符。 54 | 55 | - **search** 56 | 57 | 执行一个正则表达式匹配查找。如果查找成功,返回字符串中匹配的索引值。否则返回 -1 。 58 | 59 | 60 | 61 | ### 变数组 62 | 63 | - **split** 64 | 65 | 将字符串划分成子串,将一个字符串做成一个字符串数组。 66 | 67 | 68 | 69 | ### 变格式 70 | 71 | - **toLowerCase ** 72 | 73 | 将整个字符串转成小写字母。 74 | 75 | - **toUpperCase ** 76 | 77 | 将整个字符串转成大写字母。 78 | 79 | - **link** 80 | 81 | 字符串换成超链接 link(url) 82 | 83 | - **small** 84 | 85 | 把字符串显示为小号字 86 | 87 | - **big** 88 | 89 | 字符串显示为大号字 90 | 91 | 92 | 93 | ### 作比较 94 | 95 | - **localeCompare** 96 | 97 | 参数:要与str进行比较的字符串,返回比较结果的数字,如果 stringObject 小于 target,则 localeCompare() 返回小于 0 的数。如果 stringObject 大于 target,则该方法返回大于 0 的数。如果两个字符串相等,或根据本地排序规则没有区别,该方法返回 0。 98 | 99 | 100 | 101 | ### API扩充 102 | 103 | - 去除左边的空格 104 | 105 | ```javascript 106 | String.prototype.LTrim = function(){ 107 | return this.replace(/(^\s*)/g, ""); 108 | } 109 | ``` 110 | 111 | - 去除右边的空格 112 | 113 | ```js 114 | String.prototype.Rtrim = function() { 115 | return this.replace(/(\s*$)/g, ""); 116 | } 117 | ``` 118 | 119 | - 去除前后空格 120 | 121 | ```js 122 | String.prototype.Trim = function(){ 123 | return this.replace(/(^\s*)|(\s*$)/g, ""); 124 | } 125 | ``` 126 | 127 | - 得到左边的字符串 128 | 129 | ```js 130 | String.prototype.Left = function(len){ 131 | if(isNaN(len)||len==null){ 132 | len = this.length; 133 | }else{ 134 | if(parseInt(len)<0||parseInt(len)>this.length){ 135 | len = this.length; 136 | } 137 | } 138 | return this.substr(0,len); 139 | } 140 | ``` 141 | 142 | - 得到右边的字符串 143 | 144 | ```js 145 | String.prototype.Right = function(len){ 146 | if(isNaN(len)||len==null){ 147 | len = this.length; 148 | }else{ 149 | if(parseInt(len)<0||parseInt(len)>this.length){ 150 | len = this.length; 151 | } 152 | } 153 | return this.substring(this.length-len,this.length); 154 | } 155 | ``` 156 | 157 | - 判断是否是正确的长日期 158 | 159 | ```js 160 | String.prototype.isLongDate = function(){ 161 | var r = this.replace(/(^\s*)|(\s*$)/g, "").match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/); 162 | if(r==null){ 163 | return false; 164 | } 165 | var d = new Date(r[1], r[3]-1,r[4],r[5],r[6],r[7]); 166 | return (d.getFullYear()==r[1]&& (d.getMonth()+1)==r[3]&&d.getDate()==r[4]&&d.getHours()==r[5]&&d.getMinutes()==r[6]&&d.getSeconds()==r[7]); 167 | } 168 | ``` 169 | 170 | - 判断是否是IP地址 171 | 172 | ```js 173 | String.prototype.isIP = function(){ 174 | var reSpaceCheck = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; 175 | if (reSpaceCheck.test(this)){ 176 | this.match(reSpaceCheck); 177 | if (RegExp.$1 <= 255 && RegExp.$1 >= 0 && RegExp.$2 <= 255 && RegExp.$2 >= 0 && RegExp.$3 <= 255 && RegExp.$3 >= 0 && RegExp.$4 <= 255 && RegExp.$4 >= 0) { 178 | return true; 179 | }else{ 180 | return false; 181 | } 182 | }else { 183 | return false; 184 | } 185 | } 186 | ``` 187 | 188 | 还有很多补充API,可以参考[这篇博客](https://blog.csdn.net/xinsong520/article/details/52160072) 189 | 190 | -------------------------------------------------------------------------------- /Javascript/定时器.md: -------------------------------------------------------------------------------- 1 | ## 定时器 2 | 3 | 常见的定时器函数有 setTimeout、setInterval、requestAnimationFrame。 4 | 5 | 大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是lOOOms/60,约等于16.6ms 6 | 7 | 而setTimeout和setInterval的问题是,它们都不精确。它们的内在[运行机制](http://www.cnblogs.com/xiaohuochai/p/5773183.html#anchor3)决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。 8 | 9 | 除此之外,setInterval存在执行累积的问题,如果定时器执行过程中出现了耗时操作,多个回调函数会在耗时操作结束以后同时执行,这样可能就会带来性能上的问题。 10 | 11 | requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。 12 | 13 | ### requestAnimationFrame的特点 14 | 15 | - requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率 16 | 17 | - 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量 18 | - requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销 19 | 20 | ### questAnimationFrame用法 21 | 22 | questAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行 23 | 24 | ``` 25 | requestID = requestAnimationFrame(callback); 26 | //控制台输出1和0 27 | var timer = requestAnimationFrame(function(){ 28 | console.log(0); 29 | }); 30 | console.log(timer);//1 31 | ``` 32 | 33 | cancelAnimationFrame方法用于取消定时器 34 | 35 | ``` 36 | //控制台什么都不输出 37 | var timer = requestAnimationFrame(function(){ 38 | console.log(0); 39 | }); 40 | cancelAnimationFrame(timer); 41 | ``` 42 | 43 | 也可以直接使用返回值进行取消 44 | 45 | ``` 46 | var timer = requestAnimationFrame(function(){ 47 | console.log(0); 48 | }); 49 | cancelAnimationFrame(1); 50 | ``` 51 | 52 | ### 修改setTimeout() 53 | 54 | 可以通过代码去调整使定时器准确: 55 | 56 | ```javascript 57 | let period = 60 * 1000 * 60 * 2 58 | let startTime = new Date().getTime() 59 | let count = 0 60 | let end = new Date().getTime() + period 61 | let interval = 1000 62 | let currentInterval = interval 63 | 64 | function loop() { 65 | count++ 66 | // 代码执行所消耗的时间 67 | let offset = new Date().getTime() - (startTime + count * interval); 68 | let diff = end - new Date().getTime() 69 | //分钟 70 | let h = Math.floor(diff / (60 * 1000 * 60)) 71 | let hdiff = diff % (60 * 1000 * 60) 72 | //秒 73 | let m = Math.floor(hdiff / (60 * 1000)) 74 | let mdiff = hdiff % (60 * 1000) 75 | //毫秒 76 | let s = mdiff / (1000) 77 | let sCeil = Math.ceil(s) 78 | let sFloor = Math.floor(s) 79 | // 得到下一次循环所消耗的时间 80 | currentInterval = interval - offset 81 | console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行时间:'+offset, '下次循环间隔'+currentInterval) 82 | 83 | setTimeout(loop, currentInterval) 84 | } 85 | 86 | setTimeout(loop, currentInterval) 87 | ``` 88 | 89 | ### 修改setInterval 90 | 91 | 如果有循环定时器的需求,其实完全可以通过 requestAnimationFrame来实现 92 | 93 | ```javascript 94 | function setInterval(callback, interval) { 95 | let timer 96 | const now = Date.now 97 | let startTime = now() 98 | let endTime = startTime 99 | const loop = () => { 100 | timer = window.requestAnimationFrame(loop) 101 | endTime = now() 102 | if (endTime - startTime >= interval) { 103 | startTime = endTime = now() 104 | callback(timer) 105 | } 106 | } 107 | timer = window.requestAnimationFrame(loop) 108 | return timer 109 | } 110 | 111 | let a = 0 112 | setInterval(timer => { 113 | console.log(1) 114 | a++ 115 | if (a === 3) cancelAnimationFrame(timer) 116 | }, 1000) 117 | ``` 118 | 119 | requestAnimationFrame自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题 120 | 121 | ### requestAnimationFrame 兼容IE 122 | 123 | ```javascript 124 | if(!window.requestAnimationFrame){ 125 | var lastTime = 0; 126 | window.requestAnimationFrame = function(callback){ 127 | var currTime = new Date().getTime(); 128 | var timeToCall = Math.max(0,16.7-(currTime - lastTime)); 129 | var id = window.setTimeout(function(){ 130 | callback(currTime + timeToCall); 131 | },timeToCall); 132 | lastTime = currTime + timeToCall; 133 | return id; 134 | } 135 | } 136 | ``` 137 | 138 | ```javascript 139 | if (!window.cancelAnimationFrame) { 140 | window.cancelAnimationFrame = function(id) { 141 | clearTimeout(id); 142 | }; 143 | } 144 | ``` 145 | 146 | -------------------------------------------------------------------------------- /Javascript/异步.md: -------------------------------------------------------------------------------- 1 | ## 并发与并行的区别 2 | 3 | 并发是宏观概念,比如有任务A和B,在一定时间内通过人物间的切换完成了两个任务,这种情况下叫并发。 4 | 5 | 并行是微观概念,假设CPU存在两个核心,就可以同时完成任务A和B。同时完成多个任务的情况称为并行。 6 | 7 | ## 回调函数 8 | 9 | 戳[这里](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/CallBack.md) 10 | 11 | ## 异步操作的对比 12 | 13 | 例如:实现获取用户信息 14 | 15 | 先写一个获取用户的方法 16 | 17 | ```javascript 18 | function fetchUser() { 19 | return new Promise((resolve, reject) => { 20 | fetch('https://api.github.com/users/yiichitty') 21 | .then((data) => { 22 | resolve(data.json()); 23 | }, (error) => { 24 | reject(error); 25 | }); 26 | }); 27 | ``` 28 | 29 | 1.使用Promise 30 | 31 | ```javascript 32 | function getUserByPromise() { 33 | fetchUser() 34 | .then((data) => { 35 | console.log(data); 36 | }, (error) => { 37 | console.log(error); 38 | }) 39 | } 40 | ``` 41 | 42 | Promise 的方式虽然解决了回调地狱的问题,但是如果处理流程复杂的话,整段代码将充满 then()。 43 | 44 | 它的语义化不明显,代码流程不能很好的表示执行流程。 45 | 46 | 关于promise的深入理解可以戳[这里](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/Promise.md) 47 | 48 | 2.使用Generator 49 | 50 | ```javascript 51 | function* fetchUserByGenerator() { 52 | const user = yield fetchUser(); 53 | return user; 54 | } 55 | 56 | const g = fetchUserByGenerator(); 57 | const result = g.next().value; 58 | result.then((v) => { 59 | console.log(v); 60 | }, (error) => { 61 | console.log(error); 62 | }) 63 | ``` 64 | 65 | Generator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过next()的方式去执行。 66 | 67 | 关于Generator的深入理解可以戳[这里]([https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Generator.md](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/深入理解Generator.md)) 68 | 69 | 3.async 70 | 71 | ```javascript 72 | async function getUserByAsync(){ 73 | let user = await fetchUser(); 74 | return user; 75 | } 76 | getUserByAsync() 77 | .then(v => console.log(v)); 78 | ``` 79 | 80 | async函数完美的解决了上面两种方式的问题。流程清晰,直观、语义明显。操作异步流程就如同操作同步流程。同时async函数自带执行器,执行的时候无需手动加载。 81 | 82 | 关于async的深入理解可以戳[这里]([https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/async%E5%92%8Cawait%E8%AF%A6%E8%A7%A3.md](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/async和await详解.md)) 83 | 84 | ## 定时器 85 | 86 | 戳[这里](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/定时器.md) 87 | 88 | -------------------------------------------------------------------------------- /Javascript/数组API.md: -------------------------------------------------------------------------------- 1 | ## 数组API 2 | 3 | 写这个总结是因为做算法题的时候,数据基本是用到最多的数据结构了,不管是栈还是队列其实都用数组实现的。今天做算法题脑子不清醒,写数组操作把map写成了forEach,导致结果怎么调试都不对。debug了半天才发现,嗷,误用了API。 4 | 5 | 不熟悉API是做算法题的大忌,所以趁此机会好好整理一下,让脑子更加清醒一点 6 | 7 | 8 | 9 | 将数组API大致分为3类: 10 | 11 | - 直接修改原数组 12 | - 原数组不变,返回一个新的数组 13 | - 数组遍历方法 14 | 15 | 16 | 17 | ### 直接修改原数组的 18 | 19 | - **pop();** 20 | 21 | 删除数组中最后一个元素,减少数组length值,并且移除的这个元素。 22 | 23 | - **push();** 24 | 25 | 添加一个或多个元素到数组末尾,逐个加入,并且返回数组新长度。 26 | 27 | - **shift();** 28 | 29 | 删除数组的第一个元素,并返回删除的这个元素;如果数组为空,返回undefined。 30 | 31 | - **unshift();** 32 | 33 | 在数组开始处插入一些元素,并返回数组新长度。 34 | 35 | - **splice()** 36 | 37 | 从数组中添加/删除一些元素,然后返回被删除的元素 38 | 39 | 删除:删除的第一项位置 要删除的项数 40 | 41 | ```js 42 | splice(0,2)//删除前两项 43 | ``` 44 | 45 | 插入:起始位置 0 要插入的项 46 | 47 | ```js 48 | splice(2,0,4,6)//从位置2插入4,6 49 | ``` 50 | 51 | 替换 : 起始位置 要删除的项数 要插入的东西 52 | 53 | ```js 54 | splice(2,1,4,5)//删除位置2的一项插入4,6 55 | ``` 56 | 57 | splice方法始终返回一个数组,数组包含原始数组中删除的项,没有就返回一个空数组。 58 | 59 | - **reverse()** 60 | 61 | 颠倒数组中元素的顺序 62 | 63 | - **sort()** 64 | 65 | 数组排序 默认按升序排列(最小在前,最大在后) 66 | 67 | 在排序时,sort()会调用每个数组项的toString(),然后比较得到的字符串,即使每一项都是数值,sort()比较的也是字符串。 68 | 69 | 所以: 70 | 71 | ```js 72 | [11,24,41,3].sort();//[11,24,3,41] 73 | ``` 74 | 75 | sort()可以接受一个比较函数作为参数,比较函数接受两个参数,如果第一个参数应该位于第二个之前则返回一个负数;如果第一个参数位于第二个之后应该返回一个正数。 76 | 77 | 升序: 78 | 79 | ```js 80 | arr.sort((a,b)=>a-b) 81 | ``` 82 | 83 | 降序: 84 | 85 | ```js 86 | arr.sort((a,b)=>b-a) 87 | ``` 88 | 89 | - **fill() --ES6新增** 90 | 91 | 将数组制定区间内的元素替换为某个值: 92 | 93 | ```js 94 | var arr=[1,2,3,4] 95 | ``` 96 | 97 | 功能1:字面意思填满 98 | 99 | ```js 100 | arr.fill(5) //[5,5,5,5] 101 | ``` 102 | 103 | 功能2:指定范围替换 第一个参数替换值,第二个起始位置,第三个结束位置(不包含) 104 | 105 | ```js 106 | arr.fill(6,1,3)//[1,6,6,4] 107 | ``` 108 | 109 | 功能3:扩展对象 110 | 111 | ```js 112 | [].fill.call({length:3},4) //{0:4,1:4,2:4} 113 | ``` 114 | 115 | - **copyWithin()--ES6新增** 116 | 117 | 数组内元素之间的替换,但替换的不变。 118 | 119 | 参数三个,被替换的其实位置, 选取替换值的起始位置,选取替换值的结束位置(不包含) 120 | 121 | ```javascript 122 | var arr=[1,'c','d','a','b']//把ab放到0-1的位置 123 | 124 | arr.copyWithin(0,3,5)//['a','b','d','a','b'] 125 | ``` 126 | 127 | 128 | 129 | ### 返回新数组的API 130 | 131 | - **concat()** 132 | 133 | 将传入的数组或元素与原数组合并,组合成一个新数组返回。 134 | 135 | 这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat()方法传递参数的情况下,它只是复制当前数组并返回副本。 136 | 137 | ```js 138 | var arr = [1,3,5,7]; 139 | var arrCopy = arr.concat(9,[11,13]); 140 | console.log(arrCopy); //[1, 3, 5, 7, 9, 11, 13] 141 | console.log(arr); // [1, 3, 5, 7](原数组未被修改) 142 | ``` 143 | 144 | 所以,如果传入的不是数组,则直接把参数添加到数组后面,如果传入的是数组,则将数组中的各个项添加到数组中。但是如果传入的是一个二维数组呢? 145 | 146 | ```js 147 | var arrCopy2 = arr.concat([9,[11,13]]); 148 | console.log(arrCopy2); //[1, 3, 5, 7, 9, Array[2]] 149 | console.log(arrCopy2[5]); //[11, 13] 150 | ``` 151 | 152 | 上述代码中,arrCopy2数组的第五项是一个包含两项的数组,也就是说concat方法只能将传入数组中的每一项添加到数组中,如果传入数组中有些项是数组,那么也会把这一数组项当作一项添加到arrCopy2中。 153 | 154 | - **slice()** 155 | 156 | 返回从原数组中指定开始下标到结束下标之间的项组成的新数组。 157 | 158 | 在只有一个参数的情况下, slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。 159 | 160 | 如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。 161 | 162 | ```js 163 | var arr = [1,3,5,7,9,11]; 164 | var arrCopy = arr.slice(1); 165 | var arrCopy2 = arr.slice(1,4); 166 | var arrCopy3 = arr.slice(1,-2); 167 | var arrCopy4 = arr.slice(-4,-1); 168 | console.log(arr); //[1, 3, 5, 7, 9, 11](原数组不变) 169 | console.log(arrCopy); //[3, 5, 7, 9, 11] 170 | console.log(arrCopy2); //[3, 5, 7] 171 | console.log(arrCopy3); //[3, 5, 7] 172 | console.log(arrCopy4); //[5, 7, 9] 173 | ``` 174 | 175 | - **join()** 176 | 177 | 将数组中所有的元素连接成一个字符串。 178 | 179 | 默认逗号分隔,只接收一个参数:分隔符 180 | 181 | - **indexOf()** 182 | 183 | 接收两个参数,要查找的项和查找起点索引 184 | 185 | 用于查找在数组中第一次出现时的索引,如果没有就返回-1 186 | 187 | - **lastindexOf()** 188 | 189 | 接收两个参数,要查找的项和查找起点索引 190 | 191 | 用于从数组末尾查找第一次出现时的索引,如果没有就返回-1 192 | 193 | - **includes()**--ES7新增 194 | 195 | 判断当前数组是否包含某个指定的值,如果有返回true,否则false 196 | 197 | ```js 198 | [1, 2, 3, 4, 5].includes(4) //true 199 | [1, 2, 3, 4, NaN].includes(NaN) //true 200 | ``` 201 | 202 | - **toSource()** 203 | 204 | 返回数组的源代码,目前只有火狐实现了. 205 | 206 | 207 | 208 | 209 | 210 | ### 数组遍历方法 211 | 212 | - **forEach()** 213 | 214 | 每一项元素都执行一次传入的函数,**返回值undefined**。 215 | 216 | 参数都是function类型,默认有传参,参数分别为:遍历的数组内容;对应的数组索引,数组本身。 217 | 218 | - **map()** 219 | 220 | 遍历数组,使用传入函数处理每个元素,并**返回函数的返回值组成的新数组** 221 | 222 | - **filter()** 223 | 224 | 使用传入的函数测试所有元素,并**返回所有通过测试的元素**组成的新数组。 225 | 226 | ```js 227 | var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 228 | var arr2 = arr.filter((item, index) => { 229 | return index % 3 === 0 || item >= 8; 230 | }); 231 | console.log(arr2); //[1, 4, 7, 8, 9, 10] 232 | ``` 233 | 234 | - **every()** 235 | 236 | 使用传入的函数测试所有元素,只有所有项都满足条件,才会返回true。**验证是否每一个元素都满足的测试函数**! 237 | 238 | - **some()** 239 | 240 | 使用传入的函数测试所有元素,只要有一个元素满足条件就返回true。**验证是否有元素都满足测试函数** 241 | 242 | - **reduce()** 243 | 244 | 接收一个方法,作为累加器,从数组的第一项开始,逐个遍历到最后,最终返回一个值。 245 | 246 | 接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。 247 | 248 | 调用的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。 249 | 250 | - **reduceRight()** 251 | 252 | 接收一个方法,作为累加器,从数组的最后一项开始,向前遍历到第一项,最终返回一个值。 253 | 254 | 参数和调用函数参数同reduce。 255 | 256 | ```javascript 257 | var values = [1,2,3,4,5]; 258 | var sum = values.reduceRight((prev, cur, index, array) => { 259 | return prev + cur; 260 | },10); 261 | console.log(sum); //25 262 | //初始值是10,然后累加,得到25 263 | ``` 264 | 265 | - **find()**--ES6新增 266 | 267 | 返回数组中第一个满足条件的元素,没有返回-1。 268 | 269 | ```js 270 | [1, 2, 3, 4, 5].find((item) => {return item > 3}) //4 271 | ``` 272 | 273 | - **findIndex()**--ES6新增 274 | 275 | 返回数组中的满足条件元素的索引,没有就返回-1。 276 | 277 | - **keys()**--ES6新增 278 | - **values**--ES6新增 279 | - **entries**--ES6新增 280 | 281 | keys()返回一个数组索引的迭代器;values返回一个数组迭代器对象,包含数组中每个索引的值。entries()返回一个数组迭代器对象,该对象包含数组中每个索引的键值对。 282 | 283 | ```js 284 | var arr=['a', 'b', 'c'] 285 | for(let key of arr.keys()){ 286 | console.log(key)//0,1,2 287 | } 288 | for(let value of arr.values()){ 289 | console.log(value)//a,b,c 290 | } 291 | for(let [key, value] of arr.entries()){ 292 | console.log([key,value])//[0,'a'],[1,'b'],[2,'c'] 293 | } 294 | ``` 295 | 296 | 297 | 298 | ### 其他 299 | 300 | - Array.form() 301 | 302 | 把一些集合或者长得像数组的伪数组转化为真的数组,比如arguments,ES6的Set,js选择器找到的dom集合,以及一些对象模拟的数组。 303 | 304 | ```js 305 | Array.from(obj / arguments / 伪数组) //返回的是一个数组 306 | [].slice.call(arguments, 0) //这种方式根from方法是一样的效果 307 | 308 | //Array.from还有第二个参数,是一个回掉函数,功能类似map 309 | Array.from( [1, 2, 3], item => item * 2 )//[2,4,6] 310 | ``` 311 | 312 | - Array.of() 313 | 314 | 把参数合并成一个数组返回,如果参数为空,则返回一个空数组 315 | 316 | ```js 317 | Array.of(1,2,3,4,5);//[1,2,3,4,5] 318 | ``` 319 | 320 | -------------------------------------------------------------------------------- /Javascript/防抖节流.md: -------------------------------------------------------------------------------- 1 | ## 防抖 2 | 3 | 在开发中,经常会遇到这样的情况:在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。 4 | 5 | 这些需求都可以通过函数防抖动来实现。尤其是第一个需求,如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。 6 | 7 | PS:防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的 情况会每隔一定时间(参数wait)调用函数。 8 | 9 | 我们先来看一个袖珍版的防抖理解一下防抖的实现: 10 | 11 | ```javascript 12 | const debounce = (func, wait = 50) => { 13 | // 缓存一个定时器id 14 | let timer = 0 15 | // 这里返回的函数是每次用户实际调用的防抖函数 16 | // 如果已经设定过定时器了就清空上一次的定时器,开始一个新的定时器,延迟执行用户传入的方法 17 | return function(...args) { 18 | if (timer) clearTimeout(timer) 19 | timer = setTimeout(() => { 20 | func.apply(this, args) 21 | }, wait) 22 | } 23 | } 24 | // 不难看出如果用户调用该函数的间隔小于wait的情况下,上一次的时间还未到就被清除了,并不会执行函数 25 | ``` 26 | 27 | 实际使用举栗: 28 | 29 | ```javascript 30 | //延迟调用 31 | const debounce = (func, wait) => { 32 | let timer = 0 33 | return function(...args) { 34 | if (timer) clearTimeout(timer) 35 | timer = setTimeout(() => { 36 | func.apply(this, args) 37 | }, wait) 38 | } 39 | } 40 | 41 | //使用 42 | let count = 0; 43 | let myFunc = function() { 44 | console.log('第' + count + '次调用'); 45 | count++; 46 | }; 47 | window.test=debounce(myFunc,5000); 48 | 49 | //调用test 50 | test(); 51 | //5秒内一直调用函数不会触发,停止触发之后等待5秒才出来。 52 | //一遍用于搜索引擎调用搜索 53 | ``` 54 | 55 | 这是一个简单版的防抖,但是有缺陷,这个防抖只能在最后调用。一般的防抖会有immediate选项,表示是否立即调用。 56 | 57 | 这两者的区别,举个栗子来说: 58 | 59 | - 例如在搜索引擎搜索问题的时候,我们当然是希望用户输入完最后一个字才调用查询接口,这个时候适用**延迟执行**的防抖函数,它总是在一连串(间隔小于wait的)函数触发之后调用。 60 | - 例如用户在github上点star的时候,我们希望用户点第一下的时候就去调用接口,并且成功之后改变star按钮的样子,用户就可以立马得到反馈是否star成功了,这个情况适用**立即执行**的防抖函数,它总是在第一次调用,并且下一次调用必须与前一次调用的时间间隔大于wait才会触发。 61 | 62 | 下面我们来实现一个带有立即执行选项的防抖函数: 63 | 64 | ```javascript 65 | /** 66 | * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 67 | * 68 | * @param {function} func 回调函数 69 | * @param {number} wait 表示时间窗口的间隔 70 | * @param {boolean} immediate 设置为ture时,是否立即调用函数 71 | * @return {function} 返回客户调用函数 72 | */ 73 | function debounce (func, wait = 50, immediate = true) { 74 | let timer, context, args 75 | 76 | // 延迟执行函数 77 | const later = () => setTimeout(() => { 78 | // 延迟函数执行完毕,清空缓存的定时器序号 79 | timer = null 80 | // 延迟执行的情况下,函数会在延迟函数中执行 81 | // 使用到之前缓存的参数和上下文 82 | if (!immediate) { 83 | func.apply(context, args) 84 | context = args = null 85 | } 86 | }, wait) 87 | 88 | // 这里返回的函数是每次实际调用的函数 89 | return function(...params) { 90 | if (!timer) { 91 | // 如果没有创建延迟执行函数(later),就创建一个 92 | timer = later() 93 | // 如果是立即执行,调用函数 94 | // 否则缓存参数和调用上下文 95 | if (immediate) { 96 | func.apply(this, params) 97 | } else { 98 | context = this 99 | args = params 100 | } 101 | // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个 102 | // 这样延迟函数会重新计时 103 | } else { 104 | clearTimeout(timer) 105 | timer = later() 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | 实际使用举栗: 112 | 113 | ```javascript 114 | //立即执行的例子 115 | function debounce (func, wait, immediate) { 116 | let timer, context, args 117 | 118 | 119 | const later = () => setTimeout(() => { 120 | timer = null 121 | if (!immediate) { 122 | func.apply(context, args) 123 | context = args = null 124 | } 125 | }, wait) 126 | 127 | return function(...params) { 128 | if (!timer) { 129 | timer = later() 130 | if (immediate) { 131 | func.apply(this, params) 132 | } else { 133 | context = this 134 | args = params 135 | } 136 | } else { 137 | clearTimeout(timer) 138 | timer = later() 139 | } 140 | } 141 | } 142 | 143 | //使用 144 | let count=0; 145 | let myFunc = function() { 146 | console.log('第' + count + '次调用'); 147 | count++; 148 | }; 149 | window.test=debounce(myFunc,5000,true); 150 | 151 | //调用test 152 | test() 153 | //首次调用触发一次,之后5秒内无论怎么调用都无效,等5秒后即可再次触发 154 | //一般用于按钮点击 155 | ``` 156 | 157 | 总结: 158 | 159 | - 对于按钮防点击来说的实现:如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦开始一个定时器,只要定时器还在,每次点击都重新计时。一旦点累了,定时器时间到,定时器重置为 `null`,就可以再次点击了。 160 | - 对于延时执行函数来说的实现:清除定时器ID,如果是延迟调用就调用函数 161 | 162 | ## 节流 163 | 164 | 防抖动和节流本质是不一样的。 165 | 166 | 防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。 167 | 168 | ```javascript 169 | function throttle(func,wait){ 170 | let last; 171 | return function(...args){ 172 | let now=+new Date(); 173 | if(!last||now>last+wait){ 174 | last=now; 175 | func.apply(this,args); 176 | } 177 | } 178 | } 179 | //使用 180 | let count=0; 181 | function myFunc(){ 182 | console.log('第'+count+'调用'); 183 | count++; 184 | } 185 | window.onresize=throttle(myFunc,2000); 186 | //调用 187 | onresize(); 188 | //后面不管调用多少次,它始终是上次调用到2s后才执行。 189 | ``` 190 | 191 | 高级版本的设置:添加上了是否需要忽略开始函数的调用/结尾函数的调用。 192 | 193 | ```javascript 194 | // 这个是用来获取当前时间戳的 195 | function now() { 196 | return +new Date() 197 | } 198 | /** 199 | * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait 200 | * 201 | * @param {function} func 回调函数 202 | * @param {number} wait 表示时间窗口的间隔 203 | * @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。 204 | * 如果想忽略结尾函数的调用,传入{trailing: false} 205 | * 两者不能共存,否则函数不能执行 206 | * @return {function} 返回客户调用函数 207 | */ 208 | _.throttle = function(func, wait, options) { 209 | var context, args, result; 210 | var timeout = null; 211 | // 之前的时间戳 212 | var previous = 0; 213 | // 如果 options 没传则设为空对象 214 | if (!options) options = {}; 215 | // 定时器回调函数 216 | var later = function() { 217 | // 如果设置了 leading,就将 previous 设为 0 218 | // 用于下面函数的第一个 if 判断 219 | previous = options.leading === false ? 0 : _.now(); 220 | // 置空一是为了防止内存泄漏,二是为了下面的定时器判断 221 | timeout = null; 222 | result = func.apply(context, args); 223 | if (!timeout) context = args = null; 224 | }; 225 | return function() { 226 | // 获得当前时间戳 227 | var now = _.now(); 228 | // 首次进入前者肯定为 true 229 | // 如果需要第一次不执行函数 230 | // 就将上次时间戳设为当前的 231 | // 这样在接下来计算 remaining 的值时会大于0 232 | if (!previous && options.leading === false) previous = now; 233 | // 计算剩余时间 234 | var remaining = wait - (now - previous); 235 | context = this; 236 | args = arguments; 237 | // 如果当前调用已经大于上次调用时间 + wait 238 | // 或者用户手动调了时间 239 | // 如果设置了 trailing,只会进入这个条件 240 | // 如果没有设置 leading,那么第一次会进入这个条件 241 | // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了 242 | // 其实还是会进入的,因为定时器的延时 243 | // 并不是准确的时间,很可能你设置了2秒 244 | // 但是他需要2.2秒才触发,这时候就会进入这个条件 245 | if (remaining <= 0 || remaining > wait) { 246 | // 如果存在定时器就清理掉否则会调用二次回调 247 | if (timeout) { 248 | clearTimeout(timeout); 249 | timeout = null; 250 | } 251 | previous = now; 252 | result = func.apply(context, args); 253 | if (!timeout) context = args = null; 254 | } else if (!timeout && options.trailing !== false) { 255 | // 判断是否设置了定时器和 trailing 256 | // 没有的话就开启一个定时器 257 | // 并且不能不能同时设置 leading 和 trailing 258 | timeout = setTimeout(later, remaining); 259 | } 260 | return result; 261 | }; 262 | }; 263 | ``` 264 | 265 | -------------------------------------------------------------------------------- /MVVM/MVVM.md: -------------------------------------------------------------------------------- 1 | ## MVC 2 | 3 |  4 | 5 | M(Model):数据保存 6 | 7 | V(View):用户页面 8 | 9 | C(Controller):业务逻辑 10 | 11 | 所有通信都是单向的。 12 | 13 | 1. View传指令到Controller。 14 | 2. Controller完成业务逻辑后,要求Model改变状态。 15 | 3. Model将新的数据发送到View,用户得到反馈。 16 | 17 | ## MVP 18 | 19 |  20 | 21 | M(Model)是业务逻辑层,主要负责数据,网络请求等操作 22 | 23 | V(View)是视图层,负责绘制UI元素、与用户进行交互 24 | 25 | P(Presenter)是View与Model交互的中间纽带,处理与用户交互的逻辑 26 | 27 | MVP模式将Controller改名为Presenter,同时改变了通信方向。 28 | 29 | 1. 各部分之间的通信,都是双向的。 30 | 2. View与Model不发生联系,都通过Presenter传递。 31 | 3. View非常薄,不部署任何业务逻辑,称为“被动视图”,即没有任何主动性,而Presenter非常厚,所有业务逻辑都部署在那里。 32 | 33 | ## MVVM 34 | 35 |  36 | 37 | MVVM是`Model-View-ViewModel`的缩写。MVVM是一种设计思想。Model层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View代表UI组件,它负责将数据模型转换成UI展现出来,ViewModel是一个同步View和Model的对象。 38 | 39 | 在MVVM架构下,View和Model之间并没有直接的联系,而是通过ViewModel进行交互,Model和ViewModel之间的交互是双向的,因此View数据的变化会同步到Model中,而Model数据的变化也会立即反应到View上。 40 | 41 | ViewModel通过**双向数据绑定**把View层和Model层连接了起来,而View和Model之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由MVVM来统一管理。 42 | 43 | 对于 MVVM 来说,其实最重要的并不是通过双向绑定或者其他的方式将 View 与 ViewModel 绑定起来,**而是通过 ViewModel 将视图中的状态和用户的行为分离出一个抽象,这才是 MVVM 的精髓**。 44 | 45 | ## MVVM和MVC的区别? 46 | 47 | MVC也是一种设计思想,主要就是MVC中的Controlled演变成MVVM中的ViewModel。MVVM主要解决了MVC中大量的DOM操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当Model频繁发送变化,开发者需要主动更新到View。 48 | 49 | ## 数据的双向绑定 50 | 51 |  52 | 53 | 利用`Object.defineProperty()`对数据进行劫持,设置一个监听器`Observer`,用来监听所有属性,如果属性上发生变化了,就需要告诉订阅者`Watcher`去更新数据,最后指令解析器`Compile`解析对应的指令,进而会执行对应的更新函数,从而更新视图,实现双向绑定。 54 | 55 | ## 虚拟DOM 56 | 57 | 操作 DOM 是很慢的,原因已经在浏览器渲染部分说过了。相较于 DOM 来说,操作 JS 对象会快很多,并且我们也可以通过 JS 来模拟 DOM 58 | 59 | ```js 60 | const ul = { 61 | tag: 'ul', 62 | props: { 63 | class: 'list' 64 | }, 65 | children: { 66 | tag: 'li', 67 | children: '1' 68 | } 69 | } 70 | ``` 71 | 72 | 上述代码对应的 DOM 就是 73 | 74 | ```html 75 | 76 | 1 77 | 78 | ``` 79 | 80 | 那么既然 DOM 可以通过 JS 对象来模拟,反之也可以通过 JS 对象来渲染出对应的 DOM。 81 | 82 | 当然了,通过 JS 来模拟 DOM 并且渲染对应的 DOM 只是第一步,难点在于如何判断新旧两个 JS 对象的**最小差异**并且实现**局部更新** DOM。 83 | 84 | 首先 DOM 是一个多叉树的结构,如果需要完整的对比两颗树的差异,那么需要的时间复杂度会是 O(n ^ 3),这个复杂度肯定是不能接受的。于是 React 团队优化了算法,实现了 O(n) 的复杂度来对比差异。 实现 O(n) 复杂度的关键就是只对比同层的节点,而不是跨层对比,这也是考虑到在实际业务中很少会去跨层的移动 DOM 元素。 所以判断差异的算法就分为了两步 85 | 86 | - 首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后渲染差异 87 | - 一旦节点有子元素,就去判断子元素是否有不同 88 | 89 | 在第一步算法中,需要判断新旧节点的 `tagName` 是否相同,如果不相同的话就代表节点被替换了。如果没有更改 `tagName` 的话,就需要判断是否有子元素,有的话就进行第二步算法。 90 | 91 | 在第二步算法中,需要判断原本的列表中是否有节点被移除,在新的列表中需要判断是否有新的节点加入,还需要判断节点是否有移动。 92 | 93 | 举个例子来说,假设页面中只有一个列表,我们对列表中的元素进行了变更 94 | 95 | ```js 96 | // 假设这里模拟一个 ul,其中包含了 5 个 li 97 | [1, 2, 3, 4, 5] 98 | // 这里替换上面的 li 99 | [1, 2, 5, 4] 100 | ``` 101 | 102 | 从上述例子中,我们一眼就可以看出先前的 `ul` 中的第三个 `li` 被移除了,四五替换了位置。 103 | 104 | 那么在实际的算法中,我们如何去识别改动的是哪个节点呢?这就引入了 `key` 这个属性。这个属性是用来给每一个节点打标志的,用于判断是否是同一个节点。 105 | 106 | 当然在判断以上差异的过程中,我们还需要判断节点的属性是否有变化等等。 107 | 108 | 当我们判断出以上的差异后,就可以把这些差异记录下来。当对比完两棵树以后,就可以通过差异去局部更新 DOM,实现性能的最优化。 109 | 110 | 111 | 112 | **为什么 Virtual DOM 比原生 DOM 快?** 113 | 114 | 首先这个问题得分场景来说,如果无脑替换所有的 DOM 这种场景来说,Virtual DOM 的局部更新肯定要来的快。但是如果你可以人肉也同样去局部替换 DOM,那么 Virtual DOM 必然没有你直接操作 DOM 来的快,毕竟还有一层 diff 算法的损耗。 115 | 116 | 当然了 Virtual DOM 提高性能是其中一个优势,其实**最大的优势**还是在于: 117 | 118 | 1. 将 Virtual DOM 作为一个兼容层,让我们还能对接非 Web 端的系统,实现跨端开发。 119 | 2. 同样的,通过 Virtual DOM 我们可以渲染到其他的平台,比如实现 SSR、同构渲染等等。 120 | 3. 实现组件的高度抽象化 121 | 122 | 123 | 124 | 125 | 126 | ## 路由原理 127 | 128 | 前端路由的本质是**监听 URL 的变化**,然后匹配路由规则,显示相应的页面,并且无须刷新页面。目前前端使用的路由就只有两种实现方式 129 | 130 | - Hash 模式 131 | - History 模式 132 | 133 | ### Hash 模式 134 | 135 | `www.test.com/#/` 就是 Hash URL,当 `#` 后面的哈希值发生变化时,可以通过 `hashchange` 事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的 URL 请求永远是 `www.test.com`。 136 | 137 | ``` 138 | window.addEventListener('hashchange', () => { 139 | // ... 具体逻辑 140 | }) 141 | ``` 142 | 143 | Hash 模式相对来说更简单,并且兼容性也更好。 144 | 145 | ### History 模式 146 | 147 | History 模式是 HTML5 新推出的功能,主要使用 `history.pushState` 和 `history.replaceState` 改变 URL。 148 | 149 | 通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。 150 | 151 | ```javascript 152 | // 新增历史记录 153 | history.pushState(stateObject, title, URL) 154 | // 替换当前历史记录 155 | history.replaceState(stateObject, title, URL) 156 | ``` 157 | 158 | 当用户做出浏览器动作时,比如点击后退按钮时会触发 `popState` 事件 159 | 160 | ```javascript 161 | window.addEventListener('popstate', e => { 162 | // e.state 就是 pushState(stateObject) 中的 stateObject 163 | console.log(e.state) 164 | }) 165 | ``` 166 | 167 | ### 两种模式对比 168 | 169 | - Hash 模式只可以更改 `#` 后面的内容,History 模式可以通过 API 设置任意的同源 URL 170 | - History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值,也就是字符串 171 | - Hash 模式无需后端配置,并且兼容性好。History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置 `index.html` 页面用于匹配不到静态资源的时候. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FrontEndLearning 2 | 3 | 不忘初心,砥砺前行。 4 | 希望未来你在前端路上探索的每一天,都能如今日一样怦然心动。 5 | 6 | —— 2019.04.09 7 | 8 | 9 | 10 | ## 简介 11 | 12 | 此项目内容为:**前端学习总结和就业准备** 13 | 这里记录了Chitty从7月15日从项目中退出,开始正式为找工作准备的学习与总结,由浅入深,一步步地补齐完善自己的知识树。这里也会更新Chitty每次面试笔试后对自己的反思总结。这里还有对一些有趣问题的思考。 14 | 15 | 近期“补足短板计划”学习内容: 16 | 17 | - CSS3 18 | - CSS在不同浏览器的兼容问题 19 | - 设计模式 20 | - JS对一些数据结构的实现 21 | 22 | 23 | 24 | **学习永无止尽,此项目将会一直更新下去,期待与你的共同进步♪(^∇^*)。** 25 | 目录最后有一个关于我经历的小故事,如果你有兴趣,欢迎阅读,希望能给迷茫的你一点鼓舞。前端路上我们一起加油嗷ヾ(◍°∇°◍)ノ゙! 26 | 27 | 28 | 29 | 30 | LeetCode刷题题目与代码 可以前往另一个repository:[传送门](https://github.com/YiiChitty/leetcode-practice) 31 | 32 | 33 | 34 | ------ 35 | 36 | ## 探索篇目录 37 | 38 | 1. [对获取DOM的N种方法性能的思考](https://github.com/YiiChitty/FrontEndLearning/blob/master/一些思考/关于获取DOM节点的思考.md) 39 | 2. [虚拟DOM真的能提升性能吗?](https://github.com/YiiChitty/FrontEndLearning/blob/master/一些思考/虚拟DOM真的能提升性能吗?.md) 40 | 3. [数组去重有多少种方法?性能如何?](https://github.com/YiiChitty/FrontEndLearning/blob/master/一些思考/两个数组去重有多少种方法?性能如何?.md) 41 | 4. [class里面用箭头函数和普通函数有何区别?](https://github.com/YiiChitty/FrontEndLearning/blob/master/一些思考/Class中箭头函数和普通函数是咋回事?.md) 42 | 43 | 44 | 45 | ------ 46 | 47 | 48 | 49 | ## 学习总结篇目录 50 | 51 | ### 第一部分 HTML 52 | #### [HTML](https://github.com/YiiChitty/FrontEndLearning/blob/master/HTML/HTML.md) 53 | #### [HTML5新特性](https://github.com/YiiChitty/FrontEndLearning/blob/master/HTML/HTML5的新特点.md) 54 | 55 | 56 | ### 第二部分 CSS 57 | #### [css基础](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/CSS.md) 58 | #### [布局](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/布局.md) 59 | 60 | - [七种方式实现三栏布局](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/用7种方式实现三栏布局.md) 61 | - [垂直居中](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/垂直居中.md) 62 | - [水平居中](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/水平居中.md) 63 | - [清除浮动的方法总结](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/清除浮动的方法.md) 64 | - [Flex](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/Flex.md) 65 | - [BFC](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/BFC.md) 66 | - [静态布局&流体布局&响应式布局&弹性布局](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/自适应布局方式.md) 67 | 68 | #### [CSS3新特性](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/CSS3.md) 69 | 70 | #### [CSS模块化](https://github.com/YiiChitty/FrontEndLearning/blob/master/CSS/CSS模块化.md) 71 | 72 | ### 第三部分 JavaScript 73 | #### [Javascript基础](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/JS基础.md) 74 | #### [ES6](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/ES6.md) 75 | #### [BOM](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/Bom.md) 76 | #### [DOM](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/DOM与事件.md) 77 | 78 | #### [常用方法原理与原生实现](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/JS常用方法原理.md) 79 | 80 | #### [防抖节流](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/防抖节流.md) 81 | 82 | #### [异步](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/异步.md) 83 | 84 | - [回调函数](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/CallBack.md) 85 | - [Promise](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/Promise.md) 86 | - [Generator](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/深入理解Generator.md) 87 | - [async/await](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/async和await详解.md) 88 | - [定时器](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/定时器.md) 89 | - [回调地狱解决](https://github.com/YiiChitty/FrontEndLearning/tree/master/Javascript) 90 | #### [EventLoop](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/EventLoop.md) 91 | 92 | #### [垃圾回收机制](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/垃圾回收机制.md) 93 | 94 | #### [前后端数据交互](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/前后端数据交互.md) 95 | 96 | #### [关于Form](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/Form.md) 97 | 98 | #### [关于正则表达](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/正则表达.md) 99 | 100 | ### 第四部分 网络 101 | #### [网络基础](https://github.com/YiiChitty/FrontEndLearning/blob/master/网络/Internet%20Base.md) 102 | #### [TCP与UDP](https://github.com/YiiChitty/FrontEndLearning/blob/master/%E7%BD%91%E7%BB%9C/UDP%20and%20TCP.md) 103 | #### [HTTP](https://github.com/YiiChitty/FrontEndLearning/blob/master/%E7%BD%91%E7%BB%9C/http.md) 104 | #### [从输入URL到页面加载经历了什么](https://github.com/YiiChitty/FrontEndLearning/blob/master/网络/输入URL到页面渲染的整个流程.md) 105 | 106 | ### 第五部分 浏览器 107 | 108 | #### [跨域](https://github.com/YiiChitty/FrontEndLearning/blob/master/浏览器/跨域CORS.md) 109 | 110 | #### [浏览器存储](https://github.com/YiiChitty/FrontEndLearning/blob/master/浏览器/浏览器存储.md) 111 | #### [浏览器渲染](https://github.com/YiiChitty/FrontEndLearning/blob/master/浏览器/浏览器渲染.md) 112 | 113 | 114 | ### 第六部分 MVC、MVP、MVVM 115 | #### [MVC、MVP、MVVM](https://github.com/YiiChitty/FrontEndLearning/blob/master/MVVM/MVVM.md) 116 | 117 | ### 第七部分 前端性能优化 118 | 119 | #### [性能优化](https://github.com/YiiChitty/FrontEndLearning/tree/master/性能优化/性能优化.md) 120 | 121 | ### 第八部分 前端安全 122 | 123 | #### [前端安全与防范](https://github.com/YiiChitty/FrontEndLearning/blob/master/安全/前端的安全防范.md) 124 | 125 | ### 第九部分 设计模式 126 | 127 | 128 | 129 | ### 第十部分 数据结构与算法 130 | 131 | #### [数组API总结](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/数组API.md) 132 | 133 | #### [字符串API总结](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/字符串操作API.md) 134 | 135 | 136 | 137 | ------ 138 | 139 | ## 就业准备篇目录 140 | 141 | [集成版](https://github.com/YiiChitty/FrontEndLearning/blob/master/准备/复习.md) 142 | 143 | ### 基础准备 144 | 145 | [HTML](https://github.com/YiiChitty/FrontEndLearning/blob/master/准备/HTML面试准备.md) 146 | 147 | [CSS](https://github.com/YiiChitty/FrontEndLearning/blob/master/准备/CSS面试准备.md) 148 | 149 | [JavaScript](https://github.com/YiiChitty/FrontEndLearning/blob/master/准备/JS面试准备.md) 150 | 151 | [浏览器网络](https://github.com/YiiChitty/FrontEndLearning/blob/master/准备/浏览器相关问题的准备.md) 152 | 153 | [跨域](https://github.com/YiiChitty/FrontEndLearning/blob/master/准备/跨域面试准备.md) 154 | 155 | ### 性能优化与安全 156 | 157 | [性能优化](https://github.com/YiiChitty/FrontEndLearning/blob/master/准备/性能优化面试准备.md) 158 | 159 | [安全](https://github.com/YiiChitty/FrontEndLearning/blob/master/准备/安全问题的准备.md) 160 | 161 | ### 编程 162 | 163 | [js原生方法实现](https://github.com/YiiChitty/FrontEndLearning/blob/master/手撕代码/Javascript原生实现.md) 164 | 165 | - [JS版版本目录](https://github.com/YiiChitty/FrontEndLearning/tree/master/手撕代码/Javascript原生实现JS版) 166 | 167 | [剑指Offer](https://github.com/YiiChitty/FrontEndLearning/blob/master/手撕代码/剑指offer题目.md) 168 | 169 | [笔试面试中的题目](https://github.com/YiiChitty/FrontEndLearning/blob/master/手撕代码/面试笔试中遇到的题.md) 170 | 171 | ### 面经 172 | 173 | [面经集合](https://github.com/YiiChitty/FrontEndLearning/blob/master/面经/面经.md) 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | ------ 184 | 185 | 186 | 187 | ## 最后,想讲一个故事 188 | 189 | 人生好像终于要进入另一个阶段了,回想过去的很多经历,有过喜悦,有过遗憾,也有过无奈,但终归都过去了,并且这些经历也让我成为了现在的我。想分享一下自己的经历,或许能对你也有所启发。 190 | 191 | 我想要说的是,**一定要坚持自己想要做的事情**,并且**开始自己喜欢的事情,永远都不晚**。 192 | 193 | 和前端的故事最初要追溯到FrontPage时代,当时的微机课期末作业就是自己瞎搞了一个只有图片和文字的页面。大一的时候上了网设课,开始使用了Dreamweaver,开始学会用Html、CSS和简单的javascript。或许是因为有颜色的东西对我天生就有种莫名其妙的吸引力吧,所以我喜欢各种图像,喜欢各种花里胡哨的东西。大一下学期的时候,和小伙伴参加了学院(或者某个技术社团?)主办的网页设计大赛,拿了二等奖。 194 | 195 | 但是,在计算机专业里,前端在那几年似乎一直都是“简单”的代名词,我一直小心珍藏着这份喜爱,直到遇到了一门课——大型软件设计。这门课是在大三的时候开放的,要求做一个稍微完整的系统,当时我们组三个人,选的题目是餐厅预订系统,大致的功能就是可以通过主页面按人数预订座位(比如说3人订4人桌,没有就告诉用户已经订满了),然后后台可以看预订的情况。别的组似乎就是一个人一个功能,从后台写到前端,也没太注重页面。我们组的三个人觉得或许可以按照MVC,一人负责一端,最终决定由我写前端的订餐页面,一个小伙伴写业务逻辑,另一个小伙伴负责数据库和查询。我们各司其职,有条不紊的完成了系统,和别的组比起来,我们效率最高,页面好看,功能也比较完善,满心以为全组都能拿到90+。然而验收的时候,老师问我们分别做了什么工作,小伙伴们好像都没什么问题,直到我,他说:"你就只做了这个?这个页面我花钱随便找个人都能做,你做这个从哪里体现出来你是计算机学院的学生"。这门实验课程,我拿了我所有实验课的最低分,他只给了我70多。他当时说的话给我的打击是非常致命的,我那个时候非常在意别人对我的评价,这个事情就好像成为了我的“魔咒”。我开始反思自己,开始怀疑自己,并且因此消沉了很长的一段时间。(那时候我对这位老师可以说是讨厌至极,不过现在想想,如果没有当初的质疑,还会不会如今日这般坚定呢?所以说嘛,世事无常,每一段经历,不管是好的还是坏的,都还挺值得珍惜的。) 196 | 197 | 大三上学期跟着学院的老师做了一段时间的学术研究,发了一篇论文(中文核心,不是High level的)。暑假的时候,听说这个实验室的项目组招人就进去实习了,因为没什么别的特长,所以我人生中第一个比较大的项目做的还是前端,那是一个以表单为核心的Java Web项目,我写了表单模板批量提交和批量下载功能,接触到了当时超流行框架——jQuery。 198 | 199 | 再之后顺利保研,因为对这个实验室的老师和师兄师姐感觉不错,得到结果的第二周就和导师联系了,和他说想要以后都在实验室继续做研究和做项目。我那时候非常怀疑自己,并且对自己完全没有信心,所以和导师聊了很多。导师跟我说,如果你真的没有找到自己的方向,那我会安排你去每个岗位都走一走,等你全部都经历过了,你就能知道自己真正想要的是什么了。特别庆幸我能遇到我的导师,在我非常迷茫的时期,他给了我很多的帮助。 200 | 201 | 在这长达3年的实验室生涯里,导师如他说的那样,让我从一个产品从无到有的顶层设计、需求调研、产品设计、前端后台开发、测试、运维的每个环节都走过,还让我体验了一年多项目经理的管理岗位。我体验过和甲方开会时的紧张,体验过一次又一次修改设计稿,体验过意见不合的争论与妥协,体验过压测效果不好时的崩溃,体验过赶进度凌晨四五点的星光……我差不多把”所有的岗位“都轮流体验了一遍,也因为这些不同的体验,让我终于找到了自己最喜欢也最想要做的事情。这大概就叫做“回归初心”吧。 202 | 203 | 前端这些年的发展真的很快,我很欣慰看到这样的发展前景,但我的技术还停留在3年前,相比于其他人来说,没有什么优势。但我觉得这并不算晚,我已经想好了我要做什么,并且我也下定了决定一定要坚持做下去,不管别人说什么。 204 | 205 | 做这个决定是在今年4月份,我看了半个月左右的红宝书(当时项目上线,只能晚上看,也没有看完),5-6月份一遍完成项目交接,一边写完了小论文。小论文投了,交接结束了,7月15日正式开始为找工作作准备。我在一点点深入学习之后,就能感觉到大佬们真的是在改变世界。比如说,从我常用的回调函数,到后面的promise,再到generator,再到async,我摸索着发展的足迹,惊叹“啊,原来可以这样”,我在钦佩的同时,也非常渴望未来能够成为其中的一员。也许在那时候,我就能有一点点的勇气打破”封印“我的”魔咒“,回到当初的那天,然后告诉他,我的确是计算机学院的,我是个前端,我很开心也很庆幸我是个前端。 206 | 207 | 在过去所有的工作中,每当我遇到问题的时候,通过检索相关的博客,总能受到很多来自陌生人的“帮助”,也总是能给我遇到的问题提供思路或者解决方案。因为自己受益匪浅,所以也想整理一下自己的东西分享给别人,或许在某一天能够给一些可爱的陌生人一些帮助。 208 | 209 | 虽然有小鸡汤文的感觉(也不太符合”程序员“的形象了吧…),但如果能给阅读到的你一份鼓励或者有些微的触动,那就再好不过了。 210 | 211 | ——2019.07 Chitty 212 | 213 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /img/BFC_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/BFC_01.png -------------------------------------------------------------------------------- /img/BFC_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/BFC_02.png -------------------------------------------------------------------------------- /img/BFC_Box.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/BFC_Box.webp -------------------------------------------------------------------------------- /img/BFC_float.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/BFC_float.webp -------------------------------------------------------------------------------- /img/BFC_rule03.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/BFC_rule03.webp -------------------------------------------------------------------------------- /img/BFC_wordsfloat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/BFC_wordsfloat.webp -------------------------------------------------------------------------------- /img/CROS_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/CROS_01.png -------------------------------------------------------------------------------- /img/CROS_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/CROS_02.png -------------------------------------------------------------------------------- /img/Cache_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/Cache_01.jpg -------------------------------------------------------------------------------- /img/Effective_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/Effective_01.jpg -------------------------------------------------------------------------------- /img/EventLoop_01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/EventLoop_01.gif -------------------------------------------------------------------------------- /img/EventLoop_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/EventLoop_02.jpg -------------------------------------------------------------------------------- /img/EventLoop_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/EventLoop_03.jpg -------------------------------------------------------------------------------- /img/FontandBack_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/FontandBack_01.jpg -------------------------------------------------------------------------------- /img/FontandBack_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/FontandBack_02.jpg -------------------------------------------------------------------------------- /img/FontandBack_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/FontandBack_03.jpg -------------------------------------------------------------------------------- /img/JSBase_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/JSBase_01.jpg -------------------------------------------------------------------------------- /img/MVVM_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/MVVM_01.png -------------------------------------------------------------------------------- /img/MVVM_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/MVVM_02.png -------------------------------------------------------------------------------- /img/MVVM_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/MVVM_03.png -------------------------------------------------------------------------------- /img/MVVM_04.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/MVVM_04.webp -------------------------------------------------------------------------------- /img/Promise_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/Promise_01.png -------------------------------------------------------------------------------- /img/Render_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/Render_01.jpg -------------------------------------------------------------------------------- /img/Sort_heapSort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/Sort_heapSort.gif -------------------------------------------------------------------------------- /img/Sort_mergeSort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/Sort_mergeSort.gif -------------------------------------------------------------------------------- /img/Sort_shellSort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/Sort_shellSort.gif -------------------------------------------------------------------------------- /img/callback_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/callback_01.png -------------------------------------------------------------------------------- /img/callback_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/callback_02.png -------------------------------------------------------------------------------- /img/css_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/css_01.png -------------------------------------------------------------------------------- /img/css_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/css_02.png -------------------------------------------------------------------------------- /img/css_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/css_03.jpg -------------------------------------------------------------------------------- /img/css_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/css_04.jpg -------------------------------------------------------------------------------- /img/css_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/css_05.jpg -------------------------------------------------------------------------------- /img/http_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/http_01.png -------------------------------------------------------------------------------- /img/solution_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/solution_01.png -------------------------------------------------------------------------------- /img/sort_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/img/sort_01.png -------------------------------------------------------------------------------- /一些思考/Class中箭头函数和普通函数是咋回事?.md: -------------------------------------------------------------------------------- 1 | # Class中箭头函数和普通函数是咋回事? 2 | 3 | 今天看到了一个有趣的面经: 4 | 5 | > 假如一个Class里面有箭头函数和function, 那么他们在被调用的时候this有什么区别? 6 | 7 | 调用……无非就是new一下,生成一个实例嘛。 8 | 9 | 箭头函数的this指向定义时所在的对象,那么也就是说,如果Class A下有一个箭头函数x,这个函数x内的this会指向A。new的时候,this指向这个对象,也就是A。 10 | 11 | 乍一看好像啥区别啊…… 12 | 13 | 怀着探索的精神我在控制台敲了如下几行代码: 14 | 15 | ```js 16 | class A{ 17 | x=()=>{ 18 | console.log(this,x); 19 | } 20 | y(){ 21 | console.log(this,y); 22 | } 23 | } 24 | //生成一个实例 25 | let a=new A(); 26 | ``` 27 | 28 | 然后执行a的两个方法 29 | 30 | ```js 31 | a.x(); //输出 A{x:f} "x" 32 | a.y();//输出 A{x:f} "y" 33 | ``` 34 | 35 | 36 | 37 | 似乎没什么区别…… 38 | 39 | 点开a这个实例对象,好像有点奇怪? 40 | 41 | ``` 42 | A {x: ƒ} 43 | x: ()=>{ console.log(this,'x'); } 44 | __proto__: 45 | constructor: class A 46 | y: ƒ y() 47 | __proto__: Object 48 | ``` 49 | 50 | 似乎箭头函数x是建在实例上的,而普通函数则在A.prototype。 51 | 52 | 53 | 54 | 所以他们其实相当于: 55 | 56 | ```js 57 | function A(){ 58 | this.x=function(){ 59 | console.log(this,'x'); 60 | } 61 | } 62 | A.prototype.y=function(){ 63 | console.log(this,'y') 64 | } 65 | ``` 66 | 67 | 68 | 69 | 其实,这也很好理解。因为x的this一直被绑定在A上,其实就相当于说是this.x。类似构造函数内的一个值。 70 | 71 | 72 | 73 | 有点意思,那这样看的话,在继承上也是有区别的。 74 | 75 | 先试试普通的: 76 | 77 | ```js 78 | class A{ 79 | print(){ 80 | console.log('a1'); 81 | } 82 | } 83 | 84 | class B extends A{ 85 | print(){ 86 | super.print(); 87 | console.log('b') 88 | } 89 | } 90 | 91 | const b=new B(); 92 | b.print(); 93 | ``` 94 | 95 | 正如 我们所料的那样,输出了a1 和 b。 96 | 97 | 接下来试试箭头函数: 98 | 99 | ```js 100 | class A{ 101 | print=()=>{ 102 | console.log('a2'); 103 | } 104 | } 105 | 106 | class B extends A{ 107 | print(){ 108 | super.print(); 109 | console.log('b') 110 | } 111 | } 112 | 113 | const b=new B(); 114 | b.print(); 115 | ``` 116 | 117 | 输出的结果为a2,没有输出输出b。 118 | 119 | 120 | 121 | **Why???** 122 | 123 | 先从现象看,看看b这个实例是什么东西好了: 124 | 125 | ```js 126 | > b 127 | B {print: ƒ} 128 | print: ()=>{ console.log('a2'); } 129 | __proto__: A 130 | constructor: class B 131 | print: ƒ print() 132 | __proto__: Object 133 | ``` 134 | 135 | 可以看到print(a2)被建在了实例上。 136 | 137 | 当我们调用b.print的时候,在实例上就找到了print方法,这就不必再向上查找了。 138 | 139 | 问题来了,那我们在B里面写的print去哪了呢? 140 | 141 | 按照最开始的探索,多半是在原型上 142 | 143 | ```js 144 | > B.prototype 145 | A {constructor: ƒ, print: ƒ} 146 | constructor: class B 147 | print: ƒ print() 148 | __proto__: Object 149 | ``` 150 | 151 | 是的,没错 =。= 152 | 153 | ```js 154 | > B.prototype.print 155 | ƒ print(){ 156 | super.print(); 157 | console.log('b') 158 | } 159 | ``` 160 | 161 | 162 | 163 | 现象看完了,现在该去找原因了。 164 | 165 | 常规写法中,类的非静态属性都定义在原型对象上。箭头函数定义的方法this指向的是当前创建类的实例对象。所以其实A的内部应该是这样的 166 | 167 | ```js 168 | function A(){ 169 | this.print=function(){ 170 | console.log('a2'); 171 | } 172 | } 173 | ``` 174 | 175 | B继承A,不仅会继承A原型上的属性和方法,还会继承实例上的属性和方法,所以B中应该等价于: 176 | 177 | ```js 178 | function B(){ 179 | this.print=function(){console.log('a2');} 180 | } 181 | B._proto_=A; 182 | B.prototype._proto_===A.prototype; 183 | 184 | B.prototype.print()=function{ 185 | //... 186 | } 187 | ``` 188 | 189 | 所以,当b.print的时候只会输出a2,不会输出b。因为在实例上已经找到了,即使在原型上定了同一个属性,这个属性也不会被访问到,这就叫做“Property shadowing”。 190 | 191 | 192 | 193 | ## 总结 194 | 195 | 在class里用普通函数和箭头函数,虽说this的输出都是这个class,但是要明白,箭头函数是在实例对象上,而普通函数是在原型对象上的。 -------------------------------------------------------------------------------- /一些思考/两个数组去重有多少种方法?性能如何?.md: -------------------------------------------------------------------------------- 1 | # 两个数组去重有多少种方法? 2 | 3 | 这篇思考来自于别人的面经…… 4 | 5 | 问有多少种数组去重的方法,说实话,没想到利用Object的特性是最快的。 6 | 7 | 8 | 9 | ### 1.双重for循环-性能最差,效率最低 10 | 11 | 外层遍历,内层比较。 12 | 13 | ```js 14 | function distinct(a,b){ 15 | let arr.a.concat(b); 16 | for(let i=0,len=arr.length;iarr.indexOf(item)===index) 37 | } 38 | ``` 39 | 40 | ### 3. for...of+includes()--时间和indexOf差不多 41 | 42 | 双重for的升级版,外层用for...of替换,内层循环改用includes。 43 | 44 | 创建一个新数组,乳沟includes返回false,将该元素push进去。 45 | 46 | ```js 47 | function distinct(a,b){ 48 | let arr=a.concat(b); 49 | let result=[]; 50 | for(let i of arr){ 51 | !result.includes(i)&&result.push(i); 52 | } 53 | return result; 54 | } 55 | ``` 56 | 57 | ### 4. Array.sort()--效率比之前的高 58 | 59 | 先排序,然后比较相邻元素,从而排除重复项。 60 | 61 | ```js 62 | function distinct(a,b){ 63 | let arr=a.concat(b); 64 | arr.sort(); 65 | let result=[arr[0]]; 66 | 67 | for(let i=1,len=arr.length;i 问:`getElementById、getElementByName、getElementByTagname、querySelectorAll、querySelector`在性能上有什么差别? 6 | 7 | 8 | 9 | 感觉这个问题问的很有意思。 10 | 11 | 在jquery开发的时候,为了方便,感觉一般都是用`querySelector`+CSS选择器去做。但为了浏览器兼容性,大多数时候都是给一些不好直接getElementByxxx的设置一个ID。 12 | 13 | 好像**从来没有考虑过性能的问题**。 14 | 15 | 起初就是觉得querySelector比较慢因为要先找到选择器的东西,再去取,肯定时间慢,搜了一圈大多都是从实验得出来的结果,很少有人具体去描述原因到底是什么。 16 | 17 | 18 | 19 | 对此问题,我进行了一些探索。 20 | 21 | 下面是我的一些探索过程,对原因进行了一些尝试性地解释,**但请以辨证的眼光去阅读,因为我说的不一定对**…… 22 | 23 | 24 | 25 | 节约各位时间,先说结论吧: 26 | 27 | 1.从性能上说get系列的性能都比query系列好,get系列里面各有差异,这些差异可以结合算法如何遍历搜索去理解,都解释得通。 28 | 29 | 2.`getElementsByTagName`比`querySelectorAll`快的原因在于:`getElementsByTagName`创建的过程不需要做任何操作,只需要返回一个指针即可。而`querySelectorAll`会循环遍历所有的的结果,然后创建一个新的NodeList。 30 | 31 | 3.实际在用的过程中**取决于要获取的是什么**,再进行选择。 32 | 33 | 34 | 35 | 36 | 37 | 探索的过程很长,不过一圈下来,我对这部分内容已经有了一个非常清晰的认识了。如果你有兴趣,也可以继续读下去: 38 | 39 | **1.从问题反思** 40 | 41 | 首先,为了探索这个问题,我在HTML里面写了三个并列的p标签 42 | 43 | ```html 44 | duanluo1 45 | duanluo2 46 | duanluo3 47 | ``` 48 | 49 | 然后直接使用 50 | 51 | ```js 52 | var a=document.getElementById('p1'); 53 | var b=document.getElementsByName('p1'); 54 | var c=document.getElementsByTagName('p'); 55 | var d=document.querySelectorAll('p'); 56 | var e=document.querySelector('#p1'); 57 | 58 | 输出结果是: 59 | a //duanluo1 60 | b //NodeList[p#p1] 0:p#p1 61 | c //HTMLCollection(3)[p#p1,p,p,p1:p#p1,p2:p,p3:p] 62 | d //NodeList(3)[p#p1,p,p] 63 | e //duanluo1 64 | ``` 65 | 66 | 通过上面简单的检测发现,`getElementById`和`querySelector`返回的都是一个节点。`getElementsByName`返回的是一个长度为1的`NodeList`。`getElementsByTagName`返回的是所有p标签的`HTMLCollection`集合。`querySelectorAll`返回的是所有p标签的`NodeList`。 67 | 68 | 那么他们的性能到底哪个更好呢? 69 | 70 | **2.从实践中摸索** 71 | 72 | 实践出真知。 73 | 74 | 我们写一个循环来测试一下。 75 | 76 | ```js 77 | console.time('querySelector'); 78 | for (var i = 0; i < 10000000; i++) { 79 | document.querySelector("#p1"); 80 | } 81 | console.timeEnd('querySelector'); 82 | //11.60205078125ms 83 | ``` 84 | 85 | ```js 86 | console.time('getElementById'); 87 | for (var i = 0; i < 10000000; i++) { 88 | document.getElementById("p1"); 89 | } 90 | console.timeEnd('getElementById'); 91 | //6.268798828125ms 92 | ``` 93 | 94 | 显然,`getElementById`在性能上确实要好很多。 95 | 96 | 接着,我又测试了一下`querySelectorAll`和`getElementsByClassName`。 97 | 98 | ```js 99 | console.time('querySelectorAll'); 100 | for (var i = 0; i < 10000000; i++) { 101 | document.querySelectorAll(".p1"); 102 | } 103 | console.timeEnd('querySelectorAll'); 104 | // 4.939697265625ms 105 | ``` 106 | 107 | ```js 108 | console.time('getElementsByClassName'); 109 | for (var i = 0; i < 10000000; i++) { 110 | document.getElementsByClassName("p1"); 111 | } 112 | console.timeEnd('getElementsByClassName'); 113 | //1.815185546875ms 114 | ``` 115 | 116 | 好像还是get系列的性能更好。 117 | 118 | 再试一下`querySelectorAll`和`getElementsByTagName` 119 | 120 | ```js 121 | console.time('querySelectorAll'); 122 | for (var i = 0; i < 10000000; i++) { 123 | document.querySelectorAll("p"); 124 | } 125 | console.timeEnd('querySelectorAll'); 126 | // 6.180908203125ms 127 | ``` 128 | 129 | ```js 130 | console.time('getElementsByTagName'); 131 | for (var i = 0; i < 10000000; i++) { 132 | document.getElementsByTagName("p"); 133 | } 134 | console.timeEnd('getElementsByTagName'); 135 | //1.714111328125ms 136 | ``` 137 | 138 | **实验一圈下来的结果就是get系列比query系列在性能上确实好很多。** 139 | 140 | 但是**原因不清楚。** 141 | 142 | 143 | 144 | **3.从结果中猜测** 145 | 146 | 本着不懂就问的探索精神,我观测到了两种不同的集合:`NodeList`和`HTMLCollection`。我开始猜想,会不会与这两种不同的集合有关。 147 | 148 | 于是,我搜到了他们的定义:`HTMLCollection`是元素集合,`NodeList`是节点集合。`NodeList`的元素是Node;`HTMLCollection`的元素是Element,**Element继承自Node,是Node的一种**,在HTML中,它一般是HTML元素(比如p,a之类的标签创建出来的对象)。而Node作为父类,除了Element还有一些其他子类,比如HTML元素内的文本对应的Text,文档对应的Document,注释对应的Comment。除此之外,他们还有差异是`NodeList`没有`nameItem`,但是`HTMLCollection`是有的。 149 | 150 | 子元素我看懂了,但**nameItem是个啥玩意???** 151 | 152 | 我试着打印出了上面的c变量(`getElementsByTagName`) 153 | 154 | ``` 155 | 0: p#p1 156 | 1: p 157 | 2: p 158 | length: 3 159 | p1: p#p1.p1 160 | p2: p 161 | p3: p 162 | __proto__: HTMLCollection 163 | ``` 164 | 165 | 看完这个`HTMLCollection`的内部,其实也能明白,`HTMLCollection`给添加了name属性。所以就有了: 166 | 167 | ```js 168 | c[0]===c.p1 //true 169 | ``` 170 | 171 | 所以,就 `getElementById`和 `getElementsByName`而言,尽可以大胆猜测一下,平均查找的复杂度应该是`getElementById`更优。因为遍历是从上到下依次遍历的,id是唯一的,找到就返回,name可能有多个,不论如何都要从头到尾遍历一遍。 172 | 173 | 那么,`NodeList`又长啥样呢?(`querySelectorAll`) 174 | 175 | ``` 176 | NodeList(3) [p#p1, p, p] 177 | 0: p#p1 178 | 1: p 179 | 2: p 180 | length: 3 181 | __proto__: NodeList 182 | ``` 183 | 184 | 好像除了少了点nameitem没什么不一样…… 185 | 186 | 187 | 188 | **真的如此吗?** 189 | 190 | 我试着在浏览器的页面上添加了一个p标签。 191 | 192 | ```html 193 | duanluo1 194 | duanluo2 195 | duanluo3 196 | duanluo4 197 | ``` 198 | 199 | 再次打印,发现出来了不一样的结果 200 | 201 | ```js 202 | d //NodeList(3) [p#p1, p, p] 203 | c //HTMLCollection(4) [p#p1, p, p, p, p1: p#p1, p2: p, p3: p, p4: p] 204 | ``` 205 | 206 | 似乎……**`HTMLCollection`是动态的,NodeList是静态的**! 207 | 208 | 209 | 210 | 嗯???似乎之前`getElementsByName`返回的也是一个NodeList吧? 211 | 212 | 那么不如,再动态改一下。 213 | 214 | 我在id为p1的p标签上又添加了一个class属性。 215 | 216 | ```html 217 | duanluo1 218 | duanluo2 219 | duanluo3 220 | duanluo4 221 | ``` 222 | 223 | 随后输出: 224 | 225 | ```js 226 | b //NodeList [p#p1.p1] 227 | ``` 228 | 229 | ???**这不是动态的吗?** 230 | 231 | 为啥`QuerySeletorAll`出来的`NodeList`是静态的??? 232 | 233 | 再次输出一下`QuerySeletorAll` 234 | 235 | ```js 236 | d //NodeList(3)[p#p1.p1,p,p] 237 | ``` 238 | 239 | 所以,`NodeList`是静态的这个论断似乎不太准确。**它的属性变化也还是动态的,只不过节点数目是静态的。** 240 | 241 | 242 | 243 | 探索了这么多,先做个总结: 244 | 245 | 如果获取元素返回的列表里只有Element,那这两种类没多大区别,但事实上很多时候浏览器会将解析HTML文本时得到的Text和Comment一并放进列表里放回。所以` node.childNodes `返回`NodeList`,而 `node.children `和`node.getElementsByXXX` 返回 `HTMLCollection` 。 246 | 247 | get系列中**只有`document.getElementsByName`返回的还是`NodeList`对象**。 248 | 249 | `querySelectorAll `返回的虽然是 `NodeList` ,但是实际上是**元素集合**(不包含空格text节点,不包含注释节点),并且**元素的数量是静态的,但元素的属性是动态的**。 250 | 251 | 252 | 253 | 我个人觉得querySelectorAll可以理解为浅拷贝了一个元素集合,虽然集合里元素的数目不会被改变,但集合里面元素都是对象,属性的变化它也会随之变化。 254 | 255 | 256 | 257 | **4.从理论中找答案** 258 | 259 | 可我说了不算数啊,那么来看看大佬怎么说吧:[地址](https://humanwhocodes.com/blog/2010/09/28/why-is-getelementsbytagname-faster-that-queryselectorall/) 260 | 261 | 翻译一下就是: 262 | 263 | `NodeList`和`HTMLCollection`是两个特别的对象类型,3级DOM中是这样定义的: 264 | 265 | `NodeList`和`NamedNodeMap`在DOM中都是动态的,**改变Dom结构都会影响到`NodeList`和`NamedNodeMap`**。 266 | 267 | 例如:如果获取一个有子节点的NodeList,然后增加或者删除子节点,这个NodeList也会动态的改变。同样的,在一个tree结构中改变一个node会影响到所有引用这个node的NodeList和NamedNodeMap对象。 268 | 269 | 那么`querySelectorAll`又是咋回事呢?我们需要看一下它的API:[地址](https://www.w3.org/TR/selectors-api/#queryselectorall) 270 | 271 | API这里的说明已经非常明确了,不用再考虑了 272 | 273 | > The [`NodeList`](https://www.w3.org/TR/selectors-api/#nodelist) object returned by the `querySelectorAll()` method *must* be static, not [live](http://www.w3.org/TR/DOM-Level-3-Core/core.html#td-live) ([[DOM-LEVEL-3-CORE\]](https://www.w3.org/TR/selectors-api/#DOM-LEVEL-3-CORE), section 1.1.1). Subsequent changes to the structure of the underlying document *must not* be reflected in the [`NodeList`](https://www.w3.org/TR/selectors-api/#nodelist) object. 274 | 275 | 好了,这两个东西搞清楚了,是时候来剖析性能了。 276 | 277 | 大佬的话翻译成中文是这样的: 278 | 279 | 动态的NodeList返回的更快是因为不需要获取这个节点的所有信息,而静态的需要。`getElementsByTagName`创建的过程不需要做任何操作,只需要返回一个指针即可。而`querySelectorAll`会循环遍历所有的的结果,然后创建一个新的NodeList。 280 | 281 | 所以这才是`getElementsByTagName`比`querySelectorAll`快的真正原因! 282 | 283 | 284 | 285 | 好了,通过一圈的探索下来,我算是搞明白了,不知道读到这篇文章的你有没有看明白呢? 286 | 287 | 288 | 289 | 最后,提供一些相关的资料供你参考: 290 | 291 | [知乎问题:querySelectorAll 方法相比 getElementsBy 系列方法有什么区别](https://www.zhihu.com/question/24702250) 292 | 293 | [querySelector和getElementById性能分析与使用选择](https://blog.csdn.net/hualimeme/article/details/44410895) 294 | 295 | 还有文内提到的英文博客: 296 | 297 | [Why is getElementsByTagName() faster than querySelectorAll()?](https://humanwhocodes.com/blog/2010/09/28/why-is-getelementsbytagname-faster-that-queryselectorall/) 298 | 299 | 以及API说明 300 | 301 | [链接](https://www.w3.org/TR/selectors-api/#queryselectorall) 302 | -------------------------------------------------------------------------------- /一些思考/虚拟DOM真的能提升性能吗?.md: -------------------------------------------------------------------------------- 1 | ## 虚拟DOM真的能提升性能吗? 2 | 3 | **1.前言** 4 | 5 | 这个疑问是从拼多多一面被问到的一个问题引申出来的。 6 | 7 | 8 | 当时老哥问我说你觉得为什么现在大家都不用jQuery了,都换框架开发了? 9 | 10 | 我当时下意识就想到了操作Dom性能很差的特点。我说因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。 11 | 12 | 13 | 到这里为止,我觉得我应该都没说错……后面我说虚拟DOM可能会比较好,是因为没有这个通讯时间。我说换这个应该是性能更好吧。当时老哥笑了一下,所以我觉得我多半是回答错了。后来老哥说,换框架主要原因是因为开发效率高。 14 | 15 | 16 | 17 | 我的确没有用过框架,处于好奇,我决定深入对它了解一下,按理说应该是可以提升性能的呀…… 18 | 19 | 那么**虚拟DOM真的能提升性能吗**?? 20 | 21 | 22 | 23 | 依旧是为了读者的方便,这里先说结论: 24 | 25 | 使用虚拟 DOM,**在DOM 阶段操作少了通讯的确是变高效了,但代价是在 JS 阶段需要完成额外的工作**(diff计算),这项额外的工作是需要耗时的! 26 | 27 | 虚拟DOM**并不是说比原生DOM API的操作快,而是说不管数据怎么变化,都可以以最小的代价来进行更新 DOM**。在每个点上,其实用手工的原生方法会比diff好很多。比如说仅仅是修改了一个属性,需要整体重绘吗?显然这不是虚拟DOM提出来的意义。框架的意义在于掩盖底层的 DOM 操作,用更声明式的方式来描述,从而让代码更容易维护。 28 | 29 | 30 | 31 | 好了,结论说完了,如果你还有兴趣,可以继续往下阅读。 32 | 33 | **2.虚拟DOM** 34 | 35 | 在解决这个问题之前,需要先了解一下虚拟DOM,只有真正了解了才能知道到底能不能提升性能。 36 | 37 | 如我前言的回答所说,DOM操作问题就是在于通信,所以很多性能优化都是在尽可能地减少DOM操作次数。 38 | 39 | 40 | 41 | 虚拟DOM直接用JS实现了一个DOM树,组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的js DOM。在其上面用diff算法找出最小变更,再把这些变更写入到实际的DOM中。这个虚拟DOM以js结构的形式存在,计算性能比较好,而且由于减少了实际操作DOM的次数,从理论上来说,性能应该会有很大的提升。 42 | 43 | 用传统的源生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。 44 | 45 | 比如一次操作时,需要更新10个DOM节点,理想状态是一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。显然,计算DOM节点的坐标值等都是白白浪费性能,可能这次计算完,紧接着的下一个DOM更新请求,这个节点的坐标值就变了,前面的一次计算是无用功。 46 | 47 | 48 | 49 | 虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。例如前面的例子,假如一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,通知浏览器去执行绘制工作,这样可以避免大量的无谓的计算量。(嗯?似乎跟标记回收很像嘛,先把变得找出来,然后一次性改完。) 50 | 51 | 52 | 53 | **3.Diff算法** 54 | 55 | 两棵树完全比较的时间复杂度为O(n^3),不过Diff算法是O(n),它是采用的平层比较。 56 | 57 | 需要考虑下面四种情况: 58 | 59 | 1. 节点类型变更 60 | 61 | 直接Replace,将旧节点卸载,装载新节点。旧节点包括下面的子节点都将被卸载,如果新节点和旧节点仅仅是类型不同,但下面的所有子节点都一样时,这样做显得效率不高,但为了避免O(n^3)的时间复杂度,这样做是值得的。 62 | 63 | 2. 仅属性或者属性值变了 64 | 65 | PROPS。不会卸载和装载,而是执行节点更新。 66 | 67 | 3. 文本改变 68 | 69 | TEXT。直接修改文字内容 70 | 71 | 4. 移动、增加、删除子节点 72 | 73 | REORDER。比如在ABCD插入一个节点E,代码只需要$(B).after(F)。 74 | 75 | 但其实内部是这样, 76 | 77 | 在JSX里为数组或枚举型元素增加上key后,能根据key直接找到具体的位置进行操作。 78 | 79 | 在执行完diff算法之后深度遍历DOM把diff的结果更新进去就好了。 80 | 81 | 82 | 83 | **4.在虚拟DOM之前都用啥?** 84 | 85 | 此前更新DOM树,一般是两种做法,一种是字符串拼接,另一种是用DOM对象。 86 | 87 | 举个栗子: 88 | 89 | 字符串拼接: 90 | 91 | ```js 92 | const userList = document.getElementById("user-list"); 93 | 94 | const html = users.map(function (user) { 95 | return ` 96 | ${user.firstName}${user.lastName} 97 | EMAIL Average grade: ${user.avgGrade} Enrolled: ${user.enrolled} 98 | ` 99 | }).join(""); 100 | 101 | userList.innerHTML = html; 102 | ``` 103 | 104 | Dom对象 105 | 106 | ```js 107 | const userList = document.getElementById(“user-list”); 108 | 109 | const frag= document.createDocumentFragment(); 110 | users.forEach(function(user){ 111 | const div = document.createElement("div"); 112 | div.id = user.id; 113 | div.className ="user"; 114 | const header = document.createElement("h2"); 115 | header.className ="header"; 116 | header.appendChild(document.createTextNode(`$ {user.firstName} $ {user.lastName}`)); 117 | frag.appendChild(div); }); 118 | userList.innerHTML =""; 119 | userList.appendChild(frag); 120 | ``` 121 | 122 | 看起来代码真的很冗长,虚拟dom似乎能解决这种冗长的代码问题。 123 | 124 | 那么性能上呢? 125 | 126 | **5.性能真的好吗?** 127 | 128 | 用户界面的更改通过 DOM 操作发生。 129 | 130 | 这个过程分为两个阶段: 131 | 132 | 1. JS 部分:定义 JavaScript 世界中的变化 133 | 2. DOM 部分:使用 DOM API 函数和属性执行更改 134 | 135 | 性能是根据整个过程的速度来衡量的。 136 | 137 | 138 | 139 | 创建和更新 DOM 树的整个过程分为两个阶段。使用虚拟 DOM,DOM 阶段的确变高效了。但代价是在 JS 阶段完成的额外工作,这项额外的工作叫diff,会有损耗。(**不要以为 js 计算就不花费代价**) 140 | 141 | 142 | 143 | 虚拟 DOM 只是提供了一个更方便的 API 来创建 UI。基本思维模式是每次有变动就整个重新渲染整个应用。抛开 Virtual DOM,简单来想就是直接重置 innerHTML。 144 | 145 | 146 | 147 | 其实在一个大型列表所有数据都变了的情况下,“重置 innerHTML ”其实是一个还算合理的操作… 但真正的问题是在 “全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个 innerHTML,这时候显然就有大量的浪费。 148 | 149 | 150 | 151 | 所以 ,**虚拟DOM并不是说比原生DOM API快,而是说不管数据怎么变化,都可以以最小的代价来进行更新 DOM**。 方法就是在内存里面用新数据刷新一个虚拟 DOM 树,然后新旧 DOM 进行比较,找出差异,再更新到 DOM 树上。(diff算法) 152 | 153 | 154 | 155 | 虽然说 diff 算法号称算法复杂度 O(n) 可以得到最小操作结果,但实际上 DOM 树很大的时候,遍历两棵树进行各种对比还是有性能损耗的。特别是我在顶层 setState 一个简单的数据,要整棵树 walk 一遍,而真实中我可以一句 jQuery 就搞定,所以后来就有了 `shouldComponentUpdate` 这种东西。 156 | 157 | 158 | 159 | 框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。 160 | 161 | 162 | 163 | 针对每一个点,都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给予的保证是,在不需要手动优化的情况下,依然可以提供过得去的性能。 164 | 165 | 166 | 167 | 最后,还是要分享一个好东西: 168 | 169 | [深度剖析:如何实现一个 Virtual DOM 算法](https://github.com/livoras/blog/issues/13) 170 | 171 | 172 | 173 | 174 | 这篇博客写到一半的时候收到了一面通过的消息,感觉受到了鼓舞。 175 | 176 | 越努力,越幸运。 177 | 178 | 要加油鸭。 179 | 180 | ——2019.08.08 181 | -------------------------------------------------------------------------------- /准备/HTML面试准备.md: -------------------------------------------------------------------------------- 1 | # HTML面经问题 2 | 3 | ## 1.html中的meta标签是用来做什么的? 4 | 5 | 提供给机器解读的一些元数据。页面搜索引擎优化,定义页面实用语言等等。 6 | 属性有两个 7 | 8 | 1)**http-equiv+content** 9 | 10 | 参数有charset(编码格式)、expires(过期时间)、refresh(特定时间内自动刷新跳转)、pragma(禁止浏览器从本地计算机缓存中访问页面内容no-cache)、widows-target(设定页面在窗口中以独立页面展示,防止被当成frame页调用)、set-cookie(自定义cooke)、content-Type(字符集) 11 | 12 | 2)**name+content** 13 | 14 | 参数有keywords(关键字)、description(主要内容)、robots(none不被检索)、author、generator(使用的制作软件)、copyright、viewport(缩放比例) 15 | 16 | ## 2.DOCTYPE作用?严格模式和混杂模式如何区分?有何意义? 17 | 18 | - 声明叫文件类型定义(DTD),位于文档中最前面,作用是为了告知浏览器应该用哪种文档类型规范来解析文档。 19 | - 严格模式(标准模式),浏览器按照W3C标准来解析;混杂模式,向后兼容的解析方法,浏览器用自己的方式解析代码。 20 | - 如何区分? 21 | 用DTD来判断 22 | 严格格式DTD——严格模式; 23 | 有URL的过渡DTD——严格模式,没有URL的过渡DTD——混杂模式; 24 | DTD不存在/格式不对——混杂模式; 25 | HTML5没有严格和混杂之分 26 | - 区分的意义 27 | 严格模式的排版和js运行模式以浏览器支持的最高标准运行。如果只存在严格模式,那么很多旧网站站点无法工作。 28 | 29 | ## 3. HTML5的新特点 30 | 31 | 很多语义化的标签 32 | 33 | - canvas js绘图 34 | 35 | - draggable属性 可拖动 36 | 37 | - geolocationAPI 获取用户地理位置 38 | 39 | - audio/video 音频 视频元素 40 | 41 | - input类型增多,color、date、datetime、datetime-local、email、month、range、search、tel、time、url、week 42 | 43 | - 表单元素增强 44 | 45 | datalist 与input配合使用规定输入域的选项列表;keygen密钥;output定义不同类的输出。 46 | 47 | - Web存储,sessionStorge针对一个session进行数据存储,关闭浏览器创港口后清除,localStorage没有事件限制,不过它可能会因为本地时间修改失效。不过大量复杂数据结构一般用indexDB 48 | 49 | - Web worker 页面中执行脚本时,页面状态不可响应,直到脚本完成。在后台运行,独立于其他脚本,不会影响页面的性能。(相当于多线程并发) 50 | 51 | - SSE server-sent-event 网页自动获取来自服务器的更新。用于接收服务器发送时间通知 52 | 53 | - WebSocket 在单个TCP连接上进行全双工通讯的协议。只需要握手一次,形成快速通道,传输数据。客户端和服务器可以直接通过TCP交换数据。获取连接之后,可以用send发送数据,用onmessage接收服务器返回的数据。 54 | 55 | - 新API 56 | 57 | History、Command、Application cache …… 58 | 59 | ## 4.为什么HTML5只需要写`` 60 | 61 | HTML5不基于SGML(标准通用标记语言),因此不需要对DTD进行引用,但是需要doctype来规范浏览器的行为。 62 | 63 | ## 5.src和href的区别 64 | 65 | href 指向网络资源位置,建立当前文档和资源的连接,一般用于超链接 66 | 67 | src将资源嵌入到当前文档中,在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也是如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部。 68 | 69 | ## 6.什么是Shadow DOM? 70 | 71 | Shadow DOM是浏览器的一种功能,能自动添加子元素,比如radio元素的controls,这些相关元素由浏览器生成。 72 | 73 | ## 7. Canvas有什么用? 74 | 75 | 相当于在页面上新建了一个画布,可以用JS画图。只写一些基本的。 76 | 77 | 获取DOM,创建画布 getContext('2d'); 78 | 79 | - 矩形 80 | 81 | fillRect(x,y,width,height) 填充 82 | 83 | strokeRect(x,y,width,height) 边框 84 | 85 | clearRect(x,y,width,height)清除指定区域 86 | 87 | - 路径 88 | 89 | 创建起始点,画图,闭合路径。路径绘制完成,可以描边或者填充。 90 | 91 | beginPath()新建路径 92 | 93 | closePath()闭合路径 94 | 95 | stroke()描边 96 | 97 | fill()填充 98 | 99 | - 移动笔触 100 | 101 | moveTo(x,y) 移动到某点 102 | 103 | - 绘制直线 104 | 105 | lineTo(x,y) 从当前位置绘制到(x,y)的一条直线 106 | 107 | - 绘制圆 108 | 109 | arc(x,y,radius,startAngle,endAngle,anticlockwise) 以(x,y)为圆心,radius为半径,startAngle和endAngle以X轴为准开始结束的弧度,anticlockwise为true顺时针,false逆时针。 110 | 111 | 112 | 113 | ## 8.不改变图片原始大小画到canvas上面 114 | 115 | 第一种,直接在坐标上画图,如果图片大小超出了画布也不缩放 116 | 117 | drawImage(image,x,y); 118 | 119 | 第二种,绘制开始位置,缩放位置,图片会变形 120 | 121 | drawImage(image,x1,y1,x2,y2); 122 | 123 | 第三种,从图片的某个坐标开始截图,截取m*n的大小。然后截的图片从canvas的x1,y1画到x2,y2。 124 | 125 | drwaImage(image,xi,yi,m,n,x1,y1,x2,y2) 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /准备/JS面试准备.md: -------------------------------------------------------------------------------- 1 | # JS面试准备 2 | 3 | ## 1.基本数据类型 4 | 5 | 6种,boolean,number,string,undefined,null,symbol 6 | 7 | ## 2.null是Object吗 8 | 9 | 虽然 typeof null会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,`000` 开头代表是对象,然而 `null` 表示为全零,所以将它错误的判断为 `object` 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。 10 | 11 | ## 3.原始类型与对象类型区别 12 | 13 | 对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址。 14 | 15 | 当创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)。 16 | 17 | ## 4.typeof vs instanceof 18 | 19 | typeof对于原始类型来说,除null会显示成object,其余都可以显示正确的类型typeof 对于对象来说,除了函数都会显示 object,所以说typeof并不能准确判断变量到底是什么类型。 20 | 21 | 如果我们想判断一个对象的正确类型,这时候可以考虑使用 instanceof,因为内部机制是通过原型链来判断的。 22 | 23 | 24 | 25 | ## 5.==比较 26 | 27 | - Boolean,number,string三类比较的时候把值转换成数字,在看转换结果是否相等。证明:('1'==true) 是真 ('abc'==true)是假。 28 | - undefined 参与比较,换成了NaN,所以其他三个类型跟它比较都是false,跟null类型比较的时候是true。(NaN==NaN)是假 29 | - null参与比较,被当成对象,因为null没有valueof和toString,除了undefined谁跟他比较都是false。 30 | - 值类型与对象比较:先调用对象valueof 如果仍返回对象,调用tostring,如果还是没有就不等。 31 | 32 | ## 6.如何给元素添加事件 33 | 34 | - 在HTML元素中绑定事件 onclick=show() 35 | - 获取dom,dom.onclick 36 | - addEventListener(click,show,1/0) 37 | 38 | ## 7.addEventListener三个参数,取值意思 39 | 40 | 第一个参数是事件类型,第二个是事件发生的回调函数,第三个是个布尔值,默认是false,false是冒泡阶段执行,true是捕获阶段。 41 | 42 | ## 8.事件冒泡与事件捕获 43 | 44 | 事件是先捕获,后冒泡 45 | 46 | 捕获阶段是外部元素先触发然后触发内部元素 47 | 48 | 冒泡阶段是内部元素先触发然后触发外部元素 49 | 50 | ## 9.如何阻止事件冒泡?如何取消默认事件?如何阻止事件的默认行为? 51 | 52 | - 阻止事件冒泡: 53 | 54 | W3C: stopPropagation(); 55 | 56 | IE: e.cancelBubble=true; 57 | 58 | 写法 : 59 | 60 | window.event ? window.event.cancelBubble=true:e.stop(Propagation) 61 | 62 | - 取消默认事件 63 | 64 | W3C:preventDefault() 65 | 66 | IE: e.returnValue:false; 67 | 68 | - 阻止默认行为: 69 | 70 | return false 71 | 72 | 原生的js会阻止默认行为,但会继续冒泡; 73 | 74 | jquery会阻止默认行为,并停止冒泡。 75 | 76 | 77 | 78 | ## 10.如何判断一个对象类型是数组 79 | 80 | - 根据构造函数来判断 xxx instanceof Array 81 | - 根据class属性判断 Object.prototype.toString.call(obj)==='[object Array]' 82 | - 直接用isArray判断 83 | 84 | 85 | 86 | ## 11.怎么判断一个属性是对象上的属性还是其原型对象上的属性 87 | 88 | 使用hasOwnProperty()返回true,说明是这个对象上的;如果返回false,但是属性in 这个对象返回了true,说明是原型对象上的属性。如果都是false,那么不存在这个属性。 89 | 90 | 91 | 92 | ## 12.闭包、作用域链 93 | 94 | 作用域链针对函数作用域来说的,比如创建了一个函数,函数里面又包含了一个函数,那么就会有全局作用域、函数1的作用域、函数2的作用域。 95 | 96 | 它的查找规范就是现在自己的变量范围中找,如果找不到就沿着作用域往上找。 97 | 98 | 闭包就是能够读取其他函数内部变量的函数,其实就是利用了作用域链向上查找的特点。 99 | 100 | 闭包的作用:读取函数内部变量 让这些变量的值一直保持在内存中。 101 | 102 | ## 13.原型链 103 | 104 | 原型链是针对构造函数来说的,比如先创建了一个函数,然后通过变量a new了这个函数,那么new出来的函数就会继承出来的那个函数的属性。如果我访问这个新函数的某个属性,如果没有在新函数中定义这个变量,它就会往上查找。这个过程就是原型链。 105 | 106 | ## 14. 继承的原理 107 | 108 | 还是跟原型链有关。每个函数都有个原型对象,这个对象用来存储通过这个函数所创建的所有实例的共有属性和方法。在读取某个对象属性的时候,从实例开始,如果实例有就返回,如果没有就找原型对象,找到了就返回。通过实例只能访问原型对象里的值,但是不能修改。这就实现了继承。 109 | 110 | ## 15.Promise 111 | 112 | Promise是一种用于解决异步问题的思路、方案,简单说是个容器,里面存的是某个未来会结束的结果。是一个对象,可以获取异步操作的消息。有三种状态,pending resolved rejected ,状态变了就不能修改了。 113 | 114 | 在js里面,经常用异步的是ajax,比如sucess:一个回调,error一个回调。但是如果一次请求需要多个接口的时候就产生了回调地狱,promise可以用then来处理,它可以在一个then里面再写一个promise对象。 115 | 116 | promise.all 多任务并行,输出失败的那个,如果成功,返回所有的执行结果。 117 | 118 | promise.race 多任务执行,返回最先执行结束的任务结果。 119 | 120 | ## 16 Generator+yield 121 | 122 | 是ES6里面的新数据类型,像一个函数,可以返回多次。特点就是函数有个*号。 123 | 124 | 调用的话就是不断调用next() 返回当前的value 值 done的状态 125 | 126 | return() 直接忽略所有yield ,返回最终的结果 127 | 128 | 可以随心所欲的交出和恢复函数的执行权。 129 | 130 | 131 | 132 | ## 17.await+async 133 | 134 | async函数返回的是一个promise对象,await用于等待一个async函数的返回值。 135 | 136 | 优势在于处理then链。如果是多个Promise组成的then链,那么优势就比较明显了,可以用Promise来解决多层回调的题,可以进一步优化它。 137 | 138 | 139 | 140 | ## 18.EventLoop 141 | 142 | 浏览器执行是有一个执行栈的,当遇到异步的代码时,会被挂起并在需要执行的时候加入到task队列里面,一旦执行栈为空,eventloop会从Task队列里拿出需要执行的代码放入执行栈中执行。执行完毕后就弹出。 143 | 144 | 但是不同的任务源会分到微任务和宏任务里面。 145 | 146 | 顺序: 147 | 148 | 首先执行同步代码,属于宏任务 149 | 150 | 执行完所有宏任务之后,查询有没有异步代码需要执行,然后执行微任务 151 | 152 | 执行微任务之后,如果有必要会重新渲染页面 153 | 154 | 然后一下轮执行宏任务中异步代码,比如说setTimeout的回调函数。 155 | 156 | 157 | 158 | ## 19.数组API有哪些? 159 | 160 | 直接修改原数组的:push(),unshift(), pop(),shift(), splice() ,reverse(), sort() fill(x,start,end) copyWithin(tochangeindex,changestart,changeend) 161 | 162 | 返回新数组的: concat(),slice() 163 | 164 | 返回字符串: join() 165 | 166 | 位置或是否在数组内: indexOf() lastindexOf() includes()是否包括 find()满足条件的索引; 167 | 168 | 遍历方法:forEach()所有元素执行一次,返回undefined;map()返回值新数组;filter()返回通过元素的新数组 ;every()所有都满足返回true;some()只要有一个元素满足就返回true;reduce(fn(pre,cur,index,arr),basevalue)累加器;reduceRight()从右边往左边加 169 | 170 | 迭代器:arr.keys()返回索引迭代器;arr.values()返回迭代器,值;arr.entries()返回键值对。 171 | 172 | 173 | 174 | ## 20.sort方法底层如何排序? 175 | 176 | 谷歌浏览器:大于22的数组 快排;小于22的用插入排序 177 | 178 | 火狐浏览器:归并排序 179 | 180 | webkit:用的c++的qsort(); 181 | 182 | 183 | 184 | ## 21. 模块化CommonJS AMD CMD ES6Module的区别 185 | 186 | CommonJS 一个单独的文件就是一个模块,主要运行与服务器端,同步加载模块。require输入其他模块提供的功能,module.exports规范模块对外接口,输出一个值的拷贝。输出之后不能改变,会缓存起来。 187 | 188 | AMD 异步加载,一个单独文件一个模块,主要运行于浏览器端,模块和模块的依赖可以被异步加载。define定义模块,require用于输入其他模块提供的功能,return规范模块对外接口,define.amd是一个对象,表明函数遵守AMD规范。AMD的运行逻辑是,提前加载,提前执行,申明依赖模块的时候,会第一时间加载并执行模块内的代码,使后面的回调函数能在所需的环境中运行。 189 | 190 | CMD 通用模块。一个文件一个模块。主要在浏览器中运行,define全局函数,定义模块,通过exports向外提供接口,用require获取接口,使用某个组件时用use()调用。通过require引入的模块,只有当程序运行到这里时候才会加载执行。 191 | 192 | UMD 通用模块。解决commonJS和AMD不能通用的问题。define.amd存在,执行AMD规范;module.exports,执行CommonJS规范;都没有,原始代码规范。 -------------------------------------------------------------------------------- /准备/安全问题的准备.md: -------------------------------------------------------------------------------- 1 | # 安全 2 | 3 | ## 1.XSS是什么?怎么防范? 4 | 5 | XSS是跨站脚本攻击,分为持久型和非持久型。 6 | 7 | 持久型就是攻击的代码被写入数据库中,如果访问量大,每个用户都会被影响。 8 | 9 | 非持久型一般通过修改URL参数加入攻击代码,诱导访问链接进行攻击 10 | 11 | 防御方式一般有两种: 12 | 13 | 1 对输入输出内容转义 14 | 15 | 2 使用csp白名单过滤 16 | 17 | 在httpheader中设置content-security-policy 18 | 19 | 或者在meta标签的http-equiv设置 20 | 21 | 22 | 23 | ## CSRF是什么?怎么防范? 24 | 25 | CSRF是跨站请求伪造,利用用户申请,执行非本意的操作。 26 | 27 | 28 | 29 | 防范方式: 30 | 31 | 对Cookie设置Samesite,不随着跨域请求发送 32 | 33 | 随机下发token,每次请求将token带上,验证token是否有效 34 | 35 | 36 | 37 | ## 点击劫持 38 | 39 | 视觉欺骗手段,通过iframe嵌套尽自己的网页,诱导点击。 40 | 41 | 42 | 43 | 防范方式: 44 | 45 | 设置x-frame-option 限制iframe的加载 46 | 47 | 48 | 49 | ## 中间人攻击 50 | 51 | 同时与服务端和客户端建立了连接,并让对方认为是安全的。 52 | 53 | 54 | 55 | 防范方式: 56 | 57 | 安全通道 https 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /准备/对简历的准备.md: -------------------------------------------------------------------------------- 1 | # 对简历的准备 2 | 3 | ## 自我介绍 4 | 5 | 您好,我叫张艺,来自武汉大学,现在是学硕,开学之后研三了。我在大三的时候作为实习生加入了现在的实验室,做了第一个项目基本状态数据库,作为前端开发完成了基础模板表单的批量管理功能,大四保研之后,我就留在这个实验室接手了武汉大学本科生教学管理系统的运维工作,研一之后这个系统因为需求变更和技术的换代进行了重做,在里面承担了大部分的工作,包括前期的需求调研、产品设计以及后面的开发等等,做了差不多五六个月。到研一下学期帮助另一个项目应急,参与了地震监测系统的前端开发工作。到年底,这个教学管理系统的web端需要进行重构,承担了项目经理,一直从筹划到完整上线,7月初刚完成了项目交接。从我经历您也能看出来,我做过的岗位都是跟着需求在编,一直都不太稳定,但经历了这么多之后,我也找找到了今后的人生方向,就是好好做一个前端。 6 | 7 | ## 项目 8 | 9 | ### 项目: 武汉大学本科生教务系统 web 端 改版 10 | 11 | #### 采用jquery.i18n.properties实现英文版本 12 | 13 | 实现思路:给所有的静态资源加一个class,记录用户在页面上选的值,保存在cookie里。然后页面显示时读取这个值。遍历页面上有这个class的节点,读取它的name属性值,然后在property文件里找它对应的值显示出来。 14 | 15 | 期间还有一些英文太长的样式问题,通过world-break:break-all来解决的。 16 | 17 | 剩下的就主要是 样式布局的调整了。 18 | 19 | #### 选课撮合的流程 20 | 21 | 三种类型是不一样的 22 | 23 | 1.公必课的话是实时撮合的,循环提取学生的申请课头信息,检查课程剩余人数、学生的已修学分有没有超过最大学分,有没有选过相同课程号的课程,再判断课程号是不是tiyu开头的,如果是高级要通过name去匹配有没有修过初级。最后查看学生的课程是否有冲突,没有就选课成功,并且更新学生已选学分和课程人数。 24 | 25 | 26 | 27 | 2.公选课的话是高年级优先,所以需要按照年级排序,首先先看这门课程的编码能不能选(18级的学生不可以选老生的课程,编码长度低于13位),然后处理申请表里面信息,检查学生已修学分有没有大于最大学分,课程剩余人数,学生有没有选过这门课程(同课程号),检查冲突,没有就选课成功,并且更新学生已选学分和课程人数。 28 | 29 | 30 | 31 | 3.前面两种是按照申请表去处理的,但是专业课的处理规则是按照课头去处理的,获取课头的剩余人数。如果选这门课的学生大于剩余人数就重新按照专业、年级排序,之后再依次处理,如果没有的话,就直接进入处理单个学生的循环。判断学生有没有选这门课、有没有超过当前学年对专业课总学分的要求,检查有没有冲突,如果没有,就把这门课插入课表,剩余人数-1. 32 | 33 | ### 项目:AETA多分量地震系统 34 | 35 | ### 英文版本实现 36 | 37 | 其实跟教务系统的原理是一样的,只不过不是一个文件一个data.js数据。读取在data.js里查找罢了,相当于对象数组取值。 38 | 39 | ### Esri arcgis 40 | 41 | 用require导入Esri的插件 42 | 43 | 初始化地图new Map,输入中心的经纬度。然后创建一个图层layer,再把图层加到地图上。 44 | 45 | 获取数据和坐标,然后创建graphic 46 | 47 | 48 | 49 | 1.引申问题: 50 | 51 | require相当于动态创建script标签,引入响应的js。 52 | 53 | init.js里面修改路径的作用,路径是dojo.baseurl,指向存放dojo.js的文件夹,用于dojo模块管理。在require的时候通过路径找响应的模块。 54 | 55 | 2.引申问题:模块化? 56 | 57 | require是Commonjs的规范, 58 | 59 | 用法就是一个文件module.exports{ 方法};另一个文件require(url),直接使用这个方法。 60 | 61 | 本质上就是把要导出的对象赋值给module对象的exports属性,然后从其他文件用require方法访问这个属性。 62 | 63 | 跟ES6模块啥区别? 64 | 65 | es6是用的export导出,export default一个文件一个,然后import 并列from URL。 66 | 67 | commonjs输出的是一个值,es6输出的是值的引用。最大的区别还是commonjs是运行时加载,但是es6的是编译时输出接口。 68 | 69 | ### Echats 70 | 71 | 先初始化init。 72 | 73 | 然后设置标题、图标和空的坐标轴 74 | 75 | setOption:title tooltip legend toolbox 76 | 77 | series里面存数据和对应的名字颜色等等。 78 | 79 | ## 问题 80 | 81 | ### c#客户端的单例模式长连接怎么做到的 82 | 83 | 我们项目的客户端是胖客户端,sql语句都是在本地拼接好执行的,只要保证每次获取后台连接都是用的同一个接口,就能保证一个客户端只有一个连接。 84 | 85 | 引申问题:如果是前后端分离的情况 86 | 87 | 多个前端共享一个后台,后台只有一个数据库连接,难以保证并发性能,所以一般采用俩劫持管理多个数据库连接。如果想要保证用户使用的数据连接一直都是同一个,可以将用户标识跟数据库连接绑定,比如说sessionid 88 | 89 | ### 热更新是怎么做的 90 | 91 | 有两个程序,一个是更新程序,一个是应用程序。每次启动其实启动是更新程序,它根据版本号去服务器上检查资源是否有更新,更新的内容是先放在一个temp文件夹下面的,全部更新完了才覆盖原来的内容。如果更新失败,就直接启动应用程序。 92 | 93 | ### 台站发请求线程池超了,要怎么处理 94 | 95 | 服务器处理不了那么多请求,目前的修改方式是换了一个线程池,到一定阈值了就拒绝,等待下次请求再处理。 96 | 97 | 祥哥说有更好的解决方案: 98 | 99 | 用消息队列来做,先把数据接收了,后面让服务器来算。根据消息每秒的加入量和服务器能处理的量,来判断多少个服务器处理。 100 | 101 | ### 消息队列服务怎么做的 102 | 103 | 直接发送到消息队列的服务器,然后写一个消费者的服务,按照消息队列的顺序,调用短信接口处理。保证收到短信接口的回复了才通知消息队列。然后消息队列删除这条消息。 104 | 105 | ### 文件服务器实现的办法 106 | 107 | 给文件做标识,然后把服务器上的地址,保存在数据库里。当要获取它时,就去数据库里取它的地址,从文件服务器上通过标识来获取。 108 | 109 | ### 服务器上的缓存 110 | 111 | 其实就是建了一个HashMap,把所有的课程数据,全部放在了Map里,如果要请求课程数据,就先去Map,看是否到时间了,如果时间过期,那么就去数据库里面查询,更新Map,并更新时间。如果没有过时就直接从Map获取数据。 112 | 113 | 这种优化只能用于不常更新的数据,因为会有实时性的问题。 -------------------------------------------------------------------------------- /准备/性能优化面试准备.md: -------------------------------------------------------------------------------- 1 | # 性能优化相关问题 2 | 3 | ## 1.为什么要强调CSS要放在header里,js放在尾部? 4 | 5 | > ### DOMContentLoaded 和 load 6 | > 7 | > - DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片... 8 | > - load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成 9 | 10 | 构建Render树需要DOM和CSSOM,所以**HTML和CSS都会阻塞渲染**。所以需要让CSS**尽早加载**(如:放在头部),以**缩短首次渲染的时间**。 11 | 12 | 阻塞浏览器的解析,也就是说发现一个外链脚本时,**需等待脚本下载完成并执行后才会继续解析HTML**。 13 | 14 | 普通的脚本会阻塞浏览器解析,**加上defer或async属性,脚本就变成异步,可等到解析完毕再执行** 15 | 16 | - async异步执行,异步下载完毕后就会执行,不确保执行顺序,一定在onload前,但不确定在DOMContentLoaded事件的前后 17 | - defer延迟执行,相当于放在body最后(理论上在DOMContentLoaded事件前) 18 | 19 | ## 2.白屏、首屏 20 | 21 | ### 白屏 22 | 23 | 白屏时间指的是浏览器开始显示内容的时间,一般认为浏览器开始渲染body或者解析完head标签的时候就是页面白屏结束的时间。 24 | 25 | 计算方法:IE8-: title后输出一个时间pagestartime; 26 | 27 | head结束前 输出一个时间firstpaint。 28 | 29 | 白屏时间=firstpaint-pagestarttime/performance.timing.navigationStart; 30 | 31 | **优化** 32 | 33 | 1.加快js的执行速度,比如无限滚动的页面,可以用js先渲染一个屏幕范围内的东西 34 | 35 | 2.减少文件体积 36 | 37 | 3.首屏同步渲染html,后续的滚屏再异步加载和渲染。 38 | 39 | ### 首屏 40 | 41 | 首屏时间是指用户打开网站开始,到浏览器首屏内容渲染完成的时间。 42 | 43 | 计算方法: 44 | 45 | 1.模块标签标记。适用于内容不需要拉取数据才能生存以及页面不考虑图片等资源的加载情况。结束位置加时间戳输出时间。 46 | 47 | 2.统计首屏内图片加载最慢事件。 通常图片加载最慢,所以会把首屏内加载事件最慢的图片时间。 48 | 49 | 3.自定义计算 50 | 51 | **优化** 52 | 53 | 首屏数据拉取逻辑放在顶部(数据最快返回) 54 | 55 | 首屏渲染css及js逻辑优先内联html,返回时能立即执行 56 | 57 | 次屏逻辑延后执行 58 | 59 | ### DOM构建时间 60 | 61 | 浏览器开始对基础页文件内容进行解析,构建出一个DOM树的时间。domready事件在DOM加载后、资源加载之前被触发,在本地浏览器的DOMContentLoaded事件的形式被调用。 62 | 63 | ### 整页时间 64 | 65 | 整个页面加载完成时间 66 | 67 | loadEvntEnd-navigationStart/onload记录时间戳。 68 | 69 | 70 | 71 | ## 3.**什么情况下会阻塞渲染** 72 | 73 | 1.首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。 74 | 75 | 所以如果你想渲染的越快,你越应该**降低一开始需要渲染的文件大小**,并且做到**HTML扁平层级,优化CSS选择器**。 76 | 77 | 2.然后当浏览器在解析到 `script` 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。 78 | 79 | 所以,如果想首屏渲染的越快,就越**不应该在首屏就加载 JS 文件**,这也是都建议将 `script` 标签放在 `body` 标签底部的原因。 80 | 81 | 当然也不必一杆子打全船,可以有替换方案可以给script标签添加上defer 和 async属性。 82 | 83 | 84 | 85 | ## 4.为什么操作DOM性能很差? 86 | 87 | 因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。 88 | 89 | **操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题**。 90 | 91 | 92 | 93 | ## 5.插入几万个 DOM,如何实现页面不卡顿? 94 | 95 | 解决问题的重点应该是如何分批次部分渲染 DOM。 96 | 97 | 思路1 是通过 `requestAnimationFrame` 的方式去循环的插入 DOM; 98 | 99 | 思路2 是通过虚拟滚动。 100 | 101 | 这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容 102 | 103 | ## 6.在不考虑缓存和优化网络协议的前提下,可以通过哪些方式来最快的渲染页面? 104 | 105 | 1. 从文件大小考虑 106 | 2. 从 `script` 标签使用上来考虑 async和differ 107 | 3. 从需要下载的内容是否需要在首屏使用上来考虑 108 | 4. 最后就是从 CSS、HTML 的代码书写上来考虑了 109 | 110 | 111 | 112 | ## 图片加载优化 113 | 114 | 1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。 115 | 2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。 116 | 3. 小图使用 base64 格式 117 | 4. 将多个图标文件整合到一张图片中(雪碧图) 118 | 5. 选择正确的图片格式: 119 | - 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好 120 | - 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替 121 | - 照片使用 JPEG 122 | 123 | 124 | 125 | ## 防抖节流 126 | 127 | ### 防抖 128 | 129 | 如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。频繁触发,有足够空闲时间才执行 130 | 131 | ```js 132 | const debounce = (func, wait = 50) => { 133 | let timer = 0 134 | return function(...args) { 135 | if (timer) clearTimeout(timer); 136 | timer = setTimeout(() => { 137 | func.apply(this, args) 138 | }, wait) 139 | } 140 | } 141 | ``` 142 | 143 | ### 节流 144 | 145 | 防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。 146 | 147 | ```js 148 | function throttle(func,wait){ 149 | let last; 150 | return function(...args){ 151 | let now=+new Date(); 152 | if(!last||now>last+wait){ 153 | last=now; 154 | func.apply(this,args); 155 | } 156 | } 157 | } 158 | ``` 159 | 160 | 161 | 162 | 163 | 164 | ## 预渲染 165 | 166 | 可以通过预渲染**将下载的文件预先在后台渲染**,可以使用以下代码开启预渲染 167 | 168 | ```html 169 | 170 | ``` 171 | 172 | 预渲染虽然可以提高页面的加载速度,但是要**确保该页面大概率会被用户在之后打开**,否则就是白白浪费资源去渲染。 173 | 174 | ## 懒执行 175 | 176 | 懒执行就是**将某些逻辑延迟到使用时再计算**。 177 | 178 | 该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。 179 | 180 | 懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。 181 | 182 | ## 懒加载 183 | 184 | 懒加载就是**将不关键的资源延后加载**。 185 | 186 | 懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。 187 | 188 | 对于图片来说,先设置图片标签的 `src` 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 `src` 属性,这样图片就会去下载资源,实现了图片懒加载。 189 | 190 | 懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。 191 | 192 | ## CDN 193 | 194 | CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。 195 | 196 | 因此,我们可以将**静态资源尽量使用 CDN 加载**,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意**CDN 域名要与主站不同**,否则每次请求都会带上主站的 Cookie,平白消耗流量。 -------------------------------------------------------------------------------- /准备/浏览器相关问题的准备.md: -------------------------------------------------------------------------------- 1 | # 浏览器 2 | 3 | ## 1.浏览器的存储方式有哪些?有什么优缺点? 4 | 5 | Cookie、localStorage、sessionStorage、indexDB 6 | 7 | Cookie一般由服务器生成,可以设置过期时间;sessionStorage 页面关闭就被清理;localStorage和indexDB除非被清理,否则一直存在。 8 | 9 | Cookie上限是4K,local上限都是5M,indexDB没限制 10 | 11 | Cookie每次都会携带在 header 中,对于请求性能影响,其余不参与 12 | 13 | 14 | 15 | 处于性能考虑,没有大量数据存储需求的话,可以用localStorage和sessionStroage。 16 | 17 | 18 | 19 | ## 2.Service Worker&Web Worker&Web Socket 20 | 21 | Web Worker利用线程消息传递来实现并行,把复杂的功能推到后台线程中,主js可以正常运行,比如说事件监听和其他UI交互。当复杂功能完成时会告知结果,然后更新web app。(动态图表每个都通过一个独立的Web worker去获取数据) 22 | 23 | Web Socket 是客户端和服务器之间有一个长连接,双方可以在任何时候开始发送数据。适合用于任何需要经常与服务器进行通信,并且可以从服务器能够直接与客户沟通的Web App。(聊天软件、在线游戏等) 24 | 25 | Service Worker是一个网络代理,可以控制页面中的请求是如何处理的,一般可以用来实现缓存的功能。因为存在请求拦截,所以传输协议需要是https。(三步:先注册 Service Worker;监听到 install事件以后就可以缓存需要的文件;下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。) 26 | 27 | 28 | 29 | ## 3.浏览器的缓存位置 30 | 31 | 从缓存位置上来说分为四种,并且各自有**优先级**,当依次查找缓存且都没有命中的时候,才会去请求网络。顺序是: 32 | 33 | 1. Service Worker 34 | 2. Memory Cache 35 | 3. Disk Cache 36 | 4. Push Cache 37 | 5. 网络请求 38 | 39 | 当 Service Worker 没有命中缓存的时候,我们需要去调用 `fetch` 函数获取数据。如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。**但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。** 40 | 41 | Memory Cache 是内存中的缓存,读取内存中的数据肯定比磁盘快。**内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。** 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 42 | 43 | Disk Cache是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache **胜在容量和存储时效性上**。 44 | 45 | Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。**并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。** 46 | 47 | 如果都没有命中,那只能通过发请求了。 48 | 49 | 50 | 51 | ## 4.缓存策略 52 | 53 | 分为强缓存和协商缓存 54 | 55 | 强缓存主要通过http头部实现,有两个Expires 和 Cache-control 56 | 57 | 协商缓存,用于缓存过期,请求验证的过程。通过http头部实现。 58 | 59 | 有两种: 60 | 61 | Last-Modified和If-Modified-Since 62 | 63 | ETag 和 If-None-Match 64 | 65 | 66 | 67 | ## 5.对于频繁变动的资源 68 | 69 | 对于频繁变动的资源,首先需要使用 `Cache-Control: no-cache` 使浏览器每次都请求服务器,然后配合 `ETag` 或者 `Last-Modified` 来验证资源是否有效。 70 | 71 | 这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。 72 | 73 | 74 | 75 | ## 6.代码文件的缓存 76 | 77 | 一般来说,现在都会使用工具来打包代码,那么我们就可以对文件名进行哈希处理,只有当代码修改后才会生成新的文件名。基于此,我们就可以给代码文件设置缓存有效期一年 `Cache-Control: max-age=31536000`,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。 78 | 79 | 80 | 81 | ## 7.如何不缓存 82 | 83 | 设置cache-control为no-cache,no-store 84 | 85 | praga:no-cache 86 | 87 | expires:设置过期时间 88 | 89 | 90 | 91 | ## 8.渲染机制 92 | 93 | DOM树 CSSOM树 渲染树 94 | 95 | 96 | 97 | ## 9.重绘和回流 98 | 99 | - 重绘是当节点需要更改外观而不会影响布局的,比如改变 `color` 就叫称为重绘 100 | - 回流是布局或者几何属性需要改变就称为回流。 101 | 102 | 回流**必定**会发生重绘,重绘**不一定**会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。 103 | 104 | -------------------------------------------------------------------------------- /准备/跨域面试准备.md: -------------------------------------------------------------------------------- 1 | # 跨域 2 | 3 | ## 1.为什么要跨域? 4 | 5 | 当一个资源从与该资源本身所在服务器中不同域、协议、端口请求一个资源时,出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求,XMLHttpRequest和Fetch API。 6 | 7 | 引入这个机制主要是用来防止CSRF攻击的(利用用户的登录态发起恶意请求)。 8 | 9 | 没有同源策略的情况下,A 网站可以被任意其他来源的 Ajax 访问到内容。如果你当前 A 网站还存在登录态,那么对方就可以通过 Ajax 获得你的任何信息。 10 | 11 | (当然跨域并不能完全阻止 CSRF,因为浏览器不一定是限制了请求,也可能是拦截了返回结果) 12 | 13 | 14 | 15 | ## 2.Jsonp的实现原理 16 | 17 | 原理就是利用script标签没有跨域限制的漏洞。 18 | 19 | 通过 ` 23 | 28 | ``` 29 | 30 | JSONP 使用简单且兼容性不错,但是只限于 `get` 请求。 31 | 32 | 在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现: 33 | 34 | ```javascript 35 | function jsonp(url, jsonpCallback, success) { 36 | let script = document.createElement('script') 37 | script.src = url 38 | script.async = true 39 | script.type = 'text/javascript' 40 | window[jsonpCallback] = function(data) { 41 | success && success(data) 42 | } 43 | document.body.appendChild(script) 44 | } 45 | jsonp('http://xxx', 'callback', function(value) { 46 | console.log(value) 47 | }) 48 | ``` 49 | 50 | 51 | 52 | ## 3. CORS 53 | 54 | 浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。 55 | 56 | 服务端设置 `Access-Control-Allow-Origin` 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。 57 | 58 | 如果发起请求时设置WithCredentials标志设置为 true,从而向服务器发送cookie,但是如果服务器的响应中未携带Access-Control-Allow-Credentials: true,浏览器将不会把响应内容返回给请求的发送者。 59 | 60 | 两种请求: 61 | 62 | 简单和复杂请求,复杂请求首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务器是否允许该跨域请求,服务器确认允许之后,才发起实际的HTTP请求。 63 | 64 | 65 | 66 | ## 4.document.domain 67 | 68 | 只能在二级域名相同的情况下,只需要给页面添加 `document.domain = 'test.com'` 表示二级域名都相同就可以实现跨域了。 69 | 70 | 71 | 72 | ## 5.postMessage 73 | 74 | 常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息。 75 | 76 | ```js 77 | // 发送消息端 78 | window.parent.postMessage('message', 'http://test.com') 79 | // 接收消息端 80 | var mc = new MessageChannel() 81 | mc.addEventListener('message', event => { 82 | var origin = event.origin || event.originalEvent.origin 83 | if (origin === 'http://test.com') { 84 | console.log('验证通过') 85 | } 86 | }) 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /安全/前端的安全防范.md: -------------------------------------------------------------------------------- 1 | # 前端的安全防范 2 | 3 | 这个领域一直是我最惧怕的一个部分,因为自己完全不了解,所以也不敢瞎写瞎总结。 4 | 5 | 但是基本概念还是需要知道的,所以有了这个部分的总结。 6 | 7 | 8 | 9 | ## XSS 10 | 11 | 这个问题好像在面经的出现频率非常之高。 12 | 13 | XSS 简单点来说, 中文名为跨站脚本, 是发生在**目标用户的浏览器**层面上的,简单理解就是:攻击者想尽一切办法将可以执行的代码注入到网页中。重点在**脚本**上。 14 | 15 | 可以分为两种类型:**持久型和非持久型**。 16 | 17 | - 持久型就是攻击的代码被服务端写入进**数据库**中。 18 | 19 | 这种攻击危害性很大,因为如果网站访问量很大的话,就会导致大量正常访问页面的用户都受到攻击。 20 | 21 | 比如在一些论坛的评论中,写script标签加alert语句,如果前后端没有做好防御的话,这段评论就会被存储到数据库中,这样每个打开该页面的用户都会被攻击到。 22 | 23 | - 非持久型一般通过**修改 URL 参数**的方式加入攻击代码,诱导用户访问链接从而进行攻击。 24 | 25 | 比如说 26 | 27 | ```html 28 | http://www.domain.com?name= 29 | ``` 30 | 31 | 然后代码里正好也有这个指标: 32 | 33 | ```html 34 | 35 | {{name}} 36 | 37 | ``` 38 | 39 | 然后就凉凉了。 40 | 41 | 这种攻击方式一般Chrome浏览器自动就能防御了,但是用户不一定都用Chrome呀,所以防范还是很重要的。 42 | 43 | **划重点,如何防御** 44 | 45 | **1.转义字符** 46 | 47 | 转义输入输出的内容,对于引号、尖括号、斜杠进行转义。 48 | 49 | 50 | 51 | 但是对于显示富文本来说,显然不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。 52 | 53 | 对于这种情况,通常采用白名单过滤的办法。 54 | 55 | > 以下代码来自yuchengkai大佬的进阶之道: 56 | 57 | ```javascript 58 | const xss = require('xss') 59 | let html = xss('XSS Demo') 60 | 61 | //控制台输出 62 | console.log(html) 63 | > XSS Demo <script>alert("xss");</script> 64 | ``` 65 | 66 | 以上示例使用了 `js-xss` 来实现,可以看到在输出中保留了 `h1` 标签且过滤了 `script` 标签。 67 | 68 | 69 | 70 | **2.CSP** 71 | 72 | CSP 本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击。 73 | 74 | 通常可以通过两种方式来开启 CSP: 75 | 76 | 1. 设置 HTTP Header 中的 `Content-Security-Policy` 77 | 2. 设置 `meta` 标签的方式 `` 78 | 79 | 这里以设置 HTTP Header 来举例:(更多参看[MDN文档](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)) 80 | 81 | - 只允许加载本站资源 82 | 83 | ``` 84 | Content-Security-Policy: default-src ‘self’ 85 | ``` 86 | 87 | - 只允许加载 HTTPS 协议图片 88 | 89 | ``` 90 | Content-Security-Policy: img-src https://* 91 | ``` 92 | 93 | - 允许加载任何来源框架 94 | 95 | ``` 96 | Content-Security-Policy: child-src 'none' 97 | ``` 98 | 99 | 对于这种方式来说,只要开发者配置了正确的规则,那么即使网站存在漏洞,攻击者也不能执行它的攻击代码,并且 CSP 的兼容性也不错。 100 | 101 | 不过这里就是斗胆简单说了一些,还可以看看[这篇文章](https://www.cnblogs.com/unclekeith/p/7750681.html),感觉说的比较详细。 102 | 103 | 104 | 105 | ## CSRF 106 | 107 | 这个问题也经常出现在面经里,而且我的小伙伴在字节跳动三面的时候就被问到了。除了问CSRF之外,面试官还问她token是放在哪里的……等下也来试着回答一下这个问题。 108 | 109 | > (至于我为什么没有面字节……那时候我在写论文……一般辛酸泪……不提也罢) 110 | 111 | 112 | 113 | CSRF 中文名为**跨站请求伪造**。 114 | 115 | 原理就是攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑。 116 | 117 | CSRF攻击的本质在于**利用用户的身份,执行非本意的操作。**重点在于:**CSRF的请求是跨域且伪造的。** 118 | 119 | 简单说就是:跨站请求伪造的攻击是攻击者通过一些技术手段欺骗用户的浏览器去访问用户曾经认证过的网站并执行一些操作(如发送邮件、发消息、甚至财产操作如转账和购买商品等)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了web登录身份认证的一个漏洞:**简单的身份认证只能保证请求来自用户的浏览器,但不能识别请求是用户自愿发出的。** 120 | 121 | 122 | 123 | **划重点,如何防范** 124 | 125 | 防范 CSRF 攻击可以遵循以下几种规则: 126 | 127 | 1. Get 请求不对数据进行修改 128 | 2. 不让第三方网站访问到用户 Cookie 129 | 3. 阻止第三方网站请求接口 130 | 4. 请求时附带验证信息,比如验证码或者 Token 131 | 132 | #### SameSite 133 | 134 | 可以对 Cookie 设置 `SameSite` 属性。该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。 135 | 136 | #### 验证 Referer 137 | 138 | 对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。 139 | 140 | #### Token 141 | 142 | 服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。 143 | 144 | 145 | 146 | 更多的内容可以去看看[若愚大佬的知乎专栏](https://zhuanlan.zhihu.com/p/22521378) 147 | 148 | 149 | 150 | ## 点击劫持 151 | 152 | 点击劫持是一种**视觉欺骗**的攻击手段。攻击者将需要攻击的网站通过 `iframe` 嵌套的方式嵌入自己的网页中,并将 `iframe` 设置为透明,在页面中透出一个按钮诱导用户点击。 153 | 154 | 对于这种攻击方式,推荐防御的方法有两种。 155 | 156 | **1.X-FRAME-OPTIONS** 157 | 158 | `X-FRAME-OPTIONS` 是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头 就是为了防御用 `iframe` 嵌套的点击劫持攻击。 159 | 160 | 该响应头有三个值可选,分别是 161 | 162 | - `DENY`,表示页面不允许通过 `iframe` 的方式展示 163 | - `SAMEORIGIN`,表示页面可以在相同域名下通过 `iframe` 的方式展示 164 | - `ALLOW-FROM`,表示页面可以在指定来源的 `iframe` 中展示 165 | 166 | 167 | 168 | **JS 防御** 169 | 170 | 对于某些远古浏览器来说,并不能支持上面的这种方式,那我们只有通过 JS 的方式来防御点击劫持了。 171 | 172 | ```html 173 | 174 | 179 | 180 | 181 | 189 | 190 | ``` 191 | 192 | 以上代码的作用就是当通过 `iframe` 的方式加载页面时,攻击者的网页直接不显示所有内容了。 193 | 194 | 195 | 196 | ## 中间人攻击 197 | 198 | 中间人攻击是**攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了**。攻击者不仅能获得双方的通信信息,还能修改通信信息。 199 | 200 | 通常来说不建议使用公共的 Wi-Fi,因为很可能就会发生中间人攻击的情况。如果你在通信的过程中涉及到了某些敏感信息,就完全暴露给攻击方了。 201 | 202 | 防御中间人攻击其实并不难,只需要增加一个安全通道来传输信息。**HTTPS 就可以用来防御中间人攻击**,但是并不是说使用了 HTTPS 就可以高枕无忧了,因为如果你没有完全关闭 HTTP 访问的话,攻击方可以通过某些方式将 HTTPS 降级为 HTTP 从而实现中间人攻击。 203 | 204 | 205 | 206 | ## 总结 207 | 208 | 这里总结了一些常见的攻击和防范措施,主要是让自己有个大致的影响,不要别人说起来,自己完全不懂。 209 | 210 | 另外,yuchengkai还推荐了一个学习的respiratory-[the-book-of-secret-knowledge](https://github.com/trimstray/the-book-of-secret-knowledge)。先记在这里,后面再去深入学习一下。 -------------------------------------------------------------------------------- /性能优化/性能优化.md: -------------------------------------------------------------------------------- 1 | # 性能优化 2 | 3 | 关于优化之前在浏览器渲染的部分也提到过了,可以去参考[看一下](https://github.com/YiiChitty/FrontEndLearning/blob/master/浏览器/浏览器渲染.md)。 4 | 5 | 这里说一下JS的性能优化,在说优化之前需要了解一下引擎是怎么解析的: 6 | 7 | 8 | 9 | ## 基于Chrome 的 **V8** 引擎 10 | 11 | 在V8引擎下,JS 代码首先会解析为抽象语法树(AST),然后会通过解释器或者编译器转化为 **Bytecode** 或者 **Machine Code** 12 | 13 |  14 | 15 | JS 会首先被解析为 AST,解析的过程其实是略慢的。代码越多,解析的过程也就耗费越长,这也是我们需要压缩代码的原因之一。 16 | 17 | 然后 **Ignition** 负责将 AST 转化为 Bytecode,**TurboFan** 负责编译出优化后的 Machine Code,并且 Machine Code 在执行效率上优于 Bytecode。 18 | 19 | 20 | 21 | **问题来了,什么情况下代码会编译为MachineCode?** 22 | 23 | JS 是一门**动态类型**的语言,并且还有一大堆的规则。简单的加法运算代码,内部就需要考虑好几种规则,比如数字相加、字符串相加、对象和字符串相加等等。这样的情况也就势必导致了内部要增加很多判断逻辑,降低运行效率。 24 | 25 | 26 | 27 | 如果一个函数被**多次调用**并且参数一直传入某个类型,那么 V8 就会认为该段代码可以编译为 Machine Code,因为你**固定了类型**,不需要再执行很多判断逻辑了。 28 | 29 | 举一个栗子: 30 | 31 | ```js 32 | function example(x){ 33 | return x+1; 34 | } 35 | for (let i=i;i<100;i++){ 36 | example(i); 37 | } 38 | ``` 39 | 40 | 这种情况下,example被多次调用,并且参数一直是number类型。 41 | 42 | 43 | 44 | 但是如果一旦我们传入的参数**类型改变**,那么 Machine Code 就会被 **DeOptimized**为 Bytecode,这样就有性能上的一个损耗了。所以如果我们希望代码能多的编译为 Machine Code 并且 DeOptimized 的次数减少,就应该尽可能保证传入的**类型一致**。 45 | 46 | 另外,编译器还有个 **Lazy-Compile**,当函数没有被执行的时候,会对函数进行一次预解析,直到代码被执行以后才会被解析编译。 47 | 48 | 对于上述代码来说,example函数需要被预解析一次,然后在调用的时候再被解析编译。但是对于这种函数马上就被调用的情况来说,预解析这个过程其实是多余的,那么有什么办法能够让代码不被预解析呢? 49 | 50 | 其实很简单,我们只需要给函数**套上括号**就可以了 51 | 52 | ```js 53 | (function example(obj) { 54 | return x + 1 55 | }) 56 | ``` 57 | 58 | 但是我们不可能为了性能优化,给所有的函数都去套上括号。并且,也不是所有函数都需要这样做。 59 | 60 | 61 | 62 | **小结一下** 63 | 64 | 通过对V8引擎的学习: 65 | 66 | 为了减少编译时间,可以采用**减少代码文件的大小**或者**减少书写嵌套函数**的方式; 67 | 68 | 同时,为了让引擎来优化代码,在使用js的时候,应该尽可能保证传入参数的**类型一致** 69 | 70 | 或许第二点也是使用 TypeScript 能够带来的好处之一 71 | 72 | 73 | 74 | 相关文章: 75 | 76 | [JavaScript 引擎基础:Shapes 和 Inline Caches](https://hijiangtao.github.io/2018/06/17/Shapes-ICs/) 77 | 78 | [WebAssembly 系列(二)JavaScript Just-in-time (JIT) 工作原理](https://www.w3ctech.com/topic/2026) 79 | 80 | 81 | 82 | 关于js优化就先说到这里,接下来是一些琐碎的点: 83 | 84 | 85 | 86 | ## 图片优化 87 | 88 | ### 计算图片大小 89 | 90 | 对于一张 100 * 100 像素的图片来说,图像上有 10000 个像素点,如果每个像素的值是 **RGBA** 存储的话,那么也就是说每个像素有 4 个通道,每个通道 1 个字节(8 位 = 1个字节),所以该图片大小大概为 39KB(10000 * 1 * 4 / 1024)。 91 | 92 | 但是在实际项目中,一张图片可能并不需要使用那么多颜色去显示,我们可以通过减少每个像素的调色板来相应缩小图片的大小。 93 | 94 | 如何优化图片: 95 | 96 | - **减少像素点** 97 | - **减少每个像素点能够显示的颜色** 98 | 99 | ### 图片加载优化 100 | 101 | 1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。 102 | 2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。 103 | 3. 小图使用 base64 格式 104 | 4. 将多个图标文件整合到一张图片中(雪碧图) 105 | 5. 选择正确的图片格式: 106 | - 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好 107 | - 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替 108 | - 照片使用 JPEG 109 | 110 | 111 | 112 | ## DNS 预解析 113 | 114 | DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。 115 | 116 | ```html 117 | 118 | ``` 119 | 120 | 121 | 122 | ## 防抖与节流 123 | 124 | 这个在js部分写过了,可以直接[查看](https://github.com/YiiChitty/FrontEndLearning/blob/master/Javascript/防抖节流.md)。 125 | 126 | 127 | 128 | ## 预渲染 129 | 130 | 可以通过预渲染**将下载的文件预先在后台渲染**,可以使用以下代码开启预渲染 131 | 132 | ```html 133 | 134 | ``` 135 | 136 | 预渲染虽然可以提高页面的加载速度,但是要**确保该页面大概率会被用户在之后打开**,否则就是白白浪费资源去渲染。 137 | 138 | 139 | 140 | ## 懒执行 141 | 142 | 懒执行就是**将某些逻辑延迟到使用时再计算**。 143 | 144 | 该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。 145 | 146 | 懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。 147 | 148 | 149 | 150 | ## 懒加载 151 | 152 | 懒加载就是**将不关键的资源延后加载**。 153 | 154 | 懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。 155 | 156 | 对于图片来说,先设置图片标签的 `src` 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 `src` 属性,这样图片就会去下载资源,实现了图片懒加载。 157 | 158 | 懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。 159 | 160 | 161 | 162 | ## CDN 163 | 164 | CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。 165 | 166 | 因此,我们可以将**静态资源尽量使用 CDN 加载**,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意**CDN 域名要与主站不同**,否则每次请求都会带上主站的 Cookie,平白消耗流量。 167 | 168 | -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现.md: -------------------------------------------------------------------------------- 1 | # Javascript原生实现 2 | 3 | 为面试做准备,每次面试前必须速看一遍! 4 | 5 | ## call 6 | 7 | ```javascript 8 | Function.prototype.myCall=function(context){ 9 | if(typeof this !=='function'){ 10 | throw new TypeError('error'); 11 | } 12 | context=context||window; 13 | context.fn=this; 14 | const args=[...arguments].slice(1); 15 | const result=context.fn(...args); 16 | delete context.fn; 17 | return result; 18 | } 19 | ``` 20 | 21 | ## apply 22 | 23 | ```javascript 24 | Function.prototype.myApply=function(context){ 25 | if(typeof this !== 'function'){ 26 | throw new TypeEerror('Error'); 27 | } 28 | context=context||window; 29 | context.fn=this; 30 | let result; 31 | if(arguments[1]){ 32 | result=context.fn(...arguments[1]); 33 | }else{ 34 | result=context.fn(); 35 | } 36 | delete context.fn; 37 | return result; 38 | } 39 | ``` 40 | 41 | ## bind 42 | 43 | ```javascript 44 | Function.prototype.myBind=function(context){ 45 | if(typeof this !=='function'){ 46 | throw new TypeError('Error'); 47 | } 48 | const _this=this; 49 | const args=[...arguments].slice(1); 50 | //返回一个函数 51 | return function F(){ 52 | //通过new的方式 53 | if(this instanceof F){ 54 | return new _this(...args,...arguments) 55 | } 56 | return _this.apply(context,args,concat(...arguments)); 57 | } 58 | } 59 | ``` 60 | 61 | ## new 62 | 63 | ```javascript 64 | function create(con,...args){ 65 | let obj={}; 66 | obj._proto_=con.prototype; 67 | //绑定this并执行构造函数 68 | let result=con.apply(obj,args); 69 | return result instanceof Object? result:obj 70 | } 71 | ``` 72 | 73 | ## 防抖 74 | 75 | ```javascript 76 | function debounce=(func,wait)=>{ 77 | let timer=0; 78 | return function(...args){ 79 | if(timer){ 80 | clearTimeout(timer); 81 | } 82 | timer=setTimeout(()=>{ 83 | func.apply(this,args); 84 | },wait) 85 | } 86 | } 87 | ``` 88 | 89 | ## 节流 90 | 91 | ```javascript 92 | function throttle(func,wait,option){ 93 | let last; 94 | return function(...args){ 95 | let now+=new Date(); 96 | if(!last||now>last+wait){ 97 | last=now; 98 | func.apply(this,args); 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | ## 手写Promise 105 | 106 | ```javascript 107 | const PENDING="pending" 108 | const RESOLVED="resolved" 109 | const REJECTED="rejected" 110 | 111 | function Promise(fn){ 112 | //先写大体框架 113 | const self=this 114 | self.value=null 115 | self.reason=null 116 | self.state=PENDING 117 | //用于保存 then 中的回调,只有当 promise状态为 pending 时才会缓存,并且每个实例至多缓存一个 118 | self.onFulfilledCallbacks=[] 119 | self.onRejectedCallbacks=[] 120 | 121 | //再写resolve和reject 122 | function resolve(value){ 123 | if(self.state=PENDING){ 124 | self.state=RESOLVED 125 | self.value=value 126 | self.onFullFilledCallbacks.forEach(onFulFilled => onFulFilled()) 127 | } 128 | } 129 | function reject(reason){ 130 | if (self.state==PENDING){ 131 | self.state=REJECTED 132 | self.reason=reason 133 | self.onRejectedCallbacks.foreach(onRejected=>onRejected()) 134 | } 135 | } 136 | //处理执行函数 137 | try{ 138 | fn(resolve,reject) 139 | }catch(e){ 140 | reject(e) 141 | } 142 | } 143 | //实现Promise.then 144 | Promise.prototype.then=function(onFulFilled,onRejected){ 145 | const that = this 146 | let p2Resolve, p2Reject; 147 | //先生成一个新的对象 148 | let p2 = new promise((resolve, reject) => { 149 | p2Resolve = resolve; 150 | p2Reject = reject; 151 | }); 152 | //判断状态 153 | if(that.state==PENDING){ 154 | that.onFulfilledCallbacks.push(()=>{ 155 | onFulfilled(that.value) 156 | p2Resolve() 157 | }) 158 | that.onRejectedCallbaxks.push(()=>{ 159 | onRejected(that.reason) 160 | p2Reject() 161 | }) 162 | }else if (that.state==RESOLVED){ 163 | onFulfilled(that.value) 164 | p2Resolve() 165 | } 166 | else if (this.state == REJECTED) { 167 | onRejected(this.reason); 168 | p2Reject(); 169 | } 170 | return p2; 171 | } 172 | ``` 173 | 174 | ## 实现Promise.all 175 | 176 | ```javascript 177 | //Promise.all 178 | //多个Promise任务并行执行,输入是一个数组,最后输出一个新的Promise对象 179 | //1.如果全部成功执行,则以数组的方式返回所有Promise任务的执行结果; 180 | //2.如果有一个Promise任务rejected,则只返回 rejected 任务的结果。 181 | function promiseAll(promises){ 182 | return new Promise(resolve,reject)=>{ 183 | if(!Array.isArray(promises)){ 184 | return reject(new Error("arguments must be an array")) 185 | } 186 | let promisecounter=0, 187 | promiseNum=promises.length, 188 | //保存结果 189 | resolvedValues=new Array(promisNum); 190 | for(let i =0;i{ 193 | promiscounter++; 194 | resolvedValues[i]=value; 195 | if(promiseCounter==promiseNum) { 196 | return resolve(resolvedValues); 197 | } 198 | }).catch(err=>{reject(err)}) 199 | })(i) 200 | } 201 | } 202 | ``` 203 | 204 | ## 深拷贝 205 | 206 | ```javascript 207 | function deepClone(obj) { 208 | function isObject(o) { 209 | return (typeof o === 'object' || typeof o === 'function') && o !== null 210 | } 211 | 212 | if (!isObject(obj)) { 213 | throw new Error('非对象') 214 | } 215 | 216 | let isArray = Array.isArray(obj) 217 | let newObj = isArray ? [...obj] : { ...obj } 218 | Reflect.ownKeys(newObj).forEach(key => { 219 | newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] 220 | }) 221 | 222 | return newObj 223 | } 224 | ``` 225 | 226 | -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/Ajax.js: -------------------------------------------------------------------------------- 1 | //简单流程 2 | 3 | //实例化 4 | let xhr=new XMLHttpRequest(); 5 | //初始化 6 | xhr.open(methond,url,async); 7 | //发送请求 8 | xhr.onreadystatechange=()=>{ 9 | if(xhr.readyState===4&&xhr.status===200){ 10 | console.log(xhr.responseText); 11 | } 12 | } 13 | 14 | //有Promise的实现 15 | 16 | function ajax(options){ 17 | //地址 18 | const url=options.url; 19 | //请求方法 20 | const method=options.methond.toLocalLowerCase()||'get'; 21 | //默认为异步true 22 | const async=options.async; 23 | //请求参数 24 | const data=options.data; 25 | //实例化 26 | let xhr=new XMLHttpRequest(); 27 | //超时时间 28 | if(options.timeout&&options.timeout>0){ 29 | xhr.timeout=options.timeout; 30 | } 31 | 32 | 33 | return new Promise((resolve,reject)=>{ 34 | xhr.ontimeout=()=>reject&&reject('请求超时'); 35 | 36 | //状态变化回调 37 | xhr.onreadystatechange=()=>{ 38 | if(xhr.readyState==4){ 39 | if(xhr.status>=200&&xhr.status<300||xhr.status==304){ 40 | resolve && resolve(xhr.responseText) 41 | }else{ 42 | reject&&reject(); 43 | } 44 | } 45 | } 46 | 47 | //错误回调 48 | xhr.onerr=err=>reject&&reject(); 49 | 50 | let paramArr=[]; 51 | let encodeData; 52 | //处理请求参数 53 | if(data instanceof Object){ 54 | for(let key in data){ 55 | paramArr.push(encodeURIComponent(key)+"="+encodeURIComponent(data[key])); 56 | } 57 | encodeData=paramArr.join('&'); 58 | } 59 | 60 | //get请求拼接参数 61 | if(method==='get'){ 62 | //检查url中有没有?以及它的位置 63 | const index=url.indexOf('?'); 64 | if(index===-1) url+='?'; 65 | else if (index !==url.length -1) url+='&'; 66 | url += encodeData; 67 | } 68 | 69 | //初始化 70 | xhr.open(method,url,async); 71 | 72 | if(method ==='get') xhr.send(encodeData); 73 | else{ //post设置请求头 74 | xhr.setRequestHeader('Content-Type','application/x-www-form-unlencoded;charset=UTF-8'); 75 | xjr.send(encodeData); 76 | } 77 | }) 78 | 79 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/Apply.js: -------------------------------------------------------------------------------- 1 | //与call的区别是,第二个第二个参数传入的是数组 2 | Function.prototype.Myapply()=function(context){ 3 | if (typeof this !=='function'){ 4 | throw TypeError('Error'); 5 | } 6 | context=context||window; 7 | context.fn=this; 8 | let result; 9 | //判断是否存在数组参数,毕竟是可选参数 10 | if(arguments[1]){ 11 | result=context.fn(...arguments[1]); 12 | }else{ 13 | result=context.fn(); 14 | } 15 | delete context.fn; 16 | return result; 17 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/Bind.js: -------------------------------------------------------------------------------- 1 | //function.bind(thisArg[, arg1[, arg2[, ...]]]) 2 | //thisArg 调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。 3 | // 当使用bind在setTimeout中创建一个函数(作为回调提供)时,作为thisArg传递的任何原始值都将转换为object。 4 | // 如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg。 5 | //arg1, arg2, ... 当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。 6 | 7 | //因为返回的是一个函数,所以要考虑new的情况 8 | //由于链式调用,还要小心只有第一次传入的this才是被绑定的对象!!!!!如果是空的话,就绑在作用域上!!! 9 | //f.bind(obj,1)(2)(3) 10 | Function.prototype.myBind=function(context){ 11 | if(typeof this !=='function'){ 12 | throw TypeError ('Error'); 13 | } 14 | const _this=this; 15 | const args=[...arguments].slice(1); 16 | return function F(){ 17 | //如果采用的是new方法的话,就不动this 18 | if(this instanceof F){ 19 | //链式调用要把新旧参数加上去哦 20 | return new _this(...args,...arguments); 21 | }else{ 22 | return _this.apply(context,args.concat([...arguments])); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/Call.js: -------------------------------------------------------------------------------- 1 | //传入一个this 绑定上所有的属性 2 | Function.prototype.MyCall=function(context){ 3 | if(typeof this !=='function'){ 4 | throw new TypeError('error') 5 | } 6 | context=context||window; 7 | context.fn=this; 8 | const args=[...arguments].slice(1);//除去要绑定的对象,剩下参数应该绑定进去 9 | const result=context.fn(...args); 10 | delete context.fn; 11 | return result; 12 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/Copy.js: -------------------------------------------------------------------------------- 1 | //浅拷贝 2 | Object.assign -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/Create.js: -------------------------------------------------------------------------------- 1 | //Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 2 | Object.prototype.mycreate=function(obj){ 3 | function F(){} 4 | F.prototype=obj; 5 | return new F(); 6 | } 7 | 8 | //其实也可以这么写 9 | Object.prototype.mycreate=function(obj){ 10 | return {'_protp_':obj}; 11 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/DeepCopy.js: -------------------------------------------------------------------------------- 1 | //JSON.parse 解决不了循环引用的问题,会忽略undefined\symbol\函数 2 | let a={} 3 | let b=JSON.parse(JSON.stringify(a)); 4 | //循环引用是说a.b.d=a.b这样的 5 | 6 | //MessageChannel含有内置类型,不包含函数 7 | function structuralClone(obj){ 8 | return new Promise(resolve=>{ 9 | const{port1,port2}=new MessageChannel(); 10 | port2.onmessage=ev=>{resolve(ev.data)} 11 | port1.postMessage(obj); 12 | }) 13 | } 14 | 15 | //可以处理循环引用对象、也可以处理undefined 16 | //不过它是异步的 17 | const test =async()=>{ 18 | const clone=await structuralClone(obj); 19 | console.log(clone); 20 | } 21 | test(); 22 | 23 | 24 | //手写一个简单的deepclone 25 | function deepClone(obj){ 26 | function isObject(o){ 27 | return (typeof o==='object'||typeof o==='function')&&o !==null; 28 | } 29 | if(!isObject(obj)){ 30 | throw new Error('Not Object') 31 | } 32 | 33 | let isArray=Array.isArray(obj); 34 | let newObj=isArray?[...obj]:{...obj}; 35 | Reflect.ownKeys(newObj).forEach(item=>{ 36 | newObj[item]=isObject(obj[item])?deepClone(obj[item]):obj[item]; 37 | }) 38 | return newObj; 39 | } 40 | 41 | //还可以这样写 42 | function deepClone(){ 43 | let copy=obj instanceof Array? []:{}; 44 | for(let i in obj){ 45 | if(obj.hasOwnProperty(i)){ 46 | copy[i]=typeof obj[i] ==='object'?deepClone(obj[i]):obj[i]; 47 | } 48 | } 49 | return copy; 50 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/DoubleBind.js: -------------------------------------------------------------------------------- 1 | //双向数据绑定 2 | let obj={}; 3 | let input=document.getElementById('input'); 4 | let span=document.getElementById('span'); 5 | 6 | //数据劫持 7 | Object.defineProperty(obj,'text',{ 8 | configurable:true, 9 | enumerable:true, 10 | get(){ 11 | console.log('获取数据'); 12 | }, 13 | set(newVal){ 14 | console.log('数据更新'); 15 | input.value=newVal; 16 | span.innerHTML=newVal; 17 | } 18 | }) 19 | 20 | //监听 21 | input.addEventListener('keyup',function(e){ 22 | obj.text=e.target.value; 23 | }) -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/EventBus.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YiiChitty/FrontEndLearning/5a13a5b2cd0fd07e9f7c26d0003ab472815951e0/手撕代码/Javascript原生实现JS版/EventBus.js -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/Instanceof.js: -------------------------------------------------------------------------------- 1 | //本质是看左边变量的原型链是否含有右边的原型 2 | function myInstanceof(left,right){ 3 | //要找的原型 4 | let prototype=right.prototype; 5 | left=left._proto_; 6 | while(true){ 7 | //全部遍历完都没有 false 8 | if(left===null||left==="undefined") return false; 9 | //匹配上 true 10 | if(left===prototype) return true; 11 | //找下一个原型链 12 | left=left._proto_; 13 | } 14 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/New.js: -------------------------------------------------------------------------------- 1 | //使用new时,1 内部生成一个obj 2 链接到原型 3 obj绑定this(使用构造函数的this) 4 返回新对象(原始值的话忽略,如果是对象的话就返回这个对象) 2 | Function.prototype.myNew()=function(func,...args){ 3 | let obj={}; 4 | obj._proto_=func.prototype; 5 | let result=func.apply(obj,args); 6 | return result instanceof Object? result:obj; 7 | } 8 | 9 | //不过其实也可以用Object.create 10 | //以构造函数的原型对象为原型,创建一个空对象;等价于 创建一个新的对象,把他的_proto_指向prototype 11 | Function.prototype.myNew()=function(func,...args){ 12 | let obj=Object.create(func.prototype); 13 | let result=func.apply(obj,args); 14 | return result instanceof Object? result:obj; 15 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/Promise.js: -------------------------------------------------------------------------------- 1 | const PENDING="pending"; 2 | const RESOLVED="resolved"; 3 | const REJECTED="rejected"; 4 | 5 | function Promise(fn){ 6 | const self=this; 7 | self.state=PENDING; 8 | self.value=null; 9 | self.reason=null; 10 | //保存then的回调函数,只有当pending时才会缓存 11 | self.onFullfilledCallback=[]; 12 | self.onRejectedCallback=[]; 13 | 14 | //resolve和reject函数 15 | function resolve(value){ 16 | if(self.state==PENDING){ 17 | self.state=RESOLVED; 18 | self.value=value; 19 | self.onFullfilledCallback.array.forEach(onfullfilled => { 20 | onfullfilled(); 21 | }); 22 | } 23 | } 24 | function reject(reason){ 25 | if(self.state==PENDING){ 26 | self.state=REJECTED; 27 | self.reason=reason; 28 | self.onRejectedCallback.forEach(onrejected=>{ 29 | onrejected(); 30 | }) 31 | } 32 | } 33 | //执行fn 34 | try{ 35 | fn(resolve,reject); 36 | }catch(e){ 37 | reject(e); 38 | } 39 | } 40 | //实现promise.then 41 | Promise.prototype.then=function(onfullfilled,onrejected){ 42 | const that=this; 43 | let p2Resolve,p2Reject; 44 | //生成一个新的Promise 45 | let p2=new Promise((resolve,reject)=>{ 46 | p2Resolve=resolve; 47 | p2Reject=reject; 48 | }); 49 | if(that.state==PENDING){ 50 | that.onFullfilledCallback.push(()=>{ 51 | onfullfilled(that.value); 52 | p2Resolve(); 53 | }); 54 | that.onRejectedCallback.push(()=>{ 55 | onrejected(that.reason); 56 | p2Reject(); 57 | }) 58 | }else if (that.state==RESOLVED){ 59 | onfullfilled(that.value); 60 | p2Resolve(); 61 | }else if (that.state==REJECTED){ 62 | onrejected(that.reason); 63 | p2Reject(); 64 | } 65 | return p2; 66 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/rem的实现原理.js: -------------------------------------------------------------------------------- 1 | function setRem(){ 2 | let doc=document.documentElement; 3 | let width=doc.getBoundingClientRect().width; 4 | let rem=width/75 5 | doc.style.fontsize=rem+'px'; 6 | } 7 | addEventListener("resize",setRem); -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/setInterval.js: -------------------------------------------------------------------------------- 1 | //使用setTimeout模拟setInterval 2 | //避免因执行时间导致间隔执行时间不一致 3 | setTimeout(function(){ 4 | //do something 5 | //arguments.callee引用该函数体内当前正在执行的函数 6 | setTimeout(arguments.callee,500); 7 | },500) -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/懒加载.js: -------------------------------------------------------------------------------- 1 | //首先html的img标签设置一个无关的标签比如说data,加载到的时候再替换成src 2 | //思路就是到视口区了再替换过去加载 3 | 4 | let img=document.querySelectorAll('img'); 5 | //可视区大小 6 | let clientHeight=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight; 7 | function lazyload(){ 8 | //滚动卷走的高度 9 | let scrollTop=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop; 10 | for(let i=0;i0&&xwindow.innerWidth-drag.offsetWidth){ 17 | //超出了就放在innerWidth的位置 18 | left=window.innerWidth-drag.offsetWidth; 19 | } 20 | if(top<0) top=0; 21 | else if (top>window.innerHeight-drag.offsetHeight){ 22 | top=window,innerHeight-drag.offsetHeight; 23 | } 24 | drag.style.left=left+'px'; 25 | drag.style.top=top+'px'; 26 | } 27 | drag.onmouseup=function(e){ 28 | e=e||window.event; 29 | this.onmousemove=null; 30 | this.onmousedown=null; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/继承.js: -------------------------------------------------------------------------------- 1 | //实现一个继承方法 2 | 3 | //借助构造函数继承实例属性 4 | function Child(){ 5 | Parent.call(this); 6 | } 7 | 8 | //寄生继承原型 9 | (function(){ 10 | let Super=function(){} 11 | Super.prototype=Parent.prototype; 12 | Child.prototype=new Super(); 13 | })() -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/节流.js: -------------------------------------------------------------------------------- 1 | //节流就是说在一定时间内只会被触发一次 2 | //比如滚动事件啥的 3 | function throttle(fn,delay){ 4 | let last;//上次被触发的时间 5 | return function(...args){ 6 | let now=+new Date(); 7 | if(!last||now>last+delay){ 8 | last=now; 9 | func.apply(this,args); 10 | } 11 | } 12 | } 13 | 14 | 15 | //使用 16 | function fn(){ 17 | console.log('节流'); 18 | } 19 | addEventListener('scroll',throttle(fn,1000)); -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/路由.js: -------------------------------------------------------------------------------- 1 | //hash路由 2 | class Route{ 3 | constructor(){ 4 | //存储对象 5 | this.routes=[]; 6 | //当前hash 7 | this.currentHash='' 8 | //绑定this.避免监听时this指向改变 9 | this.freshRoute=this.freshRoute.bind(this); 10 | //监听 11 | window.addEventListener('load',this.freshRoute,false); 12 | window.addEventListener('hashmessage',this.freshRoute,false); 13 | } 14 | //存储 15 | storeRoute(path,cb){ 16 | this.routes[path]=cb||function(){}; 17 | } 18 | } -------------------------------------------------------------------------------- /手撕代码/Javascript原生实现JS版/防抖.js: -------------------------------------------------------------------------------- 1 | //实现按钮防2次点击操作 2 | //在规定时间内再次触发就清除定时后重新设置,直到不触发了 3 | function debounce(fn,delay){ 4 | let timer=0; 5 | return function(...args){ 6 | if (timer) clearTimeout(timer) 7 | timer=setTimeout(()=>{func.apply(this,args)},delay); 8 | } 9 | } 10 | 11 | function fn(){ 12 | console.log('防抖') 13 | } 14 | addEventListener('scroll',debounce(fn,1000)) -------------------------------------------------------------------------------- /手撕代码/面试笔试中遇到的题.md: -------------------------------------------------------------------------------- 1 | # 面试笔试中遇到的题 2 | 3 | ## 1.数组去重通用API 4 | 5 | 这是8月3号网易提前批笔试题问答题的第二道。 6 | 7 | 要求就是写一个数组去重的API,第一个参数是一个数组,第二个参数是一个函数(对象数组去重的规则)。 8 | 9 | 觉得这道题考得很经典,所以在这里记一下。 10 | 11 | 思路就是当它是数组的时候,直接用这个Set数据结构去重,如果是个对象数据的话,就新建一个Map,按照传入的函数返回的值,存入Map。这里用到了filter,它是用传入的函数测试所有的元素,并且返回所有通过测试的元素。 12 | 13 | ```js 14 | function fn(arr,rule){ 15 | if(!rule){ 16 | return Array.from(new Set([...arr])); 17 | }else{ 18 | const res=new Map(); 19 | return arr.filter((a)=>!res.has(rule(a))&&res.set(rule(a),1)); 20 | } 21 | } 22 | ``` 23 | 24 | ## 2.判断数组是否连续的问题 25 | 26 | 这是8月6号拼多多面试的时候让写的题目。 27 | 28 | [0,10] [8,10] [10,30] true 29 | 30 | [0,10] [25 30] [8 20] false 31 | 32 | 说让写一个方法来实现这个功能,判断数组是不是连续的 33 | 34 | 思路一: 35 | 36 | 当时脑子卡,说用每个子数组中的第一个元素跟前一个数组最大值做比较,比他小就说明是连续的。面试官说,给你的不是排好序的,我说那就先排序,他问了怎么排序,我说没啥区别啊,都是取第一个子数组的第一个数排序……快排冒泡都行。 37 | 38 | 思路二: 39 | 40 | 思路一也太复杂了,我一直在脑子里尬住,然后面完马上想到了一个骚操作,用一个新的数组来判断,直接根据每个数组的首尾,给一个新的数组全部赋值,比如都是1。最后只需要判断之歌数组有没有includes(undefined)就可以判断是不是连续的了。 41 | 42 | 思路三: 43 | 44 | 坐在位置上恰了点零食,脑子突然灵光了,这TM不就是找子数组起始坐标的最大值和结束坐标的最小值吗?????? 45 | 46 | 思路说完了,其实题挺简单的,当献祭首次面试脑子卡吧。 47 | 48 | 附上思路二和思路三的代码 49 | 50 | ```js 51 | //思路2:想的时候不觉得复杂度高,现在看……复杂度真的高 52 | function check(lines){ 53 | let result=[]; 54 | for(let i=0;i maxstart){ 73 | maxstart=lines[i][0]; 74 | }if(lines[i][1]minend ? true:false 80 | } 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /浏览器/浏览器存储.md: -------------------------------------------------------------------------------- 1 | # 浏览器存储 2 | 3 | ## 存储 4 | 5 | 刷了很多面经,里面都会出现这样一个问题: 6 | 7 | > 有几种方式可以实现存储功能,分别有什么优缺点?什么是 Service Worker? 8 | 9 | 这一节,就来解答这个问题: 10 | 11 | 首先存储的方式有以下四种: 12 | 13 | cookie,localStorage,sessionStorage,indexDB, 14 | 15 | 关于他们的区别如下: 16 | 17 | | **特性** | **cookie** | **localStorage** | **sessionStorage** | **indexDB** | 18 | | :----------- | :----------------------------------------- | ------------------------ | ------------------ | ------------------------ | 19 | | 数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 | 20 | | 数据存储大小 | 4K | 5M | 5M | 无限 | 21 | | 与服务端通信 | 每次都会携带在 header 中,对于请求性能影响 | 不参与 | 不参与 | 不参与 | 22 | 23 | 所以,处于性能考虑,如果没有大量数据存储需求的话,可以使用`localStorage` 和 `sessionStorage` 。对于不怎么改变的数据尽量使用 `localStorage` 存储,否则可以用 `sessionStorage` 存储。 24 | 25 | 对于Cookie来说,还需要注意安全性: 26 | 27 | | 属性 | 作用 | 28 | | :-------: | :----------------------------------------------------------: | 29 | | value | 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识 | 30 | | http-only | 不能通过 JS 访问 Cookie,减少 XSS 攻击 | 31 | | secure | 只能在协议为 HTTPS 的请求中携带 | 32 | | same-site | 规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击 | 33 | 34 | 35 | 36 | 关于存储的区别就大致说完了,接下来解答下一个问题: 37 | 38 | > 什么是 Service Worker? 39 | 40 | ## Service Worker 41 | 42 | Service Worker 是运行在浏览器背后的**独立线程**,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 **HTTPS**。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。 43 | 44 | Service Worker 实现缓存功能一般分为三个步骤: 45 | 46 | - 先注册 Service Worker 47 | - 监听到 `install` 事件以后就可以缓存需要的文件 48 | - 下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。 49 | 50 | 现在来模拟实现一下这个步骤: 51 | 52 | > 以下代码来自yuchengkai大佬的[前端知识图谱](https://yuchengkai.cn/docs/frontend/browser.html#service-worker) 53 | 54 | ```javascript 55 | //比如在index.js里注册一个Service Worker 56 | if (navigator.serviceWorker){ 57 | navigator.serviceWorker.register('xx.js').then( 58 | function (registration){ 59 | console.log ('注册成功') 60 | }).catch(function(e){ 61 | console.log('注册失败') 62 | }) 63 | } 64 | 65 | //xx.js 66 | //监听install事件,缓存所需要的文件 67 | self.addEventListener('install',e=>{ 68 | e.wiatUntil( 69 | caches.open('my-cache').then(function(cache){ 70 | return cache.addAll(['./index.html','./index.js']) 71 | }) 72 | ) 73 | }) 74 | 75 | //拦截请求 76 | //如果缓存中已经有数据就直接用缓存,否则去请求数据 77 | self.addEventListener('fetch',e=>{ 78 | e.respondWith( 79 | cache.match(e.request).then(function(response){ 80 | if(response){ 81 | return response; 82 | } 83 | console.log('fetch source'); 84 | }) 85 | ) 86 | }) 87 | ``` 88 | 89 | ## 浏览器缓存机制 90 | 91 | 缓存可以说是性能优化中**简单高效**的一种优化方式了,它可以**显著减少网络传输所带来的损耗**。 92 | 93 | 对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。 94 | 95 | 浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来(这和咱们的状态码304有关嗷),这样就减少了响应数据。 96 | 97 | 下面从缓存位置和缓存策略来说明: 98 | 99 | ### 1.缓存位置 100 | 101 | 从缓存位置上来说分为四种,并且各自有**优先级**,当依次查找缓存且都没有命中的时候,才会去请求网络。顺序是: 102 | 103 | 1. Service Worker 104 | 2. Memory Cache 105 | 3. Disk Cache 106 | 4. Push Cache 107 | 5. 网络请求 108 | 109 | #### Service Worker 110 | 111 | 前面已经说过了Service Worker了,它的缓存与浏览器其他内建的缓存机制不同,它可以让我们**自由控制**缓存哪些文件、如何匹配缓存、如何读取缓存,并且**缓存是持续性的**。 112 | 113 | 当 Service Worker 没有命中缓存的时候,我们需要去调用 `fetch` 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。**但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。** 114 | 115 | #### Memory Cache 116 | 117 | Memory Cache 也就是内存中的缓存,读取内存中的数据肯定比磁盘快。**内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。** 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 118 | 119 | 那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢? 120 | 121 | 先说结论,这是**不可能**的。首先计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。内存中其实可以存储大部分的文件,比如说 JSS、HTML、CSS、图片等等。但是浏览器会把哪些文件丢进内存这个过程就很**玄学**了。 122 | 123 | 不过,通过一些实践和猜测也得出了一些结论: 124 | 125 | - 对于大文件来说,大概率是不存储在内存中的,反之优先 126 | - 当前系统内存使用率高的话,文件优先存储进硬盘 127 | 128 | #### Disk Cache 129 | 130 | Disk Cache是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache **胜在容量和存储时效性上。** 131 | 132 | 在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。**并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。** 133 | 134 | #### Push Cache 135 | 136 | Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。**并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。** 137 | 138 | Push Cache 在国内能够查到的资料很少,也是因为 HTTP/2 在国内不够普及,但是 HTTP/2 将会是日后的一个趋势。 139 | 140 | 关于这个有说明: 141 | 142 | [HTTP/2 push is tougher than I thought](https://link.juejin.im/?target=https%3A%2F%2Fjakearchibald.com%2F2017%2Fh2-push-tougher-than-i-thought%2F) 143 | 144 | 总结如下: 145 | 146 | - 所有的资源都能被推送,但是 Edge 和 Safari 浏览器兼容性不怎么好 147 | - 可以推送 `no-cache` 和 `no-store` 的资源 148 | - 一旦连接被关闭,Push Cache 就被释放 149 | - 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存 150 | - Push Cache 中的缓存只能被使用一次 151 | - 浏览器可以拒绝接受已经存在的资源推送 152 | - 可以给其他域名推送资源 153 | 154 | #### 网络请求 155 | 156 | 如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。 157 | 158 | 159 | 160 | 为了性能上的考虑,大部分的接口都应该选择好缓存策略,接下来说缓存策略: 161 | 162 | ### 2.缓存策略 163 | 164 | 浏览器缓存策略分为两种:**强缓存**和**协商缓存**,并且缓存策略都是通过设置 HTTP Header 来实现的。 165 | 166 | #### 强缓存 167 | 168 | 可以通过设置http头部实现:用Expires或者cache-control。 169 | 170 | **Expires** 171 | 172 | 是http1.0内容,**Expires受限于本地时间**,如果修改了本地时间,可能会造成缓存失效 173 | 174 | ```http 175 | Expires Wed,31 jul 2019 12:29:00 GMT 176 | ``` 177 | 178 | 表示资源在12点29分后过期,需要再次请求。 179 | 180 | **Cache-control** 181 | 182 | http1.1的内容,**优先级比Expires高**,可以**在请求头或者响应头中设置**,并且可以组合使用多种指令。 183 | 184 | ```http 185 | Cache-control:max-age=30 186 | ``` 187 | 188 | 表示资源会在 30 秒后过期,需要再次请求。 189 | 190 | 常见指令: 191 | 192 |  193 | 194 | #### 协商缓存 195 | 196 | 如果缓存过期了,就需要发起请求验证资源是否有更新。协商缓存可以通过设置两种 HTTP Header 实现:`Last-Modified` 和 `ETag` 。 197 | 198 | 当浏览器发起请求验证资源时,如果资源没有做改变,那么服务端就会返回 304 状态码,并且更新浏览器缓存有效期。 199 | 200 | **Last-Modified 和 If-Modified-Since** 201 | 202 | `Last-Modified` 表示本地文件最后修改日期,`If-Modified-Since` 会将 `Last-Modified` 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。 203 | 204 | 但是 `Last-Modified` 存在一些弊端: 205 | 206 | - 如果**本地打开缓存文件,即使没有对文件进行修改,但还是会造成 `Last-Modified`被修改**,服务端不能命中缓存导致发送相同的资源 207 | - 因为 `Last-Modified` 只能以秒计时,**如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了**,不会返回正确的资源 208 | 209 | 因为以上这些弊端,所以在 HTTP / 1.1 出现了 `ETag` 。 210 | 211 | **ETag 和 If-None-Match** 212 | 213 | `ETag` 类似于文件指纹,`If-None-Match` 会将当前 `ETag` 发送给服务器,询问该资源 `ETag` 是否变动,有变动的话就将新的资源发送回来。并且** `ETag` 优先级比 `Last-Modified` **高。 214 | 215 | 216 | 217 | 看完这个部分,我有一个疑问: 218 | 219 | **如果什么缓存策略都没设置,那么浏览器会怎么处理?** 220 | 221 | 这个问题我看有人是这么回答的: 222 | 223 | > 浏览器会采用一个启发式的算法,通常会取响应头中的 `Date` 减去 `Last-Modified` 值的 10% 作为缓存时间。 224 | 225 | ??为啥? 226 | 227 | 228 | 229 | ## 实际场景应用缓存策略 230 | 231 | ### 频繁变动的资源 232 | 233 | 对于频繁变动的资源,首先需要使用 `Cache-Control: no-cache` 使浏览器每次都请求服务器,然后配合 `ETag` 或者 `Last-Modified` 来验证资源是否有效。 234 | 235 | 这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。 236 | 237 | ### 代码文件 238 | 239 | 这里特指除了 HTML 外的代码文件,因为 HTML 文件一般不缓存或者缓存时间很短。 240 | 241 | 一般来说,现在都会使用工具来打包代码,那么我们就可以对文件名进行哈希处理,只有当代码修改后才会生成新的文件名。基于此,我们就可以给代码文件设置缓存有效期一年 `Cache-Control: max-age=31536000`,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。 -------------------------------------------------------------------------------- /浏览器/浏览器渲染.md: -------------------------------------------------------------------------------- 1 | # 浏览器渲染 2 | 3 | 执行渲染有一个渲染引擎。其在不同的浏览器中也不是都相同的,比如在 Firefox 中叫做 **Gecko**,在 Chrome 和 Safari 中都是基于 **WebKit** 开发的。 4 | 5 | 说明:以下内容只针对**WebKit**的渲染引擎。 6 | 7 | 那么浏览器如何进行渲染的呢? 8 | 9 | 首先: 10 | 11 | ## 接收到HTML文件==>转化为DOM树 12 | 13 | 在网络中传输的启示是0、1这些字节数据,当浏览器接收到这些字节数据后,会将它们拼接成字符串(写的代码)。 14 | 15 | 当数据转换为字符串之后,浏览器会将字符串通过词法分析转换为标记(token),这一过程在词法分析中叫做**标记化**(tokenization)。标记还是字符串,是构成代码的**最小单位**。这一过程会将代码分拆成一块块,并给这些内容打上标记,便于理解这些最小单位的代码是什么意思。 16 | 17 | 当结束标记化后,这些标记会紧接着转换为 Node,最后这些 Node 会根据不同 Node 之前的联系构建为一颗 DOM 树。 18 | 19 | 所以浏览器从网络中接收到HTML文件后会进行如下操作: 20 | 21 | **字节数据==> 字符串==>Token==>Node==>DOM** 22 | 23 | 24 | 25 | 当然,在解析 HTML 文件的时候,浏览器还会遇到 CSS 和 JS 文件,这时候浏览器也会去下载并解析这些文件。接下来: 26 | 27 | ## 将CSS文件转换为CSSOM树 28 | 29 | 转换 CSS 到 CSSOM 树的过程和上面的过程是极其类似的 30 | 31 | **字节数据==> 字符串==>Token==>Node==>CSSOM** 32 | 33 | 在这一过程中,浏览器会确定下每一个节点的**样式**到底是什么,并且这一过程其实是**很消耗资源**的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得**递归** CSSOM 树,然后确定具体的元素到底是什么样式。 34 | 35 | 为什么很消耗资源呢? 36 | 37 | 举个简单的例子 38 | 39 | ```css 40 | cpan{ 41 | color:red; 42 | } 43 | span>a>span{ 44 | color:red; 45 | } 46 | ``` 47 | 48 | 就两种设置,第一种浏览器只需要遍历所有span标签后设置颜色就可以了,但是第二种的话,就需要先找到所有span标签,然后再找到span上有a标签的,最后再去找a里面有span的,然后再上色。 49 | 50 | 这是个递归的过程,非常耗时,所以应该**避免写这种过于具体的CSS选择器**。对于html来说,也应该尽量少的添加一个没有意义的标签,保证层级扁平化。 51 | 52 | 53 | 54 | 得到了两个树之后,就需要把两颗树组合成渲染树: 55 | 56 | ## 生成渲染树 57 | 58 |  59 | 60 | 在这一过程中,不是简单的将两者合并就行了。渲染树只会包括**需要显示的节点**和这些节点的样式信息。 61 | 62 | 比如说,如果某个节点是 `display: none` 的,那么就不会在渲染树中显示。 63 | 64 | 当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用 GPU 绘制,合成图层,显示在屏幕上。 65 | 66 | 67 | 68 | ## 反思几个问题 69 | 70 | 知晓了浏览器的渲染原理,现在来反思几个问题: 71 | 72 | **为什么操作DOM性能很差?** 73 | 74 | 因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。 75 | 76 | **操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题**。 77 | 78 | 79 | 80 | **什么情况下会阻塞渲染** 81 | 82 | 1.首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。 83 | 84 | 所以如果你想渲染的越快,你越应该**降低一开始需要渲染的文件大小**,并且做到**HTML扁平层级,优化CSS选择器**。 85 | 86 | 2.然后当浏览器在解析到 `script` 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。 87 | 88 | 所以,如果想首屏渲染的越快,就越**不应该在首屏就加载 JS 文件**,这也是都建议将 `script` 标签放在 `body` 标签底部的原因。 89 | 90 | 当然也不必一杆子打全船,可以有替换方案可以给script标签添加上defer 和 async属性。 91 | 92 | > 当 script标签加上 `defer` 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以对于这种情况你可以把 `script` 标签放在任意位置。 93 | > 94 | > 对于没有任何依赖的 JS 文件可以加上 `async` 属性,表示 JS 文件下载和解析不会阻塞渲染。 95 | 96 | 97 | 98 | ## 重绘和回流?? 99 | 100 | 这个是我在看别人面经时看到的东西,之前也不太清楚,今天看了几篇博客添加上的。 101 | 102 | 首先明确一下两者的概念: 103 | 104 | - 重绘是当节点需要更改外观而不会影响布局的,比如改变 `color` 就叫称为重绘 105 | - 回流是布局或者几何属性需要改变就称为回流。 106 | 107 | 所以说,回流**必定**会发生重绘,重绘**不一定**会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。 108 | 109 | 110 | 111 | 以下几个动作可能会**导致性能问题** 112 | 113 | - 改变 window大小 114 | - 改变字体 115 | - 添加或删除样式 116 | - 文字改变 117 | - 定位或者浮动 118 | - 盒模型 119 | 120 | 121 | 122 | 看一下这篇内容:[HTML文档](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model) 123 | 124 | **重绘和回流其实和 Eventloop 有关的!!** 125 | 126 | 整理一下内容: 127 | 128 | > 1. 当 Eventloop 执行完 Microtasks 后,会判断 `document` 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。 129 | > 2. 然后判断是否有 `resize` 或者 `scroll` 事件,有的话会去触发事件,所以 `resize` 和 `scroll` 事件也是至少 16ms 才会触发一次,并且自带节流功能。 130 | > 3. 判断是否触发了 media query 131 | > 4. 更新动画并且发送事件 132 | > 5. 判断是否有全屏操作事件 133 | > 6. 执行 `requestAnimationFrame` 回调 134 | > 7. 执行 `IntersectionObserver` 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 135 | > 8. 更新界面 136 | > 9. 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 `requestIdleCallback` 回调。 137 | 138 | 139 | 140 | 有点云里雾里……不过没关系,虽然目前不太明白,但是至少我们知道了这肯定是影响性能问题的。所以,得知道怎么减少重绘和回流! 141 | 142 | 以下是一些方案: 143 | 144 | - 使用 `transform` 替代 `top` 145 | 146 | - 使用 `visibility` 替换 `display: none` ,因为前者只会引起重绘,后者会引发回流(改变了布局) 147 | 148 | - 不使用 `table` 布局,可能很小的一个小改动会造成整个 `table` 的重新布局 149 | 150 | - CSS选择符从右往左匹配查找,避免节点层级过多 151 | 152 | - 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。(will-change属性或者video,iframe标签等) 153 | 154 | - 节点属性不要放在一个循环里当循环变量 155 | 156 | ```javascript 157 | //每次都要去取正确的值才行 158 | for(let i=0;i<100;i++){ 159 | console.log(document.querySeletor('.test').style.offsetTop); 160 | } 161 | ``` 162 | 163 | 164 | 165 | ## 面试题 166 | 167 | ### 1.插入几万个 DOM,如何实现页面不卡顿? 168 | 169 | 解决问题的重点应该是如何分批次部分渲染 DOM。 170 | 171 | 思路1 是通过 `requestAnimationFrame` 的方式去循环的插入 DOM; 172 | 173 | 思路2 是通过虚拟滚动。 174 | 175 | **这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。** 176 | 177 | 即使列表很长,但是渲染的 DOM 元素永远只有那么几个,当我们滚动页面的时候就会实时去更新 DOM。 178 | 179 | 这个思路的解决方案可以戳[react-virtualized](https://github.com/bvaughn/react-virtualized) 180 | 181 | ### 2.在不考虑缓存和优化网络协议的前提下,可以通过哪些方式来最快的渲染页面? 182 | 183 | 这个问题的,其实在了解渲染的过程之后,解决方案已经不言而喻了,无非就是减少生成渲染树的时间了。那么回顾下渲染树是怎么生成的呢:DOM+CSSOM。 184 | 185 | 所以答案如下: 186 | 187 | 1. 从文件大小考虑 188 | 2. 从 `script` 标签使用上来考虑 async和differ 189 | 3. 从需要下载的内容是否需要在首屏使用上来考虑 190 | 4. 最后就是从 CSS、HTML 的代码书写上来考虑了 191 | 192 | 193 | 194 | ## 总结 195 | 196 | 这个部分还是挺重要的,如何写出高质量代码,理解这些东西是非常有必要的。 197 | 198 | 感觉这个方面其实还有很多东西可以学习,搜罗了一些大佬的相关博客,放在后面,供后面慢慢学习: 199 | 200 | [深入浅出浏览器渲染原理 ](https://github.com/ljianshu/Blog/issues/51) 201 | [DOM操作成本到底高在哪儿?](https://segmentfault.com/a/1190000014070240) 202 | [JS 一定要放在 Body 的最底部么?聊聊浏览器的渲染机制](https://segmentfault.com/a/1190000004292479) 203 | [Web图片资源的加载与渲染时机](https://segmentfault.com/a/1190000010032501) 204 | [渲染性能](https://github.com/sundway/blog/issues/2) 205 | [浏览器的渲染:过程与原理](https://zhuanlan.zhihu.com/p/29418126) 206 | 207 | -------------------------------------------------------------------------------- /浏览器/跨域CORS.md: -------------------------------------------------------------------------------- 1 | # 跨域 2 | 3 | ## 概述 4 | 5 | 当一个资源从与该资源本身所在服务器中不同域、协议、端口请求一个资源时,出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求,XMLHttpRequest和Fetch API。 6 | 7 | 引入这个机制主要是用来防止CSRF攻击的(利用用户的登录态发起恶意请求)。 8 | 9 | 没有同源策略的情况下,A 网站可以被任意其他来源的 Ajax 访问到内容。如果你当前 A 网站还存在登录态,那么对方就可以通过 Ajax 获得你的任何信息。(当然跨域并不能完全阻止 CSRF) 10 | 11 | **请求跨域了,那么请求到底发出去没有?** 12 | 13 | 请求必然是发出去了,但是浏览器拦截了响应。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会。因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。 14 | 15 | > 所以,关于浏览器限制,不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但返回结果被浏览器拦截。 16 | 17 | 那么跨域问题**如何解决**呢 18 | 19 | ## 1.JSONP 20 | 21 | JSONP 的原理很简单,就是利用 ` 27 | 32 | ``` 33 | 34 | JSONP 使用简单且兼容性不错,但是只限于 `get` 请求。 35 | 36 | 在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现: 37 | 38 | ```javascript 39 | function jsonp(url, jsonpCallback, success) { 40 | let script = document.createElement('script') 41 | script.src = url 42 | script.async = true 43 | script.type = 'text/javascript' 44 | window[jsonpCallback] = function(data) { 45 | success && success(data) 46 | } 47 | document.body.appendChild(script) 48 | } 49 | jsonp('http://xxx', 'callback', function(value) { 50 | console.log(value) 51 | }) 52 | ``` 53 | 54 | 55 | 56 | ## 2.CORS概述 57 | 58 | CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 `XDomainRequest` 来实现。 59 | 60 | 浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。 61 | 62 | 服务端设置 `Access-Control-Allow-Origin` 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。 63 | 64 | 虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为**简单请求和预检请求**。 65 | 66 | 另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),**浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务器是否允许该跨域请求。** 67 | 68 | **服务器确认允许之后,才发起实际的HTTP请求**。 在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 cookie 和 HTTP 认证相关数据)。 69 | 70 |  71 | 72 | ### 简单请求 73 | 74 | 不会触发 CORS 预检的请求称为简单请求,满足以下**所有条件**的才会被视为简单请求: 75 | 76 | - 使用 GET、POST、HEAD 其中一种方法 77 | - 只使用了如下的安全首部字段,不得人为设置其他首部字段 78 | - Accept 79 | - Accept-Language 80 | - Content-Language 81 | - Content-Type 仅限以下三种 82 | - text/plain 83 | - multipart/form-data 84 | - application/x-www-form-urlencoded 85 | - HTML 头部 header filed 字段:DPR、Download、Save-Data、Viewport-Width、Width。 86 | - 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload对象可以使用XMLHttpRequest.upload属性访问 87 | - 请求中没有使用ReadableStream对象 88 | 89 | ### 预检请求 90 | 91 | 需预检的请求要求必须使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。 92 | 93 | 预检请求的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。 94 | 95 | 下面的请求会触发预检请求,其实**非简单请求之外的都会触发预检** 96 | 97 | - 使用 PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH方法 98 | - 人为设置了非规定内的其他首部字段,参考上面简单请求的安全字段集合,还要特别注意 Content-Type的类型 99 | - XMLHttpRequestUpload对象注册了任何事件监听器 100 | - 请求中使用了ReadableStream对象 101 | 102 | ### 请求附带身份凭证 => cookie 103 | 104 | 如果发起请求时设置WithCredentials标志设置为 true,从而向服务器发送cookie,但是如果服务器的响应中未携带Access-Control-Allow-Credentials: true,浏览器将不会把响应内容返回给请求的发送者。 105 | 106 | 对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin的值为`*`,必须是某个具体的域名。 107 | 108 | 注意,简单 GET 请求不会被预检;如果此类带有身份凭证请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。 109 | 110 | ### 完整请求流程 111 | 112 |  113 | 114 | ## 3.document.domain 115 | 116 | 该方式只能用于**二级域名相同**的情况下,比如 `a.test.com` 和 `b.test.com`适用于该方式。 117 | 118 | 只需要给页面添加 `document.domain = 'test.com'` 表示二级域名都相同就可以实现跨域了。 119 | 120 | ## 4.postMessage 121 | 122 | 这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息。 123 | 124 | ```javascript 125 | // 发送消息端 126 | window.parent.postMessage('message', 'http://test.com') 127 | // 接收消息端 128 | var mc = new MessageChannel() 129 | mc.addEventListener('message', event => { 130 | var origin = event.origin || event.originalEvent.origin 131 | if (origin === 'http://test.com') { 132 | console.log('验证通过') 133 | } 134 | }) 135 | ``` 136 | 137 | -------------------------------------------------------------------------------- /算法/树.md: -------------------------------------------------------------------------------- 1 | # 树 2 | 3 | ## 1.二叉查找树BST 4 | 5 | 二叉查找树一种特殊的二叉树,相对较小的值保存在左节点,较大的保存在右节点。这一特性使得查询的效率很高。 6 | 7 | ```js 8 | function Node(data,left,right){ 9 | this.data=data; 10 | this.left=left; 11 | this.right=right; 12 | this.show=show; 13 | } 14 | function show(){ 15 | return this.data; 16 | } 17 | 18 | function BST(){ 19 | this.root=null; 20 | this.insert=insert;//添加新的节点 21 | this.inOrder=inorder; 22 | } 23 | //insert 的功能: 24 | //1 如果root为空,新树,当前节点; 25 | //2 小于当前节点,如果左节点为空,新节点插入;否则迭代下个循环 26 | //3 大于当前节点,如果右节点为空,新节点插入,否则迭代下个循坏 27 | function insert(data){ 28 | var n=new Node(data,null,null); 29 | if(this.root==null){ 30 | this.root=n; 31 | }else{ 32 | var current=this.root; 33 | var parent; 34 | while(true){ 35 | parent=current; 36 | if(data 这个查询是操作系统自己做的。 14 | > 15 | > 当你在浏览器中想访问 `www.google.com` 时,会进行一下操作: 16 | > 17 | > 1. 操作系统会首先在本地缓存中查询 IP 18 | > 2. 没有的话会去系统配置的 DNS 服务器中查询 19 | > 3. 如果这时候还没得话,会直接去 DNS 根服务器查询,这一步查询会找出负责 `com` 这个一级域名的服务器 20 | > 4. 然后去该服务器查询 `google` 这个二级域名 21 | > 5. 接下来三级域名的查询其实是我们配置的,你可以给 `www` 这个域名配置一个 IP,然后还可以给别的三级域名配置一个 IP 22 | 23 | 之后就是**TCP握手** 24 | 25 | TCP 的握手情况以及 TCP 的一些特性。 26 | 27 | 如果是https的话,还会 TCP 握手结束后就会进行 TLS 握手,然后就开始正式的传输数据。 28 | 29 | **应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了。** 30 | 31 | **数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件。** 32 | 33 | 接收到响应的文件之后,**首先浏览器会判断状态码是什么**,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定向 34 | 35 | **浏览器开始解析文件**,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件。 36 | 37 | 文件解码成功后会正式开始**渲染流程**,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行。如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。 38 | 39 | 遇到文件下载的会去下载文件,这里如果使用 HTTP/2 协议的话会极大的提高多图的下载效率。 40 | 41 | **CSSOM 树和 DOM 树构建完成后会开始生成 Render 树**,这一步就是确定页面元素的布局、样式等等诸多方面的东西 42 | 43 | 在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了。 44 | 45 | > 另外就是: 46 | > 47 | > `DOMContentLoaded`事件触发代表初始的HTML被完全加载和解析,不需要等待CSS,JS,图片加载。 48 | > 49 | > `Load`事件触发代表页面中的DOM,CSS,JS,图片已经全部加载完毕。 50 | 51 | 最后就是TCP断开连接,四次挥手。 52 | 53 | > 客户端:我没有数据要发送了,准备挂了 54 | > 55 | > 服务器:收到,但我还有一些数据没发送完,稍等一下 ...... 56 | > 57 | > 服务器:好了,发送完了,可以断开连接了 58 | > 59 | > 客户端:OK,你断开连接吧(内心独白:我将会在2倍的最大报文段生存时间后关闭连接,如果再收到服务器的消息,那么服务器就是没听到我最后这句话,我就再发送一遍) 60 | 61 | -------------------------------------------------------------------------------- /面经/面经.md: -------------------------------------------------------------------------------- 1 | # 拼多多 2 | 3 | ## 一面 4 | 5 | 怎么做的英文版本? 6 | 7 | 英文切换后,数据加载延时的问题怎么优化的?老哥估计是想问浏览器的存储的问题,但我忘了回答,就说了我们是直接从后台一次性拉取的数据。老哥说了解了,然后就换了个问题。 8 | 9 | 客户端开发和web开发有哪些区别?客户端开发更新的时候是怎么更新的? 10 | 11 | 有没有了解过新的框架,跟Jquery有什么区别?为什么操作Dom的性能不好,虚拟Dom为什么好? 12 | 13 | 为什么要换新的框架?我说可能就是性能上会好很多吧,老哥说其实并不是从性能考虑的,是为了开发成本更方便。 14 | 15 | 项目里有没有一些很有成就感的事情? 16 | 17 | Echats Esri 的图像是怎么实现的?老哥想问canvas ,我说不了解,然后过了问下一个 18 | 19 | var let 区别是什么 然后做题 20 | 21 | EventLoop 题 原因分析 说一下宏任务微任务区别 22 | 23 | Post和Get的区别是什么?什么是幂等? 24 | 25 | http1和2的区别? 26 | 27 | 跨域的方式有哪些? 为什么要跨域? 28 | 29 | XSS攻击是啥?XSS攻击怎么防范的。 30 | 31 | 项目里用到了哪些性能优化? 雪碧图为什么比一张一张的好?懒加载? 32 | 33 | 编程1 判断数组是否连续的问题 34 | 35 | `[0 10][8 10][10 30]`返回ture 36 | 37 | `[0 10][25 30][8 20]`返回false 38 | 39 | 让实现一下 40 | 41 | 编程2: 42 | 43 | 写一个sleep函数,返回一个promise(setTimeout) 44 | 45 | 46 | 47 | 提问: 48 | 49 | - 问了一下是什么部门? 50 | 51 | 统一的,还没有分部门。 52 | 53 | - 问了一下大概多久有结果? 54 | 55 | 他说一周内 56 | 57 | ## 二面 58 | 59 | 问项目 英文版本的具体实现 60 | 61 | (把项目吹太狠了,老哥以为我贼强) 62 | 63 | service worker 是什么?生命周期? 64 | 65 | 事件冒泡怎么写?具体代码怎么写? 66 | 67 | Promise.race 一个执行完了,其他执行吗? 我说会执行完,他说执行完是啥意思,我说那些设置的操作都能完成呀,他说能console就是执行完吗? 68 | 69 | 继承的方式有哪些?你在项目里写的?不要随便举例子 70 | 71 | cache-control的值有哪些? 72 | 73 | 编程题: 74 | 75 | fetch 获取参数,如果说1秒内返回了就正常出结果,如果超时返回写超时的时间 76 | 77 | 78 | 79 | 提问: 80 | 81 | - 今天表现不太好,您能说说我的问题吗?我后面可以从哪些方面去提升自己呢? 82 | 83 | 老哥说了很多干货,欢迎我后面向他提问 84 | 85 | 86 | 87 | # 网易(凉) 88 | 89 | ## 一面 90 | 91 | 数组如何实现栈和队列 92 | 93 | h5新标签 94 | 95 | 获取元素dom的方式有哪些? 96 | 97 | 怎么给dom添加子元素? 98 | 99 | 怎么获取兄弟元素、父元素? 100 | 101 | 如何用数组实现队列和栈? 102 | 103 | 原型链 104 | 105 | 作用域链 106 | 107 | 闭包 108 | 109 | 有一个变量在外层,然后内层有个函数要一直访问这个变量,一直往上查找很影响性能,问怎么优化? 我答的直接传值进去或者bind(“”,变量) 不知道对不对 110 | 111 | cookie怎么写 我写的cookie=new cookie() 然后cookie.setxxx() 最后addCookie就行了 他说要写原生的 112 | 113 | 在一个div里面鼠标点击释放,怎么知道是点击了这个div? 我说事件委托,让它冒泡。 他问具体怎么实现的呢? 114 | 115 | rm em区别 116 | 117 | outline border 118 | 119 | CSS3里面操作动画用什么 120 | 121 | **写了很多个我没咋用过的CSS属性,一个一个问有什么参数可以设置** 122 | 123 | 有没有用过flex?这个参数是啥?怎么做某个布局? 124 | 125 | jsonp怎么实现,我说了原理,然后手写了一个。(他说**你jq不错**……emmmmm……我写的是原生吧orz。 126 | 127 | A页面下有一个iframeB,在B内点击按钮,在A当中显示数据。具体如何实现? 128 | 129 | 想了一下,说事件监听吧,然后他说怎么监听监听谁……想不起来怎么在父页面获取iframe的节点了,其实之前项目里有写过,但可能心态崩了,脑子一热没想到。 130 | 131 | 132 | 133 | 提问环节: 134 | 135 | 生气,无话可说,挂了。 136 | 137 | 138 | 139 | ## 总结 140 | 141 | 对CSS不太熟悉的确是这次面试的硬伤,后续的复习当中要分一部分时间出来补齐短板才行。新出的属性需要了解,知道怎么用。 142 | 143 | 网易面试偏向解决某个具体的问题要怎么操作,更多的是DOM操作,感觉不太问基础,也不太问异步什么的,不知道是不是跟部门的业务有关,或者跟面试官有关。 144 | 145 | 这次面试下来的体验感非常不好,感觉要凉的,比较擅长的基本上都没有被问到。基本上80%都在问DOM操作和CSS。崩溃… 146 | 147 | # 腾讯 148 | 149 | ## 一面 150 | 151 | 先来自我介绍,然后问了一下为什么想要做前端?怎么学习的前端? 152 | 153 | 先问的http2.0的新特点,然后往下深挖每一个特点具体是如何提升性能的?跟http1.0的对比起来在哪里优化了 154 | 然后问了既然http2.0有多路复用,还是要用雪碧图的原因?我说少了http请求,他说为什么要减少?我没想到,他提示我说想想http2.0的缺点,我说会有丢包和阻塞的问题。这时候我还没反应过来是提醒,他说这么明显了还没想明白吗,然后我顺着话回答上来了,我说只发一张图,可以减少丢包的可能性。 155 | 156 | 接着就说到了加载的问题,问了load事件和DOMcontentLoaded事件的先后问题,script标签放在哪里会导致什么问题,然后加属性花式放在不同位置,问会不会阻塞。然后又问Domready和DOMcontentLoaded。 157 | 158 | 接着问到了缓存,强缓存的两种方式?哪个更好?没到过期时间,如果要强制更新缓存怎么做? 159 | localstorage能存储什么文件? 你既然不知道,你提它做什么。你是不是没分清浏览器缓存和存储方式? 160 | 161 | 然后说到了事件机制,说了捕获 发生 冒泡。问能有些什么用?我说事件委托,比如ul li 多个li的操作可以把事件监听放在ul,他说为什么不放在li,我写个for循环不一样吗?我说性能不好,说到了js进程和dom是两个进程,通讯要时间。他说我只触发一个呢,时间是一样的。然后我没答上,下来看了书,因为存函数需要占用内存。唉……没想到。然后问还可以做什么?提示说元素被隐藏的情况,我想起来,然后说对于元素隐藏的情况可以在父节点上添加事件监听。 162 | 163 | 然后说了跨域的方式,说了三个,还没说postMessage呢。然后直接问jsonp的缺点是什么?这边没问太多,可能本来也没啥好问的。 164 | 165 | 之后问了安全方面的内容。XSS是什么,可以怎么防范?然后转义字符都转义一些什么字符?怎么转义,然后转义成什么?CSRF攻击是什么?原理是什么?解决方案的token怎么生成的?token放在哪里?我不太清楚,猜测是在cookie里,他说你刚刚说原理的时候cookie不是会被偷走吗?那你放在里面让别人偷吗?我也没太想明白,然后他提示我说没有cookie之前session是怎么记录的,我说用URL,然后顺着话说,是放在URL里面的。其实没咋明白,但是他放过我了。下来之后跟cc问了一下,他说可能就是想听到token是随机生成的,虽然hacker能拿到,但是不知道是啥东西。所以不管是放在cookie还是放在localstorage还是url里,应该都可以。关键在于随机性,你拿到了不知道是啥,只有服务器知道。 166 | 167 | 168 | 169 | 提问环节: 170 | 171 | 问了一下前端后面发展,怎么看待这个行业的发展 172 | 173 | 问了一下部门主要做什么的 174 | 175 | 176 | 177 | 面试官评价: 178 | 179 | 基础还行,但是实践和工程化太缺了,webpack,gulp这些构建工具要会用。 180 | 181 | 后面几面会问算法,好好准备。 182 | 183 | 184 | 185 | ## 二面 186 | 187 | 自我介绍 188 | 189 | 对学术方向感兴趣,给了讲了生物模型,然后讲了计算机模型,最后说了算法以及可用来做的领域。 190 | 191 | 浏览器渲染过程 192 | 193 | http2.0 1.1 1.0区别 问的超细,keep-alive 系列;并行传输和顺序传输;滑动窗口;文本和二进制传输帧优劣;多TCP和单TCP优劣。 194 | 195 | 在庞大的数据里找出最大的1000个 196 | 197 | oralce用过哪些功能 198 | 199 | 有用过索引吗?用索引的场景有哪些?索引的原理是什么? 200 | 201 | 你对前端和后台的理解 202 | 203 | 说一个能够证明你学习能力不错的例子 说到了当初接手运维怎么学习看delphi的过程 204 | 205 | delphi和C#开发的区别 206 | 207 | C#开发里的角色,做了哪些工作 208 | 209 | 怎么理解HTML,CSS,JS前端三巨头的关系 210 | 211 | 如何进行前端优化 212 | 213 | 你提到了缓存,http头部里有哪些缓存相关的 214 | 215 | 216 | 217 | # 东方财富 218 | 219 | ## 一面 220 | 221 | 自我介绍 222 | 223 | 学硕方向是做的什么?为什么学习前端? 224 | 225 | 做道题吧: 226 | 227 |  228 | 229 | 230 | 231 | 输入一个URL之后会发生什么?说详细些,越详细越好。 232 | 233 | session和cookie的区别?session可不可以跨域? 234 | 235 | Post和Get的区别? 236 | 237 | http和https的区别? 238 | 239 | (还有一些别的,很基础的点……忘记了,不过都很简单) 240 | 241 | 对程序开发的理解? 242 | 243 | 喜欢做开发吗?之前有没有参加ACM竞赛什么的? 244 | 245 | 246 | 247 | 提问: 248 | 249 | 1. 东方财富是个证券股票公司,对技术岗位的人员是什么样的培养模式? 250 | 2. 提前批的招聘流程? 251 | 252 | # 小米 253 | 254 | ## 一面 255 | 256 | 电话面,因为那天是周六,马上要做腾讯笔试了,所以就是简单聊了一下。 257 | 258 | 先问的算法,问了快排,快排时间复杂度。我解释了为什么是nlogn 259 | 260 | 然后跟他搜快排其实就是二分加冒泡的合体,他就问了二分解决什么问题,怎么做二分,时间复杂度,为什么是logn。 261 | 262 | 可能因为答得比较顺,所以面试官觉得我算法还不错,就直接没问了。 263 | 264 | 然后问了一下CSS的box-sizing属性。 265 | 266 | CSS选择器的权重顺序 267 | 268 | 动画的两个属性区别和适用场景 269 | 270 | 然后JS的问题: 271 | 272 | 闭包 273 | 274 | 作用域链 275 | 276 | 原型链 277 | 278 | 作用域链和原型链的区别 279 | 280 | 事件机制 冒泡有什么用 除了事件委托呢? 281 | 282 | 301和302状态码的区别是什么 分别用于什么情况下 283 | 284 | 为什么有了301还要有302 我说可能需要临时跳转一下,不是永久的;他说还可以用来做站点统计,如果301的话,浏览器下次就直接去那个新的地址了。 285 | 286 | 304是干啥的 287 | 288 | 强缓存和协商缓存 细说 289 | 290 | 然后问了http1.0和2.0的区别 291 | 292 | 293 | 294 | 提问环节: 295 | 296 | - 部门是做什么的? 297 | 298 | 大数据部 299 | 300 | - 招聘流程 301 | 302 | 现场面还有两面,然后直接跟我敲定了现场面时间 303 | 304 | ## 二面 305 | 306 | 是个人美心善的小姐姐。 307 | 308 | 先聊了会儿天 309 | 310 | **手写垂直居中的多种实现方法** 311 | 312 | **原生实现Ajax** 313 | 314 | 说一下httpXMLRequest的5个state是啥? 315 | 316 | 然后问了一些CSS的问题,问了很多,知道就是知道不知道就不知道的那种,具体的想不太起来了 317 | 318 | 闭包是什么 然后做了道题。解释原因。 319 | 320 | 浏览器渲染机制 321 | 322 | 304的两个强缓存 协商缓存 区别,什么时候用哪个? 323 | 324 | http的请求头部有哪些比较常用的? 325 | 326 | http有哪些请求方法?分别能做什么? 327 | 328 | 具体说一下post和get的区别. 329 | 330 | 中间聊了会儿项目 331 | 332 | 用过的ES6方法 具体说,怎么用的? 333 | 334 | **手写事件委托,循环判断有没有父节点** 335 | 336 | 然后问了跟事件有关的东西 337 | 338 | MVVM和虚拟DOM 339 | 340 | 为什么没用过框架,还去了解了这些?我说这些是框架的原理和基础,我看了有助于我后面对框架的学习,但我还没有系统地去看框架。 341 | 342 | 探讨了一下框架到底有什么好处? 343 | 344 | **最后手写两个数组合并后排序 要求尽可能时间复杂度低** 345 | 346 | ## 三面 347 | 348 | 问了很多项目的东西 349 | 350 | 聊天,说各种情景下的问题如何解决 主要看解决问题的能力 351 | 352 | 问了一些html 非常基础的东西,很基础:比如说 doctype是啥, HTML5的写法,为啥是那样写的 353 | 354 | 问了CSS引用方式和加载顺序 355 | 356 | scrpit标签位置对加载的影响 357 | 358 | 然后问了onready事件和onload事件 359 | 360 | CSS的display属性有哪些 361 | 362 | EventLoop 宏任务 微任务 363 | 364 | http的状态码有哪些‘?都有啥用? 从100 到500 常用的全部挨个说了一遍:101 200 202 203 204 206 301 302 304 307 400 500 502 365 | 366 | 网络的又乱七八糟问了一堆……想不起来了,但了解的话就是很简单。 367 | 368 | ES6的方法有了解哪些 369 | 370 | 块级作用域是什么有哪些? 371 | 372 | 异步都在什么时候用? 373 | 374 | 有没有看英文文档什么的?我说我都是遇到了问题才去看,他让我举个例子,然后我说了V8团队在EventLoop上对async做了一些改进,原来是什么样的,现在是什么样的。给他说了个实例,就是那种async里面的await后面跟了一个async里面console.log(1)的。他问我为什么会去看这个,我说我看事件流的时候,两个电脑的浏览器版本不一样,跑出来的结果不同,然后我去百度搜了没搜到,但是在stack overflow看到了一个类似的问题,顺着人家的答案找到了V8团队的官方说明。然后跟面试官具体介绍了一下这个改进。 375 | 376 | 之后又聊天,说项目里面遇到比较困难的事情是什么?有什么有成就感的事情?需求沟通怎么做的? 377 | 378 | **最后让写了个爬梯子的问题,就是斐波拉契数列啦~想骚操作写矩阵算法,后来没有骚动,写了个简单的数组递归。** 379 | 380 | 381 | 382 | 提问: 383 | 384 | - 新员工的培训模式 385 | - 实习? 386 | 387 | # 贝壳(凉) 388 | 389 | ## 一面 390 | 391 | 照着简历挨着问,常规问题,写了啥问啥,可能写的不全,但就是简历上的东西。 392 | 393 | HTML meta元素的作用 原型 闭包 http 状态码 说自己的知道的 301 302 区别 500 502 区别 304 的情况 394 | 395 | 浏览器安全有了解哪些?说了XSS CSRF 中间人 和防范措施 396 | 397 | 性能优化可以怎么优化 说了从网络加载、渲染、图片三个维度各怎么加载。 398 | 399 | 跨域的方法知道哪些? 你用过的,说了jsonp postMessage,然后说了解CROS,然后跟他说了简单请求和预检请求。最后补充了一下同域名可以用domain 400 | 401 | 排序算法 快排 归并 堆排 原理和时间复杂度 402 | 403 | 一个字符串很多个括号,让判断括号是不是成对的。说了三种方法,从右到左用栈,碰到左括号出栈;分别统计数目;正则match一对括号,然后丢掉递归match。 404 | 405 | 发布订阅模式 没答太好,他说我说的是用法和现象。然后跟我讲了eventbus,补充了我的回答 406 | 407 | 操作系统 状态机 不记得了,就说了像Promise机制应该就是状态机,一旦变了就不能改。然后面试官跟我讲了一下状态机。 408 | 409 | ## 二面 410 | 411 | 学术方向是做什么的?具体用在什么上面?你经历这么乱七八糟是项目需要还是不知道要做什么? 412 | 413 | **写一下函数链式调用怎么实现?** 写的lodash惰性调用那种方式 414 | 415 | 讲一下MVC、MVP、MVVM 416 | 417 | **写一下数组扁平化** 递归写的,他说还有别的写法吗,用js咋写,我说没写过,如果非要写估计就是map吧,跟这个差不多,他开始问下一个问题 418 | 419 | 讲一下https 420 | 421 | **写一下堆排序** 卡了下,但是还是写出来了 422 | 423 | **写一下数字字符串,每三位数加个逗号** 遍历写的,让写正则,只会写\d{3},跟他说了一下可能是用replace,具体咋写忘了。他说不是,问他咋写,他让我下来自己写…… 424 | 425 | 职业规划是什么?有啥要问我的吗? 426 | 427 | **二面挂了,明明聊得还可以的,但是挂得莫名其妙,我可去NMD吧** --------------------------------------------------------------------------------
duanluo1
duanluo2
duanluo3
duanluo4
EMAIL
Average grade: ${user.avgGrade}
Enrolled: ${user.enrolled}