star
~Node.js
、Electron
、React-native
、 Linux
、Docker
、数据库、redis
、消息队列和操做系统等知识.根据经验,看上篇的人最多,由于毕竟这部分的前端占比大。有必定技术基础的,能够看看后面文章。display
出现“动画
”APP
,display
为"none"<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #app { width: 200px; height: 200px; background-color: red; display: none; transition: all 1s; } </style> </head> <body> <div id="app"> </div> <button id="test">测试</button> </body> </html>
none
,而且写入脚本文件<style> #app { width: 200px; height: 200px; background-color: red; display: none; } </style> 。。。 <script> test.onclick = function () { const app = document.querySelector('#app') console.log(app, 'app') app.style.transform = "translateX(200px)" app.style.display = "block" } </script>
test.onclick = function () { const app = document.querySelector('#app') console.log(app, 'app') app.style.display = "block" const height = app.offsetHeight app.style.transform = "translateX(200px)" }
const height = app.offsetHeight
这行代码的时候,再点击测试按钮,display
切换就顺带出来了“动画”,有了过分效果dom
的这些特殊属性时,浏览器就会强制清空渲染队列一次,让我拿到最新的值。也就是说读取的时候,其实已是display
为"block"了,所以。咱们出现了过渡动画<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #app { width: 200px; height: 200px; background-color: red; display: none; transition: all 1s; } </style> </head> <body> <div id="app"> </div> <button id="test">测试</button> </body> <script> test.onclick = function () { const app = document.querySelector('#app') console.log(app, 'app') app.style.display = "block" const height = app.offsetHeight app.style.transform = "translateX(200px)" } </script> </html>
答:html
js
代码执行层面的webpack
/或其余工具打包,精细化分割了代码。将node_modules
(vendor
开头)和本身写的src
(default
开头)源码分开打包了.这样最小的限度的缩小了hash
值改变带来的http
缓存失效问题.在webpack
中配置splitChunks
.splitChunks: { // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all chunks: "async", // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。 minSize: 30000, // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。 minChunks: 1, // 表示按需加载文件时,并行请求的最大数目。默认为5。 maxAsyncRequests: 5, // 表示加载入口文件时,并行请求的最大数目。默认为3。 maxInitialRequests: 3, // 表示拆分出的chunk的名称链接符。默认为~。如chunk~vendors.js automaticNameDelimiter: '~', // 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。 name: true, // cacheGroups 下能够能够配置多个组,每一个组根据test设置条件,符合test条件的模块,就分配到该组。模块能够被多个组引用,但最终会根据priority来决定打包到哪一个组中。默认将全部来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。 cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, // default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } }
js
文件仍是有几MB
大小,这个问题能忽略吗?固然不能够。咱们通常生产环境部署时候,会借助CDN
,在各类云厂商提供/本身搭建的CDN
服务配置相应的解析。掘金如今已由头条系接手,我看静态资源方案的域名开头使用的是s3.pstatp.com
,我查询大概是跟抖音/相关的域名。或许是本身搭建的CDN
服务(千万不要忽略CDN,静态资源服务对于服务器来讲是很是占带宽的,速度和体验确定会比CDN差不少,越大越重的系统提高越大
)网络传输层面, 仅仅是静态资源吗?前端
ajax
请求APP
改造方案,弱网/无网络状况拥有正常的体验,以为分块传输就是核心,特别在APP
端)对于ws
/wss
协议、TCP等私有协议双工通讯的node
hash
算法和接收到的错误上报,生成对应的后缀空文件,发布到CDN。这样前端每次上报时候,都会用统一hash
算法生成对应后缀,请求那个地址,若是存在,就说明后端已经接受到了这个上报,能够丢弃这个上报。同理,咱们能够参考这个,能够用空的CDN
文件作网络检测质量检测(由于一些跨平台APP框架没法精确获取当前网络质量),而后再将发送请求队列持久化便可保证请求不丢失。(对于IM场景的APP,那就要考虑更多,这里不对IM展开讲)Js
代码执行层面的,先看一段代码:// 真奥义之阻塞JS主线程之休眠 const sleep = (delay = 500) => { let t = Date.now(); while(Date.now() - t <= delay) { continue; } };
JS
主解析线程,默认是500ms
,这意味着,这500ms
,用户操做咱们的系统,是不会有任何相应的,由于此时JS主解析线程一直被阻塞。(若是是10秒,那么会怎样?若是把这段代码换成其余计算的函数/循环,效果也是同样)。这就是由于JS
代码致使的性能问题,如何解决?固然是分片分片分片!不要写太耗时的JS
同步代码.web worker wasm PWA
等技术能够解决一些问题啊.可是我劝你在技术选型的时候,要考虑一个复杂度和收益的问题。如今仍是有人用JQ
和原生JS
、Canvas
开发项目,由于开发最复杂的功能,每每都须要他们。(例如富文本编辑器、可视化编辑等)web worker
技术,使用事件通讯,将大量计算工做交给web worker
。Worker 也能够建立新的 Worker,固然,全部 Worker 必须与其建立者同源(注意:Blink暂时不支持嵌套 Worker)。var myWorker = new Worker('worker.js'); var first = document.querySelector('#number1'); var second = document.querySelector('#number2'); first.onchange = function() { myWorker.postMessage([first.value,second.value]); console.log('Message posted to worker'); }
web worker
兼容性很好,若是是webpack
环境,须要对应作一些工程化环境处理,web worker
在我看来,适用于纯粹的大量计算场景。我记得有团队是用于3D数据可视化处理的,他们使用web worker
后提高就很大webAssembly
技术,更适用于音视频领域,更底层的功能处理,例如浏览器端要音视频编解码或者图片压缩处理之类就很适合用它.它有点像浏览器版的Node.js的c++
插件。Node.js
能够调用C# Go Object-c
等语言的插件&通讯,同理,webAssembly
也是能够用各类语言编写。关于webAssembly
这个技术,我写过一篇文章方便你们入门。self.importScripts('ffmpeg.js'); onmessage = function(e) { console.log('ffmpeg_run', ffmpeg_run); var files = e.data; console.log(files); ffmpeg_run({ arguments: ['-i', '/input/' + files[0].name, '-b:v', '64k', '-bufsize', '64k', '-vf', 'showinfo', '-strict', '-2', 'out.mp4'], files: files, }, function(results) { console.log('result',results); self.postMessage(results[0].data, [results[0].data]); }); }
V8
,因为它是自动垃圾回收机制,那么有一可能下一次回收的内存垃圾达到500MB
,这个时候就会产生卡顿,有人会莫名其妙,为何会卡?由于v8
垃圾回收也会阻塞主解析线程,因此形成了让你卡的假象
.这里留下一个问题,若是使用Node.js c++
插件或者wasm
技术,调用它们模块暴露的方法,它们的方法很是耗时,此时js
主解析线程挂起吗?
渲染层面的性能问题:webpack
function defer(fn) { //requestIdleCallback的兼容性很差,对于用户交互频繁屡次合并更新来讲,requestAnimation更有及时性高优先级,requestIdleCallback则适合处理能够延迟渲染的任务~ // if (window.requestIdleCallback) { // return requestIdleCallback(fn); // } //高优先级任务 异步的 先挂起 return requestAnimationFrame(fn); } export function enqueueSetState(stateChange, component) { //第一次进来确定会先调用defer函数 if (setStateQueue.length === 0) { //清空队列的办法是异步执行 defer(flush); } //向队列中添加对象 key:stateChange value:component setStateQueue.push({ stateChange, component }); //若是渲染队列中没有这个组件 那么添加进去 if (!renderQueue.some(item => item === component)) { renderQueue.push(component); } }
SSR
,SSR
有成熟的框架和库,能够考虑用nuxt,next
等直接生成静态资源文件性能是一个很是大的学问,每每都看重这方面,可是真正有对性能要求很高的项目,每每都是环境很复杂的,例如使用JS
和C++
插件去作解析播放同一个格式的音频,解析速度哪一个快,这些都是须要实际项目和团队支撑你去实践,这里我以为也写得比较浅,虽然说核心技术不外泄,可是这些大部分人确定都够用了
Promise
核心原理.then
实现链式调用,每次都是返回一个新的Promise
,跟JQuery
的链式调用类似(返回this
)MyPromise.prototype.then = function (onResolved, onRejected) { // 定义then const self = this; // 指定回调函数的默认值(必须是函数) onResolved = typeof onResolved==='function' ? onResolved : value => value; onRejected = typeof onRejected==='function' ? onRejected : reason => {throw reason}; return new MyPromise((resolve,reject)=>{ // 返回一个新的MyPromise对象 function handle(callback) { // 返回的MyPromise结果由onResolved/onRejected的结果决定 // 一、抛出异常MyPromise结果为失败 reason为结果 // 二、返回的是MyPromise MyPromise为当前的结果 // 三、返回的不是MyPromise value为结果 // 须要经过捕获获取才能知道有没有异常 try{ const result = callback(self.data) // 判断是否是MyPromise if ( result instanceof MyPromise){ // 只有then才知道结果 result.then(value=>resolve(value),reason=>reject(reason)) }else{ resolve(result) } }catch(error){ reject(error) // 返回的结果为reject(error) 上面第一点 } } // 判断当前的status if (self.status === FULFILLED){ // 状态为 fulfilled setTimeout(()=>{ // 当即执行异步回调 handle(onResolved); }) } else if (self.status === REJECTED){ // 状态为 rejected setTimeout(()=>{ // 当即执行异步回调 handle(onRejected); }) }else{ // pendding将成功和失败保存到callbacks里缓存起来 self.callbacks.push({ onResolved(value){ //函数里面调用回调函数 而且根据回调函数的结果改变MyPromise的结果 handle(onResolved) //为何这里没有setTimeout,由于上面已经写到过改变状态后回去callbacks里循环待执行的回调函数 }, onRejected(reason){ handle(onRejected) } }) } }) }
createStore
里的实现,根据是否传入了中间件作处理export default function createStore(reducer, enhancer) { if (typeof enhancer !== 'undefined') { return enhancer(createStore)(reducer) } let state = null const listeners = [] const subscribe = (listener) => { listeners.push(listener) } const getState = () => state const dispatch = (action) => { state = reducer(state, action) listeners.forEach((listener) => listener()) } dispatch({}) return { getState, dispatch, subscribe } }
reduce
,将上次的结果逐个传入,核心在于compose
,支持了多个中间件使用.import compose from './compose'; export default function applyMiddleware(...middlewares) { return (createStore) => (reducer) => { const store = createStore(reducer) let dispatch = store.dispatch let chain = [] const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } export default function compose(...funcs) { return funcs.reduce((a, b) => (...args) => a(b(...args))); }
微前端原理:c++
entry
入口,去对应的地址拉取index.html
文件,获取他们所需的资源和全部标签、DOM
节点DOM
节点和标签塞入基座中以及用key-value
形式缓存内存中(避免重复发送请求拉取)async function loadApp() { const shouldMountApp = Apps.filter(shouldBeActive); const App = shouldMountApp.pop(); fetch(App.entry) .then(function (response) { return response.text(); }) .then(async function (text) { const dom = document.createElement('div'); dom.innerHTML = text; const entryPath = App.entry; const subapp = document.querySelector('#subApp-content'); subapp.appendChild(dom); handleScripts(entryPath, subapp, dom); handleStyles(entryPath, subapp, dom); }); }
async function handleScripts(entryPath, subapp, dom) { const scripts = dom.querySelectorAll('script'); const paromiseArr = scripts && Array.from(scripts).map((item) => { if (item.src) { const url = window.location.protocol + '//' + window.location.host; return fetch(`${entryPath}/${item.src}`.replace(url, '')).then( function (response) { return response.text(); } ); } else { return Promise.resolve(item.textContent); } }); const res = await Promise.all(paromiseArr); if (res && res.length > 0) { res.forEach((item) => { const script = document.createElement('script'); script.innerText = item; subapp.appendChild(script); }); } }
webpack
原理webpack打包过程git
webpack打包原理es6
AST
抽象语法树 ,一个AST
抽象语法树以下所示:Node { type: 'File', start: 0, end: 32, loc: SourceLocation { start: Position { line: 1, column: 0 }, end: Position { line: 1, column: 32 } }, program: Node { type: 'Program', start: 0, end: 32, loc: SourceLocation { start: [Position], end: [Position] }, sourceType: 'module', interpreter: null, body: [ [Node] ], directives: [] }, comments: [] }
AST
阶段中去处理代码AST
抽象语法树变成浏览器能够识别的代码, 而后输出const fs = require('fs'); const path = require('path'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; // traverse 采用的 ES Module 导出,咱们经过 requier 引入的话就加个 .default const babel = require('@babel/core'); const read = fileName => { const buffer = fs.readFileSync(fileName, 'utf-8'); const AST = parser.parse(buffer, { sourceType: 'module' }); console.log(AST); // 依赖收集 const dependencies = {}; // 使用 traverse 来遍历 AST traverse(AST, { ImportDeclaration({ node }) { // 函数名是 AST 中包含的内容,参数是一些节点,node 表示这些节点下的子内容 const dirname = path.dirname(filename); // 咱们从抽象语法树里面拿到的路径是相对路径,而后咱们要处理它,在 bundler.js 中才能正确使用 const newDirname = './' + path.join(dirname, node.source.value).replace('\\', '/'); // 将dirname 和 获取到的依赖联合生成绝对路径 dependencies[node.source.value] = newDirname; // 将源路径和新路径以 key-value 的形式存储起来 } }) // 将抽象语法树转换成浏览器能够运行的代码 const { code } = babel.transformFromAst(AST, null, { presets: ['@babel/preset-env'] }) return { filename, dependencies, code } }; read('./test1.js');
// 建立依赖图谱函数, 递归遍历全部依赖模块 const makeDependenciesGraph = (entry) => { const entryModule = read(entry) const graghArray = [ entryModule ]; // 首先将咱们分析的入口文件结果放入图谱数组中 for (let i = 0; i < graghArray.length; i ++) { const item = graghArray[i]; const { dependencies } = item; // 拿到当前模块所依赖的模块 if (dependencies) { for ( let j in dependencies ) { // 经过 for-in 遍历对象 graghArray.push(read(dependencies[j])); // 若是子模块又依赖其它模块,就分析子模块的内容 } } } const gragh = {}; // 将图谱的数组形式转换成对象形式 graghArray.forEach( item => { gragh[item.filename] = { dependencies: item.dependencies, code: item.code } }) console.log(gragh) return gragh; }
gragh
获得的对象:{ './app.js': { dependencies: { './test1.js': './test1.js' }, code: '"use strict";\n\nvar _test = _interopRequireDefault(require("./test1.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(test 1);' }, './test1.js': { dependencies: { './test2.js': './test2.js' }, code: '"use strict";\n\nvar _test = _interopRequireDefault(require("./test2.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(\'th is is test1.js \', _test["default"]);' }, './test2.js': { dependencies: {}, code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports["default"] = void 0;\n\nfunction test2() {\n console.log(\'this is test2 \');\n}\n\nvar _default = tes t2;\nexports["default"] = _default;' } }
const generateCode = (entry) => { // 注意:咱们的 gragh 是一个对象,key是咱们全部模块的绝对路径,须要经过 JSON.stringify 来转换 const gragh = JSON.stringify(makeDependenciesGraph(entry)); // 咱们知道,webpack 是将咱们的全部模块放在闭包里面执行的,因此咱们写一个自执行的函数 // 注意: 咱们生成的代码里面,都是使用的 require 和 exports 来引入导出模块的,而咱们的浏览器是不认识的,因此须要构建这样的函数 return ` (function( gragh ) { function require( module ) { // 相对路径转换成绝对路径的方法 function localRequire(relativePath) { return require(gragh[module].dependencies[relativePath]) } const exports = {}; (function( require, exports, code ) { eval(code) })( localRequire, exports, gragh[module].code ) return exports; } require('${ entry }') })(${ gragh }) `; } const code = generateCode('./app.js'); console.log(code)
code
以下:(function( gragh ) { function require( module ) { // 相对路径转换成绝对路径的方法 function localRequire(relativePath) { return require(gragh[module].dependencies[relativePath]) } const exports = {}; (function( require, exports, code ) { eval(code) })( localRequire, exports, gragh[module].code ) return exports; } require('./app.js') })({"./app.js":{"dependencies":{"./test1.js":"./test1.js"},"code":"\"use strict\";\n\nvar _test = _interopRequireDefault(require(\"./test1.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log(_test[\"default\"]);"},"./test1.js":{"dependencies":{"./test2.js":"./test2.js"},"code":"\"use strict\";\n\nvar _test = _interopRequireDefault(require(\"./test2.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log('this is test1.js ', _test[\"default\"]);"},"./test2.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = void 0;\n\nfunction test2() {\n console.log('this is test2 ');\n}\n\nvar _default = test2;\nexports[\"default\"] = _default;"}})
赞
,让更多人看到这篇文章前端巅峰
],个人gitHub
源码地址是:https://github.com/JinJieTan/Peter-
,记得Star
哦