前端知识系统总结(带思维导图)

177 阅读17分钟

一、CSS的知识点

1. BFC的概念

  • 定义:块级格式化上下文,是指一块独立渲染的区域,该区域拥有一套自己的渲染规则,与外部无关
  • 如何创建BFC:
    1.float不是none
    2.position的值不是static或者relative
    3.display的值是inline-blockflex、或者inline-flex
    4.overflow:hidden
  • BFC的作用
    1.BFC可以消除盒子的margin塌陷
    2.BFC可以阻止元素被浮动元素覆盖

二、JS知识点

1. 一个变量是数组还是对象,多种判断方法

const a = [];
const b = {}

// 判断a为数组
Array.isArray(a)
// 可以判断a和b
a instanceof Array
b instanceof Object
// 可以判断a和b,通过原型链顶端判断,在项目中常用的方法
Object.prototype.toSrting.call(a) === '[object Array]'
Object.prototype.toSrting.call(b) === '[object Objec]'
// 可以判断a和b
a.constructor === Array
b.constructor === Object

2.判断数组和对象的方法instanceofconstructor的弊端

var iframe = document.createElement('iframe')
document.body.append(iframe)
var xArray = window.frames[window.frames.length - 1].Array
var arr = new xArray(1,2,3)

console.log(arr instanceof Array) // false,类型没判断出来
console.log(arr.constructor === Array) // false, 没判断出来
console.log(Object.prototype.toString.call(arr)) // [object, Array], 类型判断出来了

3.节流函数和防抖函数

防抖函数

  • 定义:当持续触发事件时,在一定时间内没有触发事件,触发事件才会再执行一次,如果在时间内触发了,则重新开始延时。
  • 实际的应用:1.使用echarts时,改变浏览器宽度,希望重新渲染。2.输入搜索内容,输入结束后n秒进行请求。
  • 实现代码:
// 防抖函数的封装
function debounce (delay) {
  let timer
  // 返回出去一个函数,利用了闭包的特性,使timer保存到了内存中
  return (value,callBack)=> {
    clearTimeout(timer)
    timer = setTimeout(()=>{
      // 把业务逻辑用外面的函数进行执行
      callBack(value)
   },delay)
  }
}

const fn = debounce(1000)

function callBackFun(value) {
  console.log(value)
}
    

节流函数

  • 定义:在持续的一段时间内,只调用一次事件处理函数
  • 实际应用:在频繁多次点击按钮的时候,在一定时间内只触发一次点击事件
// 节流函数的封装
function thro (func,delay) {
  let timer
  //返回一个函数,利用闭包的特性,使timer保存到内存中
  return function (value) {
    if (!timer) {
      timer = setTimeout(()=>{
        func(value)
        timer = null
      },delay)
    }
  }
}
function callBack() {
  console.log('11111')
}

4.预编译的规则

定义:在定义域的创建阶段也就是预编译阶段,会创建一个AO对象,这个对象是供js引擎去访问的, 它的创建规则步骤是:

  1. 创建一个AO对象
  2. 找形参和变量的声明,作为AO对象的属性名,值是undefined
  3. 实参和形参相统一
  4. 找函数声明,会覆盖变量的声明
// 预编译的练习题
function fn(a,c) {
  console.log(a) // function a() {}
  var a = 123
  console.log(a) // 123
  console.log(c) // function c() {}
  function a() {}
  if(false) {
    var d = 678
  }
  console.log(d) // undefined
  console.log(b) // undefined
  var b = function(){}
  console.log(b) // function(){}
  function c() {}
  console.log(c) // function c() {}
}
fn(1,2)
// AO对象的1个步骤后的预编译后的伪代码为:
AO= {}
// AO对象的2个步骤后的预编译后的伪代码为:
AO= {
  a: undefined,
  c: undefined,
  d: undefined,
  b: undefined
}
// AO对象的3个步骤后的预编译后的伪代码为:
AO= {
  a: 1,
  c: 2,
  d: undefined,
  b: undefined
}
// AO对象的4个步骤后的预编译后的伪代码为:
AO= {
  a: function a() {},
  c: function c() {},
  d: undefined,
  b: undefined
}

