模块化
使用模块化可以给我们带来以下好处
解决命名冲突
提供复用性
提高代码可维护性
Proxy
Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。 Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
let p = new Proxy(target, handler)复制代码
target 代表需要添加代理的对象 ,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
接下来我们通过 Proxy 来实现一个数据响应式
let onWatch = (obj, setBind, getLogger) => { let handler = { get(target, property, receiver) { getLogger(target, property) return Reflect.get(target, property, receiver) }, set(target, property, value, receiver) { setBind(value, property) return Reflect.set(target, property, value) } } return new Proxy(obj, handler)}let obj = { a: 1 }let p = onWatch( obj, (v, property) => { console.log(`监听到属性${property}改变为${v}`) }, (target, property) => { console.log(`'${property}' = ${target[property]}`) })p.a = 2 // 监听到属性a改变p.a // 'a' = 2复制代码
在上述代码中,我们通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。
reduce
const arr = [1, 2, 3]const sum = arr.reduce((acc, current) => acc + current, 0)console.log(sum)复制代码
对于 reduce 来说,它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce 的过程
首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入 回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组
在一次执行回调函数时,当前值和初始值相加得出结果1,
该结果会在第二次执行回调函数时当做第一个参数传入。
所以在第二次执行回调函数时,相加的值就分别是 1 和2,以此类推,循环结束后得到结果 6
就通过 reduce 来实现 map和filter 函数
const numbers = [10, 20, 30, 40];const go = numbers.reduce((finalList, num) => { //实现map方法,映射的作用 num = num * 2; if (num > 50) { //实现filter方法过滤 finalList.push(num); } return finalList;}, []);alert(go); // [60, 80]复制代码
setInterval
其实这个函数作用和 setTimeout 基本一致,只是该函数是每隔一段时间执行一次回调函数。
通常来说不建议使用 setInterval。第一,它和 setTimeout 一样,不能保证在预期的时间执行任务。第二,它存在执行累积的问题,请看以下伪代码
function demo() { setInterval(function(){ console.log(2) },1000) sleep(2000)}demo()复制代码
以上代码在浏览器环境中,如果定时器执行过程中出现了耗时操作,多个回调函数会在耗时操作结束以后同时执行,这样可能就会带来性能上的问题。
如果你有循环定时器的需求,其实完全可以通过 requestAnimationFrame 来实现
特点
【1】requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
【2】在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
【3】requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
function setInterval(callback, interval) { let timer const now = Date.now let startTime = now() let endTime = startTime const loop = () => { timer = window.requestAnimationFrame(loop) endTime = now() if (endTime - startTime >= interval) { startTime = endTime = now() callback(timer) } } timer = window.requestAnimationFrame(loop) return timer}let a = 0setInterval(timer => { console.log(1) a++ if (a === 3) cancelAnimationFrame(timer)}, 1000)复制代码
首先 requestAnimationFrame 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题,当然你也可以通过该函数来实现 setTimeout。
进程与线程
进程和线程都是一个时间段的描述,是CPU工作时间段的描述。放在应用上来说就代表了一个程序。线程是进程中的更小单位,描述了执行一段指令所需的时间。
把这些概念拿到浏览器中来说,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
bind
和call很相似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。
bind返回值是函数
var obj = { name: 'Dot'}function printName() { console.log(this.name)}var dot = printName.bind(obj)console.log(dot) // function () { … }dot() // Dot复制代码
bind 方法不会立即执行,而是 返回一个改变了上下文 this 后的函数。 而原函数 printName 中的 this 并没有被改变,依旧指向全局对象 window。
参数的使用
function fn(a, b, c) { console.log(a, b, c);}var fn1 = fn.bind(null, 'Dot');fn('A', 'B', 'C'); // A B Cfn1('A', 'B', 'C'); // Dot A Bfn1('B', 'C'); // Dot B Cfn.call(null, 'Dot'); // Dot undefined undefined复制代码
call 是把第二个及以后的参数作为 fn 方法的实参传进去,而 fn1 方法的实参实则是在 bind 中参数的基础上再往后排。
有时候我们也用bind方法实现函数珂里化,以下是一个简单的示例:
var add = function(x) { return function(y) { return x + y; };};var increment = add(1);var addTen = add(10);increment(2);// 3addTen(2);// 12复制代码
在低版本浏览器没有 bind 方法,我们也可以自己实现一个。
if (!Function.prototype.bind) { Function.prototype.bind = function () { var self = this, // 保存原函数 context = [].shift.call(arguments), // 保存需要绑定的this上下文 args = [].slice.call(arguments); // 剩余的参数转为数组 return function () { // 返回一个新函数 self.apply(context, [].concat.call(args, [].slice.call(arguments))); } }}复制代码
call apply bind应用场景
求数组中的最大和最小值
var arr = [1,2,3,89,46]var max = Math.max.apply(null,arr)//89var min = Math.min.apply(null,arr)//1复制代码
将类数组转化为数组
var trueArr = Array.prototype.slice.call(arrayLike)复制代码
数组追加
var arr1 = [1,2,3];var arr2 = [4,5,6];var total = [].push.apply(arr1, arr2);//6// arr1 [1, 2, 3, 4, 5, 6]// arr2 [4,5,6]复制代码
判断变量类型
function isArray(obj){ return Object.prototype.toString.call(obj) == '[object Array]';}isArray([]) // trueisArray('dot') // false复制代码
利用call和apply做继承
function Person(name,age){ // 这里的this都指向实例 this.name = name this.age = age this.sayAge = function(){ console.log(this.age) }}function Female(){ Person.apply(this,arguments)//将父元素所有方法在这里执行一遍就继承了}var dot = new Female('Dot',2)复制代码
使用 log 代理 console.log
function log(){ console.log.apply(console, arguments);}// 当然也有更方便的 var log = console.log()复制代码
总结
call、apply和bind函数存在的区别:
bind返回对应函数, 便于稍后调用; apply, call则是立即调用。 除此外, 在 ES6 的箭头函数下, call 和 apply 将失效, 对于箭头函数来说:
箭头函数体内的 this 对象, 就是定义时所在的对象,而不是使用时所在的对象;所以不需要类似于var _this = this这种丑陋的写法
箭头函数不可以当作构造函数,也就是说不可以使用 new 命令, 否则会抛出一个错误
箭头函数不可以使用 arguments 对象,,该对象在函数体内不存在. 如果要用, 可以用 Rest 参数代替
不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数
为什么 0.1 + 0.2 != 0.3
因为 JS 采用 IEEE 754 双精度版本(64位),0.1 在二进制中是无限循环的一些数字,其实不只是 0.1,其实很多十进制小数用二进制表示都是无限循环的。 JS 采用的浮点数标准却会裁剪掉我们的数字。 那么这些循环的数字被裁剪了,就会出现精度丢失的问题,也就造成了 0.1 不再是 0.1 了,而是变成了 0.100000000000000002
解决的办法有很多,这里我们选用原生提供的方式来最简单的解决问题
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true复制代码
存储
cookie,localStorage,sessionStorage,indexDB
对于 cookie 来说,我们还需要注意安全性。 Service WorkerService Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。 使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
浏览器缓存机制
缓存可以说是性能优化中简单高效的一种优化方式了,它可以显著减少网络传输所带来的损耗。
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。
缓存位置
Service Worker
Memory Cache
Disk Cache
Push Cache
网络请求
缓存策略
通常浏览器缓存策略分为两种:强缓存和协商缓存,并且 缓存策略都是通过设置 HTTP Header 来实现的。
强缓存
强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control 。
强缓存表示在缓存期间不需要请求,state code 为 200。
Expires
Expires: Wed, 22 Oct 2018 08:41:00 GMT Expires 是 HTTP/1 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。并且 Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
Cache-control
Cache-control: max-age=30 Cache-Control 出现于 HTTP/1.1,优先级高于 Expires 。该属性值表示资源会在 30 秒后过期,需要再次请求。
协商缓存
如果缓存过期了,就需要发起请求验证资源是否有更新。协商缓存可以通过设置两种 HTTP Header 实现 :Last-Modified 和 ETag 。
当浏览器发起请求验证资源时,如果资源没有做改变,那么服务端就会返回 304 状态码,并且更新浏览器缓存有效期。
Last-Modified 和 If-Modified-Since
Last-Modified 表示本地文件最后修改日期,
If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。
但是 Last-Modified 存在一些弊端:
如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
因为以上这些弊端,所以在 HTTP / 1.1 出现了 ETag 。
ETag 和 If-None-Match
ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级比 Last-Modified 高。
以上就是缓存策略的所有内容了,看到这里,不知道你是否存在这样一个疑问。如果什么缓存策略都没设置,那么浏览器会怎么处理?
对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。
实际场景应用缓存策略
频繁变动的资源
对于频繁变动的资源,首先需要使用 Cache-Control: no-cache
使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
代码文件
这里特指除了 HTML 外的代码文件,因为 HTML 文件一般不缓存或者缓存时间很短。
一般来说,现在都会使用工具来打包代码,那么我们就可以对文件名进行哈希处理,只有当代码修改后才会生成新的文件名。基于此,我们就可以给代码文件设置缓存有效期一年 Cache-Control: max-age=31536000,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。
浏览器渲染原理
浏览器从网络中接收到 HTML 文件然后一系列的转换过程。
接下来就是css树与dom树合并成渲染树。渲染树只会包括需要显示的节点和这些节点的样式信息, 如果某个节点是 display: none 的,那么就不会在渲染树中显示。为什么操作 DOM 慢
因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实 这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。 操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。
经典面试题:插入几万个 DOM,如何实现页面不卡顿?
大部分人应该可以想到通过 requestAnimationFrame (requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率)的方式去循环的插入 DOM。
其实还有种方式去解决这个问题:虚拟滚动(virtualized scroller)。
这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。
什么情况阻塞渲染
首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器。
然后当浏览器在解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。
当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。
当 script 标签加上 defer 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以对于这种情况你可以把 script 标签放在任意位置。
对于没有任何依赖的 JS 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染。
减少重绘和回流
使用 transform 替代 top
复制代码
使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
不要把节点的属性值放在一个循环里当成循环里的变量
for(let i = 0; i < 1000; i++) { // 获取 offsetTop 会导致回流,因为需要去获取正确的值 console.log(document.querySelector('.test').style.offsetTop)}复制代码
不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
CSS 选择符从右往左匹配查找,所以要避免节点层级过多
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。
设置节点为图层的方式有很多,我们可以通过以下几个常用属性可以生成新图层
will-change
video、iframe 标签
在不考虑缓存和优化网络协议的前提下,考虑可以通过哪些方式来最快的渲染页面,也就是常说的关键渲染路径,这部分也是性能优化中的一块内容。
当发生 DOMContentLoaded 事件后,就会生成渲染树,生成渲染树就可以进行渲染了,这一过程更大程度上和硬件有关系了。提示如何加速:
从文件大小考虑
从 script 标签使用上来考虑
从 CSS、HTML 的代码书写上来考虑
从需要下载的内容是否需要在首屏使用上来考虑
性能优化
图片优化
计算图片大小
对于一张 100 * 100 像素的图片来说,图像上有 10000 个像素点,如果每个像素的值是 RGBA 存储的话,那么也就是说每个像素有 4 个通道,每个通道 1 个字节(8 位 = 1个字节),所以该图片大小大概为 39KB(10000 * 1 * 4 / 1024)。
减少像素点
减少每个像素点能够显示的颜色
图片加载优化
1、修饰图片完全可以用 CSS 去代替。
2、没有必要去加载原图浪费带宽。一般图片都用 CDN加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
3、小图使用 base64 格式
4、将多个图标文件整合到一张图片中(雪碧图)
5.选择正确的图片格式:
对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积, 而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替 照片使用 JPEG
DNS 预解析
DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。
复制代码
防抖节流也是性能优化
预加载
有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。
预加载其实 是声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载
复制代码
预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。
预渲染
可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
复制代码
预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开,否则就是白白浪费资源去渲染。
懒加载
懒加载的原理就是只加载可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入可视区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。
CDN
CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。
因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。
Webpack 性能优化
减少 Webpack 打包时间
优化 Loader
对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。
优化方法:
1、首先我们可以优化 Loader 的文件搜索范围,只作用在 JS 代码上的,然后 node_modules 中使用的代码都是编译过的,所以我们也完全没有必要再去处理一遍
2、将 Babel 编译过的文件缓存起来(下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间)
MVVM
首先先申明一点,不管是 React 还是 Vue,它们都不是 MVVM 框架,只是有借鉴 MVVM 的思路。文中拿 Vue 举例也是为了更好地理解 MVVM 的概念。
传统的 MVC 架构通常是使用控制器更新模型,视图从模型中获取数据去渲染。当用户有输入时,会通过控制器去更新模型,并且通知视图进行更新。
但是MVC 有一个巨大的缺陷就是控制器承担的责任太大了,随着项目愈加复杂,控制器中的代码会越来越臃肿,导致出现不利于维护的情况。
在 MVVM 架构中,引入了 ViewModel 的概念。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。
以 Vue 框架来举例,ViewModel 就是组件的实例。View 就是模板,Model 的话在引入 Vuex 的情况下是完全可以和组件分离的。除了以上三个部分,其实在 MVVM 中还引入了一个隐式的 Binder 层,实现了 View 和 ViewModel 的绑定。
同样以 Vue 框架来举例, 这个隐式的 Binder 层就是 Vue 通过解析模板中的插值和指令从而实现 View 与 ViewModel 的绑定。对于 MVVM 来说,其实最重要的并不是通过双向绑定或者其他的方式将 View 与 ViewModel 绑定起来,而是通过 ViewModel 将视图中的状态和用户的行为分离出一个抽象,这才是 MVVM 的精髓。
路由原理
前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面。目前前端使用的路由就只有两种实现方式
Hash 模式
History 模式
Hash 模式
www.test.com/#/ 就是 Hash URL,当 # 后面的哈希值发生变化时,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的 URL 请求永远是 www.test.com。
window.addEventListener('hashchange', () => { // ... 具体逻辑})复制代码
Hash 模式相对来说更简单,并且兼容性也更好。
History 模式
History 模式是 HTML5 新推出的功能,主要使用 history.pushState 和 history.replaceState 改变 URL。
通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。
// 新增历史记录history.pushState(stateObject, title, URL)// 替换当前历史记录history.replaceState(stateObject, title, URL)复制代码
当用户做出浏览器动作时,比如点击后退按钮时会触发 popState 事件
window.addEventListener('popstate', e => { // e.state 就是 pushState(stateObject) 中的 stateObject console.log(e.state)})复制代码
两种模式对比
Hash 模式只可以更改 # 后面的内容,History 模式可以通过 API 设置任意的同源 URL
History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值,也就是字符串
Hash 模式无需后端配置,并且兼容性好。History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置 index.html 页面用于匹配不到静态资源的时候
computed 和 watch 区别
computed 是计算属性,依赖其他属性计算值,来动态获得值并且 computed 的值有缓存,只有当计算值变化才会返回内容。
watch 监听到值的变化就会执行回调,在回调中可以进行一些复杂业务逻辑操作。
keep-alive 组件有什么作用
如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。
对于 keep-alive 组件来说,它拥有两个独有的生命周期钩子函数,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
Vue进阶知识
Object.defineProperty 的缺陷
如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作,更精确的来说,对于数组而言,大部分操作都是拦截不到的
所以 Vue 内部通过重写函数的方式解决了这个问题。
编译过程
将模板解析为 AST
优化 AST
将 AST 转换为 render 函数
NextTick 原理分析
nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。
持续补充
关于function a()与var a = function()
mili(); function mili() { console.log('mili'); } mogu(); var mogu = function () { console.log('mogu'); };复制代码
打印结果是:mili Typeerror:mogu is not a function
原因 :因为通过function a ()这种方式是函数声明和赋值都提前了。而通过var a = function 则只是函数声明提前了,而赋值要到执行到var a = function这步才会赋值。
浏览器缓存图解
window.onload和document.ready的区别
最基本区别
1.执行时间
window.onload必须等到页面内包括图片的所有元素加载完毕后再去执行。
$(document).ready()时DOM结构回执完毕后就执行,不必等到加载完毕。
2.编写个数不同
window.onload不同同时编写多个,如果有多个window.onload方法,只会执行一个
$(document).ready()可以同时编写多个,并且可以得到执行
document.onDOMContentLoaded在页面中触发[DOMContentLoaded]事件时触发。此时,文档被加载和解析,并且DOM被完全构造,但链接的资源(例如图像,样式表和子帧)可能尚未被加载。