面试

136 阅读41分钟

//一。 //1.写一下冒泡排序,快速排序,他们的时间复杂度? // 冒泡排序的时间复杂度是O((n²-n)/2) 快速排序的时间复杂度是O(nlogn) //let arr=[4,3,5,2,1] /function bubbleSort (arr) { var _arr = [].concat(arr) for (var i=0;i<_arr.length-1;i++){ for (var j=0;j<_arr.length-1-i;j++){ if (_arr[j]>_arr[j+1]){ var temp = _arr[j] _arr[j]=_arr[j+1] _arr[j+1]=temp } } } return _arr } console.log(bubbleSort(arr))/

/function quickSort(arr){ if (arr.length<= 1){ return arr } var flag=arr[0] var bigger=[] var smaller=[] for (var i=1;i<arr.length;i++){ if (flag>=arr[i]){ smaller.push(arr[i]) }else{ bigger.push(arr[i]) } } return quickSort(smaller).concat(flag,quickSort(bigger)) } console.log(quickSort(arr))/

//2.数组去重?去除两数组中对象相同的id? // arr =[3,2,1,3,2,2,1,4,4,{a:1},{a:1},NaN, NaN,true,true] /function clearDb1 (arr) { return [...new Set(arr)] }/ /function clearDb2(arr){ var res = [] for (var i=0;i<arr.length;i++){ if (!res.includes(arr[i])){ res.push(arr[i]) } } return res }/ /function clearDb3 (arr) { return arr.reduce((prev,cur) =>prev.includes(cur)?prev:[...prev,cur],[]) } console.log(clearDb3(arr))/ /* function clearDb4(arr){ var obj={} return arr.filter(function (item,index) { return obj.hasOwnProperty(typeof item+item)?false:obj[typeof item+item]=true }) }*/

/function clearObj (arr) { var obj ={} var res =[] res=arr.reduce(function (prev,cur) { obj[cur.name]?"":obj[cur.name]=prev.push(cur) return prev },[]) return res } var arr = [ {id: 1, name: '周瑜'}, {id: 3, name: '王昭君'}, {id: 2, name: '李白'}, {id: 1, name: '周瑜'}, {id: 4, name: '李白'}, {id: 3, name: '王昭君'} ] console.log(clearObj(arr))/ /function clearDb(arr1,arr2){ return arr2.filter(item => !arr1.some(ele=>ele.id===item.id)); } var arr2 = [ {id: 1, name: '周瑜'}, {id: 3, name: '王昭君'}, {id: 2, name: '李白'}, ] var arr1 = [ {id: 1, name: '周瑜'}, {id: 3, name: '王昭君'}, ] console.log(clearDb(arr1,arr2));/

//3.数组扁平化? /function bianping (arr) { var res = [] for (var i=0;i<arr.length;i++){ if(Array.isArray(arr[i])){ res = res.concat(bianping(arr[i])) }else{ res.push(arr[i]) } } return res } var arr=[1,[2,3,[4,5]],6] console.log(bianping(arr))/

//4.统计数组里数据出现多少次? //var arr = [1,1,1,2,3,4,4,4,2,3,5,4] /function countTimes (arr) { return arr.reduce(function (prev,cur) { return prev.set(cur, (prev.get(cur) || 0) + 1) },new Map()) }/ /function countTimes(arr){ return arr.reduce(function (prev,next) { if(next in prev){ prev[next]++ }else{ prev[next]=1 } return prev },{}) } console.log(countTimes(arr))/

//5.函数柯里化,什么是偏函数,属于设计模式中的什么模式,高阶函数? //柯里化:一个函数传递实参少于形参 那么就返回一个函数 等待调用此函数继续执行程序 使代码模块化,减少耦合增强其可维护性 //常见柯里化后的工具 lodash(fp模块) Ramda /*function curry (fn) { return function () { var args = arguments return function () { return fn(...args,...arguments) } } } function funAdd (a,b,c,d) { return a+b+c+d } funAdd=curry(funAdd) var fn = funAdd(1,2,3) console.log(fn(4))

//偏函数 策略模式 参数为函数时带入 偏函数相当于是预定了某些特定功能的高阶函数 function checkType (type) { return function (o) { return Object.prototype.toString.call(o)===[object ${type}] } } var check = checkType('Array') console.log(check([2,3]))*/

//6.对象深拷贝? /function deepClone (o) { if(typeof o ==="string"||typeof o ==="number"||typeof o ==='boolean'||typeof o ==='undefined'){ return o }else if (Array.isArray(o)){ let _arr=[] for (var i=0;i<o.length;i++){ _arr.push(deepClone(o[i])) } return _arr }else if (typeof o === 'object'){ let _o={} for (var k in o){ _o[k]=deepClone(o[k]) } return _o } } let aaa={a:1,b:2,c:{}} console.log(deepClone(aaa)===aaa) console.log(deepClone(aaa.c)===aaa.c)/

//7.闭包是什么有什么作用? //闭包是指有权访问另一个函数作用域中的变量的函数。实质上是将函数内部与外部连接起来的桥梁。 //当一个函数返回时,一个没有释放资源的栈区。闭包只记住自己定义时所处的作用域。 /var a =20 function bibao () { var a =1 return function () { return a } } console.log(bibao()())//1/ /var a = 10 function fun () { console.log(a) //10 定义时所处的环境只能找到a=10 } (function (fn) { var a=100 fn() })(fun)/ //闭包的作用: //1.结果缓存:将一个处理过程很耗时的函数,每次调用都会花费很长时间,需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值 //2.匿名自执行函数:由于外部无法引用它内部的变量,因此在函数执行完后会立刻释放资源,不污染全局对象。 //3.封装私有变量:变量作用域为函数内部,外部无法访问 //4.实现类和继承prototype添加都是闭包 //闭包的优缺点: //1.利用闭包可以突破作用域链,将函数内部的变量传递到外部。 //2.可以让这些局部变量保存在内存中,实现变量数据共享。 //3.避免全局变量的污染。私有成员变量的存在。 //1.函数中的变量都被保存在内存中,内存消耗很大,解决方法是,在退出函数之前,将不使用的局部变量全部设置为null //2.闭包会在父函数外部,改变父函数内部变量的值。如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。

//8.怎么判断是数组,对象,是不是空对象?for..in for Object.keys() Object.getOwnPropertyNames() for..of /var o = [2,3,2] console.log(Array.isArray(o)) //true console.log(Object.prototype.toString.call(o)) //[object Array] console.log(o.constructor === Array)//[Function: Array] console.log(o instanceof Array)//true 不准确 console.log(o instanceof Object)//不准确 arr会一直搜寻Array.prototype之后的prototype 一直到null console.log(typeof o)//object 不能具体区分数组和对象/

/var o = {} //1.Object.keys(o).length //2.for...in //3.JSON.stringify(obj) == "{}" //4.Object.getOwnPropertyNames(o).length/

//for..in 返回所有能够通过对象访问的、可枚举的属性,包括实例中的属性和原型中的实例属性。 //如果想定义不被for..in的属性 1.Object.defineProperty(Array.prototype,'demo',{enumerable: false}) 2.hasOwnProperty()过滤 //Object.keys()获取对象自身所有的可枚举的属性值,但不包括原型中的属性,返回一个由属性名组成的数组 //Object.getOwnPropertyNames()返回对象的所有自身属性的属性名(包括不可枚举的属性defineProperty定义)组成的数组,但不会获取原型链上的属性。 //for..of遍历可迭代的对象(Array, Map, Set, arguments),它主要用来获取对象的属性值,而for..in主要获取对象的属性名。 //想要for..of遍历对象?1.类数组对象,Array.from 2.非类数组对象:添加一个[Symbol.iterator]属性,并指向一个迭代器即可 /obj[Symbol.iterator] = function(){ var keys = Object.keys(obj); for(var k of keys){ yield [k,obj[k]] } };*/ //性能方面 for>for..of>for..in原因是for..in需要穷举对象的所有属性,包括自定义的添加的属性也能遍历到且for...in的key是String类型,有转换过程,开销比较大但是for循环的i是Number类型,开销较小