5.this的概念

  • 在函数中直接使用时this指向window
  • 函数作为对象的方法被调用的时候,this指向谁
  • 箭头函数没有this,它的this是继承自父之行上下文的this

6.手写call和apply方法

  1. call和apply的作用和区别? 都是改变this的指向,区别是传参的方式上面,call是传递参数列表,apply是传递一个数组
  2. call在项目中的使用有哪些?
    (1)通过call改变构造函数的继承
    (2)通过Object.prototype.toString().call 来判断数据的类型是数组还是对象类型
    (3)用es5的方法把伪数组转换成数组Array.prototype.slice.call(arguments),
    (ps:es6的方法是[...arguments])
  3. 手写call方法的代码
Function.prototype.myCall = function(context) {
  // 如果this不是函数,则报错
  if (typeof this !== 'function') {
    throw new Error('error')
  }
  // 没有传递this指向的时候,则指向window
  context = context || window

  // 获取除了第一个的参数的参数数组
  const args = [...arguments].slice(1)
  // 要在person1上面来假设有一个方法
  context.fn = this
  var result = context.fn(...args)
  delete context.fn
  return result
}

var person = {
  name: '詹姆斯',
  getName: function() {
    return this.name
  }
}

var person1 = {
  name: '科比'
}

console.log(person.getName.myCall(person1, 1,2,3)) // 科比
  1. 手写apply方法
