前端知识梳理

348 阅读12分钟

Webpack

ESLint

CI(Continuous integration)

浏览器原理

  • async 和 defer
    • 一张图:

Web Component

React

生命周期

  • 一张图:
  • setState 为什么是异步的?
    • 批量更新事务
      • batchingStrategy.isBatchingUpdates 和 batchingStrategy.batchedUpdates()
      • react是通过合成事件实现了对于事件的绑定,当进入事件处理流程后,该事务的isBatchingUpdates为true,如果在事件中调用setState方法,也会进入dirtyComponent流程。
      • 如果我们在浏览器的 click 句柄(handler)中,Child 和 Parent 都调用了 setState,则我们不希望重新渲染 Child 两次,而是倾向于将它们标记为脏,然后在浏览器事件结束的时候重新渲染它们。
    • 保证内部一致性
    • 使并发更新变得可行:React 可以根据 setState() 来自的不同的位置:事件句柄,网络响应,动画等,来为它们分配不同的优先级。
  • 钩子函数(函数组件不能使用这些方法,因为这需要继承react Component这个类)
    • constructor(props, context)
    • getDefaultProps()
      • 对于每个组件实例来讲,这个方法只会调用一次,该组件类的所有后续应用,getDefaultPops 将不会再被调用,其返回的对象可以用于设置默认的 props值。
    • getInitialState()
      • 对于组件的每个实例来说,这个方法的调用有且只有一次,用来初始化每个实例的 state,在这个方法里,可以访问组件的 props。
      • getInitialState 和 getDefaultPops 的调用是有区别的,getDefaultPops 是对于组件类来说只调用一次,后续该类的使用都不会被调用,相当于 Object.prototype.XXX。而 getInitialState 是对于每个组件实例来讲都会调用,并且只调一次,相当于 this.XXX。
    • componentWillMount()
      • 在组件挂载到DOM前调用,且只会被调用一次,在这边调用this.setState不会引起组件重新渲染,也可以把写在这边的内容提前到constructor()中,所以项目中很少用。
    • render()
      • 根据组件的props和state(无两者的重传递和重赋值,论值是否有变化,都可以引起组件重新render) ,return 一个React元素(描述组件,即UI),不负责组件实际渲染工作,之后由React自身根据此元素去渲染出页面DOM。render是纯函数(Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有副作用),不能在里面执行this.setState,会有改变组件状态的副作用。
      • setState引起的state更新 或 父组件重新render引起的props更新,更新后的state和props相对之前无论是否有变化,都将引起子组件的重新render。可通过shouldComponentUpdate方法优化。
    • componentDidMount()
      • 组件挂载到DOM后调用,且只会被调用一次,这里一般是发起异步请求,因为在componentWillMount里发起AJAX,不管多快得到结果也赶不上首次render。
      • 该方法被调用时,已经渲染出真实的 DOM,可以再该方法中通过 this.getDOMNode() 访问到真实的 DOM(推荐使用 ReactDOM.findDOMNode())。
    • componentWillReceiveProps(nextProps)
      • 在此函数中调用 this.setState() 将不会引起第二次渲染。所以可以把外部 props 转换成自己的state,达到优化渲染的效果。
    • shouldComponentUpdate(nextProps, nextState)
      • true:重新加载组件
      • false:不更新组件
    • componentWillUpdate(nextProps, nextState)
      • 此方法在调用render方法前执行,在这边可执行一些组件更新发生前的工作,一般较少用。
    • re- render() 重新调用
    • componentDidUpdate(prevProps, prevState)
      • 此方法在组件更新后被调用,可以操作组件更新的DOM,prevProps和prevState这两个参数指的是组件更新前的props和state
    • componentWillUnmount()
      • 此方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清楚组件中使用的定时器,清楚componentDidMount中手动创建的DOM元素等,以避免引起内存泄漏。
  • 16版本新增生命周期
    • static getDerivedStateFromProps(props, state)
      • 在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容。强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的state,而已。
    • getSnapshotBeforeUpdate()
      • 被调用于render之后,可以读取但无法使用DOM的时候。它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate(prevProps, prevState, snapshot)

