《框架篇》

272 阅读14分钟

一. 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 如何实现组件间通信?

  1. 父子组件之间通信:$emit('xxx',data) v-on:'xxx'=function(){}一个触发一个监听

  2. 爷孙组件之间通信:使用两次 v-on 通过爷爷爸爸通信,爸爸儿子通信实现爷孙通信

  3. 任意组件之间(包括兄弟组件,爷孙组件,父子组件):创建一个空的vue实例eventbus作为事件总线,通过eventbus触发,监听。

    var eventBus=new Vue()
    eventBus.$emit('xxx',data)     eventBus.$on('xxx',fn)
    
  4. 任意组件:通过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官方的路由管理器。

常用的组件:

  1. <router-link to='/xxx'> router-link组件用来导航,通过to属性指定路由。还有一个active-class属性,路由被激活后拥有的类名。
  2. <router-view> 是路由的出口, 不同的路由匹配到对应的组件,渲染在router-view的位置
  3. route是指当前被激活的路由对象,它包含了这个路由的所有状态信息。 常用:this.$route.params 它是一个键值对的对象,表示当前的路由参数。可以和动态路由匹配结合使用,:语法表示动态路由参数,这个参数就能通过params这个对象拿到。
  4. 编程式的导航:导航除了可以用router-link(声明式导航)外,还可以用router实例上的方法实现导航。this.$router.push('./xxx') 导航到不同的url,而且会向history添加一个新纪录,所以点击浏览器的后退按钮时,会返回到之前的url。this.$router.replace('/xxx') 和push一样,导航到指定的url,但是不会向history添加记录,所以点击后退按钮,会回到上上个url。 this.$router.go(1) 在浏览器记录里前进或后退几步

关键概念:

  1. 怎么实现路由懒加载?把不同的路由对应的组件分割成不同的代码块,然后当路由被访问的时候再去加载它对应的组件。实现:import('./money')

  2. 什么是导航守卫?导航守卫通俗来说就是在路由跳转的过程中,提供给我们的一些钩子函数。vue router提供的导航守卫主要是用来通过跳转或取消的方式来守卫导航。有几种机会植入到导航过程中:全局的,单个路由独享的,组件内的。

    全局的: 全局前置守卫 beforeEach 在跳转之前被调用;全局后置钩子 afterEach 在跳转完成之后被调用; 全局解析钩子 beforeResolve

    单个路由独享的: beforeEnter 进入这个路由之前被调用,直接定义在某个路由配置里。

    组件内的: beforeRouteEnter 进入该组件对应的路由之前被调用;beforeRouteUpdate 路由正在改变,而且依然渲染该组件时被调用,比如在具有动态路由参数的路由之间跳转,都会映射到同一个组件;beforeRouteLeave 导航离开该组件对应的路由之前被调用

    三个参数:to:要跳转到的目标路由对象;from:当前即将要离开的路由对象;next:是一个函数,一定要调用一次next函数来完成当前的钩子,否则就无法继续下一个钩子。

  3. 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(){}
})