一. DOM
1. 事件委托
简陋版:
ul.addEventListener('click',function(e){
if(e.target.tagName.toLowerCase()==='li'){
执行函数
}
})
bug:如果li里有span,我点击了span, e.target就是span元素,就不会触发函数。但是正确的应该触发,因为span也在li里,点击span就是点击了li
优化:针对 如果点击了li里边的子代元素的情况 进行判断。拿到点击的元素e.target ,只要它不是li,就循环往上一直查找它的父元素,看它的父元素是不是li,直到触顶到了ul。如果在其中某一次找到了li,就退出循环,执行函数;如果直到循环到ul都没有li,就说明不需要触发函数
function delegate(element,eventType,selector,fn){
element.addEventListener(eventType,e=>{
let el=e.target
while(!el.matches(selector)){
if(el===element){
el=null
break
}
el=el.parentNode
}
el && fn.call(el,e,el)
})
return element
}
2. 用 mouse 事件写一个可拖曳的 div
<div id="xxx"></div>
因为要移动div,所以必定会改变它的位置,使用绝对定位,之后改变left top
div{
border: 1px solid red;
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
}
*{margin:0; padding: 0;}
监听三个事件:mousedown 鼠标按下,标志着开始拖动了。mouseup 鼠标弹上去,拖动结束。mousemove 移动鼠标,改变div位置。
所以首先需要一个拖动标志,鼠标按下置为true,鼠标弹上去置为false,只有为true的时候,才能拖动。
var dragging = false
var position = null
xxx.addEventListener('mousedown',function(e){
dragging = true
position = [e.clientX, e.clientY]
})
document.addEventListener('mousemove', function(e){
if(dragging === false){return}
console.log('hi')
const x = e.clientX
const y = e.clientY
const deltaX = x - position[0]
const deltaY = y - position[1]
const left = parseInt(xxx.style.left || 0)
const top = parseInt(xxx.style.top || 0)
xxx.style.left = left + deltaX + 'px'
xxx.style.top = top + deltaY + 'px'
position = [x, y]
})
document.addEventListener('mouseup', function(e){
dragging = false
})
mousemove和mouseup事件要绑定到document上,如果监听了div,如果鼠标移动太快,div跟不上就掉下去了。
根据上一次position和当前位置计算出距离,把属性left和top也增加同样距离即可。这里的e.clientX是数字,所以deltaX也是数字,但是 xxx.style.left是字符串'1px' ,所以要把上一次的left先转换成数字parseInt,还要加一个保底值0
二. HTTP
1. HTTP 状态码知道哪些?分别什么意思?
2xx :请求成功
3xx :还需要进一步操作
4xx :客户端错误
5xx :服务器错误
100:继续请求
200:请求成功;201:请求成功并且创建新的资源;202:请求接受;204:没有内容
301:永久移动;302:临时移动;304:未修改
400:请求有语法错误;404:找不到;413:请求的实体过大;414:请求的网址太长
500:服务器内部错误
2. HTTP缓存有哪几种
(详细的了解 ETag、CacheControl、Expires)
浏览器的缓存机制有强缓存和协商缓存。浏览器请求资源时,根据Expires和Cache-Control判断是否命中强缓存。(1)如果缓存还没失效,就命中了强缓存,直接从缓存读取资源,不发请求;(2)如果没有命中强缓存,浏览器就一定会发请求到服务器,通过Last-Modified和ETag判断资源是否被修改,也就是判断协商缓存。如果资源没被修改,命中了协商缓存,服务器返回304,告诉浏览器从缓存读取资源。(3)两者都没有命中,说明缓存失效了,而且资源也被修改了,服务器会返回新的资源。
相同点:如果命中了,都是从浏览器的缓存读取资源,而不是从服务器获取
不同点:强缓存不发请求,协商缓存要发请求到服务器
ETag是服务器根据某种算法,给每个资源计算出来一个唯一的标志值如MD5。在浏览器请求该资源时,以ETag的头部发过去。下次浏览器再次请求时,会带着这个值一起,用If-None-Match的头部。服务器会和自己这里的资源的ETag比较,如果相同,说明没有修改,返回304。如果不同,说明被修改了,返回新的资源,状态码200
考点1:Expire和Cache-Control的区别
Expire是以时刻值表示过期时间,绝对时间,bug在于如果用户的本地时间错乱,就会有问题;而Cache-Control是以max-age时间段表示过期时长,这是相对时间,与本地时间无关。
考点2:Etag和Cache-Control的区别
Cache-Control是从本地,浏览器的文件缓存里读取缓存的,而ETag还是会发请求的,Cache-Control是无请求的。
3. GET 和 POST 的区别
GET参数通过URL传递,POST放在Request body中。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
GET幂等,POST不幂等。
GET只需要一个报文,POST需要两个以上。
(GET 用于获取资源,POST 用于提交资源)
4. Cookie V.S. LocalStorage V.S. SessionStorage V.S. Session
1. Cookie 和 Session区别
cookie是服务器发给浏览器的一段字符串,浏览器在之后向同一服务器发起请求时携带cookie。session是会话,表示浏览器和服务器一段时间内的一次会话。cookie是在保存浏览器上的,session是在保存服务器上的。session是基于cookie实现的,一般把session_id放在cookie里
2. Cookie 和 LocalStorage区别
存储大小限制不同,Cookie 一般最大 4k,LocalStorage 可以用 5Mb 甚至 10Mb(各浏览器不同)
Cookie 会被发送到服务器,而 LocalStorage 不会
3. LocalStorage 和 SessionStorage 区别
存储数据的生命周期不同,LocalStorage 一般不会自动过期(除非用户手动清除),而 SessionStorage 在会话结束时过期(如关闭浏览器)
3. HTTP2.X 相对于HTTP1.X的新特性
多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
服务端推送(server push)服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。
三. Vue
1. watch 和 computed 和 methods 区别是什么?
computed是计算属性,根据依赖的一些属性计算得来的属性;watch是监听,当监听的数据变化时,执行一个函数。computed计算属性可以以函数的形式定义,调用的时候不加括号,直接当作属性使用。有缓存,只有当依赖的数据变化时,才会重新计算。watch没有缓存。有两个常用的选项;deep和immediate
watch 和 computed 相比,computed 是计算出一个属性(废话),而 watch 则可能是做别的事情(如上报数据)
而且,computed不能执行异步任务,watch可以
computed 和 methods 相比,最大区别是 computed 有缓存:如果 computed 属性依赖的属性没有变化,那么 computed 属性就不会重新计算。计算属性也可以改成定义在methods里的函数,通过调用这个函数得到结果。但是定义在methods里的方法每次都会调用,都会执行。
2. Vue 有哪些生命周期钩子函数?分别有什么用?
beforeCreate created beforeMount mounted beforeUpdate updated beforeDestroy destroyed
翻译一遍,特别强调:mounted 组件被挂载到页面后,做一些事情 。请求数据
3. Vue 如何实现组件间通信?
-
父子组件之间通信:
$emit('xxx',data)v-on:'xxx'=function(){}一个触发一个监听 -
爷孙组件之间通信:使用两次 v-on 通过爷爷爸爸通信,爸爸儿子通信实现爷孙通信
-
任意组件之间(包括兄弟组件,爷孙组件,父子组件):创建一个空的vue实例eventbus作为事件总线,通过eventbus触发,监听。
var eventBus=new Vue() eventBus.$emit('xxx',data) eventBus.$on('xxx',fn) -
任意组件:通过Vuex通信。
这就引入了另一个问题:Vuex是怎么用的?
先说概念:Vuex是专门为vuejs设计的状态管理工具。核心是store仓库。然后介绍四个核心概念和作用
state: state对象里存储全局管理的状态,在组件里读取状态可以用计算属性,返回this.$store.state.x
getters: 当我们需要从state里的状态派生出一些状态时,可以在store里定义getters,它类似于计算属性,可以根据state里依赖的状态返回一个状态,也是有缓存的。在组件里读取getter,this.$store.getters.y
mutations: 在mutations里定义改变state状态的方法。这个函数接受state作为第一个参数,以操作state。触发它只有提交mutation,store.commit('xxx',x) Mutation 必须是同步函数
actions: 类似于mutations,不同之处是action可以是异步的。actions是通过提交mutation间接改变状态。在组件里this.$store.dispatch('xxx') 调用action,action里又提交mutation里的方法,从而更改状态。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
actions: {
increment ({ commit }) { // 解构写法
commit('increment')
}
}
})
modules: 把store分割成模块。如果应用很复杂,store对象也会很复杂。
4. Vue 双向绑定/数据响应式怎么做到的?
怎么实现双向绑定?v-model ,在input元素上绑定一个内部数据到value属性,实现改变内部数据UI上的值也改变;监听input事件,把用户输入的值赋给内部数据。v-model就是这两句代码的语法糖。实现双向数据绑定。v-model="x"x是内部数据。 它等价于::value="x" @input="x=$event.target.value"
双向绑定/数据响应式的原理?当我们把普通的JS对象传入vue实例的data选项里,Vue会遍历它的所有属性,使用 Object.defineProperty 把这些属性全部转为 getter/setter,这样Vue就能监听这些属性,对这些属性的更改都会通知vue,进而渲染UI。
但是有一个问题,Vue 不能检测到对象属性的添加或删除,解决方法是手动调用 Vue.set 或者 this.$set向嵌套对象添加响应式 property
5. Vue.set 作用
为嵌套对象添加响应式属性。Vue不允许动态添加根级响应式属性。但是可以用Vue.set为嵌套对象添加响应式属性。
6. Vue Router 怎么用的
Vue Router 是Vuejs官方的路由管理器。
常用的组件:
<router-link to='/xxx'>router-link组件用来导航,通过to属性指定路由。还有一个active-class属性,路由被激活后拥有的类名。<router-view>是路由的出口, 不同的路由匹配到对应的组件,渲染在router-view的位置- route是指当前被激活的路由对象,它包含了这个路由的所有状态信息。 常用:
this.$route.params它是一个键值对的对象,表示当前的路由参数。可以和动态路由匹配结合使用,:语法表示动态路由参数,这个参数就能通过params这个对象拿到。 - 编程式的导航:导航除了可以用router-link(声明式导航)外,还可以用router实例上的方法实现导航。
this.$router.push('./xxx')导航到不同的url,而且会向history添加一个新纪录,所以点击浏览器的后退按钮时,会返回到之前的url。this.$router.replace('/xxx')和push一样,导航到指定的url,但是不会向history添加记录,所以点击后退按钮,会回到上上个url。this.$router.go(1)在浏览器记录里前进或后退几步
关键概念:
-
怎么实现路由懒加载?把不同的路由对应的组件分割成不同的代码块,然后当路由被访问的时候再去加载它对应的组件。实现:
import('./money') -
什么是导航守卫?导航守卫通俗来说就是在路由跳转的过程中,提供给我们的一些钩子函数。vue router提供的导航守卫主要是用来通过跳转或取消的方式来守卫导航。有几种机会植入到导航过程中:全局的,单个路由独享的,组件内的。
全局的: 全局前置守卫
beforeEach在跳转之前被调用;全局后置钩子afterEach在跳转完成之后被调用; 全局解析钩子beforeResolve单个路由独享的:
beforeEnter进入这个路由之前被调用,直接定义在某个路由配置里。组件内的:
beforeRouteEnter进入该组件对应的路由之前被调用;beforeRouteUpdate路由正在改变,而且依然渲染该组件时被调用,比如在具有动态路由参数的路由之间跳转,都会映射到同一个组件;beforeRouteLeave导航离开该组件对应的路由之前被调用三个参数:to:要跳转到的目标路由对象;from:当前即将要离开的路由对象;next:是一个函数,一定要调用一次next函数来完成当前的钩子,否则就无法继续下一个钩子。
-
history模式:vue router默认是hash模式。可以设置成history模式,但是要有后台的配置支持。
四. React
1. 受控组件和非受控组件
针对表单元素而言的。区别是表单的value值是否受组件state的控制。
受控组件:表单的value值受组件内部状态的控制。<input type='text' value={state.x} onChange={(e)=>{setState(e.target.value)}} 输入框的状态由内部状态来控制,同时要监听onChange事件,如果输入框的值被改变了,就要把新的值也映射回内部的状态上,实现双向绑定。
非受控组件:表单的value值不受状态的控制。不能给input传value,但是可以通过defaultValue 给表单一个默认值。它是通过使用ref从DOM节点中获取表单数据。
const Note: React.FC=()=>{
const [note,setNote]=useState('')
const refInput=useRef(null)
const x=()=>{
setNote(refInput.current.value)
}
return (
<input type='text' defaultValue={note} ref={refInput}
onBlur={x}>
)
}
给input一个默认值。创建一个ref,使用ref从DOM节点里获取表单的数据。具体做法:绑定input的ref属性为refInput变量的值,所以refInput.current就代表了这个input元素。onBlur是监听鼠标移出事件,通过refInput.current.value 拿到表单的数据,把这个数据存在note里。
2. React 有哪些生命周期函数?分别有什么用?
constructor :初始化状态,数据等,创建组件的时候被调用
render :渲染页面,创建虚拟DOM
componentDidMount :组件被挂载之后调用(出现在页面后)。AJAX数据请求放在这里
componentDidUpdate :页面更新后被调用。数据请求也可以放在这里
componentWillUnmount :组件将要被从页面移除然后销毁时调用,清除定时器,取消订阅等
3. React 如何实现组件间通信?
父子组件之间:父组件给子组件通过props传递数据。还使用props给子组件传一个函数,子组件通过这个函数向父组件传递数据。
爷孙组件:两次传props
任意组件:redux 通过redux来管理多个组件共享的状态,从而解决组件之间的通信
4. shouldComponentUpdate 有什么用?
它可以让我们手动判断是否要进行组件的更新。我们可以根据不同的场景灵活的设置它的返回值,默认返回true,表示只要组件的state或props变了,就会更新UI。如果返回false,就是不更新UI。避免一些不必要的更新。
这个钩子要手动判断,React内置了这个功能,React.PureComponent.PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。 如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。 提高渲染性能
5. 虚拟 DOM 是什么?
执行render函数得到的结果并不是真正的DOM节点,而是一个轻量级的JS对象,这个render函数返回的JS对象我们就把他叫做虚拟DOM。
会通过diff算法比较新旧的虚拟DOM,只对变化的部分更新。
调用 DOM 的开销是很大的。而 Virtual DOM 的执行完全都在 Javascript 引擎中,完全不会有这个开销。
6. redux是什么
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
核心概念:
store:存放状态,一个redux应用只能有一个store。它的常用的API:store.getState() 用来获取state store.dispatch(action) 更新state
action:action就是一个普通的JS对象,用来描述发生了什么。要想更新state里的数据,唯一的方法就是发起一个action 。所以action就是把数据从应用传到store的载荷。
reducer:首先它是一个纯函数,接受state和action作为参数,返回一个新的state。他就是把state和action关联起来,action只说明了有什么事情要发生。具体如何更新数据就是在reducer这个函数里完成的。reducer指定了状态的变化是怎么响应action并且发送到store的
redux的核心就是单向数据流,就是说所有数据都要依照相同的生命周期,主要是四个步骤:首先调用store.dispatch发起一个action,然后调用reducer函数返回一个新的state,根reducer通过combineReducers方法把多个子reducer合并成一个state树,最后就是store保存这个新的state树。
五. WebPack
1. 有哪些常见 loader 和 plugin,你用过哪些?区别
loader :加载器,加载文件。
js:babel-loader
图片:image-loader
css:css-loader,style-loader,less-loader,sass-loader
plugin:插件,扩展webpack的功能
html: html-webpack-plugin
css:mini-css-extract-plugin
2. 如何按需加载代码 懒加载
动态import 语法
import('./xxx.css').then(fn1,fn2)
3. 如何提高构建速度?
dllplugin
code split
happypack :多线程加速编译
4. 转义出的文件过大怎么办?
压缩css,js,图片
import()按需加载文件
CommonsChunkPlugin来提取公共代码
整理
1. 封装localstroge模块,让其能够在指定时间失效
let myStorage={
setItem(params){
const obj={
name:'',
value:'',
max-age:'',
startTime:new Date().getTime() //得到当前时间的格林威治时间,可以相减的数字
}
let option={}
Object.assign(option,obj,params) //用户不会传开始时间,要由自己指定
if(option.max-age){
localStorage.setItem(option.name,JSON.stringify(option))
}else{ //如果用户没传失效时间,那后两项就没有存储的意义了
localStorage.setItem(option.name,JSON.stringify(option.value))
}
},
getItem(name){
let item=JSON.parse(localStorage.getItem(name))
if(!item){return false}
if(item.max-age){
let now=new Date().getTime()
// 没过期
if(now-item.startTime<item.max-age){
return item
}else{
localStorage.removeItem(name)
return false
}
}
}
}
export default myStorage
myStorage.setItem({name:'hu',value:'1',maxage:10000000})
undefined
myStorage.getItem('hu')
{name: "hu", value: "1", maxage: 10000000, startTime: 1595417434581}
2. 哪些操作可以发起http请求
1. AJAX
let request=new XMLHttpRequest()
request.open('GET','./xxx')
request.onreadystatechange=()=>{
if(request.readyState===4){
}
}
2. jQuery.ajax
$.ajax({
type:"GET",
url:"XXX/XXX/XXX",
data:{},
success:function(){},
error:function(){}
})