vue
-
vue双向数据绑定
- vue2 Object.defineProperties数据劫持 + 发布者-订阅者模式
- vue3 使用Proxy数据劫持
-
vue-router实现
路由监听
-
hash监听function changeView(view) { document.body.innerHTML = view; } const routes = [{ path: "/asd", view: `<div>asd</div>` }]; window.onload = window.onhashchange = function () { const path = location.hash.substr(1); const {view=""} = routes.find(({path: rPath}) => rPath === path) || {}; changeView(view); }; -
history监听function changeView(view) { document.body.innerHTML = view; } const routes = [{ path: "/asd", view: `<div>asd</div>` }, { path: "/asdd", view: `<div>asdd</div>` }]; function updateView() { const path = location.pathname; const {view = ""} = routes.find(({path: rPath}) => rPath === path) || {}; changeView(view); } window.onpopstate = updateView; history.pushState = (() => { const fn = history.pushState; return (url, title = "", data = {}) => (fn.call(history, data, title, url), updateView()); })(); history.replaceState = (() => { const fn = history.replaceState; return (url, title = "", data = {}) => (fn.call(history, data, title, url), updateView()); })(); updateView();
路由组件实例化切换
import Vue from "vue"; let lastCmp; export function changeView(CmpTemp, el = document.querySelector("#app")) { if (lastCmp) { lastCmp.$destroy(); lastCmp.$el.parentNode.removeChild(lastCmp.$el); } const CmpConstructor = Vue.extend(CmpTemp); lastCmp = new CmpConstructor(); lastCmp.$mount(); el && el.appendChild(lastCmp.$el); } -
-
vuex概念 和几个api
概念
- 集中式的状态管理库
- 所有属性是响应式的 自动更新使用其属性的组件
- 只能使用
commit改变状态 实现单向数据流
api
- commit 提交一个mutation改变state
- dispatch 提交一个action
- mapState/Mutations/Actions各种语法糖
- namespace开启子模块命名空间 然后可以将子模块注册到父模块的modules中
-
vue异步加载
-
使用
()=>import(组件)来加载组件当判断为组件是一个函数的时候会在用到它的时候执行,返回一个promise 当组件加载完成后挂载到页面上 -
require.ensure已被import代替 效果相似resolve => require.ensure([依赖], () => resolve(require('./asd.vue')), 'chunkName')
-
-
vue父子通讯几种方法
- props/$emit
- provide/inject
- parent/\children
- vuex
- eventBus
$on/$off/$emit
-
指令/插件
//指令+插件 export default function CustomPlugin(Vue: VueConstructor, option) { Vue.mixin({ directives:{ custom:{ //初始化时调用,尚未加入到dom中 bind(el,binding){}, //插入父节点时调用 inserted(){}, //使用了该指令的整个组件更新时 update(){} } } }); } //使用 Vue.use(CustomPlugin); -
生命周期
create从外到内 mount/destory从内到外
create/mount/update/active(keepalive专属)/destory
-
ssr和spa
spa:
单页应用,即整个应用的排版结构都是由js在页面解析完后生成的,页面的切换也是用js进行修改dom结构
优点:减少服务器压力,因为js等静态文件可以进行缓存,服务器只需要提供首页很少的数据。
缺点:垃圾搜索算法引擎无法识别内容,因为客户端渲染需要一个js执行环境 并且是异步进行的,搜索引擎无法判断
ssr:
服务端渲染,将整个页面构建完成后输出
优点:利于垃圾搜索引擎识别其内容
缺点:压力较大,需要在服务端执行js生成页面接口输出
-
webpack优化 / 首屏优化 vendor拆分
-
多线程打包
happypack -
第三方依赖包缓存
autodll-webpack-plugin -
gzip压缩代码
-
import() 异步组件 懒加载页面
-
合理调整 splitChunks 使vendor变小 因为资源放CDN的话 并发请求比单独请求一个大js要快
-
开启http2 多路复用 规避并发资源问题
-
code split 代码分割 异步加载组件代码
-
图片压缩/精灵图合并
-
使用webpack的配置,将代码中用到的模块使用cdn导入,然后配置到
externals中,打包时会自动忽略改模块的引入 而是直接使用对应的全局变量 vendor拆分//先将模块引入 <script src="./lodash.js"></script> //在webpack配置中设置映射对应的全局变量 configureWebpack: {externals: {"lodash": "_"}} //在代码中导入,当打包时会自动忽略该引入 而改为lod = window._ import lod from "lodash"; console.log(lod)
-
-
vue中key的作用
- 更高效的让vdom diff时识别差异,提高节点复用率,不至于浪费性能渲染可复用dom
- 防止就地复用的导致
transition-group动画不生效,给每个标签打上标识后利于动画识别
-
watch和computed的区别
computed 会缓存值,只有在下一次获取该值时才会重新计算 应用:一个值依赖另一个值进行计算
watch 只要监听的值发生改变就会触发 应用:需要根据数据实时发生操作的情况 例如请求或者绘制
-
vue为什么不能监听数组
Object.defineProperty不能监听数组的未知下标,所以当数组发生动态改变时无法触发更新 -
为什么data是一个函数
因为模板组件导出的是一个对象,如果进行复用的话那每个生成的vue对象引用的data对象是同一个,当其中一个发生改变,所有的都会进行改变,所以使用函数,保证每个data都是唯一的 互不影响
-
v-model
v-model是:value/$emit("input",xxx)的语法糖 -
vue优化
- v-if/v-show 区分场景使用
- computed/watch 区分场景使用
- 路由/组件懒加载
- 懒加载列表
- 懒加载图片
- 预加载静态资源
-
mvc与mvvm区别
- mvc是model发生变化后controller去修改view
- mvvm是model发生后 根据view和model的绑定关系 自动修改view
dom
-
前端性能优化
- 精灵图 减少请求
- 静态资源缓存
- 压缩代码、图片
- gzip
- 异步加载代码
- 字体图标使用iconfont
- base64小图片
- 使用cdn
- 图片懒加载、内容骨架图
- 减少重绘回流
- 优化dom结构删除不必要元素,优化css选择器 浏览器从右向左匹配 避免过深的匹配
-
网页从输入网址到渲染完成经历了哪些过程?
并行创建dom tree、cssom tree -> 合并成render tree -> 计算布局layout -> 绘制paint -
事件流 阻止浏览器默认行为/阻止事件冒泡
-
事件流
从外到内捕获->从内到外冒泡
event.target是当前触发事件的元素,event.currentTarget是当前绑定事件的元素 -
阻止默认行为
e.preventDefault()阻止事件冒泡e.stopPropagation()
-
-
解决跨域问题/jsonp实现
-
jsonp
function jsonp(url, callback, params = {}) { const script = document.createElement("script"); //防止并发重名 const fnName = `fnName${Math.random().toString(16).substr(2)}`; window[fnName] = function () { const res = callback.apply(this, arguments); window[fnName] = null; script.parentElement.removeChild(script); return res; }; script.src = `${url}${obj2query({...params, callback:fnName })}`; document.body.appendChild(script); } -
后端设置允许跨域响应头
res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
-
-
优化dom操作
- 使用
innerHTML或者createDocumentFragment插入大量节点 - 在遍历外缓存
offsetWidth等需要计算元素布局的值 - dom操作是通过浏览器操作c构建的数据结构,而非直接在js环境操作 所以比较耗时。在大量操作时使用vdom代替
- 使用
-
行内元素/块级元素有哪些
-
行内元素
span/em/i/img/label/strong/input
-
块级元素
div/section/p/main/figure/figcaption/ul/ol
-
-
img标签alt和title区别
alt是图片加载失败时的代替文字,title是鼠标放在图片上的描述详情 -
h5本地存储几种方式 过期时间
cookie默认浏览器关闭消失,可以设置其过期时间localStorage不主动删除不会消失sessionStorage标签页关闭消失indexeddb类似sqllite 不主动删除不会消失 不常用
-
回流和重绘
回流一定会重绘,重绘不一定会回流
-
回流触发
- 窗口调整
- 元素布局或内容变化
- dom操作
- css伪类激活
- 计算元素的布局信息
offsetWidth
-
重绘触发 当页面渲染发生改变时就会触发
-
优化
- 少用table布局
- 使用
innerHTML/DocumentFragment进行dom操作 - 使用display:none,将元素隐藏后操作,只引发两次回流和重绘;
- 使用translate来代替left top
- 使用绝对定位将元素单独提出来 这样回流仅影响一小部分
-
-
浏览器缓存机制 内存/硬盘 http
加载图片速度:直接加载内存中已经准备好的image对象 > 内存读取图片 > 硬盘读取图片 > http
-
已经被加载一次的小文件下次访问时会直接从内存中取出
-
请求的文件如果带有缓存策略响应头,下次访问则会重定向到本地硬盘读取
-
http缓存策略
-
expires
-
cache-control
-
no-store 禁止缓存
-
no-cache 强制确认缓存 每次需要发个请求给服务器 判断文件是否有修改 未修改才会拿本地缓存
-
max-age = 999999999 缓存时间 直接从本地拿去 无需发请求确认
当max-age时间到后不会直接清除本地缓存, 而是发请求判断本地缓存的文件是否还是最新鲜的 即未修改 则304继续使用本地的缓存 节省带宽
expires是http1.0的,cache-control是http1.1的当两者同时存在时cache-control覆盖expires -
-
304本地重定向
- last-modified/etag 判断文件最后修改时间
-
-
ajax实现
function ajax(method = "GET", url, params = {}) { const xhr = new XMLHttpRequest(); return new Promise((resolve, reject) => { xhr.onreadystatechange = function () { if (this.readyState !== 4) return; if (this.status !== 200) return reject("请求出错"); resolve(JSON.parse(this.response)) }; xhr.open(method, method === "GET" ? `${url}${obj2query(params)}` : url, true); if (method === "POST") xhr.setRequestHeader("Content-Type", "application/json"); xhr.send(JSON.stringify(params)); }) }
-
js
-
异步是什么/闭包是什么/递归是什么
- 异步:浏览器是单线程环境,为了防止操作阻塞UI渲染,会将耗时操作放在同步任务结束后再执行。
- 闭包:闭包是指有权限访问(引用)另一个函数作用域的变量的函数,用途:创建一个私有变量环境,防止变量污染。
- 递归:重复调用自身函数,需要一个出口,不然会导致堆栈溢出。
-
深拷贝/浅拷贝
- 浅拷贝 只拷贝基础类型,引用类型只是复制引用
Object.assign()- 拓展符
{...obj}
- 深拷贝
JSON.parse(JSON.stringify(obj))不能复制不能被解析的值(symbol,function)和循环引用lodash.deepClone- 或者可以自己实现一个,需要注意循环引用,可以用weakMap来缓存对象,将循环引用的属性指向缓存的对象引用
- 浅拷贝 只拷贝基础类型,引用类型只是复制引用
-
数组排序/乱序/分组/去重/reduce
-
排序
[5,2,1,5,3].sort((p,c)=>p-c) //正数降序 负数升序 0排一起 for (let i = arr.length-1; i > 0 ; i--) { for (let j = 0; j < i; j++) { if (arr[i] < arr[j]) continue; [arr[i], arr[j]] = [arr[j], arr[i]] } } -
乱序
for (let i = 0; i < arr.length; i++) { const idx = Math.floor(Math.random() * arr.length); [arr[i], arr[idx]] = [arr[idx], arr[i]]; } -
分组
arr.reduce((map, item) => (map[item] = (map[item] + 1 || 1), map), {}) -
去重
Array.from(new Set(arr)) arr.reduce((map, item) => (map.set(item, ""), map), new Map()).keys() arr.reduce((arr, item) => arr.includes(item)?arr:arr.concat(item), []); -
reduce
有初始值则从0开始,无则从1角标开始,并把0角标作为初始值
Array.prototype.cusReduce = function (fn, initialVal) { let start = 0, arr = this; if (!initialVal) { start++; initialVal = arr[0] } while (start < arr.length) { initialVal = fn.call(this, initialVal, arr[start++], arr); } return initialVal; };
-
-
节流/防抖
-
节流
function throttle(fn, timeout) { let timer = null; return function () { if (timer) return; fn.apply(this, arguments); timer = setTimeout(() => timer = null, timeout) } } -
防抖
function debounce(fn, timeout) { let timer = null; return function () { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, arguments), timeout) } }
-
-
call/apply/bind实现
-
call
Function.prototype.cusCall = function () { const fn = this; const [ctx, ...args] = [].slice.call(arguments); Object.defineProperty(ctx, "call", {enumerable: false, value: fn}); let res = null; eval(`res = ctx["call"](` + args.map((arg) => JSON.stringify(arg)) + `)`); ctx["call"] = null; return res; }; -
apply
Function.prototype.cusApply = function () { const [ctx, args] = [].slice.call(arguments); let res = null; eval(`res = this.cusCall(ctx,` + args.map((arg) => JSON.stringify(arg)) + `)`); return res; } -
bind
Function.prototype.cusBind = function () { const [ctx, ...args] = [].slice.call(arguments); const fn = this; return function () { const args2 = [].slice.call(arguments); return fn.cusCall(ctx, [].concat(args, args2)); } };
-
-
数据类型/原型
-
数据类型 Number/Boolean/String/null/undefined/Symbol/Object
-
原型
每个对象都有原型(__proto__)指向构造器的
prototype,每个对象都可以调用原型链上的所有属性方法
-
应用:
- 继承,给构造器的原型指向其他构造器生成的对象 从而继承该函数上的属性方法
-
事件循环event loop
浏览器的事件循环:宏任务->微任务->宏任务....
宏任务:一个script标签内的任务属于一个宏任务,settimeout
微任务:promise
-
模块化的几种方式
-
commonjs
同步用于nodejs-
定义模块 根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性
-
模块输出: 模块只有一个出口,
module.exports对象,我们需要把模块希望输出的内容放入该对象 -
加载模块: 加载模块使用
require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象 -
exports是module.exports的引用 所以当exports = xxx时将丢失引用,正常用法应该为exports.xxx = xxx
-
-
AMD
异步用于浏览器主要有require.js库支持- 定义模块 define(id?, dependencies?, factory);
- id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
- dependencies:是一个当前模块依赖的模块名称数组
- factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值
- 引用模块 require([dependencies], function(){});
- 第一个参数是一个数组,表示所依赖的模块
- 第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
- 定义模块 define(id?, dependencies?, factory);
-
CMD
异步用于浏览器主要有SeaJs库支持// 定义模块 myModule.js define(function(require, exports, module) { var $ = require('jquery.js') $('div').addClass('active'); }); // 加载模块 seajs.use(['myModule.js'], function(my){}); -
es6模块化
统一由webpack支持- 是将所有文件打包到同一文件中,可使用代码分割插件进行分割。互相依赖
- 是用webpack内建函数进行require和define 其原理与cmd amd差不多
-
-
async/await实现
- 生成器维护了一个promise,专门用于返回函数最终值的
- 生成器另外维护了一串promise,专门用于执行每个yield返回的promise或者普通值,将其强行转换成promise并.then串连继续执行,直到gen最终返回done=true
- 整个过程就是为了让
generator在promise.then的进行下自动执行next函数
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, value) { let info; try { info = gen.next(value); } catch (e) { return reject(e); } if (info.done) return resolve(info.value); Promise.resolve(info.value).then(_next, _throw); } function asyncToGen(fn) { return function (...args) { return new Promise((resolve, reject) => { const gen = fn.apply(this, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, value) } function _throw(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, value) } _next(); }) } } -
instanceOf
function cusInstanceOf(left, right) { while (left = Object.getPrototypeOf(left)) { if (left === right.prototype) return true; } return false; } -
curry
function curry(fn) { return function closure(...args) { if (args.length === fn.length) return fn.apply(this, args); return (...others) => closure.apply(this,args.concat(others)); } } -
单例化
function Singleton(Constructor) { let instance = null; return new Proxy(Constructor, { construct(target, argArray, newTarget) { instance || (instance = Reflect.construct(target, argArray, newTarget)); return instance; } }) } -
深入剖析:Vue核心之虚拟DOM
css
-
瀑布流
-
flex
.c-waterfall { //横放 display: flex; &__item { //竖放 flex-direction: column; display: flex; li { width: 50px; margin: 3px; background: antiquewhite; } @for $j from 1 to 4 { &:nth-child(#{$j}) { @for $i from 1 to 11 { li:nth-child(#{$i}) { height: get-random(); } } } } } }
-
-
1px
-
媒体查询
.border { border: 1px solid #999 } @media screen and (-webkit-min-device-pixel-ratio: 2) { .border { border: 0.5px solid #999 } } @media screen and (-webkit-min-device-pixel-ratio: 3) { .border { border: 0.333333px solid #999 } } -
transofrm:scale@mixin one-border($ratio) { $size: 100% * $ratio; width: $size; height: $size; position: absolute; left: 50%; top: 50%; border: 1px solid #306eff; transform: translate(-50%, -50%) scale(1 / $ratio); } .c-box2 { position: relative; @include rect(100px); &:after { content: ""; @include one-border(3); } } -
box-shadowbox-shadow: 0 0 0 .333px #306eff; -
border-image让UI切一个一半透明 一半实线 -
background背景图片
-
-
水平垂直居中
flex+margin:auto#app { height: 100px; background: #306eff; display: flex; .box{ background: black; width: 30px; height: 30px; margin: auto; } }flex#app { height: 100px; line-height: 100px; background: #306eff; display: flex; justify-content: center; align-items: center; .box { background: black; width: 30px; height: 30px; } }absolute + transform#app { height: 100px; background: #306eff; position: relative; .box{ background: black; width: 30px; height: 30px; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); } }absolute + margin:auto#app { height: 100px; background: #306eff; position: relative; .box{ background: black; width: 30px; height: 30px; position: absolute; left: 0; top: 0; bottom: 0; right: 0; margin: auto; } }table-cell#app { height: 100px; background: #306eff; display: table; width: 100%; .container { display: table-cell; vertical-align: middle; text-align: center; } .box { background: black; margin: 0 auto; width: 30px; height: 30px; } }line-height+inline-block+vertical-align#app { height: 100px; line-height: 100px; background: #306eff; text-align: center; .box { display: inline-block; background: black; width: 30px; height: 30px; vertical-align: middle; } } -
优化动画
- 使用translate代替left top
- 使用scale+transform-origin代替width height动画
- 使用translate3d或will-change开启移动端设备动画加速
- 防止布局回流 将动画层绝对定位提出来
- 使用webgl或canvas
-
选择器有哪些 优先级排行
**选择器:**兄弟 相邻 伪类/伪元素 后代 子元素 属性 通配符 id class 标签
优先级:!important > 内联 > id > class/属性/伪类 > 标签/伪元素 > 通配符
权重相同 按顺序覆盖
-
rem自适应方案/响应式方案
首先移动端需要设置元属性,禁用双击放大,将宽度设置为设备宽度(如果不设置的话当宽度发生变化 内容会相对应的比例进行缩放)
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">-
rem自适应 flexible
根据设计稿和当前页面的宽度计算出比例,设置为根元素的字体,当页面的尺寸使用rem时,就是根据根元素的字体进行计算的。只需要计算出根元素的字体 就可以在不同宽度设备上进行等比例缩放了
-
响应式方案 bootstrap 各类UI框架的栅格布局
使用媒体查询 在不同宽度下给指定类名设置宽度比例 使得元素可以在不同设备上显示不同布局和尺寸
-
-
BFC
布局规则:内部的Box会在垂直方向,一个接一个地放置,Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠,每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
- 创建
- html根元素
- float不为none
- dispaly不为block
- overflow:不为visible
- position不为relative和static
- 应用
- 防止margin重叠
- 防止float父元素高度塌陷
- 防止被float元素重叠
- 创建