Context

  • 解决嵌套组件自顶向下多层传递 props 和 state 的问题,this.context.XXX 可以跨级传递数据。
  • 生产者消费者模式
    • 父组件是生产者,父组件必须用静态属性 childContextTypes 声明提供给子组件的 Context 内容,并实现一个实例getChildContext方法,返回一个代表Context的纯对象 (plain object)
    • 子组件是消费者,子组件必须用静态属性 contextTypes 申请要消费的 Context 内容,之后才能通过 this.context.XXX 来访问父组件提供的 Context 内容。
  • 无状态子组件
    • 使用:ChildComponent = (props, context) => {console.log(context.propsA)};
    • 声明:ChildComponent.contextProps = { propA: PropTypes.string };
  • React.createContext() 会创建一个 Context 对象(例如 TestContext)
    • <Provider /><TestContext.Provider value={{propsA: 'xxxxx'}} /> 的value相当于的 getChildContext()
    • <Consumer /><TestContext.Consumer>{context => (<h1>{context.propsA}</h1>)}</TestContext.Consumer> 的children必须是一个函数,通过函数的参数获取<Provider />提供的Context。
  • context 从哪来
    • this.context
    • constructor(props, context)
    • componentWillReceiveProps(nextProps, nextContext)
    • shouldComponentUpdate(nextProps, nextState, nextContext)
    • componetWillUpdate(nextProps, nextState, nextContext)
    • 无状态函数式组件 ChildComponent = (props, context) => {console.log(context.propsA)};
  • 为什么要用父声明子申请的设计
    • 父子组件是一对多的线性依赖。随意的使用Context其实会破坏这种依赖关系,导致组件之间一些不必要的额外依赖,降低组件的复用性,进而可能会影响到App的可维护性。通过childContextTypes和contextTypes这两个静态属性的约束,可以在一定程度保障,只有组件自身,或者是与组件相关的其他子组件才可以随心所欲的访问Context的属性,无论是数据还是函数。

JSX

React-Router

Redux

数据流

  • 一张图:
  • Store > subscribe > Component > trigger >Action > dispatch > Reducer > put > Store

