文章旨在整理收集前端基础、计算机网络、浏览器、vue、react源码以及算法相关知识点。内容来自平时的阅读学习以及实践理解,因为内容量巨大,会进行持续的更新和补充。文章中涉及大量优秀博文的引用,大都在文末注明引用,若有遗漏或侵权,请联系做者处理。
版本 | 操做时间 | 操做说明 |
---|---|---|
v_0.1.0 | 2019-4-16 20:57 | 第一版 |
v_0.1.1 | 2019-4-17 13:20 | 正则表达式 |
v_0.1.2 | 2019-4-18 17:20 | 调整原型链相关表述 |
v_0.1.3 | 2019-4-18 21:11 | 跨域解决方案、设计模式、回流重绘 |
v_0.1.4 | 2019-4-21 15:02 | 修正事件循环表述、增长requestAnimationFrame理解 |
v_0.2.0 | 2019-4-21 17:17 | Vue源码相关:nextTick原理解析 |
v_0.2.1 | 2019-4-24 11:33 | Vue源码相关:v-on原理解析 |
v_0.2.2 | 2019-4-29 20:33 | 算法相关,排序与动态规划 |
设置父元素高度;造成bfc;伪元素clear: both;css
引用:Flex 布局教程:语法篇
容器元素display: flex
;
容器属性html
flex-direction // 决定主轴的方向(即项目的排列方向)。 flex-wrap // 若是一条轴线排不下,如何换行。 flex-flow // 是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。 justify-content // 定义了项目在主轴上的对齐方式。 align-items // 属性定义项目在交叉轴上如何对齐。 align-content // 属性定义了多根轴线的对齐方式。若是项目只有一根轴线,该属性不起做用。
元素属性前端
order // 属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。 flex-grow // 属性定义项目的放大比例,默认为0,即若是存在剩余空间,也不放大。 flex-shrink // 属性定义了项目的缩小比例,默认为1,即若是空间不足,该项目将缩小 flex-basis // 属性定义了在分配多余空间以前,项目占据的主轴空间(main size) flex // flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto align-self // align-self属性容许单个项目有与其余项目不同的对齐方式,可覆盖align-items属性。
函数的对象属性,包含一个constructor属性,指向函数自身:Fun.prototype.constructor === Fun。vue
原型链由原型对象构成,是js对象继承的机制。每一个对像都有[[prototype]]属性,主流浏览器以__proto__
暴露给用户。__proto__
指向对象的构造函数的原型属性(prototype)。
prototype的__proto__
又指向它prototype对象(注意,这个prototype是一个对象结构)的构造函数的原型;经过__proto__
属性,造成一个引用链。在访问对象的属性时,若是对象自己不存在,就会沿着这条[[prototype]]链式结构一层层的访问。node
能够经过new来新建一个对象的函数。react
函数拥有原型(prototype
);而对象能够经过原型链关系([[prototype]]
)实现继承。函数是一种特殊对象,也拥有__proto__
,即函数也会继承。函数的原型链是:函数静态方法—>原生函数类型的原型(拥有函数原生方法)—>原生对象原型(拥有对象原生方法) —> null class A {}; class B extends A {};
ios
B.__proto__ = A.prototype; B.prototype.__proto__ = A.prototype
;git
类型:标记引用(没法解决循环引用问题);标记清除(如今主流回收算法)。
标记清除算法的核型概念是:从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还须要使用的。那些没法由根部出发触及到的对象被标记为再也不使用,稍后进行回收。es6
对于再也不用到的内存,没有及时释放,就叫作内存泄漏(memory leak)。
常见内存泄漏:github
事件循环是指: 执行一个宏任务,而后执行清空微任务列表,循环再执行宏任务,再清微任务列表。
异步任务分为宏任务(macrotask)与微任务 (microtask),不一样的API注册的任务会依次进入自身对应的队列中,而后等待Event Loop将它们依次压入执行栈中执行。
- 微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃)
- 宏任务 macrotask(task): setTimout / script / IO / UI Rendering
每次事件循环:
执行完主执行线程中的任务(这里简单,没按执行顺序写)
[b, f, i]
;[a, c, j]
;in outer
,in outer2
;执行microtask
in promise
;[a, c, j, g]
,添加h到microtask->[i, h]
;in promise2
;in promise's promise
,至此,microtask清空。执行macrotask
in timeout
;[j,g].push(d)
, [].push(e) // 微任务队列
;in time's promise
;in timeout2
;in promise's timeout
;in timeout's timeout
。requestAnimationFrame(cb)与setTimeout(cb, 0)同时注册时,可能在setTimeout以前执行,也可能在它以后执行。由于只有在一轮浏览器渲染结束时才回去调用raf。
执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境斗鱼一个与之关联的变量对象,环境中定义的全部变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境,在web浏览器中,全局执行环境被认为是window对象,所以全部全局变量和函数都是做为window对象的属性和方法被建立的。某个执行环境中的全部代码执行完毕后,该环境被销毁,保存在其中的全部变量和函数定义也随之销毁。(全局执行环境直到应用程序推出时才会被销毁)。
当代码在一个环境中执行时,会建立变量对象的一个做用域链,保证对执行环境有权访问的全部变量和函数的有序访问。
做用域链的前端,始终都是当前执行的代码所在环境的变量对象。若是这个环境是函数,则将其活动对象做为变量对象,其在最开始的时候只包含一个变量:arguments。做用域链的下一个变量来自包含环境,而️再下一个变量对象则来自下一个包含环境一直延续到全局执行环境。做用域链在建立的时候就会被生成,保存在内部的[[scope]]属性当中。其本质是一个指向变量对象的指针列表,它指引用但不实际包含变量对象。
函数建立过程当中,会建立一个预先包含外部活动对象的做用域链,并始终包含全局环境的变量对象。这个做用域链被保存在内部的[[scope]]属性中,当函数执行时,会经过复制该属性中的对象构建起执行环境的做用域链。
执行上下文是指当前Javascript代码被解析和执行时所在环境的抽象概念,JavaScript 任何代码的运行都是在执行上下文中。
执行上下文分为两个过程:
主要作了三件事
变量赋值,语句执行等。
闭包是指有权访问另外一个函数做用域中的变量的函数。其本质是函数的做用域链中保存着外部函数变量对象的引用。能够经过[[scope]]属性查看。所以,即便外部函数被销毁,可是因为外部环境中定义的变量在闭包的scope中还保持着引用,因此这些变量暂时不会被垃圾回收机制回收,所以依然能够在闭包函数中正常访问。注意:同一个函数内部的闭包函数共享同一个外部闭包环境。从下图能够看出,即便返回的闭包里面没有引用c,d变量,可是因为内部函数closure1中引用链,因此即便closure1未调用,闭包做用域中依然保存这些变量的引用。
<script>
引入<script>
<script defer>
: 延迟加载,元素解析完成后执行<script async>
: 异步加载,但执行时会阻塞元素渲染
其中蓝色表明js脚本网络加载时间,红色表明js脚本执行时间,绿色表明html解析。
更多分析见下文浏览器->页面的加载与渲染
拷贝一层,内部若是是对象,则只是单纯的复制指针地址。
Object.assign()
// 保持引用关系 function cloneForce(x) { // ============= const uniqueList = []; // 用来去重 // ============= let root = {}; // 循环数组 const loopList = [ { parent: root, key: undefined, data: x, } ]; while(loopList.length) { // 深度优先 const node = loopList.pop(); const parent = node.parent; const key = node.key; const data = node.data; // 初始化赋值目标,key为undefined则拷贝到父元素,不然拷贝到子元素 let res = parent; if (typeof key !== 'undefined') { res = parent[key] = {}; } // ============= // 数据已经存在 let uniqueData = find(uniqueList, data); if (uniqueData) { parent[key] = uniqueData.target; break; // 中断本次循环 } // 数据不存在 // 保存源数据,在拷贝数据中对应的引用 uniqueList.push({ source: data, target: res, }); // ============= for(let k in data) { if (data.hasOwnProperty(k)) { if (typeof data[k] === 'object') { // 下一次循环 loopList.push({ parent: res, key: k, data: data[k], }); } else { res[k] = data[k]; } } } } return root; } function find(arr, item) { for(let i = 0; i < arr.length; i++) { if (arr[i].source === item) { return arr[i]; } } return null; }
将屡次高频操做优化为只在最后一次执行,一般使用的场景是:用户输入,只需再输入完成后作一次输入校验便可
function debounce(fn, wait, immediate) { let timer = null; return function(...args) { if (immediate && !timer) { fn.apply(this, args); } if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, args); }, wait); }; }
每隔一段时间后执行一次,也就是下降频率,将高频操做优化成低频操做,一般使用场景: 滚动条事件 或者 resize 事件,一般每隔 100~500 ms执行一次便可。
function throttle(fn, wait, immediate) { let timer = null; let callNow = immediate; return function(...args) { if (callNow) { fn.apply(this, args); callNow = false; } if (!timer) { timer = setTimeout(() => { fn.apply(this, args) timer = null; }, wait); } } }
let和const能够造成块级做用域,而且不存在声明提高。而且在同一个块级做用域内不可重复声明。
好比 let [a, b, c] = [1, 2, 3]; let { foo } = { foo: 'bar' }
;
可枚举性
let s = Symbol(); typeof s // "symbol"
结构相似数组,成员惟一。
const s = new Set(); [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); for (let i of s) { console.log(i); } // 2 3 5 4
操做方法:
WeakSet 结构与 Set 相似,也是不重复的值的集合。可是,它与 Set 有两个区别。
首先,WeakSet 的成员只能是对象,而不能是其余类型的值。
const ws = new WeakSet(); ws.add(1) // TypeError: Invalid value used in weak set ws.add(Symbol()) // TypeError: invalid value used in weak set
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,若是其余对象都再也不引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
相似于对象,也是键值对的集合,可是“键”的范围不限于字符串,各类类型的值(包括对象)均可以看成键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。若是你须要“键值对”的数据结构,Map 比 Object 更合适。
const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
WeakMap与Map的区别有两点。
首先,WeakMap只接受对象做为键名(null除外),不接受其余类型的值做为键名。
其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
Proxy 能够理解成,在目标对象以前架设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
实现一个简单的Promise
const FULLFILLED = 'FULLFILLED'; const REJECTED = 'REJECTED'; const PENDING = 'PENDING'; const isFn = val => typeof val === 'function'; class Promise { constructor(handler) { this._state = PENDING; this._value = null; this.FULLFILLED_CBS = []; this.REJECTED_CBS = []; try { handler(this._resolve.bind(this), this._reject.bind(this)); } catch (err) { this._reject(err); } } _resolve(value) { const async = () => { if (this._state !== PENDING) { return; } this._state = FULLFILLED; const fullfilLed = (val) => { this._value = val; this.FULLFILLED_CBS.forEach(cb => cb(val)); }; const rejected = (err) => { this._value = err; this.REJECTED_CBS.forEach(cb => cb(err)); }; if (value instanceof Promise) { value.then(fullfilLed, rejected); } else { fullfilLed(value); } } requestAnimationFrame(async); } _reject(err) { const async = () => { if (this._state !== PENDING) { return; } this._state = REJECTED; this._value = err; this.REJECTED_CBS.forEach(cb => cb(err)); } requestAnimationFrame(async); } then(onFullfilled, onRejected) { return new Promise((resolve, reject) => { const handerResolve = (value) => { try { if (!isFn(onFullfilled)) { resolve(value); } const res = onFullfilled(value); if (res instanceof Promise) { res.then(resolve, reject); } else { resolve(res); } } catch (err) { reject(err); } }; const handlerReject = (err) => { try { if (!isFn(onRejected)) { reject(err); } const res = onRejected(err); if (res instanceof Promise) { res.then(resolve, reject); } else { reject(res); } } catch (err) { reject(err); } }; switch(this._state) { case PENDING: this.FULLFILLED_CBS.push(handerResolve); this.REJECTED_CBS.push(handlerReject); break; case FULLFILLED: handerResolve(this._value); break; case REJECTED: handlerReject(this._value); break; default: break; } }); } catch(onRejected) { return this.then(undefined, onRejected); } finally(cb) { const P = this.constructor; return this.then( /** 使用then保证promise的流是正常的,由于promise的下一步老是创建在上一步执行完的基础上 */ value => P.resolve(cb()).then(() => value), reason => P.resolve(cb()).then(() => { throw reason }) ); } static all(list) { return new Promise(resolve, reject) { let count = 0; const values = []; const P = this.constructor; for (let [key, fn] of list) { P.resolve(fn).then(res => { values[key] = res; count++; if (count === list.length) { resolve(values); } }, err => reject(err)); } } } static race(list) { return new Promise((resolve, reject) => { const values = []; let count = 0; const P = this.constructor; for (let fn of list) { P.resolve(fn).then(res => { resolve(res); }, err => { reject(err); }) } }) } static resolve(value) { if (value instanceof Promise) { return value; } return new Promise(resolve => resolve(value)); } static reject(value) { return new Promise((resolve, reject) => reject(value)); } }
async函数返回一个promise对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。
function spawn(genF) { return new Promise(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch (e) { return reject(e); } if (next.done) { return resolve(next.value); } Promise.resolve(next.value).then((v) => { step(() => gen.next(v)); }, (e) => { step(() => gen.throw(e)); }); } step(() => gen.next(undefined)); } }
es6的类语法是es5构造函数的语法糖,可是也有一些不一样点
抓住两点便可:假设A为父类,B为子类那么
B.__proto__ = A; // 子类的__proto__属性,表示构造函数的继承,老是指向父类。 B.prototype.__proto__ = A.prototype; // 子类prototype属性的__proto__属性,表示方法(实例)的继承,老是指向父类的prototype属性。
在一个函数中,首先填充几个参数,而后再返回一个新的函数的技术,称为函数的柯里化。一般可用于在不侵入函数的前提下,为函数 预置通用参数,供屡次重复调用。
const add = function add(x) { return function (y) { return x + y } } const add1 = add(1) add1(2) === 3 add1(20) === 21
字符组: [a-z] [^a-z]
;常见简写模式:
\d // 数字 digit \D // 非数字 \w // 表示数字、大小写字母和下划线word \W // 非单词 \s // [ \t\v\n\r\f] 空白符,space \S // 非空白符 . // 通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外
量词 {m, } {m}, ?, +, *
贪婪匹配:尽量多的匹配
var regex = /\d{2,5}/g; var string = "123 1234 12345 123456"; console.log( string.match(regex) ); // => ["123", "1234", "12345", "12345"]
惰性匹配:量词后面加个问号,匹配最少能知足要求的字符串
var regex = /\d{2,5}?/g; var string = "123 1234 12345 123456"; console.log( string.match(regex) ); // => ["12", "12", "34", "12", "34", "12", "34", "56"]
^ // 匹配开头 $ // 匹配结尾 \b // 匹配单词边界 \B // 匹配非单词边界 (?=p) // 正向先行断言 其中p是一个子模式,即p前面的位置。 (?!p) // 负向先行断言 其中p是一个子模式,即不是p的前面的位置
数字的千位分隔符表示法:好比把"12345678",变成"12,345,678"。
const reg = /\B(?=(\d{3})+\b)/g
这个正则的意思是匹配1到多组3个相连的数字前面的位置|123 // 123前面的|表明这个位置
且这些数字的右侧是单词的边界,好比123|
`123|.456,左侧是非单词边界,即数字前仍是单词好比
9|123`。
(ab)+
;(JavaScript|Regular Expression)
反向引用 使用+序号来引用当前正则里面的捕获项,好比\1
引用第一个捕获
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/; var string1 = "2017-06-12"; var string2 = "2017/06/12"; var string3 = "2017.06.12"; var string4 = "2016-06/12"; console.log( regex.test(string1) ); // true console.log( regex.test(string2) ); // true console.log( regex.test(string3) ); // true console.log( regex.test(string4) ); // false
正则在涉及贪婪量词,惰性量词以及分支的时候每每会形成回溯,更多细节:JS正则表达式完整教程(略长)
三次握手
浏览器渲染
浏览器下载html文件并进行编译,转化成相似下面的结构
图片来源再谈 load 与 DOMContentLoaded
浏览器会对转化后的数据结构自上而下进行分析:首先开启下载线程,对全部的资源进行优先级排序下载(注意,这里仅仅是下载,区别于解析过程,解析过程可能被阻塞)。同时主线程会对文档进行解析
遇到 script 标签时,首先阻塞后续内容的解析(可是不会阻塞下载,chrome对同一个域名支持6个http同时下载),当下载完成后,便执行js文件中的同步代码。而后接着解析html
若是script标签设置了async或者defer标签,下载过程不会阻塞文档解析。defer执行将会放到html解析完成以后,dcl事件以前。async则是下载完就执行,而且阻塞解析。
在 body 中第一个 script 资源下载完成以前,浏览器会进行首次渲染。将该 script 标签前面的 DOM 树和 CSSOM 合并成一棵 Render 树,渲染到页面中。这是页面从白屏到首次渲染的时间节点,比较关键。若是是第一个标签是link,表现有点奇怪,下载和解析会阻塞html的解析,并有可能进行首次渲染,也可能不渲染。
渲染过程大体为:计算样式
Recalculate style
->布局:layout->更新布局树update layer tree -> 绘制paint
DOM 构建的意思是,将文档中的全部 DOM 元素构建成一个树型结构。
将文档中的全部 css 资源合并。生成css rule tree
将 DOM 树和 CSS 合并成一棵渲染树,render 树在合适的时机会被渲染到页面中。(好比遇到 script 时, 该 script 尚未下载到本地时)。
以下图:
主线程继续解析文档,到达 head 节点 ,head 里的外部资源无非是外链样式表和外链 js。
在 body 中第一个 script 资源下载完成以前,浏览器会进行首次渲染。将该 script 标签前面的 DOM 树和 CSSOM 合并成一棵 Render 树,渲染到页面中。这是页面从白屏到首次渲染的时间节点,比较关键。若是是第一个标签是link,表现有点奇怪,下载和解析会阻塞html的解析,并有可能进行首次渲染,也可能不渲染。随后js的下载和执行依然会阻塞解析,可是css的下载和解析则不会阻塞html的解析。
因为html页面经过相应的标签从不一样的域名下加载静态资源文件是被浏览器容许的,因此能够经过动态建立script标签的方式,来实现跨域通讯,缺点是只支持get请求
let script = document.createElement('script'); script.src = 'http://www.example.com?args=agrs&callback=callback'; document.body.appendChild(script); function callback(res) { console.log(res);}
RS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 它容许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS须要浏览器和服务器同时支持。目前,全部浏览器都支持该功能,IE浏览器不能低于IE10。IE8+:IE8/9须要使用XDomainRequest对象来支持CORS。Access-Control-Allow-Origin: http://www.example.com
,经过Access-Control-Allow-Credentials: true
控制是否容许发送cookie
正向代理(forward)是一个位于客户端【用户A】和原始服务器(origin server)【服务器B】之间的服务器【代理服务器Z】,为了从原始服务器取得内容,用户 A 向代理服务器 Z 发送一个请求并指定目标(服务器B),而后代理服务器 Z 向服务器 B 转交请求并将得到的内容返回给客户端。客户端必需要进行一些特别的设置才能使用正向代理。
对于客户端而言代理服务器就像是原始服务器,而且客户端不须要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将得到的内容返回给客户端。
Nginx 是一个高性能的HTTP和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器。
events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server_names_hash_max_size 512; server_names_hash_bucket_size 128; server { # 端口 listen 8888; # 匹配请求中的host值 server_name localhost; # 监听请求路径 location / { # 查找目录 root D:/www/work/branch/test/; # 默认查找 index index.html index.htm; } # 反向代理接口 location /h5nc { # 代理服务器 proxy_pass http://test.xxx.com; } } }
# 反向代理接口 location /h5nc { # 代理服务器 proxy_pass http://test.xxx.com; }
负载均衡是Nginx 比较经常使用的一个功能,可优化资源利用率,最大化吞吐量,减小延迟,确保容错配置,将流量分配到多个后端服务器。
这里举出经常使用的几种策略
轮询(默认),请求过来后,Nginx 随机分配流量到任一服务器
upstream backend { server 127.0.0.1:3000; server 127.0.0.1:3001; }
weight=number 设置服务器的权重,默认为1,权重大的会被优先分配
upstream backend { server 127.0.0.1:3000 weight=2; server 127.0.0.1:3001 weight=1; }
backup 标记为备份服务器。当主服务器不可用时,将传递与备份服务器的链接。
upstream backend { server 127.0.0.1:3000 backup; server 127.0.0.1:3001; }
ip_hash 保持会话,保证同一客户端始终访问一台服务器。
upstream backend { ip_hash; server 127.0.0.1:3000 backup; server 127.0.0.1:3001; }
least_conn 优先分配最少链接数的服务器,避免服务器超载请求过多。
upstream backend { least_conn; server 127.0.0.1:3000; server 127.0.0.1:3001; }
当元素的尺寸、结构或触发某些属性时,浏览器会从新渲染页面,称为回流。此时,浏览器须要从新通过计算,计算后还须要从新页面布局,所以是较重的操做。
从上图中能够看到回流过程当中发生了recalculate style
->layout
->update layer tree
->paint
->composite layers
;
当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时因为只须要UI层面的从新像素绘制,所以损耗较少。
从上图中能够看到重绘过程当中发生了recalculate style
->update layer tree
->paint
->composite layers
;
从以上图中对比能够看出,重绘的过程比回流要少作一步操做,即layout
vm.$nextTick([callback])
将回调延迟到下次 DOM 更新循环以后执行。在修改数据以后当即使用它,而后等待 DOM 更新。它跟全局方法 Vue.nextTick 同样,不一样的是回调的 this 自动绑定到调用它的实例上。
附上源代码,不感兴趣能够直接跳过代码,不要紧。
/* @flow */ /* globals MutationObserver */ import { noop } from 'shared/util' import { handleError } from './error' import { isIE, isIOS, isNative } from './env' export let isUsingMicroTask = false const callbacks = [] let pending = false function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // Here we have async deferring wrappers using microtasks. // In 2.5 we used (macro) tasks (in combination with microtasks). // However, it has subtle problems when state is changed right before repaint // (e.g. #6813, out-in transitions). // Also, using (macro) tasks in event handler would cause some weird behaviors // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). // So we now use microtasks everywhere, again. // A major drawback of this tradeoff is that there are some scenarios // where microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690, which have workarounds) // or even between bubbling of the same event (#6566). let timerFunc // The nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // Use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // Fallback to setImmediate. // Techinically it leverages the (macro) task queue, // but it is still a better choice than setTimeout. timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } } export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) // this binding } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
降级处理顺序为Promise.resolve().then
->MutationObserver
->setImmediate
->setTimeout
export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } nextTick(flushSchedulerQueue) // fulshSchedulerQueue核心代码:watcher.run()更新视图 } } }
页面视图的渲染更新,也是向nextTick回调队列中添加一个回调函数,这个函数内部也保存着一个更新队列,不过这个队列是个同步的。
而这个回调会在设置data属性值的过程当中添加到nextTick的cbs里面的,早于用户使用nextTick添加的回调,因此能够保证回调的调用顺序。咱们来实验一下:
因为数据修改是同步的操做,而视图更新是异步的,因此咱们两次打印data.nextTickText都是修改后的值,而视图更新是异步的,因为第一个nexttick回调在视图更新回调函数fulshSchedulerQueue以前添加,因此此时读取到的是视图更新以前的值。咱们经过断电跟踪也能够看出此轮事件回调里面保存的三个函数最终会分别执行printNextTick、flushSchedulerQueue、printNextTick
之前面试的时候,别人问我nextTick的原理,我答完以后,别人接着问我那v-on呢?我当时一脸懵逼,v-on不就是事件绑定吗?你想表达什么?(黑人问号脸.png)
@
Function | Inline Statement | Object
event
.stop,.prevent,.capture,.self,.{keyCode | keyAlias},.native,.once,.left,.right,.middle,.passive
vue的事件绑定没有像react那样作事件代理,只是单纯的将事件所对应函数的this绑定到当前的vm。vue的事件绑定处理在vnode生成以前,即在生成ast模板层面就作好了处理,后续的函数只是负责调用。
genHandlers
去对修饰符作预处理,包括修饰符和是否为native,源码是一些常规的处理,这里就不贴出来了,感兴趣的能够自行查阅,代码目录/src/compiler/codegen/events.js
// 冒泡排序的本质是,两层循环,每一层外循环,将数组中的最大或者最小值挪到数组的尾部 function bubbleSort(arr) { let i, j; const len = arr.length; for (i = 0; i < len - 1; i++) { for (j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j + 1]) { [arr[j + 1], arr[j]] = [arr[j], arr[j + 1]]; } } } return arr; } // 选择排序 选择排序遍历并与自身比较,将最小或者最大的数组项与自身替换 function selectSort(arr) { const len = arr.length; let i, j; for (i = 0; i < len - 1; i++) { for ( j = i + 1; j < len; j++) { if (arr[i] > arr[j]) { [arr[i], arr[j]] = [arr[j], arr[i]]; } } } return arr; } // 插入排序 function insertSort(arr) { const len = arr.length; let i, j; for (i = 1; i < len; i++) { for (j = i; j > 0; j--) { if (arr[j] < arr[j - 1]) { [arr[j], arr[j - 1]] = [arr[j - 1], arr[j]]; } else { break; } } } return arr; } // 快速排序 function quickSort(arr) { if(arr.length <= 1) { return arr; //递归出口 } const baseItem = arr.splice(0, 1)[0]; const leftArr = [], rightArr = []; arr.forEach(item => { if (item <= baseItem) { leftArr[leftArr.length] = item; } else { rightArr[rightArr.length] = item; } }); return [...quickSort(leftArr), baseItem, ...quickSort(rightArr)]; }
// 斐波那契数列 function fiber(n) { if (n <= 2) { return 1; } return fiber(n - 1) + fiber(n - 2); }; // 斐波那契数列动态规划优化。使用缓存 function fiberProcess(n) { if (n <= 2) { return 1; } const cache = { 1: 1, 2: 1 }; let i = 3; for (i; i <= n; i++) { cache[i] = cache[i - 1] + cache[i - 2]; } return cache[n]; }
// 寻找最长公共子串 动态规划 function maxSubStr(A, B) { const lenA = A.length, lenB = B.length; let i = 0, j = 0; const tmp = []; let max = 0, index = 0; for (i; i< lenA + 1; i++) { tmp[i] = []; for (j = 0; j < lenB + 1; j++) { if (i === 0 || j === 0) { tmp[i][j] = 0; } else if (A[i - 1] === B[j - 1]) { tmp[i][j] = tmp[i - 1][j - 1] + 1; if (tmp[i][j] > max) { max = tmp[i][j]; index = i; } } else { tmp[i][j] = 0; } } } if (max > 0) { return A.slice(index - max, index); } return ''; }