一、CSS的知识点
1. BFC的概念
- 定义:块级格式化上下文,是指一块独立渲染的区域,该区域拥有一套自己的渲染规则,与外部无关
- 如何创建BFC:
1.float不是none
2.position的值不是static或者relative
3.display的值是inline-block、flex、或者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.判断数组和对象的方法instanceof和constructor的弊端
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引擎去访问的, 它的创建规则步骤是:
- 创建一个AO对象
- 找形参和变量的声明,作为AO对象的属性名,值是undefined
- 实参和形参相统一
- 找函数声明,会覆盖变量的声明
// 预编译的练习题
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方法
- call和apply的作用和区别? 都是改变this的指向,区别是传参的方式上面,call是传递参数列表,apply是传递一个数组
- call在项目中的使用有哪些?
(1)通过call改变构造函数的继承
(2)通过Object.prototype.toString().call来判断数据的类型是数组还是对象类型
(3)用es5的方法把伪数组转换成数组Array.prototype.slice.call(arguments),
(ps:es6的方法是[...arguments]) - 手写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)) // 科比
- 手写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是正解
- 当用Symbol作为属性名的时候,for...in、for...of不会遍历出该属性名,Objec.keys()也不会得到Symbol创建的属性名,
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是一个订阅与发布的模式,先通过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中