Reducer

  • 一个纯函数,执行一个action并且返回一个新的状态。
  • 入参是state 和 action
    • state是当前保存在store中的数据,初始时无值需要入参处指定初始值。
    • action相当于一个容器,有两个 key
      • type - 一个简单的字符串常量,例如ADD, UPDATE, DELETE等。
      • payload - 用于更新state的数据。
  • 代码设计上不应该在reducer里用 if-else/switch 来取针对不同的 action.type做业务处理,service code 不应该写在这一层,应该用 myActionsaction.type 解耦。dva就是做了这件事,把 action处理单独抽出来
  • 多个 reducer 的场景:let store = redux.createStore(redux.combineReducers({reducer1, reducer2}));
  • Action触发流:
    • 抽离的 action 方法是接收 payload,组装并返回一个 action 容器,例如 function action1(data1, data2) {return {type: 'ACTION_1', payload: {data1, data2}}};
    • 用 store.dispatch()去分发这个 action:如 store.dispatch(action1('data_1', 'data_2'));
    • reducer接收到相应的 action 开始处理:如 reducer1(state, action){ if(action.type === 'ACTION_1'){return {...state, ...action.payload}};
  • Immutability(不可变性)
    • 如果代码只处理原始数据类型(numbers, strings, booleans),那么你不用担心。但是,如果在处理Arrays和Objects时,则需要小心执行可变操作。不可变操作实现有 immutable.js/Object.assign()/{...Obj}
    • immutable.js
  • React 结合
    • react-redux
      • 用 Provider 包起来 ReactDOM.render(<Provider store={store}> <h1>xxx</h1></Provider>, document.getElementById('root'));
  • 异步数据处理
    • Redux本身只能处理同步的Action,但可以通过中间件来拦截处理其它类型的action,比如 Thunk 函数,再用回调触发普通Action,从而实现异步处理,在这点上所有Redux的异步方案都是类似的。
    • 概念
      • 模板代码
      • 乐观更新
      • 错误处理
      • 竞态
    • 模块
      • redux-thunk
      • redux-promise
      • redux-loop
      • redux-saga
      • redux-observable
      • redux-action-tools

Diff

Vue

生命周期

  • 一张图:
  • 钩子函数(Vue的所有生命周期函数都是自动绑定到this的上下文上,所以箭头函数会报错)
    • beforeCreate

    • created

    • el 和 template 过程参与

      • 实例没有指定 el 属性时就不会触发下面的 beforeMount 和 mounted,可以用$mount 方法手动挂载 el,生命周期方法会按序触发。
      • render函数 > template属性 > 外部html, vue 的编译过程就是把tempalte编译成render函数的过程。
    • beforeMount

    • mounted

    • beforeUpdate

      • 这里的数据只有和模版中的数据绑定了才会发生更新,才会调用 berforeUpdate 和 updated 这两个钩子。
    • updated

    • beforeDestroy

      • 在 beforeDestroy 生命钩子调用之前,所有实例都可以用,但是当调用后,Vue 实例的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
    • destroyed

    • 其他钩子

      • activated:当组件激活的时候调用
      • deactivated:当组件停用的时候调用
      • errorCaptured:这个生命钩子可以看官网,2.5.0之后才有的。当捕获一个来自子孙组件的错误时被调用。

自带属性方法

  • data = $data
    • 在定义一个组件时,data 必须声明为一个返回初始数据对象的无副作用的函数,因为可能会使用此函数创建多个实例。如果 data 是一个普通对象,则所有创建出来的实例将共享引用同一个数据对象!
    • data: (vm) => ({x: vm.myProp});

指令

  • v-text = textContent
  • v-html = innerHtml
  • v-bind:href = :href
    • 作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
    • 语法:v-bind:href="url"
    • 简写::href="url"
    • 说明:绑定的数据从 data 或 props中获取
  • v-on:click = @click
    • 作用:绑定事件
    • 语法:v-on:click="say" or v-on:click="say('参数', $event)"
    • 简写:@click="say"
    • 说明:绑定的事件从methods中获取
    • 事件修饰符 *v-on:click.stop="say" 阻止冒泡,调用 event.stopPropagation()
      • v-on:click.prevent 阻止默认事件,调用 event.preventDefault()
      • v-on:click.capture="say" 添加事件侦听器时使用事件捕获模式
      • v-on:click.self="say" 只当事件在该元素本身(比如不是子元素)触发时触发回调
      • v-on:click.once="say" 事件只触发一次
      • 顺序很重要v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
  • v-model
    • 作用:在表单元素上创建双向数据绑定
    • 说明:监听用户的输入事件以更新数据
    • demo: <input v-model="message" placeholder="edit me"><p>Message is: {{ message }}</p>
  • v-for
    • item 为值,key 为键,index 为索引
    • demo: <p v-for="(item, key, index) in obj" :key="item.key">{{item}} -- {{key}}</p>
    • 性能:使用 key,VUE会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
  • 样式处理 v-bind
    • :class="[{active: true}, 'warning', errorClass]"
    • :style="[{fontSize: fs + 'px'}, baseStyle, overridingStyles]"
  • v-if: 根据表达式的值的真假条件,销毁或重建元素
  • v-show: 根据表达式之真假值,切换元素的 display CSS 属性
  • 性能和体验
    • v-cloak: 这个指令保持在元素上直到关联实例结束编译。
      • 防止未编译的 Mustache 模板数据出现在页面上。
      • CSS 样式 [v-cloak] { display: none } 可以在实例 ready 前隐藏未编译的 Mustache 标签。
    • v-pre: 跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签,跳过大量没有指令的节点会加快编译。
    • v-once: 只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
  • 自定义指令
    • 全局注册
      • 注册方法:Vue.directive( directiveName, [definition] )
      • demo:
      <div id="app" class="demo">
          <!-- 全局注册 -->
          <input type="text" v-focus>
      </div>
      <script>
          Vue.directive("focus", {
              inserted: function(el){
                  el.focus();
              }
          })
          new Vue({
              el: "#app"
          })
      </script>
      
    • 局部注册
      • 注册方法:在Vue实例中添加 directives 对象
      • demo:
      <div id="app" class="demo">
          <!-- 局部注册 -->
          <input type="text" v-focus2>
      </div>
      <script>
          new Vue({
              el: "#app",
              directives: {
                  focus2: {
                      inserted: function(el){
                          el.focus();
                      }
                  }
              }
          })
      </script>
      
    • 自定义指令的钩子函数
      • bind(el, binding, vnode):只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
      • inserted(el, binding, vnode):被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
      • update(el, binding, vnode, oldVnode):所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
      • componentUpdated(el, binding, vnode, oldVnode):指令所在组件的 VNode 及其子 VNode 全部更新后调用。
      • unbind(el, binding, vnode):只调用一次,指令与元素解绑时调用。

Vue-Router

VueX

Node

EventLoop

  • 一张图:
  • Timer(setTimeout/setInterval) --tick-- I/O --tick-- Check(setImmediate)
  • macrotasks: setTimeout setInterval setImmediate I/O UI渲染
  • microtasks: Promise process.nextTick Object.observe MutationObserver
  • 一个有趣的例子:process.nextTick是注册在当前的tick阶段进行拼接,继续执行,从而导致了死循环
process.nextTick(function tick () {
  process.nextTick(tick) 
});

函数式编程

柯里化

function realSum(...args) {
    return args.reduce((prev, next) => prev + next, 0);
}
function sum(...args1) {
    let value = realSum(...args1);
    function fn(...args2) {
        value += realSum(...args2)
        return fn
    };
    fn.valueOf = () => value;
    return fn;
}

sum(1,2)(3)(4)(2,3).valueOf(); // 15

寻找对称数

function findSymmetricNum(min, max) {
    let num = min;
    let temp = '';
    const result = [];
    for(let i=0; i< max-min+1;i++) {
        temp = num + '';
        // 字符串反转后是否与之前相等
        if(temp.split('').reverse().join('') === temp) {
            result.push(num);
        }
        num ++;
    }
    return result;
};

异步链式调用


Sass

Less

HTTP

原理

HTTP2

长链接复用

数据 chunk 方式通信

  • HTTP头:Transfer-Encoding: chunked
  • 编码格式:[chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n]

CDN

  • 查找链路
    • 一张图:
    • 客户端 -> ( * CNAME:把自有域名换成 cdn 域名) -> DNS查询 -> 全局负载均衡系统 -> (透传客户端 IP 和目标资源 URL) -> 区域负载均衡系统 ->(根据客户端 IP 和目标资源 URL,获取距离客户端最近且有相应资源的cdn 缓存服务器的地址) -> 全局负载均衡系统 -> (cdn 缓存服务器地址)-> 客户端 -> (请求缓存服务器上的文件)
  • 回源
    • 当 cdn 缓存服务器中没有符合客户端要求的资源的时候,缓存服务器会请求上一级缓存服务器,以此类推,直到获取到。最后如果还是没有,就会回到我们自己的服务器去获取资源。没有资源,资源过期,访问的资源是不缓存资源等都会导致回源。
  • 更新数据
    • 主动(PUSH)
    • 被动(PULL)

DNS

  • 一张图

  • 查找方式

    • 递归查询:递归查询是一种DNS 服务器的查询模式,在该模式下DNS 服务器接收到客户机请求,必须使用一个准确的查询结果回复客户机。如果DNS 服务器本地没有存储查询DNS 信息,那么该服务器会询问其他服务器,并将返回的查询结果提交给客户机。
    • 迭代查询:DNS 服务器会向客户机提供其他能够解析查询请求的DNS 服务器地址,当客户机发送查询请求时,DNS 服务器并不直接回复查询结果,而是返回客户机另一台DNS 服务器地址,客户机再向这台DNS 服务器提交请求,依次循环直到返回查询的结果为止。
  • 多域名查询优化

TypeScript

Node

多进程开发

nginx 原理

PM2

反模式