Function.prototype.myApply = function(context) {
  // 如果this不是函数,则报错
  if (typeof this !== 'function') {
    throw new Error('error')
  }
  // 没有传递this指向的时候,则指向window
  context = context || window

  // 要在person1上面来假设有一个方法
  context.fn = this
  let result
  // 判断有没有传参,第二个参数是一个数组类型的
  if(arguments[1]) {
   result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

7.深浅拷贝的概念

  • 数据类型
    1.一般数据类型(存储在栈里)
    number、string、bollean、null、undefined、Symbol
    2.引用数据类型(存储在堆里)
    object、array、Set、Map

  • 浅拷贝和深拷贝的区别
    浅拷贝:拷贝的数据类型是一般数据的话,则拷贝的就是基本类型,如果属性是引用类型,则拷贝的是该引用的内存地址。
    深拷贝:如果拷贝的是引用数据类型,则会开辟一个新的空间。

  • 深拷贝的方法
    JSON.parse(JSON.stringify(this.list))

8.Symbol的概念

  • 定义:之前的对象的属性名都是字符串的形式,可能会存在属性名重复,从而导致属性值被覆盖的情况。比如当使用他人提供的对象,又想要在对象添加方法的时候,添加的操作很容易就覆盖了原有的方法。
  • 特点:
    • 唯一性。
    • 不能用作四则运算,只能String(Symbol())、Boolean(Symbol())进行转换。
    • Symbol('a')里面的参数a作为修饰符
  • Symbol的写法
let mySymbol = Symbol()

// 第一种写法
let a = {}
a[mySymbol] = 'Hellow'

// 第二种写法
let a = {
  [mySymbol] : 'Hellow'
}

// 第三种写法  
let a = {}
Object.defineProperty(a,mySymbol,{value: 'Hellow'})
// 以上的结果都可以得到a[mySymbol] // "Hellow"
  • Symbol对象属性的遍历
    • 当用Symbol作为属性名的时候,for...in、for...of不会遍历出该属性名,Objec.keys()也不会得到Symbol创建的属性名,Object.getOwnPropertyNames()JSON.stringfy()同理
    • Object.getOwnPropertySymbols()可以得到Symbol属性名的数组
    • Reflect.ownKeys()可以得到全部的属性名数组,用这个api是正解
let person = {
  name: '小明',
  age: 18,
  [Symbol('level')]: 'A'
}
 // for...in遍历的结果
for ( var i in person) {
  // console.log(i) // 只打印了name和age
}
// for...of遍历的结果
for (var i of person) {
  console.log(i) // TypeError: person is not iterable,直接报错,不可遍历
}

// Object.keys() 的结果
const result1 =  Object.keys(person)
console.log(result1) // ['name', 'age'] ,没有打印出Symbol创建的属性名

// Object.getOwnPropertyNames() 的结果
const result2 = Object.getOwnPropertyNames(person)
console.log(result2) // ['name', 'age'] ,没有打印出Symbol创建的属性名

// JSON.stringify() 的结果
const result3 = JSON.stringify(person)
console.log(result3) // {"name":"小明","age":18} , 没有打印出Symbol创建的属性名

const result4 = Object.getOwnPropertySymbols(person)
console.log(result4) // [Symbol(level)] ,打印出了Symbol创建的属性,但是其他的没有打印出来

const result5 = Reflect.ownKeys(person)
console.log(result5) // ['name', 'age', Symbol(level)], 全部打印出来了
  • Symbol.for()和Symbol.keyFor()
    • Symbor.for()可以创建相同key的Symbol类型,原理是当用Symbol.for创建的时候,会进行检索,如果已经创建了则全局登记,不会再创建。Symbol直接创建的话则不会进行检索
    • Symbol.keyFor()方法返回一个已经登记的Symbol类型的key
let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')

console.log(s1 == s2) // true
console.log(Symbol.keyFor(s1)) // 'foo'
console.log(Symbol.keyFor(s2)) // 'foo'

三、Vue的知识点

四、React的知识点

1、基本使用

(1)条件

三元表达式、if...else、||、&&

(2)事件(重点)

1.事件里的this指向的是undefined,如果想改变this的指向有三种办法

(1)函数定义后,在组件初始化的时候使用bind更改this的指向

(2)在用onClick的时候,使用bind更改this的指向,这个方法不好,因为每次渲染都需要进行bind更改this指向

(3)函数定义为箭头函数,这样this的指向永远指向当前的实例,这个方法最好

2.react事件参数event

(1)react里的event是自己模拟的不是原生的event(MouseEvent),是SyntheticEvent(组合事件,模拟出DOM事件的所有能力)

(2)用event.nativeEvent可以获取原生event

(3)react把事件绑定到了document上了,没有绑定到组件上(17版本以后事件不会绑定到document上,而是绑定到root组件上,这样做的好处是有利用多个React版本并存,例如微前端)

3.事件传参 除了正常接收自定义参数外,最后一个参数会追加一个参数,可以接收event

(3)JSX语法

大括号包裹起来进行插值,被包裹的内容可以是表达式、变量、子组件、子元素

(4)受控组件

类似于vue的v-modul,只不过vue的v-modul是数据双向绑定的形式,react是单向数据流,通过表单里的value和state进行绑定,这样通过onChange事件调用setState修改state的数据,就可以控制表单的数据。

(5)props(父子组件通信)

(1)父组件可以传递数据和方法,子组件通过this.props来获取

(2)在子组件中可以通过propTypes对参数进行类型检查,isRequired是必传参数

(3)利用props实现父子组件的通信,有一个状态(数据)提升的设计理念,即把数据放到父组件中,子组件只负责渲染和修改父组件里的数据。

(6)setState(重点)

1.setState的三个特性

(1)不可变值

不能直接修改当前state的值,如果当前state的值是字符串或者数字,直接用setState进行重新赋值,如果是数组形式则不能用push、pop、splice方法,如果是对象形式则不能直接修改其属性

// 数组的类型
this.setState({
  list1: this.state.list1.concat(100), // concat方法追加
  list2: [...this.state.list2, 100], // 扩展运算符方法追加
  list3: this.state.list3.slice(0,1), // slice方法来截取
  list4: this.state.list4.filter(item=>item<100), // 过滤
  list5: list5Copy // 深拷贝后的其他操作
})

// 对象的类型
this.setState({
    obj1: Object.assign({},this.state.obj1, {a: 100}),
    obj2: {...this.state.obj2, a:100}
})

(2)可能是异步或者同步更新

setState直接去使用是异步更新,如果想拿到最新的值需要在第二个参数里传入一个函数进行获取。

在setTimeOut和自定义DOM的事件中,setState是同步的(在react18版本,setState都是异步更新的,是用的Automatic Batching 自动批处理的方法)

this.setState({
  content: this.state.content + 1,
},() =>{
  // 类似Vue里的$nextTick
  console.log(this.state.content); // 可以拿到最新的值
}
)
console.log(this.state.content); // 拿不到最新的值,因为是异步

(3)可能会被合并

传入对象的时候,state会被合并,传入函数的时候不会被合并,因为setState是异步更新,所以setState设置数据的时候会根据当前数据进行更新合并,而函数是一个可执行的代码,没办法合并只能执行。

// 最后的结果是content只加了1,被合并了
this.setState({
    content: this.state.content + 1,
  }
)
this.setState({
    content: this.state.content + 1,
  }
)
this.setState({
    content: this.state.content + 1,
  }
)

// 最后结果是content加了3,没有被合并
this.setState((prevState,props)=>{
    return { content: prevState.content +1 }
  }
)

this.setState((prevState,props)=>{
    return { content: prevState.content +1 }
  }
)

this.setState((prevState,props)=>{
    return { content: prevState.content +1 }
  }
)

(7)生命周期(重点)

组件生命周期图示

1.单组件生命周期:

挂载时:constructor、render、componentDidMount

更新时:shouldComponentUpdate、render、componentDidUpdate

卸载时:componentWillUnmount

2.父子组件生命周期

挂载时:父组件先执行constructor,然后子组件执行完挂载的生命周期后,父组件再执行render、componentDidMount

更新时:父组件先执行update、然后子组件执行更新时的生命周期,父组件再执行render、didupdate

卸载时:子组件先执行willUnmount、父组件再执行

2.react的高级特性

(1)函数组件

1.函数组件是

(1)纯函数,输入props,输出JSX

(2)没有实例,没有生命周期,没有state

(3)不能扩展其它的方法

(2)非受控组件

表单里的值不受state的控制,如果想获取表单的数据的话,需要通过ref获取组件的dom节点,然后再取到值,

实际的使用场景:在必须通过dom节点才能获取属性的情况下用非受控组件,如文件上传、富文本编辑器

this.fileInputRef = React.createRef() // 创建ref
 ......
 render() {
   return <div> 
            <input type="file" ref={this.fileInputRef} />
          </div>
  }

(3)Portals(传送门)

解决了一下样式问题,可以自定义被渲染到的dom节点,如fixed 元素放在body上,有更好的兼容性

使用场景:overflow:hidden、父组件的z-index 太小、fixed元素

import ReactDom from 'react-dom'

render() {
  return ReactDom.createPortal(
    <div className="div">portal</div>,
    document.body // 任意DOM节点
  )
}

(4)context(上下文)

在最外层定义的一些类似于主题,语言的时候,需要数据去往下传递的时候去使用,用React.creatContext创建一个数据的默认值,然后用Provider来生产数据,在函数组件中用consumer来消费数据,类组件中直接定义一个静态属性来获取创建的context

import React from 'react'

// 定义需传递的数据的默认值
const ThemeContext = React.createContext('light');

// 父组件
this.state = {
    theme: 'light'
}
render() {
  return {
    // Provider生产context信息
    <ThemeContext.Provider value={this.state.theme}>
      <Toolbar />
    </ThemeContext.Rrovider>
  }
}

// 子组件
render() {
  return {
    // consumer来消费数据
    <ThemeContext.Consumer>
      {(value)=>value}
    </ThemeContext.Consumer>
  }
}

(5)异步组件(懒加载)

场景:当引入比较大的组件的时候用异步组件,提高性能。

用法:用React.lazy和import引入一个组件,此时lazy会对组件进行包装,使之变成异步组件。用React.suspense和里面的fallback可以定义一个loading的效果

import React from 'react'

const Demo = React.lazy(()=>import(./demo))
.......
render() {
  return {
    <React.Suspense fallback={<div>loading...</div>}>
      <Demo />
    </React.Suspense>
  }
}

(6)性能优化(重点)

性能优化可以通过:SCU、PureComponent(类组件)、memo(函数组件)、immutable.js

SCU:React 默认父组件更新,子组件无条件更新,所以性能优化对 React 更加重要,可以通过SCU进行优化(shouldComponentUpdate),SCU优化一定要配合不可变值。在比较对象或者数组的时候可以用_.isEqual方法来进行深度比较(通过一次性递归到底的方法)。

PureComponent / memo:由于_isEqual()方法是一次性递归的比较原理,所以尽量不用使用,太耗费性能,可以把state层次设计的少一点,然后用 PureComponent / memo 进行浅层进行比较。

immtable.js:每次赋值的时候都深拷贝一次,但是深拷贝也会消耗大量性能,而immtable.js基于共享数据的方法来解决深拷贝的缺点,它的缺点是有一定的学习成本。

// SCU性能优化
shouleComponentUpdate(nextProps, nextState) {
  if(nextProps.text !== this.props.text) {
    return true // 可以渲染
  }
  return false // 不可以渲染
}

// SCU性能优化 在对象或者数组的时候
shouleComponentUpdate(nextProps, nextState) {
  // _.isEqual 做对象或者数组的深度比较,这个比较的值要符合不可变值的特点,否则比较的结果会一直相等
  if(_.isEqual(nextProps.list,this.props.lits)) {
    return false // 相等,则不重复渲染
  }
  return false // 不相等,则渲染
}

 // PureComponent 的使用方法
 class List extends React.PureComponent {
   // 此时相当于隐藏的加了一个生命周期SCU,做了一个前不急哦安
 } 

 // memo 的用法
 function MyComponent(props) {
 }
 // 定义一个类似于equal的比较函数
 function areEqual(prevProps, nextProps){
}
export default React.memo(MyComponent, areEqual)

(7)高阶组件(HOC)重点

定义:HOC本质是一个工厂函数,接收一个组件,再经过内部公共逻辑的包装后返回一个高阶的组件,比如connect就是一个高阶组件

const HOCFactory = (Component) => {
  class HOC extends React.Component {
    // 在这里定义多个组件的公共逻辑
    render() {
      return <Component {...this.props}/>
    }
  }
  return HOC
}
 // HOC1就是一个高阶组件
 const HOC1 = HOCFactory(DemoComponent)

在项目中使用高阶组件的举例:

(1)对列表页面,定义一个函数,接收一个列表组件,默认值,和获取列表数据的方法,可以对有类似结构的列表进行复用。

(2)对几个页面新增了一个白名单功能,对在白名单的用户可以正常访问,不在白名单的会给一些正在上线中的提示信息。这个功能只用维持一周就要去掉。这里可以封装一个高阶函数,里面进行权限方法的判断,然后根据权限进行展示,不需要这个功能后,也只需去掉高阶函数就行,不用改页面组件里的东西。

(8)render props

概念:通过一个函数将 class 组件里的state作为props传递给纯函数组件,比HOC用起来更加的优雅

const App = (props) => {
//这里把纯函数传过去
  <Mouse render={
    ({x,y}) => <h1>the mouse ponsite is {x} , {y}</h1>
  }/>
}
class Mouse extends Component {
  this.state = {
    x: 100,
    y: 200,
  }
  // ....  进行其他逻辑处理
  render() {
    return (
      <div>
        {this.props.render(this.state)}
      </div>
    )
  }
}

(9)Hooks

Hook存在意义:由于函数式组件的问题是,没有组件实例,没有生命周期,没有想state的东西去管理数据,而类组件在大型组件中很难拆分,复用逻辑复杂(HOC、Render Prop),所以用Hooks来增强函数的能力。

Hook命名规范:所有的Hook都以use开头,包括自定义Hook

1.State Hook

作用:代替类组件的state,

使用方法:通过useState来初始值,返回数组[staet, setState]; 通过state来获取值;通过setState来修改值

import React, {useState} from 'react'

function ClickDemo () {
  const [count, setCount] = useState(0) // 初始化count数据

 // setCount来更改count数据
  return <div>
    <p>你点击了 {count} 次</p>
    <button onclick={()=>setCount(count + 1)}>点击</button>
  </div>
}

2.Effect Hook

作用:帮助函数式组件模拟生命周期,可以模拟DidMount、DidUpdate、DidUnMount

使用方法

只传一个函数参数,则模拟 calss 组件的DidMount、DidUpdate

第二个参数传空数组,则只模拟组件的DidMount

如果第二个参数数组中有数据,则模拟DidUpdate,其中的数据是一种依赖,只有依赖发生改变,DidUpdate才会触发;

第一个函数参数里返回一个函数,则模拟WillUnMount,特别注意的是,此处不完全等同于WillUnmount,如果useEffect具有DidMount和DidUpdate两个功能的时候,则当props 发生变化,及更新的时候也会触发,准确的说,返回的函数会在下一次effect执行之前,被执行

function LifeDemo () {
  const [count, setCount] = useState(0)
  
  useEffect(() => {
    console.log('DidMount 和 DidUpdate 生命周期')
  })

  useEffect(() => {
    console.log('DidMount生命周期')
  }, [])

  useEffect(() => {
    console.log('DidUpdate生命周期, 依赖于count是否发生改变')
  }, [count])
  
  useEffect(() => {
    renten () => {
      console.log('WillUnMount生命周期')
    }
  })

  return .....
}

3.其他Hook

(1)useRef

(2)useContext

(3)useReducer

(4)useMemo

(5)useCallback

4.自定义Hook(重点)

5.组件逻辑复用(重点)

6.规范和注意事项(重点)

3.react的高级原理

(1)vdome和diff算法

(2)JSX的本质

(3)合成事件

(4)setState和batchUpdate

(5)组件渲染过程

4.redux和react-redux

(1)redux

redux中文文档

redux数据流图.png

redux的工作流程:redux是一个订阅与发布的模式,先通过createStore定义全局的state和修改state的reducer规则,在外面可以用getStore获取当前的state的值,如果想更改state的值,需要通过dispath(action) 发起一个对store的通知,然后store根据reducer的规则进行处理,返回一个全新的state,这样外面通过subsribe订阅到的数据就会更新,然后相关视图也会进行更新。

相关属性的概念:

store:用creatStore(reducer) 创建存放数据的,通过getStore可以获取当前的数据

reducer:定义了state的初始化数据,和修改state的规则

action:一个对象,被dispatch调用,分为同步action和异步action,当是异步action的时候需要中间件redux-thunk、redux-promise

dispatch:改变state的唯一方法,就是通过dispatch发起一个通知到store

subscribe:当state的数据发生改变的时候,通过subscribe订阅到数据会发生改变,从而更新相关视图层,在组件销毁的时候要手动关闭订阅

中间件:对dispatch进行改造,在createStore创建store的时候,通过applyMiddleware引入中间件

redux-thunk:可以穿异步action给dispatch

redux-promise:可以穿Promise对象给dispatch

(2)react-redux

概念:使组件不用自己订阅与取消订阅,而是通过Provider 传递store,然后通过connect包装子组件,使之变成容器组件,在里面通过dispatch的方式消费数据。

相关属性的概念

provider:下发store的数据,使被connect包装的子组件可以使用store里的数据

connect:包装子组件成高阶组件,connect可以接受两个参数,第一个是传递给子组件的自定义属性,第二个时传递给子组件的自定义回调函数

mapStateToProps:映射状态到属性,可以代替connect的第一个属性

mapDispatchToProps:映射dispatch到属性,可以代替connect的第二个属性

redux的持久化:通过redux-persist这个插件,会把redux的store的数据缓存到localStorage中

5.react-router

五、面试的技巧