//9.狭义和广义中的HTML5新特性? //标签 <link> <style> <script> <noscript> //布局标签语义化:<div><span><header><footer><section><article><aside><details><summary><dialog><nav><hgroup> //功能性标签:<audio autoplay loop preload="auto" muted静音> <video><source src="music.mp3"> //获取地理位置API:navigator.geolocation.getCurrentPosition() //画布canvas: (插件:html2canvas) /*<canvas id="myCanvas" width="200px" height="100px"> 你的浏览器不支持<canvas>标签! </canvas> var c=document.getElementById('myCanvas'); var ctx=c.getContext('2d'); ctx.fillStyle='#FF0000'; ctx.fillRect(0,0,100,100);//盒 ctx.moveTo(0,0);//开始 ctx.lineTo(400,200);//结束 ctx.moveTo(0,200);//开始 ctx.lineTo(400,0);//结束 ctx.closePath(); ctx.stroke(); ctx.restore(); */ //webSocket //创建在TCP上的协议没有同源限制通过HTTP/1.1 协议的101状态码进行握手 /<em>var ws = new WebSocket("wss://echo.websocket.org") ws.onopen=function(){} ws.addEventListener("close", function(event) {} ws.onclose/onmessage/send()</em>/ //web worker 多线程 worker允许一段js程序运行在主线程之外的线程,子线程与主线程互不干扰,当子线程的代码执行完成,将结果返回给主线程 //worker通过postMessage()和onmessage = () => {} 来进行通信,postMessage传递的数据都是拷贝传递 //worker限制 1 必须同源(协议,域名,端口) 2 访问 worker子线程所在的全局对象,与主线程不在一个上下文环境,所以无法读取主线程所在网页的DOM,无法使用document,window,parent这些对象,global指向有变更,window需要改写成self,不能执行alert()和comfire()的方法,只能读取部分navigator对象内的数据 3 worker子线程中支持js的异步操作,但仍然不可以跨域</p> <p><a href="//10.sessionStorage" target="_blank">//10.sessionStorage</a>,localStorage,cookie,session,token都是什么有什么区别? /<em>cookie? cookie是在http响应报文头中,添加set-cookie属性,要求浏览器设置cookie,在服务器和浏览器之间传递, 浏览器每次在请求的报文头中携带cookie,方便服务器对自己进行识别,cookie一定是k=v形式的字符串 用cookie做用户登录不安全,因为cookie是明文传输,做登录要用session技术,cookie主要充当本地存储 session? session就是秘文cookie localStorage、sessionStorage? localStorage.setItem("c",3) localStorage.getItem("data") localStorage.removeItem("a") 不同浏览器无法共享localStorage或sessionStorage中的信息。 相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享sessionStorage的信息 token? 后端其实就是用用户名 密码 浏览器 ip等各种特征md5加密32位 token不能判断用户是谁 只能判断用户是否合法登录过</em>/</p> <p><a href="//11.xn--3brv92aqsjci3akxh" target="_blank">//11.正则表达式</a>? /*regex元字符: .任意字符 \w字母数字下划线汉字 \s空白符 \d数字 \b字母的开头或结束 ^开始 $结束 重复限定符: <em>零次或更多次 +一次或更多次 ?零次或一次 {n}重复n次 {n,}重复n次或更多次 {n,m}重复n到m次 ()分组 \转义 |条件或 区间:[0-9] [A-Z] [56] 懒惰量词是在贪婪量词后面加个“?” 反义: \W 不是字母数字下划线汉字 \S不是空白符 \D非数字 [^xyz]不是以xyz开头</em>/</p> <p><a href="//12.xn--js-ry2cua30mx5o2xmsns7jb7uu93d5r0aquml3l" target="_blank">//12.js中操作数组和字符串的方法</a>? //数组的方法 // push() pop() shift() unshift() slice() splice() concat() join() reverse() sort() isArray() toString() // map() forEach() filter() every() some() indexOf() lastIndexOf() reduce() reduceRight() // from() of() find() findIndex() fill() includes() copyWithin() keys()、values()、entries() //字符串的方法 // indexOf() lastIndexOf() search() slice() split() substring() substr() replace() concat() trim() // length str[idx] charAt() charCodeAt() toUpperCase() toLowerCase()</p> <p><a href="//13.xn--js-8d6cu2m" target="_blank">//13.js原型</a>,原型链,继承? //JS中每个对象都会在其内部初始化一个prototype属性, //原型链:对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,知道找到object的原型null /<em>实现继承的几种方式: 构造函数继承:通过call()方法改变Child的this指向使子类的函数体内执行父级的构造函数从而实现继承效果 Parent.call(this)构造函数继承法只能实现部分继承,如果我们在父类Parent的原型链上添加属性或者方法的时候子类的实例无法继承到 原型链继承: 让子类的原型直接等于父类实例Child2.prototype = new Parent2() 借助原型链实现继承虽然解决了父类原型的方法能让子类实例对象继承的问题,但是如果我们通过子类的实例对象修改父类上的属性和方法,那么所有子类的所有实例对象上的属性和方法都会被改变。 因为生成的实例instanceof都指向同一个constructor 组合继承(最佳实践):Object.create()会使用指定的原型对象及其属性去创建一个新的对象 <em>/ /</em> 实现一个单继承:组合继承: function BABA(name,age) { this.name = name this.age = age } BABA.prototype.say=function (name) { console.log(<code>say baba ${name}</code>) } function Son(name) { BABA.call(this,name) } Son.prototype = Object.create(BABA.prototype) Son.prototype.constructor = Son</em>/</p> <p><a href="//14.xn--eventLoopnodeeventLoop-xm78aj05gzyjm83mk73bpa4214l" target="_blank">//14.浏览器的eventLoop和node中的eventLoop</a>(事件循环)? /<em>Js有一个主线程和执行栈(后进先出)和多个任务队列(先进先出),所有的任务都会被放到执行栈等待主线程执行。 同步任务和异步任务:js单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空onclick,onload),被读取到栈内等待主线程的执行。 执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去执行Task(宏任务),每次宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环。</em>/ /<em>宏任务:setTimeout, setInterval, setImmediate(当前poll阶段完成后check阶段执行脚本), I/O ,UI Rendering 微任务:Promise.then(),process.nextTick(node api)(将 callback 添加到next tick队列。 一旦当前事件轮询队列的任务全部完成,在next tick队列中的所有callbacks会被依次调用。),MutationObserver 浏览器中:程序自上到下执行,优先执行script(属于全局任务/宏观任务),碰到任务放入任务队列,优先清空微观任务后执行下一个宏观任务 Node环境中:node事件循环阶段:timers--pending callback--idle, prepare--poll – check--close callbacks timers:执行setTimeout和setInterval中到期的callback poll:执行I/O回调/处理轮询队列中的事件 poll队列不为空,事件循环将遍历其同步执行它们的callback队列 如果poll队列为空如果有setImmediate()回调需要执行,则会立即停止执行poll阶段并进入执行check阶段以执行回调 没有setImmediate()回调需要执行,poll阶段将等待callback被添加到队列中,然后立即执行 check:当代码被执行时,事件循环最终将达到poll阶段,它将等待传入连接,请求等。但是,如果已经调度了回调setImmediate(),并且轮询阶段变为空闲,则它将结束并且到达check阶段,而不是等待poll事件</em>/ /<em>process.nextTick:将 callback 添加到next tick队列。一旦当前事件轮询队列的任务全部完成,在next tick队列中的所有callbacks会被依次调用 方法属于微任务,它指定的任务总是发生在所有异步任务之前。 setTimeout和 setImmediate:setImmediate在poll(轮询)阶段完成时执行,即check阶段。该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。setTimeout在poll阶段为空闲时,且设定时间到达后执行, 但其在timer阶段执行 process.nextTick() 效率最高,消费资源小,但会阻塞CPU的后续调用; setTimeout(),精确度不高,可能有延迟执行的情况发生,且因为动用了红黑树,所以消耗资源大; setImmediate(),消耗的资源小,也不会造成阻塞,但效率也是最低的,将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行setImmediate指定的回调函数</em>/</p> <p><a href="//15.xn--ES6-ik9dk20airrn9i1uc942b" target="_blank">//15.ES6有哪些新特性</a>? /*let const 模板字符串 函数默认参数 对象属性,函数名简写 展开运算符 对象,数组解构 箭头函数:箭头函数的this永远指向其父作用域,任何方法都改变不了,包括call,apply,bind。 箭头函数不能作为构造函数,不能使用new 由于this必须是对象实例,而箭头函数是没有实例的,此处的this指向别处,不能产生person实例 箭头函数本身没有arguments,如果箭头函数在一个function内部 箭头函数中要想接收不定参数,应该使用rest参数...解决。 箭头函数没有原型属性 Object.assign({},obj1,obj2) 合并浅复制对象 import 和 export: 1.当用export default导出时,就用 import导入(不带大括号) 2.一个文件里,有且只能有一个export default。可以有多个export。 3.当用export name 时,就用import { name }导入(记得带上大括号) 4.当一个文件里,既有一个export default people, 又有多个export name 或者 export age时,导入就用 import people, { name, age } 5.当一个文件里出现n多个 export 导出很多模块,导入时除了一个一个导入,也可以用import * as example 生成器( generator加星函数):一个返回迭代器的函数 函数会在每个yield后暂停 function *creatI () { yield 1 yield 2 yield 3 } console.log(creatI().next()) //{value:1,done:false} 异步调用对于我们来说是很困难的事,我们的函数并不会等待异步调用完再执行,你可能会想到用回调函数,Promise,Async/await 生成器可以让我们的代码进行等待。就不用嵌套的回调函数。使用generator可以确保当异步调用在我们的generator函数运行一下行代码之前完成时暂停函数的执行 set和map: 为啥有了对象还要有map? Js中的对象有个小问题,键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的,所以出现了map new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]) new Set([1, 2, 3]) async/await: promise的语法糖(async后是then,await后是promise) async 定义的函数会默认的返回一个Promise对象resolve的值,因此对async函数可以直接进行then操作,返回的值即为then方法的传入函数 await 关键字 只能放在 async 函数内部, await关键字的作用 就是获取 Promise中返回的内容, 获取的是Promise函数中resolve或者reject的值</p> <ul> <li>*/</li> </ul> <p><a href="//16.xn--promise-sb4kz8a847m" target="_blank">//16.promise是什么</a>,手写promise? async..await ,一个函数传入两个值2s后输出相加? //Promise 是异步编程的一种解决方案 是一个语法糖 promise实例可以将异步执行的结果传递给then方法 //以前常见异步操作:事件监听,回调,Promise可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果 /<em>new Promise( function (resolve, reject) { // 一段耗时的异步操作 resolve('成功') // 数据处理完成 reject('失败') // 数据处理出错 throw new Error('错误信息') } ).then( (res) => {console.log(res)}, // 成功 (err) => {console.log(err)} // 失败 ).catch((err) => {console.log(err)} // 失败)</em>/ <a href="//Promise.all" target="_blank">//Promise.all</a>([p1, p2, p3]).then() 批量执行 返回一个数组形式的结果 <a href="//Promise.race" target="_blank">//Promise.race</a>([p1, p2, p3]).then() 竞赛执行 返回最先执行完的结果 常见用法:异步操作和定时器放在一起,如果定时器先触发,就认为超时 //async函数会返回一个promise,并且Promise对象的状态值是resolved(成功的) await后面接一个会return new promise的函数并执行它 /<em>function affterAdd (a,b,cb) { setTimeout(function () { return cb(a+b) },2000) } affterAdd(2,3,function (m) { console.log(m) })</em>/ /<em>function afferAdd (a,b) { return new Promise((resolve,reject) =>{ setTimeout(()=>{ resolve(a+b) },2000) }) } afferAdd(3,4).then(function (m) { console.log(m) })</em>/ /<em>async function afterAdd (a,b) { let res = await new Promise(function (resolve,reject) { setTimeout(function () { resolve(a+b) },1000) }) console.log(res) } afterAdd(1,2)</em>/</p> <p><a href="//17.xn--6iq3kj4lv0vqsj3jkgok" target="_blank">//17.什么是同源策略</a>,跨域有哪些方式? /<em>域名,协议,端口 任一不同即是跨域, 1.代理跨域 :就是自己的服务器偷其他需要服务器的数据 再返回给自己的浏览器 前端需要配置 webpack-dev-sever中 devSever{proxy:{}} 2.CORS跨域:cross origin resources sharing 跨站资源共享策略 在HTTP响应头的字段 Access-Control-Allow-Origin(允许连接来源):</em> Access-Control-Allow-Methods:GET CORS的预检请求 GET/POST/HEAD没有预检请求 遇到某些请求方式如Delete PUT PATCH等需要预检 返回options 需要options放行 3.jSONP:网页通过添加一个<script>元素,向服务器请求JSON数据 服务器收到请求后,将数据拼接好放在一个指定名字的回调函数里传回来 script.src = "<a href="http://freegeoip.net/json/?callback=handLeResponse" target="_blank">http://freegeoip.net/json/?callback=handLeResponse</a>"; 4.postMessage:postMessage(data,origin) window.addEventListener('message', function(e) {e.data}) 解决问题:1页面和其打开的新窗口的数据传递 2多窗口之间消息传递 3页面与嵌套的iframe消息传递 4上面三个场景的跨域数据传递*/</p> <p><a href="//18.xn--url-098d3k3ly1nrwbixgot0aimqxjq1qtlfw8v4g" target="_blank">//18.输入url到渲染完成发生了什么</a>?浏览器进程/线程模型?URL的组成?GET和POST的区别?回流重绘?浏览器缓存? /*1.DNS解析 浏览器查找当前URL是否存在缓存(强缓存),并比较缓存是否过期。如果浏览器有缓存,直接使用浏览器缓存, 如果本地没有缓存或过期,向dns域名服务器解析URL对应的IP(由于dns的解析很耗时,当解析域名过多的时候,便会导致首屏加载变得过慢。) 2.发送HTTP请求(三次握手),服务器处理请求,浏览器接受HTTP响应(304(协商缓存)) 3.浏览器解析并渲染页面,关闭TCP连接(四次挥手)</p> <p>浏览器是多进程的,只有一个主进程(协调主控)和一个GPU进程(3D绘制),每个Tab页面和使用插件都是一个进程。 每个Tab是多线程的包括:JS引擎线程 GUI渲染线程 事件触发线程 定时触发器线程 异步HTTP请求线程 URL的组成部分:协议头:HTTP,HTTPS,FTP / host 主机域名或IP地址 /port 端口号,http默认80,https默认443/path 目录路径/query 查询参数*/</p> <p>//浏览器缓存 /* 1.强缓存(不需要向服务器发送请求):判断HTTP首部字段:cache-control:max-age,Expires。 cache-control中的max-age保存一个相对时间。例如Cache-Control: max-age = 484200,表示浏览器收到文件后,缓存在484200s内均有效。 Expires是一个绝对时间,即服务器时间。浏览器检查当前时间,如果还没到失效时间就直接使用缓存文件。但是该方法存在一个问题:服务器时间与客户端时间可能不一致 如果同时存在cache-control和Expires,浏览器总是优先使用cache-control 2.协商缓存(需要向服务器发送请求):通过HTTP的last-modified,Etag字段进行判断 last-modified:第一次请求资源时,服务器返回的字段,表示最后一次更新的时间。下一次浏览器请求资源时就发送if-modified-since字段。 服务器用本地Last-modified时间与if-modified-since时间比较,如果不一致则认为缓存已过期并返回新资源给浏览器;如果时间一致则发送304状态码,让浏览器继续使用缓存。 Etag:资源的实体标识(哈希字符串),当资源内容更新时,Etag会改变。服务器会判断Etag是否发生变化,如果变化则返回新资源,否则返回304。 */</p> <p>//TCP的三次握手和四次挥手 /*三次握手 1.客户端:SYN=1用来建立连接 我能和你建立连接吗?2.服务端:ACK=X+1 可以和你建立连接 回Y 3.客户端:收到建立连接 ACK=Y+1</p> <ul> <li>四次挥手 1客户端:FIN=1结束连接 2服务端:收到ACK=X+1 3.服务端:我也没数据发送了回复B 4.收到ACK=B+1 等待两个报文传送周期。。。*/</li> </ul> <p>/<em>GET方法:查询字符串(名称/值对)是在 GET 请求的 URL 中发送的; GET 请求可被缓存;GET 请求保留在浏览器历史记录中;GET 请求不应在处理敏感数据时使用;GET 请求有长度限制;GET 请求只应当用于取回数据; POST方法: 查询字符串(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的; POST 请求不会被缓存;POST 请求不会保留在浏览器历史记录中;POST 请求对数据长度没有要求; 其实以上说的都只是GET、POST在http层面上的区别,在TCP/IP层面也有区别,也是二者真正的区别,即GET会产生一个tcp数据包,post两个。具体就是: GET请求时,浏览器会把headers和data一起发送,服务器响应200(返回数据); POST请求时,浏览器会先发送headers,服务器响应100 continue,浏览器再发送data,服务器响应200(返回数据)。</em>/ //浏览器获取html,解析,渲染? /*1.解析HTML,生成DOM树 (把他们转换为对象,这些对象分别定义他们的属性和规则,树形结构)2.解析CSS,生成CSS规则树 3.合并CSS和DOM数,生成render树 4.布局render树(layout/reflow),负责各元素尺寸、位置的计算</p> <ul> <li>5.绘制render树(paint),绘制页面像素信息 6.浏览器将各层信息发送给GPU,GPU将各层合成,显示在屏幕上 */ /<em>回流(Layout/Reflow)意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树. 重绘(Repaint)意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就可以了. 引起回流的原因:1.页面渲染初始化 2.DOM结构改变,比如删除了某个节点 3.render树变化,比如减少了padding 4.窗口resize 5.获取某些属性,引发回流, 很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流, 但是除了render树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效(getComputedStyle() width,height,offset) 减少回流和重绘的方法:1一次性更改style或改变className并一次性更新 2.处理完后一起更新 使用DocumentFragment进行缓存操作,引发一次回流和重绘;创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document 3.避免多次读取offset等属性。无法避免则将它们缓存到变量 4.将复杂的元素绝对定位或固定定位,使得它脱离文档流,否则回流代价会很高</em>/ /<em>浏览器加载机制: 遇到外链时,会单独开启一个下载线程去下载资源, CSS下载时异步,不会阻塞浏览器构建DOM树,但是会会阻塞渲染,也就是在构建render时,会等到css下载解析完毕后才进行 JS脚本资源会阻塞浏览器的解析,现代浏览器有优化,在脚本阻塞时,也会继续下载其它资源(当然有并发上限)虽然脚本可以并行下载,解析过程仍然是阻塞的,也就是说必须这个脚本执行完毕后才会接下来的解析,并行下载只是一种优化而已 defer与async,普通的脚本是会阻塞浏览器解析的,但是可以加上defer或async属性,这样脚本就变成异步了,可以等到解析完毕后再执行 遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有src的地方 提高CSS加载速度:使用CDN 将CSS压缩 合理使用缓存 减少HTTP请求数,多个CSS合并</em>/</li> </ul> <p><a href="//19.xn--HTTPHTTPS-tw9o" target="_blank">//19.HTTP和HTTPS</a>?HTTP请求和相应中的字段? //HTTP的缺点:1通信使用明文,2不验证通信方身份,可能遭遇伪装,3无法验证报文的完整性,有可能被篡改 /*HTTPS:它是安全版的http,相当于ssl+http。每次在请求前,先建立ssl,确保接下来的通信是加密的,HTTP先和SSL通信,SSL再和TCP通信,无法轻易被截取分析。 对称加密:加密和解密用同一个密钥,易被截取密钥 非对称加密(公开密钥加密):发送密文一方使用对方的共有密钥进行加密处理,对方收到加密信息后,再使用自己的私有密钥进行解密。 HTTPS采用二者结合的方式,因为非对称加密相比对称加密处理速度较慢,所以使用非对称加密传输对称加密的共享密钥,再使用共享密钥进行通信。 HTTPS的好处:SEO搜索引擎排名,更高安全性,更高数据的完整性 HTTPS的缺点:使页面加载时间延长,影响缓存,增加数据开销,占用服务器资源高 HTTP1.0 /HTTP1.1/HTTP2.0? http1.0中,默认使用的是短连接,没有Connection: keep-alive,http1.1中,默认使用的是长连接,Connection: keep-alive,keep-alive不会永远保持,它有一个持续时间,一般在服务器中配置(如apache) http2.0中,一个tcp/ip请求可以请求多个资源分割成更小的帧请求,速度明显提升。http1.1中,每请求一个资源,都是需要开启一个tcp/ip连接的,由于tcp/ip本身有并发数限制,所以当资源一多,速度就显著慢下来 HTTP2.0特性:1.一个tcp/ip可以请求多个资源 2.HTTP头部压缩,减少体积 3.服务端可以对客户端的一个请求发出多个响应,可以主动通知客户端 4.如果流被赋予了优先级,它就会基于这个优先级来处理 5.应用层和传输层之间增加二进制分帧层,改进传输性能,实现低延迟和高吞吐量 SSL/TLS握手流程: 1.浏览器请求建立SSL链接,并向服务端发送一个随机数–Client random客户端支持的加密方法,此时是明文传输。 2.服务端从中选出一组加密算法与Hash算法,回复一个随机数–Server random,并将自己的身份信息以证书的形式发回给浏览器(证书里包含了网站地址,非对称加密的公钥,以及证书颁发机构等信息) 3.浏览器收到服务端的证书后验证证书的合法性,浏览器会生产新的随机数–Premaster secret,然后证书中的公钥以及指定的加密方法加密<code>Premaster secret</code>,发送给服务器。 利用Client random、Server random和Premaster secret通过一定的算法生成HTTP链接数据传输的对称加密key-<code>session key</code>,使用约定好的HASH算法计算握手消息,并使用生成的<code>session key</code>对消息进行加密,最后将之前生成的所有信息发送给服务端 4. 服务端收到浏览器的回复,利用已知的加解密方式与自己的私钥进行解密,获取<code>Premaster secret</code> ,和浏览器相同规则生成<code>session key</code> 使用<code>session key</code>解密浏览器发来的握手消息,并验证Hash是否与浏览器发来的一致, 使用<code>session key</code>加密一段握手消息,发送给浏览器 5. 浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的https通信数据将由之前浏览器生成的session key并利用对称加密算法进行加密 */ /<em>HTTP请求和响应的构成: 请求行:请求方法 +请求url+ HTTP版本协议 比如:POST index.html HTTP/1.1 (GET, POST, PUT, DELETE, OPTIONS, HEAD) 请求头:( Accept, Accept-Charset, Accept-Encoding, Accept-Language, Cookie, User-Agent) Accept:text/html 指定客户端能够接收的内容类型 Cookie: $Version=1; Skin=new;;jsessionid=5F4771183629C9834F HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 Referer: <a href="http://www.zcmhi.com/archives/71.html" target="_blank">http://www.zcmhi.com/archives/71.html</a> 先前网页的地址,当前请求网页紧随其后,即来路 Cache-Control: max-age=3600 指定请求和响应遵循的缓存机制 Connection: keep-alive 表示是否需要持久连接。告诉客户端本次HTTP请求结束之后并不需要关闭TCP连接 请求体:当使用POST, PUT等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。在请求包头中有一些与请求正文相关的信息,例如: 现在的Web应用通常采用Rest架构,请求的数据格式一般为json。这时就需要设置Content-Type: application/json。 响应行:协议和版本+状态码和描述 HTTP/1.1 200 OK 响应头: Cache-Control: max-age=3600 告诉客户端如何控制响应内容的缓存 3600秒内,如果客户再次访问该资源,直接从客户端的缓存中返回内容给客户 ETag: “737060cd8c284d8af7ad3082f209582d” 请求变量的实体标签的当前值 服务端资源发生变化了,这个ETag就会相应发生变化。 Location: <a href="http://www.iteye.com" target="_blank">http://www.iteye.com</a> 需要Redirect到的页面URL 303 Set-Cookie:UserID=JohnDoe; Max-Age=3600; Version=1 服务端可以设置客户端的Cookie Expires: Thu, 01 Dec 2010 16:00:00 GMT响应过期的日期和时间 Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT请求资源的最后修改时间 响应体: 状态码: 1xx 消息,一般是告诉客户端,请求已经收到了,正在处理,别急... 2xx 处理成功,一般表示:请求收悉、我明白你要的、请求已受理、已经处理完成等信息. 3xx 重定向到其它地方。它让客户端再发起一个请求以完成整个处理。 4xx 责任在客户端,如客户端的请求一个不存在的资源,客户端未被授权,禁止访问等。 5xx 责任在服务端,如服务端抛出异常,路由出错,HTTP版本不支持等 204 No Content:表示客户端发送给客户端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分(没有资源可以返回) 301 Moved Permanently:永久性重定向,表示请求的资源被分配了新的URL,之后应使用更改的URL 302 Found:临时性重定向,表示请求的资源被分配了新的URL,希望本次访问使用新的URL 303 307 See Other:表示请求的资源被分配了新的URL,应使用GET方法定向获取请求的资源 304 只有header 没有body 400 Bad Request:表示请求报文中存在语法错误 401 Unauthorized:未经许可,需要通过HTTP认证 403 Forbidden:服务器拒绝该次访问(访问权限出现问题) 500 Inter Server Error:表示服务器在执行请求时发生了错误,也有可能是web应用存在的bug或某些临时的错误时 503 Server Unavailable:表示服务器暂时处于超负载或正在进行停机维护,无法处理请求</em>/</p> <p><a href="//20.xn--4oqy1fjre79a6jbsxr5v2blek" target="_blank">//20.前端性能优化方式</a>? /* 1.减少HTTP请求:http协议是无状态的应用层协议,意味着每次http请求都需要建立一次tcp连接,由于tcp/ip本身有并发数限制,所以当资源一多,速度就显著慢下来 合并CSS、javascript文件,图片合并(雪碧图)/字体图标或SVG图标 iconfont(iconmoon)代替png /图片base64格式(webpack的url-loader设置limit参数即可) 懒加载(延迟加载) / 2.使用缓存:设置Http Header里面缓存相关的字段cache-control:max-age,Expires(强缓存)设置一个很长的过期头变化不频繁而又可能会变的资源可以使用 变化不频繁而又可能会变的资源可以使用last-modified,Etag来做请求验证(协商缓存) 3.压缩静态资源体积:利用webpack的工具包/在线压缩图片工具tinypng.com 服务器端开启gzip,压缩css/js 60%-70%,前端部署用node/express时中间件compression即可开启gzip压缩 var express = require('express'); var compress = require('compression'); var app = express(); app.use(compress()); 4.减少cookie传输:太大的cookie会严重影响数据传输,对于某些静态资源的访问,如CSS、js等,发送cookie没有意义,可以考虑静态资源使用独立域名访问 5.代码层面: CSS放在页面最上部(浏览器会在下载完成全部CSS之后才对整个页面进行渲染,不放在header标签里会出现无 CSS状态跳转到 CSS状态,用户体验不好), javascript放在页面最下面(浏览器在加载javascript后立即执行,有可能会阻塞整个页面,造成页面显示缓慢)有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步) 有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。 能用css做的效果,不要用js做,能用原生js做的,不要轻易去使用第三方插件。避免引入第三方大量的库</p> <p>webpack做优化: 一.如何减少打包时间 1.优化loader:优化Loader的搜索范围 webpack.config.js(webpack.base.conf.js)/module/rules/loader:babel-loader 另外可以将babel编译过文件缓存起来,以此加快打包时间,设置loader:'babel-loader?cacheDirectory=true' module.exports={ module:[ rules:[ { test:'.js$', //js文件才使用babel loader:'babel-loader', include:[resolve('src')], //只在src文件夹下查找 exclude:/node_modules/ //不会去查找路径 } 2 HappyPack:Webpack在打包的过程中也是单线程的,使用HappyPack可以将Loader的同步执行转为并行 webpack.config.js(webpack.base.conf.js)/module.exports/plugins plugins:[ new HappyPack({ id:'Happybabel', loaders:['babel-loader?cacheDirectory'], threads:4//开启4个线程 }) ] 3 DllPlugin:DllPlugin可以将特定的类库框架提前打包然后引入。极大的减少打包类库的次数,并且也实现了将公共代码抽离成单独文件的优化方案 //单独配置在一个文件中 const path=require('path'); const webpack=require('webpack'); module.export={ entry:{ vendor:['react']; //统一打包类库 }, output:{ path:path.join(__dirname,'dist'), filename:'[name].dll.js', library:'[name]-[hash]' }, plugins:[ new webpack.Dllplugin({ name:'[name]-[hash]', context:__dirname, path:path.join(__dirname,'dist','[name]-manifest.json') }) ] } 配置文件生成依赖文件,接下来我们需要使用DllReferencePlugin将依赖文件引入项目中 plugins:[ new webpack.DllRefercePlugin({ context:__dirname, manifest:require('./dist/vendor-manifest.json'), }) ] 4代码压缩:使用UglifyJS,不止可以压缩JS代码,还可以压缩HTML、CSS代码,并且在压缩JS代码的过程中,我们还可以通过配置实现比如删除console.log这类代码的功能 在webpack4中只要启动了mode为production就默认开启了该配置,webpack3中单线程运行的,为了加快效率,使用webpack-parallel-uglify-plugin来运行UglifyJS 5.其他:reslove.extensions:[’.js’,’.json’],减少后缀列表长度,将出现频率高的后缀排在前面 resolve.alias:可以通过别名的方式来映射一个路径,能让webpack更快找到路径 module.noParse:如果你确定一个文件下没有其他依赖,就可以使用该属性让webpack不扫描该文件,这种方式对大型类库很有帮助 二.如何减少打包大小 1按需加载,首页或大型类库lodash加载文件越小越好,将每个路由页面单独打包为一个文件,使用的时候再去下载对应的文件 原理是,返回一个promise,当promise成功后再去执行回调 2 Scope Hoisting :Scope Hoisting会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中 ,在webpack4中只需开启optimization.concatenateModules:true即可 3 Tree shaking :它会删除项目中未被引用的代码,而如果在webpack4中只要开启生产环境就会自动启动这个功能</p> <p>打包工具的核心原理? 1.找出入口文件所有的依赖关系 2.然后通过构建CommonJS代码来获取exports导出的内容 Babel转化代码:读取文件内容--生成AST(抽象语法树)它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构-- 寻找当前文件的依赖关系---通过AST将代码转为ES5 3.读取入口文件,然后创建一个数组,该数组的目的是存储代码中涉及到的文件,遍历这个数组,一开始这个数组中只有入口文件,在遍历的过程中,如果入口文件有依赖其他的文件,那么就会被push到这个数组中 在遍历的过程中,我们先获得该文件对应的目录,然后遍历当前文件的依赖关系 在边路文件依赖过程中,首先生成依赖文件的绝对路径,然后判断当前文件CSS文件还是JS文件 如果是CSS文件的话,我们不用Babel去编译了,只需要读取CSS文件中的代码,然后创建一个style标签,将代码插入进标签并且放入head中即可 如果是js文件的话,我们还需要分析JS文件是否还有别的依赖关系 最后将读取文件后的对象push进数组中</p> <p>webpack原理: Entry:入口,Webpack执行构建的第一步将从Entry开始 Module:模块,在Webpack里一切皆模块,一个模块对应着一个文件。Webpack会从配置的Entry开始递归找出所有依赖的模块 Chunk:代码块,一个Chunk由多个模块组合而成,用于代码合并与分割。 Loader:模块转换器,用于把模块原内容按照需求转换成新内容。 Plugin:扩展插件,在Webpack构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。 构建流程: 1.初始化配置参数(读取与合并配置参数),加载Plugin,实例化Compiler对象 2.开始编译:Entry发出,针对每个模块串行调用对应的Loader去翻译文件内容,再找到该模块依赖的其他模块,递归地进行编译处理 3.输出资源:对编译后的Module组合成Chunk,把Chunk转换成文件,输出到文件系统 以上过程中,Webpack会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用Webpack提供的API改变Webpack的运行结果 详细过程: 1.初始化配置:从配置文件和Shell语句中读取与合并参数,得出最终的参数。 这个过程中还会执行配置文件中的插件实例化语句new Plugin()。 用上一步得到的参数初始化Compiler实例,Compiler负责文件监听和启动编译。Compiler 实例中包含了完整的 Webpack 配置,全局只有一个Compiler实例。 依次调用插件的apply方法,让插件可以监听后续的所有事件节点。同时给插件传入compiler实例的引用,以方便插件通过compiler调用Webpack提供的API。 entry-option事件:读取配置的Entrys,为每个Entry实例化一个对应的EntryPlugin,为后面该Entry的递归解析工作做准备。 after-resolvers事件:根据配置初始化完resolver,resolver负责在文件系统中寻找指定路径的文件。 2.编译:run事件: 启动一次新的编译。 watch-run监听事件:该事件是为了告诉插件一次新的编译将要启动,同时会给插件带上compiler对象。 compilation事件:当Webpack以开发模式运行时,每当检测到文件变化,一次新的Compilation将被创建。一个Compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation对象也提供了很多事件回调供插件做扩展。 一个新的Compilation创建完毕,即将从Entry开始读取文件,根据文件类型和配置的Loader对文件进行编译,编译完后再找出该文件依赖的文件,递归的编译和解析。 所有模块及其依赖的模块都通过Loader转换完成后,根据依赖关系开始生成Chunk。 compilation事件包括小事件: build-module :使用对应的Loader去转换一个模块。 program:从配置的入口模块开始,分析其AST,当遇到require等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系。 seal:所有模块及其依赖的模块都通过Loader转换完成后,根据依赖关系开始生成Chunk。 3 输出:emit事件, 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。 在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系,并且把相关模块组合在一起形成一个个Chunk。 在输出阶段会根据Chunk的类型,使用对应的模版生成最终要要输出的文件内容。 输出的bundle.js是什么样子? 看上去复杂的代码其实是一个立即执行函数,可以简写为如下: (function(modules) { // 模拟 require 语句 function <strong>webpack_require</strong>() { } // 执行存放所有模块数组中的第0个模块 <strong>webpack_require</strong>(0);</p> <p>})([存放所有模块的数组]) bundle.js能直接运行在浏览器中的原因在于输出的文件中通过__webpack_require__函数定义了一个可以在浏览器中执行的加载函数来模拟Node.js中的require语句 原来一个个独立的模块文件被合并到了一个单独的bundle.js的原因在于浏览器不能像Node.js那样快速地去本地加载一个个模块文件,而必须通过网络请求去加载还未得到的文件。 如果模块数量很多,加载时间会很长,因此把所有模块都存放在了数组中,执行一次网络加载。 分析__webpack_require__函数的实现,你还有发现Webpack做了缓存优化:执行加载过的模块不会再执行第二次,执行结果会缓存在内存中,当某个模块第二次被访问时会直接去内存中读取被缓存的返回值。</p> <p>分割代码时的输出:main.js // 异步加载 show.js import('./show').then((show) => { // 执行 show 函数 show('Webpack'); }); 重新构建后会输出两个文件,分别是执行入口文件bundle.js和异步加载文件0.bundle.js。 分割代码后的bundle.js 多了一个__webpack_require__.e用于加载被分割出去的,需要异步加载的Chunk对应的文件; 多了一个webpackJsonp函数用于从异步加载的文件中安装模块。 使用了CommonsChunkPlugin去提取公共代码时输出的文件和使用了异步加载时输出的文件是一样的,都会有__webpack_require__.e和webpackJsonp。 原因在于提取公共代码和异步加载本质上都是代码分割 */</p> <p><a href="//21.xn--15qqvu5tvw6a" target="_blank">//21.前端安全</a>? /<em>XSS(跨站脚本攻击) cross site scripting 反射型XSS:需要欺骗用户自己去点击链接才能触发,一般容易出现在搜索页面 存储型XSS:存储在服务器中的,如在个人信息或发表文章等地方,加入代码 DOM型XSS:DOM中有很多对象,其中一些是用户可以操纵的,如uRI ,location,refelTer等,客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行 1.主流的浏览器内置了防范 XSS 的措施 2.设置cookie HttpOnly 防止劫取 Cookie , 可以防止页面被XSS攻击时,Cookie信息被盗取,只能保证Cookie的安全 3.在输出html时,加上Content Security Policy的Http Header 4.后端一些方法,本质是进行转义,过滤所有HTTML JS CSS标签,过滤异常字符编码 CSRF(跨站请求伪造): Cross-site request forgery CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器 用户登入A未登出,接着访问B,B发送了请求A的请求 ,请求会携带用户cookie 1 token解决 2 验证码 强制用户交互,作为辅助(服务端) 3 检查 Referer :在 HTTP header中 Referer,它记录了该 HTTP 请求的来源地址。 4.Cookie Hashing(所有表单都包含同一个伪随机值): 在表单里增加Hash值,以认证这确实是用户发送的请求。然后在服务器端进行Hash值验证 SQL注入 在 HTTP 协议下,数据是明文传输 HTTPS解决</em>/</p> <p><a href="//22.xn--w0t69vc70akda" target="_blank">//22.设计模式</a>? /* 单例模式 定义:是保证一个类只有一个实例,并且提供一个访问它的全局访问点。 需求:一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象、登录浮窗等。 实现:用一个变量标识当前是否已经为某个类创建过对象,如果是,则在下一次获取这个类的实例时,直接返回之前创建的对象。 优点:可以用来划分命名空间,减少全局变量的数量 可以被实例化,且实例化一次,再次实例化生成的也是第一个实例</p> <p>观察者模式 定义:对象间的一种一对多的依赖关系。 需求:当一个对象的状态发生变化时,所有依赖于他的对象都将得到通知。 优点:时间上的解耦,对象之间的解耦。 实现: 首先,指定好谁充当发布者; 然后,给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者; 最后,发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数。</p> <p>工厂模式 定义:将其成员对象的实例化推迟到子类来实现的类。 需求:创建对象的流程赋值的时候,比如依赖于很多设置文件等 ;处理大量具有相同属性的小对象;注:不能滥用 优点:不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中。 分类:简单工厂,工厂方法和抽象工厂。 实现:简单工厂模式 (创建单一对象,需要的类比较少) 工厂方法模式 (创建多类对象,需要的类比较多) 抽象工厂模式 (创建父类,子类继承父类,具体实现在子类)</p> <p>*/</p> <p>//二。 <a href="//1.xn--fiq5j15fio97dl6by99czjec23bzv3bbyoh5pvyhvma" target="_blank">//1.项目中遇到哪些问题及解决方案</a>?做过比较复杂/得意的项目? /<em>mpvue的坑:mounted()生命周期只走一次,只能放在onshow里 / 对slot支持不好,多层tab混乱了,只能不用slot改为emit传变量 移动端兼容性问题,回复,华为手机的内置浏览器推出键盘问题/ 文件上传</em>/</p> <p><a href="//2.xn--vhqq0h2zx0ga940ejnn3tp" target="_blank">//2.未来的职业规划</a>? /<em>我觉得个人性格适合搞技术,希望在前端架构或者全栈方向吧</em>/</p> <p><a href="//3.xn--xmqs38bt4u9yc" target="_blank">//3.自我介绍</a>? /<em>我叫XX,12年毕业于xx大学,计算机专业,毕业之后两年做后端开发相关工作,距今有4年多的前端经验。 最近一家公司是新浪阅读,主要负责项目的前端技术选型、项目结构搭建、开发,需求评审。 最近做的项目--说wap站 小程序 pc h5活动页,前端技术栈vue/react/mpvue+webpack(项目构建和优化)。 业余时间喜欢逛掘金、segmentfault(思否)等网站</em>/</p> <p><a href="//4.xn--e6qt9qbrv78y" target="_blank">//4.兴趣爱好</a>?优点缺点?平时下班做什么?平时浏览什么网站?你还有什么想问的? //坚持夜跑 跟朋友打篮球 周末下象棋 //适应能力抗压能力强(四天学习react)条理清楚(立计划)、立场坚定 缺点:不能容忍工作的怠慢 做工作着急打提前量(催同事,) /<em>要看加班到几点,如果太晚可能洗漱倒头就睡了,平时坚持夜跑,读枕边书 累了看个电影 周末可以充电 看慕课网视频, 想想一周所学不足的技术问题订计划学习</em>/ //Github 掘金、segmentfault(思否) 知乎 博客园 LintCode(算法) 慕课 //公司会开什么新业务线?公司的人员配备?采用的技术方案?</p> <p><a href="//5.xn--fiq5j99bbye20cf8uxv6blhfw08c5fs" target="_blank">//5.项目中做过哪些工程化</a>? /* 1.代码管理工具: git/svn 2.项目创建、开发、发布 项目创建包含了技术选型,目录结构设计,模块化等。开发中可能会遇到页面适配、样式预处理以及开发便捷性。 发布涉及到了代码打包、发布到服务器,你可能还会做一些打包优化等等。 3.前端自动化工具 比如图片压缩、开启本地服务代理等等 4.单元测试、规范制定 :eslint-airbnb标准</p> <p>项目创建四种方式: 1.copy后修改一下。 2.git clone -> npm install -> npm start(克隆自己的空项目开发) 3.使用框架官方脚手架(vue-cli, Angular-cli,create-react-app),然后再改改 4.有自己的脚手架构建器,统一管理 我们需要做的有很多,比如要适配各种移动端机型,要兼容低版本,sass/less,es6/ts,eslint等等 项目构建: 项目构建目前来说基本都是使用webpack,需要做开发热更新,各种预处理,结合性能优化所需要的一些配置(雪碧图,代码分割,压缩,cdn), 多页配置,当项目很大的时候我们还需要优化构建速度 前端工具: nodejs的盛行给前端开发带来了更多的便捷,各种npm包,node工具。commander.js让我们更加便捷的开发命令式工具,常见的脚手架初始化、代码转化等 适合自己的工作流: 前端工作流工具feflow,从创建项目到业务开发,再到打包发布。官方有给出react+redux的脚手架以及基于webpack4的构建器,而且还可以根据官方文档自定义自己的脚手架和构建器 部署vue前端项目到服务器上: 环境:xshell+nginx+自己的服务器 1使用xshell登录到阿里云服务器。安装nginx 2.配置nginx,然后再根据配置文件做下一步操作,打开/etc/nginx/nginx.conf文件 3.配置文件中的listen是nginx监听的端口号,所以需要在自己服务器上为80端口添加安全组规则 4.配置文件中的root和index那两行表示我们把项目文件夹放在/home/my-project下,例如有两个项目文件夹分别为test1,test2,里面都有index.html。 5.我们也需要在本地项目的config/index.js里的build下进行修改,如果要把项目放到test1下assetsPublicPath: '/test1/', 用到了vue-router,则修改/router/index.js 6.nginx配置文件中的location则是针对跨域处理,表示把对/datas的请求转发给http://ip:port,本文中这个http://ip:port下就是需要的数据 7.修改后在本地命令行下运行:cnpm run build 生成dist文件。把dist文件里的index.html和static文件上传到服务器的/home/my-project/test1下 8.启动nginx.至此项目部署成功,在浏览器下输入: <a href="http://ip/test1/index.html" target="_blank">http://ip/test1/index.html</a> 即可 **/</p> <p><a href="//6.xn--SPASSR-k76j" target="_blank">//6.SPA和SSR</a>? 做ssr路由如何配置? /<em>SPA的优点 1页面响应速度快 因为js运行速度很快,所以js在做本地路由的时候,就会非常快 2减轻服务器压力 SPA的缺点 1不利于SEO搜索引擎优化 2首屏打开速度很慢 SSR将组件或页面通过服务器生成html字符串,再发送到浏览器</em>/ /*路由基本配置: import Router from 'vue-router' import routes from './routes' //导入router 映射规则 const router = new Router({ routes }) export default router</p> <p>ssr配置: import Router from 'vue-router' import routes from './routes' //导入router 映射规则 //每次import都创建一个新的router 避免内存溢出 export default () => { return new Router({ routes, mode: 'history' //改为history模式方便服务端渲染做SEO }) } spa的路由方式: hash方式。 使用location.hash和hashchange事件实现路由。 history api。使用html5的history api实现,主要就是popState事件等 react-ssr之路由配置: 在src目录下新建router.js 在components目录里新增导航模块 客户端引入router.js服务端引入router.js */</p> <p><a href="//7.xn--vue-8q3e2hwos94b3a611p3weps1cx3a60v7m5a" target="_blank">//7.vue实现双向数据绑定的原理</a>? /<em>v-m: 触发input事件(或者keyup、change事件)获取输入框的value并赋值给vm实例的text属性。 我们会利用defineProperty将data中的text劫持为vm的访问器属性,因此给vm.text赋值,就会触发set方法。 在set方法中主要做两件事,第一是更新属性的值,第二(订阅发布模式notify方法(观察者模式)文本节点则是作为订阅者, 在收到消息后执行相应的更新操作。) m-v: 每当new一个Vue,主要做了两件事:第一个是监听数据:observe(data),第二个是编译HTML:nodeToFragement(id)。 在监听数据的过程中,会为data中的每一个属性生成一个对象dep。 在编译HTML的过程中,会为每个与数据绑定相关的节点生成一个订阅者watcher,watcher会将自己添加到相应属性的dep中。 Watcher函数中发生了什么呢? 首先,将自己赋给了一个全局变量Dep.target; 其次,执行了update方法,进而执行了get方法,get的方法读取了vm的访问器属性,从而触发了访问器属性的get方法, get方法中将该watcher添加到了对应访问器属性的dep中; 再次,获取属性的值,然后更新视图。 最后,将Dep.target设为空。因为它是全局变量,也是watcher与dep关联的唯一桥梁,任何时刻都必须保证Dep.target只有一个值。</em>/</p> <p>//8.Vue-router实现的原理? /<em>vue-router 将自身作为一个插件安装到了 Vue,通过 Vue.mixin() 注册了一个 beforeCreate() 钩子函数, 给了检查是否有 router 参数,从而进行初始化的机会。进而通过层层调用执行了监听 hashchange 事件 new Vue() 执行 vue-router 注入的beforeCreate钩子函数 执行router.init(vm) 执行history.setupListeners(),注册事件监听 地址变更如何通知到vm? hashchange 时,执行 history.transitionTo(...),在这个过程中,会进行地址匹配,得到一个对应当前地址的 route, 然后将其设置到对应的 vm._route 为什么 vm 就可以感知到路由的改变了呢? 答案在 vue-router 注入 Vue 的 beforeCreate 钩子函数中 Vue.util.defineReactive(this,'_route',this._router.history.current) 采用与 Vue 本身数据相同的“数据劫持”方式,这样对 vm._route 的赋值会被 Vue 拦截到,并且触发 Vue 组件的更新渲染流程。</em>/</p> <p>//9.V-model实现原理? /<em>v-model只不过是一个语法糖而已,实现靠的还是1.v-bind:绑定响应式数据触发 2 input 事件 并传递数据监听原生组件的事件, 当获取到原生组件的值后把 值通过调用 $emit('input' ,data) 方法去触发 input事件,父组件<child v-model=‘’></em>/</p> <p>//10.v-for中的:key作用及实现? /*vue和react的虚拟DOM的Diff算法大致相同,其核心是基于两个简单的假设:</p> <ol> <li>两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。2. 同一层级的一组节点,他们可以通过唯一的id进行区分。 当页面的数据发生变化时,Diff算法只会比较同一层级的节点 如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。 如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。 当某一层有很多相同的节点时,也就是列表节点时,要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点, 找到正确的位置区插入新的节点 key的作用主要是为了高效的更新虚拟DOM。 另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果 */</li> </ol> <p>//11.vue-router的使用及两种模式? /<em>history 模式原理:HTML5 中新增的 pushState() 和 replaceState() 方法。 pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL; pushState()设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中; pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串; pushState() 可额外设置 title 属性供后续使用。 hash模式下hash符号之前的内容会被包含在请求中,如http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。 history模式下,前端的 URL 必须和实际向后端发起请求的URL一致,如 <a href="http://www.abc.com/book/id%E3%80%82" target="_blank">http://www.abc.com/book/id。</a> // 如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。” 结合自身例子,对于一般的 Vue + Vue-Router + Webpack + XXX 形式的 Web 开发场景,用 history 模式即可, 只需在后端(Apache 或 Nginx)进行简单的路由配置,同时搭配前端路由的 404 页面支持。</em>/</p> <p><a href="//12.xn--vue-7e4eudv41kzqu" target="_blank">//12.vue生命周期</a>? /*构造函数生成vue实例, beforeCreate:初始化事件,进行数据的观测 created :this.<img src="https://juejin.cn/equation?tex=data%20this.message%2F%2F%E5%B7%B2%E8%A2%AB%E5%88%9D%E5%A7%8B%E5%8C%96%20%20this." class="equation" alt="data this.message//已被初始化 this.">el //undefined beforeMount:判断对象是否有el选项 有继续,没有停止编译,直到在该vue实例上调用vm.<img src="https://juejin.cn/equation?tex=mount(el)%0A%20%20%20%20%E6%9C%89%E4%BA%86el%E4%B9%8B%E5%90%8E%EF%BC%8C%E5%A6%82%E6%9E%9Cvue%E5%AE%9E%E4%BE%8B%E5%AF%B9%E8%B1%A1%E4%B8%AD%E6%9C%89template%E5%8F%82%E6%95%B0%EF%BC%8C%E5%88%99%E5%B0%86%E5%85%B6%E4%BD%9C%E4%B8%BA%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91%E6%88%90render%E5%87%BD%E6%95%B0%EF%BC%88%E7%94%9F%E6%88%90%E8%99%9A%E6%8B%9Fdom%EF%BC%89%E3%80%82%E9%80%9A%E8%BF%87%7B%7Bmessage%7D%7D%E5%8D%A0%E4%BD%8D%0A%20%20%20%20%E5%A6%82%E6%9E%9C%E6%B2%A1%E6%9C%89template%E9%80%89%E9%A1%B9%EF%BC%8C%E5%88%99%E5%B0%86%E5%A4%96%E9%83%A8HTML%E4%BD%9C%E4%B8%BA%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91%3D%3E%E7%BB%99vue%E5%AE%9E%E4%BE%8B%E5%AF%B9%E8%B1%A1%E6%B7%BB%E5%8A%A0" class="equation" alt="mount(el) 有了el之后,如果vue实例对象中有template参数,则将其作为模板编译成render函数(生成虚拟dom)。通过{{message}}占位 如果没有template选项,则将外部HTML作为模板编译=>给vue实例对象添加">el成员,并且替换掉虚拟DOM mounted:this.<img src="https://juejin.cn/equation?tex=data%20this.message%20this." class="equation" alt="data this.message this.">el//全部初始化 beforeUpdate: vue发现data中的数据发生了改变 updated:虚拟dom重新渲染补丁以最小的dom开支来重新渲染dom beforeDestroy:实例仍然完全可用 destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的watchers事件监听器会被移除,所有的子实例也会被销毁 vue中内置的方法 属性和vue生命周期的运行顺序:props => methods =>data => computed => watch 用到 $refs 来直接访问子组件的方法,但是这样调用的时候可能会导致数据的延迟滞后的问题,则会出现bug。 解决方法则是推荐采取异步回调的方法,然后传参进去,严格遵守vue的生命周期就可以解决 推荐 es6 的promise */</p> <p><a href="//13.xn--react-ph9h0ez30nsl0a85c" target="_blank">//13.react的生命周期</a>? /*Mount: 组件第一次在DOM树中渲染的过程 调用了六个函数 Constructor一个组件调用构造函数,往往是为了两个目的:初始化state/绑定成员函数的this环境 getInitialState getDefaultProps初始化state props componentWillMount这个函数没什么存在感,因为在这个时候没有任何渲染出来的结果,调用setState修改状态也不会触发重新渲染, 并且在这里做的事情完全可以提前到constructor中去做render render应该是一个纯函数,完全根据state和props来决定返回结果,而不产生副作用,所以render中调用setState是错的, 因为纯函数不应该引起状态的改变,render并不做渲染工作,只是返回一个JSX描述的结构,最终由React库根据返回对象决定如何渲染 componentDidMount componentDidMount并不是在render调用后立即调用,其调用的时候render返回的JSX已经渲染了 componentWillMount可以在服务端和浏览器端调用, 但是componentDidMount只能在浏览器端调用(因为"装载"过程是不可能在服务端完成的)异步请求数据一般都在该函数内进行。</p> <p>Update: 组件重新渲染的过程过程并不总是执行所有函数 componentWillReceiveProps:只要是父组件的render函数被调用,在render里渲染的子组件就会经历更新过程,不管父组件传递给子组件的props有没有改变,都会触发componentWillReceiveProps通过setState触发的更新过程不会调用这个函数,不然岂不是死循环了? shouldComponentUpdate它决定了一个组件什么时候需不需要渲染。 render和shouldComponentUpdate是React中唯二需要有返回值的函数,shouldComponentUpdate返回一个布尔值,告诉React是否需要继续更新,若为true则继续,为false则停止更新,不会触发之后的重新渲染。 componentWillUpdate即将render时执行,初始化render时不执行。在这里同样不能setState, 这个函数调用之后,就会把nextProps和nextState分别设置到rops和state中,紧接着调用render render componentDidUpdate组件更新完成后执行,初始化render时不执行</p> <p>Unmount: 组件从DOM中删除的过程 componentWillUnmount当react组件要从DOM树上删除前,该函数会被调用在componentDidMount中用非react方法创建的DOM元素,如果不处理可能会发生内存泄漏,因此可以在该函数中将其清理干净*/</p> <p><a href="//14.xn--vuereact-9e1n03b2z2ewbesxbz88m" target="_blank">//14.vue和react的异同感悟</a>? /*1 vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化, 不需要特别的优化就能达到很好的性能 React 默认是通过比较引用的方式进行的, 如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的VDOM的重新渲染 Vue 和 React 设计理念上的区别, Vue 使用的是可变数据,而React更强调数据的不可变 Vue更加简单,而React构建大型应用的时候更加棒 2 Vue中子组件向父组件传递消息有两种方式:事件和回调函数,而且Vue更倾向于使用事件。但是在 React 中我们都是使用回调函数的 3 React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等, 都是通过JS语法实现的 Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现 举个例子,说明React的好处:react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。 但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转, 所以我们import 一个组件完了之后,还需要在 components 中再声明下</p> <p>react 类组件和函数式组件有什么区别? 函数式组件没有自己的state 只有props 函数式组件没有生命周期 不能连接redux react 如何实现vue中的指令的 v-for => map v-if =>元素三元 v-show=> style display属性三元 v-model =><input value={} onChage={e.target}> v-bind=> style={{}}*/</p> <p><a href="//15.xn--vuex-830g32cwz6j0feww1dzja" target="_blank">//15.vuex的认识和原理</a>? /<em>State、 Getter、Mutation 、Action、Module</em>/</p> <p><a href="//16.xn--vuexredux-yx4oe7gzufds0m" target="_blank">//16.vuex和redux的区别</a>? /<em>在 Vuex 中,<img src="https://juejin.cn/equation?tex=store%20%E8%A2%AB%E7%9B%B4%E6%8E%A5%E6%B3%A8%E5%85%A5%E5%88%B0%E4%BA%86%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B%E4%B8%AD%EF%BC%8C%E5%9B%A0%E6%AD%A4%E5%8F%AF%E4%BB%A5%E6%AF%94%E8%BE%83%E7%81%B5%E6%B4%BB%E7%9A%84%E4%BD%BF%E7%94%A8%EF%BC%9A%0A%20%201.%E4%BD%BF%E7%94%A8%20dispatch%20%E5%92%8C%20commit%20%E6%8F%90%E4%BA%A4%E6%9B%B4%E6%96%B0%0A%20%202.%E9%80%9A%E8%BF%87%20mapState%20%E6%88%96%E8%80%85%E7%9B%B4%E6%8E%A5%E9%80%9A%E8%BF%87%20this." class="equation" alt="store 被直接注入到了组件实例中,因此可以比较灵活的使用: 1.使用 dispatch 和 commit 提交更新 2.通过 mapState 或者直接通过 this.">store 来读取数据 在 Redux 中,我们每一个组件都需要显示的用 connect 把需要的 props 和 dispatch 连接起来。 另外 Vuex 更加灵活一些,组件中既可以 dispatch action 也可以 commit updates, 而 Redux 中只能进行 dispatch,并不能直接调用 reducer 进行修改。 从实现原理上来说,最大的区别是两点: Redux 使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改 Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的, 而Vuex其实和Vue的原理一样,是通过 getter/setter来比较的(如果看Vuex源码会知道,其实他内部直接创建一个Vue实例用来跟踪数据变化)</em>/</p> <p><a href="//17.react" target="_blank">//17.react</a> diff? /*react高效的diff算法,在页面渲染的时,计算出Virtual DOM真正变化的部分,只针对该部分进行的原生DOM操作,而不是渲染整个页面 react的diff将 O(n3)的时间复杂度转换成 O(n) 策略一:Web UI 中 DOM 节点跨层级的移动操作特别少。可以忽略不计。 策略二:拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。 策略三:对于同一层级的一组子节点,它们可以通过唯一 id 进行区分 tree diff: React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只对相同层级的DOM节点进行比较,即同一父节点下的所有子节点, 当发现该节点已经不存在时,则该节点及其子节点会被完全删除掉,不会用于进一步的比较 当出现节点跨级移动时,并不会像想象中的那样执行移动操作,而是以 A 为根节点的整个树被整个重新创建, 这是影响 React 性能的操作,所以 官方建议不要进行 DOM 节点跨层级的操作 component diff: 如果是同一类型的组件,按照原策略继续比较 Virtual DOM 树即可 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点 对于同一类型下的组件,有可能其 Virtual DOM 没有任何变化,如果能确切知道这一点,那么就可以节省大量的 diff 算法时间。 因此, React 允许用户通过 shouldComponentUpdate()来判断该组件是否需要大量 diff 算法分析。 element diff: 当节点处于同一层级时,diff 提供三种节点操作: 插入:如果新的组件类型不在旧集合里,即全新的节点,需要对新节点执行插入操作。 移动:旧集合中有新组件类型,且 element 是可更新的类型,generatorComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。 删除:旧组件类型,在新集合里也有,但对应的 elememt 不同则不能直接复用和更新,需要执行删除操作,或者旧组件不在新集合里的,也需要执行删除操作。 允许开发者对同一层级的同组子节点,添加唯一key进行区分,虽然只是小小的改动,但性能上却发生了翻天覆地的变化。 */</p> <p><a href="//18.xn--fhq4v5d78kf56aop2a" target="_blank">//18.前端文件上传</a>?大文件上传? /*普通文件上传的几种方式: 普通表单上传:enctype="multipart/form-data",表明表单需要上传二进制数据。 文件编码上传:将文件进行base64编码,然后在服务端进行解码,base64编码的缺点在于其体积比原图片更大因为Base64将三个字节转化成四个字节 formData异步上传:FormData对象主要用来组装一组用 XMLHttpRequest发送请求的键/值对,可以更加灵活地发送Ajax请求。可以使用FormData来模拟表单提交 iframe无刷新页面:把form的target属性设置为一个看不见的iframe</p> <p>大文件上传解决方案:七牛SDK,腾讯云SDK 在JavaScript中,文件FIle对象是Blob对象的子类,Blob对象包含一个重要的方法slice,通过这个方法,我们就可以对二进制文件进行拆分 前端大文件上传,核心是利用 Blob.prototype.slice 方法,和数组的 slice 方法相似,调用的 slice 方法可以返回原文件的某个切片 这样我们就可以根据预先设置好的切片最大数量将文件切分为一个个切片,然后借助 http 的可并发性,同时上传多个切片, 这样从原本传一个大文件,变成了同时传多个小的文件切片,可以大大减少上传时间 另外由于是并发,传输到服务端的顺序可能会发生变化,所以我们还需要给每个切片记录顺序 还原切片:在每个切片请求上传递一个相同文件的context参数 找到同一个context下的所有切片,确认每个切片的顺序,这个可以在每个切片上标记一个位置索引值 上面有一个重要的参数,即context,我们需要获取为一个文件的唯一标识,可以通过下面两种方式获取 根据文件名、文件长度等基本信息进行拼接,为了避免多个用户上传相同的文件,可以再额外拼接用户信息如uid等保证唯一性 根据文件的二进制内容计算文件的hash,这样只要文件内容不一样,则标识也会不一样,缺点在于计算量比较大. 断点续传: 在切片上传成功后,保存已上传的切片信息 当下次传输相同文件时,遍历切片列表,只选择未上传的切片进行上传 所有切片上传完毕后,再调用mkfile接口通知服务端进行文件合并 保存的方式: 可以通过localStorage等方式保存在前端浏览器中,这种方式不依赖于服务端,实现起来也比较方便,缺点在于如果用户清除了本地文件,会导致上传记录丢失 服务端本身知道哪些切片已经上传,因此可以由服务端额外提供一个根据文件context查询已上传切片的接口,在上传文件前调用该文件的历史上传记录 上传进度和暂停: 通过xhr.upload中的progress方法可以实现监控每一个切片上传进度。 通过xhr.abort可以取消当前未完成上传切片的上传,实现上传暂停的效果,恢复上传就跟断点续传类似,先获取已上传的切片列表,然后重新发送未上传的切片。 */ <a href="//19.xn--mpvue-dq1h59dwy9bu6emsmd87dnfzc?%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%8E%9F%E7%94%9F%E5%92%8Cwepy%E3%80%81mpvue%E3%80%81uni-app%E3%80%81taro%E7%AD%89%E4%B8%BB%E6%B5%81%E5%BC%80%E5%8F%91%E6%A1%86%E6%9E%B6%EF%BC%8C%E5%93%AA%E4%B8%AA%E5%A5%BD%EF%BC%9F" target="_blank">//19.小程序为何选择mpvue?小程序原生和wepy、mpvue、uni-app、taro等主流开发框架,哪个好?</a> <a href="//20.vue3.xn--0-6d8a415c" target="_blank">//20.vue3.0初探</a>?</p> <p>//三。 <a href="//1.xn--BFC-098dlvz83i" target="_blank">//1.BFC是什么</a>?有什么作用?如何形成? /<em>块级格式化上下文,触发条件: 1浮动 非float:none 2绝对定位和相对定位 3 display:inline-block 4 overflow:hidden 实用性:1 生成BFC 实现自适应两栏布局 2 清除元素内部浮动 父盒子overflow:hiddden 3 防止垂直 margin 重叠 要解决margin重叠问题,只要让它们不在同一个BFC就行了</em>/</p> <p><a href="//2.xn--vqq33d90gg5cw7dlt9a" target="_blank">//2.如何减少回流</a>、重绘? /<em>1直接改变className 2.让要操作的元素进行”离线处理”,处理完后一起更新 使用DocumentFragment进行缓存操作,引发一次回流和重绘 3不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存</em>/</p> <p><a href="//3.xn--jorq0rwnbtqg39ayy1bvvu" target="_blank">//3.双栏自适应布局</a>(左边固定右边自适应)方案? /<em>1双inline-block方案: 通过width: calc(100% - 140px)来动态计算右侧盒子的宽度 两个inline-block的盒子,必须设置vertical-align来使其顶端对齐 为了简单的计算右侧盒子准确的宽度,设置了子元素的box-sizing:border-box;以及父元素的box-sizing: content-box; 缺点:需要知道左侧盒子的宽度,两个盒子的距离,还要设置各个元素的box-sizing 需要消除空格字符的影响 设置父容器的font-size: 0 需要设置vertical-align: top满足顶端对齐 2双float方案 通过width: calc(100% - 140px)来动态计算右侧盒子的宽度 缺点:需要知道左侧盒子的宽度,两个盒子的距离,还要设置各个元素的box-sizing。父元素需要清除浮动 3 float+margin-left方案 缺点:需要清除浮动 需要计算右侧盒子的margin-left 4 absolute+margin-left方法 缺点:使用了绝对定位,若是用在某个div中,需要更改父容器的position。 没有清除浮动的方法,若左侧盒子高于右侧盒子,就会超出父容器的高度。因此只能通过设置父容器的min-height来放置这种情况。 5 float+BFC方法 float+overflow:hidden 缺点: 父元素需要清除浮动 6 flex方案 .wrapper-flex { display: flex; align-items: flex-start; } .wrapper-flex .left { flex: none; } .wrapper-flex .right { flex: auto; }</em>/</p> <p><a href="//4.xn--ydsw2jwrbwxs" target="_blank">//4.圣杯布局</a>(左右固定中间自适应)解决方案? /*两边固定 中间自适应 设置left部分的margin-left为-100%,就会使left向左移动一整个行的宽度 .main { background-color: skyblue; width: 100%; } .left { background-color: red; width: 200px; margin-left: -100%; } .right { background-color: green; width: 300px; margin-left: -300px; }</p> <p>Flex布局: .container { display: flex; } .main ,.left ,.right { min-height: 200px; } .main { background-color: skyblue; flex-grow: 1; } .left { background-color: red; order: -1; flex-basis: 200px; } .right { background-color: green; flex-basis: 200px; }*/ <a href="//5.xn--gwt091a" target="_blank">//5.水平</a>,垂直居中? /*水平:margin:0 auto 垂直line-height:1 position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);</p> <p>display: flex; justify-content: center; align-items: center; */</p> <p><a href="//6.xn--css3-y39fh54p" target="_blank">//6.css3动画</a>? /* transform 转换:translate坐标变换,rotate旋转,scale缩放,skew 倾斜 transition: 过渡.:transition:width(属性),1s(时间),ease(方式) animation: 动画, @keyframes声名一个动画,animation:myAnimaiton(动画名),2s(时间) ,linear(方式) infinite */</p> <p><a href="//7.xn--Flex-t49g4f76ilzf" target="_blank">//7.Flex布局属性</a>? /*Flex布局父容器属性: justify-content:flex-start | flex-end | center | space-between | space-around;水平(主轴上)对齐方式 align-items:flex-start | flex-end | center | baseline | stretch;十字交叉轴上对齐方式 flex-direction:row | row-reverse | column | column-reverse;项目排列方向 flex-wrap:nowrap(不换行) | wrap(向下换) | wrap-reverse(向上换);换行方式 align-content:flex-start | flex-end | center | space-between | space-around | stretch;多根轴线的对齐方式 Flex布局子元素属性: order定义自身排列顺序/flex-grow定义自身放大比例/flex-shrink定义了空间不足时自身缩小比例/ flex-basis定义最小空间/flex:flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto/align-self定义自身对齐方式</p> <ul> <li>*/ <a href="//8.xn--rem-py0fv1f" target="_blank">//8.rem布局</a>?flex布局?百分比布局? /*Rem是相对于 <html> 元素的 font-size 的相对值 1根据 layout viewport 来设置 rem@function px2rem(<img src="https://juejin.cn/equation?tex=px)%20%7B%0A%40return" class="equation" alt="px) { @return">px / 37.5 + rem; } width: px2rem(50) 2.devicePixelRatio 来设置 rem 1rem = 50px * dpr 设置 <meta name="viewport"> 标签的 initial-scale 的值 initial-scale = 1/ dpr。 dpr 为 1 的设备上,initial-scale = 1,dpr 为 2 的设备上,initial-scale = 0.5 测量某个元素为 12px,则转换为 0.12rem */ <a href="//9.xn--CSS3-3f5f64oumqdiggpjxl2d" target="_blank">//9.CSS3中的属性大全</a>?视口?媒体查询? /*border-radius 圆角 box-shadow 阴影 box-sizing盒模型基本结构如图 background-image背景图 background-size 调整背景图片的大小 background-origin 规定背景图片的定位区域 background-clip 规定背景的绘制区域 linear-gradient 背景的颜色渐变 text-shadow 文本阴影 word-wrap | word-break 是否允许长单词换行</li> </ul> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">*/ //三。 //1.模块化开发webpack? /*gulp是基于任务和流的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等 webpack是基于入口的。 webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能 <p>常见loader: file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去 source-map-loader:加载额外的 Source Map 文件,以方便断点调试 image-loader:加载并且压缩图片文件 babel-loader:把 ES6 转换成 ES5 css-loader:加载 CSS,支持模块化、压缩、文件导入等特性 style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。 eslint-loader:通过 ESLint 检查 JavaScript 代码 常见plugin: define-plugin:定义环境变量 commons-chunk-plugin:提取公共代码 uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码</p> <p>//loader和Plugin的异同? Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。 Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。 Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options) Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。</p> <p>//webpack的构建流程是什么? 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数; 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译; 确定入口:根据配置中的 entry 找出所有的入口文件; 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块, 再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理; 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系; 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表, 这步是可以修改输出内容的最后机会; 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。</p> <p>//是否写过Loader和Plugin? 编写Loader时要遵循单一原则,每个Loader只做一种"转义"工作。 每个Loader的拿到的是源文件内容(source), 可以通过返回值的方式将处理后的内容输出,也可以调用this.callback()方法,将内容返回给webpack。 还可以通过 this.async()生成一个callback函数,再用这个callback将处理后的内容输出出去。 此外webpack还为开发者准备了开发loader的工具函数集——loader-utils。 相对于Loader而言,Plugin的编写就灵活了许多。 webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件, 在合适的时机通过 Webpack 提供的 API 改变输出结果。</p> <p>//webpack的热更新是如何做到的?说明其原理? HMR热更新:可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。 第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。 第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。 第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。 webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。 HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。 而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。 最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。</p> <p>//如何利用webpack来优化前端性能?(提高性能和体验) 1.压缩代码。删除多余的代码、注释、简化代码的写法等等方式。 可以利用webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件,利用cssnano(css-loader?minimize)来压缩css 2.利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。 可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径 3.删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数--optimize-minimize来实现 4.提取公共代码。commons-chunk-plugin</p> <p>//如何提高webpack的构建速度? 多入口情况下,使用CommonsChunkPlugin来提取公共代码 通过externals配置来提取常用库 利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。 使用Happypack 实现多线程加速编译 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度 使用Tree-shaking和Scope Hoisting来剔除多余代码</p> <p>//怎么配置单页应用?怎么配置多页应用? 单页应用可以理解为webpack的标准模式,直接在entry中指定单页应用的入口即可,这里不再赘述 多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。 多页应用中要注意的是: 每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。比如,每个页面都引用了同一套css样式表 随着业务的不断扩展,页面可能会不断的追加,所以一定要让入口的配置足够灵活,避免每次添加新页面还需要修改构建配置</p> <p>//npm打包时需要注意哪些?如何利用webpack来更好的构建? NPM模块需要注意以下问题: 要支持CommonJS模块化规范,所以要求打包后的最后结果也遵守该规则。 Npm模块使用者的环境是不确定的,很有可能并不支持ES6,所以打包的最后结果应该是采用ES5编写的。并且如果ES5是经过转换的,请最好连同SourceMap一同上传。 Npm包大小应该是尽量小(有些仓库会限制包大小) 发布的模块不能将依赖的模块也一同打包,应该让用户选择性的去自行安装。这样可以避免模块应用者再次打包时出现底层模块被重复打包的情况。 UI组件类的模块应该将依赖的其它资源文件,例如.css文件也需要包含在发布的模块里。 基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化: CommonJS模块化规范的解决方案: 设置output.libraryTarget='commonjs2'使输出的代码符合CommonJS2 模块化规范,以供给其它模块导入使用 输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 ES5 的代码。再通过开启devtool: 'source-map'输出SourceMap以发布调试。 Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件 不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。 对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现</p> <p>//如何在vue项目中实现按需加载? 很多组件库已经提供了现成的解决方案,如Element出品的babel-plugin-component和AntDesign出品的babel-plugin-import 安装以上插件后,在.babelrc配置中或babel-loader的参数中进行设置,即可实现组件按需加载了。 但是随着业务的不断扩展,会面临一个严峻的问题——首次加载的代码量会越来越多,影响用户的体验 通过import(<em>)语句来控制加载时机,webpack内置了对于import(</em>)的解析, 会将import(<em>)中引入的模块作为一个新的入口在生成一个chunk。 当代码执行到import(</em>)语句时,会去加载Chunk对应生成的文件。 import()会返回一个Promise对象,所以为了让浏览器支持,需要事先注入Promise polyfill */</p> <p><a href="//2.express?%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AARESTful%E6%9C%8D%E5%8A%A1%EF%BC%9F" target="_blank">//2.express?实现一个RESTful服务?</a> /<em>Node创建HTTP服务器 调用http.createServer()它只有一个参数,是个回调函数,服务器每次收到HTTP请求后都会调用这个回调函数。这个回调会收到两个参数,请求和响应对象 const http = require('http') const sever = http.createServer((req,res)=>{ // 设置cors跨域 res.setHeader('Access-Control-Allow-Origin', '</em>') res.setHeader('Access-Control-Allow-Headers', 'Content-Type') res.setHeader("Content-Type","text/html;charset=UTF-8")//设置响应头 res.statusCode = 200 //设置状态码 switch (req.method) {case 'OPTIONS': case 'POST':} res.end("<h1>你好</h1>") }) sever.listen(3000) 表征状态转移 (REST)。它是一种基于 HTTP 协议的网络应用的接口风格 req.method GET:获取 请求获取指定资源。 POST:新增 向指定资源提交数据。 PUT:更新 请求服务器存储一个资源。 DELETE:删除 请求服务器删除指定资源。 HEAD:请求指定资源的响应头。 TRACE:回显服务器收到的请求,主要用于测试或诊断。 CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 OPTIONS:返回服务器支持的HTTP请求方法。</p> <p>express框架 : 可以设置中间件来响应 HTTP 请求/定义了路由表用于执行不同的 HTTP 请求动作。/可以通过向模板传递参数来动态渲染 HTML 页面。 安装 Express:<img src="https://juejin.cn/equation?tex=cnpm%20install%20express%20--save" class="equation" alt="cnpm install express --save"> cnpm install body-parser --save node.js 中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。 <img src="https://juejin.cn/equation?tex=cnpm%20install%20cookie-parser%20--save%20%E4%B8%80%E4%B8%AA%E8%A7%A3%E6%9E%90Cookie%E7%9A%84%E5%B7%A5%E5%85%B7%E3%80%82%E9%80%9A%E8%BF%87req.cookies%E5%8F%AF%E4%BB%A5%E5%8F%96%E5%88%B0%E4%BC%A0%E8%BF%87%E6%9D%A5%E7%9A%84cookie%EF%BC%8C%E5%B9%B6%E6%8A%8A%E5%AE%83%E4%BB%AC%E8%BD%AC%E6%88%90%E5%AF%B9%E8%B1%A1%E3%80%82" class="equation" alt="cnpm install cookie-parser --save 一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。"> cnpm install multer --save node.js 中间件,用于处理 enctype=“multipart/form-data”(设置表单的MIME编码)的表单数据。 var express = require('express'); var app = express(); app.get('/路由', function (req, res) { res.send('Hello World'); }) var server = app.listen(8081, function () { var host = server.address().address var port = server.address().port console.log("应用实例,访问地址为 http://%s:%s", host, port) }) 静态文件:Express 提供了内置的中间件 express.static 来设置静态文件 app.use('/public', express.static('public'));将图片, CSS, JavaScript 文件放在 public 目录下 GET 方法/POST 方法 文件上传 Cookie 管理var cookieParser = require('cookie-parser') var app = express() app.use(cookieParser()) */</p>