美文网首页2020前端
2020 前端 React 面试

2020 前端 React 面试

作者: Actoress | 来源:发表于2020-03-29 23:05 被阅读0次

    性能优化

    性能优化,永远是面试的重点,性能优化对于 React 更加重要

    • 在页面中使用了setTimout()addEventListener()等,要及时在componentWillUnmount()中销毁
    • 使用异步组件
    • 使用 React-loadable 动态加载组件
    • shouldComponentUpdate(简称SCU )、React.PureComponentReact.memo
    • 不可变值 ImmutableJS
    shouldComponentUpdate (nextProps, nextState) {
        return true // 可以渲染,执行 render(),默认返回 true
        return false // 不能渲染,不执行 render()
    }
    

    什么情况下需要使用 shouldComponentUpdate

    在React中,默认情况下,如果父组件数据发生了更新,那么所有子组件都会无条件更新 !!!!!!
    通过shouldComponentUpdate()retrun fasle 来判断阻止 Header 组件做无意义的更新
    shouldComponentUpdate()并不是每次都需要使用,而是需要的时候才会优化

    class App extends React.Component {
        constructor () {
            this.state = { list: [] }
        }
        render () {
            return (
                <div>
                    {/* 当list数据发生变化时,Header组件也会更新,调用 render() */}
                    <Header />
                    <List data={this.state.list}
                </div>
            )
        }
    }
    

    shouldComponentUpdate()判断中,有一个有意思的问题,解释为什么 React setState() 要用不可变值

    // 父组件中
    changeList () {
        this.state.list.push({id: 2})
        this.setState({
            list: this.state.list
        })
    }
    // 子组件中
    import _ from 'lodash'
    shouldComponentUpdate(nextProps, nextState) {
        // 数组深度比较(一次性递归到底,耗费性能,工作中慎用)
        if (_.isEqual(nextProps.list, this.props.list)) {
            return false // 相等,不渲染
        }
        return true // 不相等,渲染
    }
    

    子组件将始终不会渲染,因为在shouldComponentUpdate()中,this.state.list.push()已经修改了this.props.list,而this.setState()修改了nextProps.list所以两个值深度比较,将始终相同。

    PureComponent 和 memo

    • class类组件中用PureComponent,无状态组件(无状态)中用memo
    • PureComponent, SCU中实现了浅比较
    • 浅比较已使用大部分情况(尽量不要做深度比较)

    PureComponent 与普通 Component 不同的地方在于,PureComponent自带了一个shouldComponentUpdate(),并且进行了浅比较

    // memo用法
    function MyComponent (props) {
        /* 使用 props 渲染 */
    }
    // areEqual 也可不传
    function areEqual(prevProps, nextProps) {
        if (prevProps.seconds===nextProps.seconds) {
            return true
        } else {
            return false
        }
    }
    export default React.memo(MyComponent, areEqual)
    

    immutable.js

    • 彻底拥抱“不可变值”
    • 基础共享数据(不是深拷贝),速度快
    • 有一定学习和迁移成本

    常见基础面试考题

    React 组件如何通讯

    1. 父子组件通过 属性 和 props 通讯
    2. 通过 context 通讯
    3. 通过 Redux 通讯

    this.setState()相关

    import React from 'react'
    
    class App extends React.Component {
        constructor (props) {
            super(props)
            this.state = { count: 0 }
        }
        componentDidMount () {
            this.setState({ count: this.state.count + 1 })
            console.log(this.state.count) // 0
            this.setState({ count: this.state.count + 1 })
            console.log(this.state.count) // 0
            setTimeout(() => {
                this.setState({count: this.state.count + 1 })
                console.log(this.state.count) // 2
            }, 0)
            setTimeout(() => {
                this.setState({count: this.state.count + 1 })
                console.log(this.state.count) // 3
            }, 0)
            // setTimeout(function () {
            //     this.setState({count: this.state.count + 1 })
            //     console.log(this.state.count) // 报错,this 指向问题
            // }, 0)
        }
        render () {
            return <h1>{this.state.count}</h1>
        }
    }
    
    export default App // 返回高阶函数
    

    JSX本质是什么.....

    前端富文本 dangerouslySetInnerHTML

    const rawHtml = '<div><p>Title</p></div>'
    const rawHtmlData = {
        __html: rawHtml // 这里有个下划线
    }
    return <div dangerouslySetInnerHTML={rawHtmlData}></div>
    

    两种绑定事件

    <button onClcik={bindClcik1.bind(this)}> 使用 .bind(this) </button>
    <button onClcik={bindClcik2}> 箭头函数 </button>
    
    // 使用 class 的自带函数,需要重定向 this
    bindClcik1 () { alert('bindClcik1') }
    // 使用静态方法,使用箭头函数不需要使用 bind(this)
    bindClick2 = () => { alert('bindClcik2') }
    

    Event、默认事件、事件冒泡

    这里打印出来的Event对象是 React 封装过的SyntheticEvent,可以看__proto__.constructor。React 标准化了事件对象,因此在不同的浏览器中都会有相同的属性。

    React 中事件绑定跟 Vue 中完全不同,Vue中事件绑定和触发的对象为同一元素,React中事件触发的对象为document,绑定元素为当前元素。React的所有事件都会被挂载到document上和DOM事件不同。

    Vue 的Event是原生,事件被挂载到当前元素和DOM事件一样

    <a href="www.baidu.com" onClick={this.getEvent} target="blank">Get Event</a>
    
    getEvent = (event) => {
        event.preventDefault() // 阻止默认事件
        event.stopPropagation() // 阻止事件冒泡
        console.log(event) // 非原生的 Event
        console.log(event.nativeEvent) // 获取原生的 Event
        console.log(event.nativeEvent.target) // 绑定事件的对象,这里为 <a></a>
        console.log(event.nativeEvent.currentTarget) // 触发事件的对象,这里为 document
    }
    

    事件传参

    通过.bind()传参

    <div onClick={this.getParams1.bind(this, 'id1', 'title1')}>get params 1</div>
    
    getParams1 (id, title, event) {
        console.log('id', id)
        console.log('title', title)
        console.log('event', event)  // 最后一个参数为Event对象
    }
    

    通过箭头函数传参

    <div onClick={(event) => { this.getParams2('id2', 'title2', event) }}>get params 2</div>
    
    getParams2 (id, title, event) {
        console.log('id', id)
        console.log('title', title)
        console.log('event', event) 
    }
    

    表单

    <div>
        <label htmlFor="userName"></label>
        <input value={this.state.userName} onChange={this.handleInputChange.bind(this)} />
    </div>
    // 实现类似双向数据绑定
    handleInputChange (even t) {
        const userName = event.target.value
        this.setState(() => ({
            userName
        }))
        // 下面这种写法会报错,因为 this.setState 传递一个函数时,为异步方法,等异步执行时已经没有 event
        this.setState(() => ({
            userName = event.target.value
        }))
    }
    

    组件传参

    普通参数/函数

    // 父组件
    <div>
        <Child text={this.state.text} />
    </div>
    
    // 子组件
    <div>
        <p>{this.props.text}</p>
    </div>
    

    属性类型检查

    import PropTypes from 'prop-types'
    
    // 对传递的参数强校验
    TodoItem.propTypes = {
      content: PropTypes.string.isRequired, // 限制为字符串且必传
    }
    

    setState()

    1. 不可变值
    2. 可能是异步更新
    3. 可能会被合并
    // 错误的写法
    this.setState({
        count: this.state.count + 1
    })
    // 正确的写法
    const count = this.state.count + 1
    this.setState({ count })
    

    正确修改数组值

    // 不能使用 push pop splice 等,这样违反了不可变值,会影响 shouldCompententUpdate 判断
    this.setState(() => ({
        list1: this.state.list1.concat(100), // 追加
        list2: [...this.state.list2, 100], // 追加
        list3: this.state.list3.slice(0, 3) // 截取
        list4: this.state.list4.filter(item => item > 100) // 筛选
    }))
    

    正确修改对象值

    this.setState(() => ({
        obj1: Object.assign({}, this.state.obj1, {a: 100}),
        obj2: {...this.state.obj2, a: 100}
    }))
    

    通常情况下,setState()为异步更新数据

    const count = this.state.count + 1
    this.setState({
        count: count
    }, () => {
        // 这个函数没有默认参数
        // 类比 Vue 的 $nextTick
        console.log(this.state.count) // 打印更新后的值
    })
    console.log(this.state.count) // 打印更新前的值
    

    setState()同步更新数据,在setTimeout()setState()是同步的

    setTimeout(() => {
        const count = this.state.count + 1
        this.setState({ count })
        console.log(this.state.count)
    })
    

    自己定义的 DOM 事件,setState() 是同步的

    componentDidMount () {
        document.body.addEventListener('click', () => {
            const count = this.state.count + 1
            this.setState({ count })
            console.log(this.state.count)
        })
    }
    

    【重点】 传入对象,会被合并,结果只执行一次,类似于Object.assgin()

    初始值 this.state.count = 0
    this.setState({
        count: this.state.count + 1
    })
    this.setState({
        count: this.state.count + 1
    })
    this.setState({
        count: this.state.count + 1
    })
    输出值 this.state.count = 1
    

    【重点】 传入函数,不会被合并,因为函数无法合并

    初始值 this.state.count = 0
    this.setState((prevState, props) => {
       return {
           count: prevState.count + 1
       } 
    })
    this.setState((prevState, props) => {
       return {
           count: prevState.count + 1
       } 
    })
    this.setState((prevState, props) => {
       return {
           count: prevState.count + 1
       } 
    })
    输出值 this.state.count = 3
    

    组件生命周期

    image

    Initialization 初始化

    • constructor() : class 的构造函数,并非React独有

    Mounting 挂载

    • componentWillMount() : 在组件即将被挂载到页面的时刻自动执行;
    • render() : 页面挂载;
    • componentDidMount() : 组件被挂载到页面之后自动执行;

    componentWillMount()componentDidMount(),只会在页面第一次挂载的时候执行,state变化时,不会重新执行

    Updation 组件更新

    • shouldComponentUpdate() : 该生命周期要求返回一个bool类型的结果,如果返回true组件正常更新,如果返回false组件将不会更新;
    • componentWillUpdate() : 组件被更新之前执行,如果shouldComponentUpdate()返回false,将不会被被执行;
    • componentDidUpdate() : 组件更新完成之后执行;

    componentWillReceiveProps() : props独有的生命周期,执行条件如下:

    1. 组件要从父组件接收参数;
    2. 只要父组件的render()被执行了,子组件的该生命周期就会执行;
    3. 如果这个组件第一次存在于父组件中,不会执行;
    4. 如果这个组件之前已经存在于父组件中,才会执行;

    Unmounting 组件卸载

    • componentWillUnmount() : 当组件即将被从页面中剔除的时候,会被执行;

    生命周期简单使用场景

    1. 使用shouldComponentUpdate()防止页面进行不必要的渲染
    # 用生命周期进行性能优化
    shouldComponentUpdate () {
        if (nextProps.content !== this.props.content) {
          return true;
        }
        return false;
    }
    
    1. Ajax 请求页面初始数据componentDidMount()

    不能写在render()之中,因为会重复调用,也不能写在componentWillMount()之中,会与RN等其它框架冲突,不然也可以写在这里面,同样是只执行一次。

    同样也可以写在构造函数constructor()之中,但是不建议这样做。

    import axios from 'axios'
    
    componentDidMount () {
        axios.get('/api/todolist').then((res) => {
          console.log(res.data);
          this.setState(() => ({
            list: [...res.data]
          }));
        }).catch((err) => {
          console.log(err);
        });
    }
    

    无状态组件(函数组件)

    当一个组件只有一个render()函数时,我们就可将这个组件定义为无状态组件,无状态组件只有一个函数。
    无状态组件的性能比较高,因为它仅是一个函数,而普通组件是一个class

    • 纯函数
    • 输入props,输出JSX
    • 没有实例
    • 没有生命周期
    • 没有state
    • 不能扩展其它方法
    function List (props) {
        const { text } = this.props
        return (
            <div>{text}</div>
        )
    }
    

    非受控组件

    class App extends React.Component {
        constructor (props) {
            super(props)
            this.state = {
                name: '',
                flag: true
            }
            this.nameInputRef = React.createRef() // 创建 ref
            this.fileInputRef = React.createRef() // 创建 ref
        }
        render () {
            return (
                <div>
                    {/* 这里使用 defaultValue 而不是value,使用 ref */}
                    <input defaultValue={this.state.name} ref={this.nameInputRef} />
                    <button onClick={this.alertName.bind(this)}>alert value</button>
                    {/* file 类型的必须用 ref 获取 dom 来获取数据 */}
                    <input type="file" ref={this.fileInputRef} />
                </div>
            )
        }
        alertName () {
            const ele = this.nameInputRef.current // 通过 ref 获取 dom 节点
            alert(ele.value)
        }
    }
    

    portals 传送门

    使用 Portals 渲染到 body 上,fixed 元素要放在 body 上,有更好的浏览器兼容。

    常见使用场景:

    1. 父组件 overflow: hidden , 但是子组件又想展示;
    2. 父组件的 z-index 太小;
    3. fixed 需要放在 body 第一层;
    import ReactDOM from 'react-dom'
    render () {
        return ReactDOM.creatPortal(
            <div>{this.props.children}</div>,
            document.body
        )
    }
    

    context 上下文

    使用场景:公共信息(语言、主题)传递给每个组件,如果组件层级过多,用props传递就会繁琐,用 redux 小题大做。

    import React from 'react'
    
    // 创建 Context 填入默认值(任何一个 js 变量)
    export const {Provider,Consumer} = React.createContext("默认名称")
    
    // 在最上级组件中
    constructor (props) {
        super(props)
        this.state = { theme: 'light' }
    }
    render () {
        // 这里使用 this.state.theme 是为了可以修改,初始化的值为默认值,不能修改
        // value 中放共享的数据
        return (
            <Provider value={this.state.theme}>
                ....
                <button onClick={this.changeTheme}></button>
            </Provider>
        )
    }
    
    // 子组件中调用
    import { Consumer } from "./index";//引入父组件的Consumer容器
    render () {
        return (
            // Consumer 容器,可以拿到上文传递下来的 theme 属性,并可以展示对应的值
            <Consumer>
                { theme => <div>子组件。获取父组件的值: {theme} </div> }
            </Consumer>
        )
    }
    

    异步组件

    // 引入需要异步加载的组件
    const LazyComponent = React.lazy(() => import('./lazyDemo') )
    
    // 使用异步组件,异步组件加载中时,显示fallback中的内容
    <React.Suspense fallback={<div>异步组件加载中</div>}>
        <LazyComponent />
    </React.Suspense>
    

    组件公共逻辑的抽离

    • Vue 中的 mixin,已被 React弃用
    • 高阶组件 HOC
    • Render Props

    高阶组件

    高阶组件不是一种功能,而是一种模式

    // 高阶组件 基本用法
    const HOCFactory = (Component) => {
        class HOC extends React.Component {
            // 在此定义多个组件的公共逻辑
            render () {
                return <Component {...thi.props} /> // 返回拼装的结果
            }
        }
        return HOC
    }
    const MyComponent1 = HOCFactory(WrappedComponent1)
    const MyComponent2 = HOCFactory(WrappedComponent2)
    

    实际案例

    import React from 'react'
    // 高阶组件
    const withMouse = (Component) => {
        class withMouseComponent extends React.Component {
            constructor(props) {
                super(props)
                this.state = { x: 0, y: 0 }
            }
            handleMouseMove = (event) => {
                this.setState({
                    x: event.clientX,
                    y: event.clientY
                })
            }
            render() {
                return (
                    <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
                        {/* 1. 透传所有 props 2. 增加 mouse 属性 */}
                        <Component {...this.props} mouse={this.state}/>
                    </div>
                )
            }
        }
        return withMouseComponent
    }
    
    const App = (props) => {
        const a = props.a
        const { x, y } = props.mouse // 接收 mouse 属性
        return (
            <div style={{ height: '500px' }}>
                <h1>The mouse position is ({x}, {y})</h1>
                <p>{a}</p>
            </div>
        )
    }
    export default withMouse(App) // 返回高阶函数
    

    Render Props

    Render Props 核心思想:通过一个函数将 class 组件的 state 作为 props 传递给纯函数组件

    class Factory extends React.Component {
        constructor () {
            this.state = {
                /* 这里 state 即多个组件的公共逻辑的数据 */
            }
        }
        /* 修改 state */
        render () {
            return <div>{this.props.render(this.state)}</div>
        }
    }
    
    const App = () => {
        /* render 是一个函数组件 */
        <Factory render={
            (props) => <p>{props.a} {props.b}...</p>
        } />
    }
    

    Redux 单项数据流

    1. dispatch(action)
    2. reducer 产生 newState
    3. subscribe 触发通知

    Redux 单项数据流图

    image

    React-router

    路由模式

    后者需要 server 端支持,因此无特殊需求可选择前者

    常用组件

    import {
        HashRouter,
        BrowserRouter,
        Switch,
        Route
    } from 'react-router-dom'
    
    function RouterComponent () {
        return (
            <BrowserRouter>
                <Switch>
                    <Route path='/' exact component={Home}>
                    {/* 动态路由 */}
                    <Route path='/detail/:id' exact component={Detail}></Route>
                    {/* 匹配404等页面 */}
                    <Route path='*' exact component={NotFound}></Route>
                </Switch>
            </BrowserRouter>
        )
    }
    

    路由跳转

    • 标签跳转,通过 <Link to="/"> 这个隐性 a 标签跳转
    • JS跳转,import { useHistory } from 'react-router-dom' && history.push('/')

    路由配置懒加载

    import React from 'react'
    import { BrowserRouter, Route, Switch } from 'react-router-dom'
    
    const Home = React.lazy(() => import('./pages/Home'))
    const Detail = React.lazy(() => import('./pages/Detail'))
    
    const App = () => (
        <BrowserRouter>
            <React.Suspense fallback={<div>Loading...</div>}>
                <Switch>
                    <Route exact path="/" component={Home} />
                    <Route exact path="/detail" component={Detail} />
                </Switch>
            </React.Suspense>
        </BrowserRouter>
    )
    

    Vue 原理

    数据驱动视图(MVVM, setState),Vue MVVM ( Model + View + ViewModel )


    image

    Vue响应式

    组件 data 的数据一旦变化,立刻触发视图的更新,实现数据驱动视图的第一步

    核心API:Object.defineProperty,Object.defineProperty 有一些缺点,Vue3.0 开始启用 Proxy, Proxy有兼容性问题,且无法 polyfill(兼容性问题解决方案)

    // Object.defineProperty 基础使用
    const data = {}
    const name = 'Actoress'
    Object.defineProperty(data, "name", {
        get: function () {
           console.log('get')
           return name
        },
        set: function (newValue) {
            console.log('set')
            name = newValue
        }
    })
    
    // 调用
    console.log(data.name) // get() 执行 => 'Actoress'
    data.name = 'Wu' // set() 执行
    

    深度监听

    • 深度监听,需要递归到底,一次性计算量大
    • 无法监听新增属性/删除属性
    • 数组需要重新定义数组原型
    // 触发更新视图
    function updateView() {
       console.log('视图更新')
    }
    
    // 重新定义数组原型
    const oldArrayProperty = Array.prototype
    // 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
    const arrProto = Object.create(oldArrayProperty);
    ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
       arrProto[methodName] = function () {
           updateView() // 触发视图更新
           oldArrayProperty[methodName].call(this, ...arguments)
           // Array.prototype.push.call(this, ...arguments)
       }
    })
    
    // 重新定义属性,监听起来
    function defineReactive(target, key, value) {
       // 深度监听
       observer(value)
    
       // 核心 API
       Object.defineProperty(target, key, {
           get() {
               return value
           },
           set(newValue) {
               if (newValue !== value) {
                   // 深度监听
                   observer(newValue)
                   // 设置新值
                   // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                   value = newValue
                   // 触发更新视图
                   updateView()
               }
           }
       })
    }
    
    // 监听对象属性
    function observer(target) {
       if (typeof target !== 'object' || target === null) {
           // 不是对象或数组
           return target
       }
    
       // 不能写在这里,这样会污染全局的 Array 原型
       // Array.prototype.push = function () {
       //     updateView()
       //     ...
       // }
    
       if (Array.isArray(target)) {
           target.__proto__ = arrProto
       }
    
       // 重新定义各个属性(for in 也可以遍历数组)
       for (let key in target) {
           defineReactive(target, key, target[key])
       }
    }
    
    // 准备数据
    const data = {
       name: 'Actoress',
       age: 20,
       info: {
           address: '北京' // 需要深度监听
       },
       nums: [10, 20, 30]
    }
    
    // 监听数据
    observer(data)
    
    // 测试
    // data.name = 'Wu'
    // data.age = 21
    // // console.log('age', data.age)
    // data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
    // delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
    // data.info.address = '上海' // 深度监听
    

    虚拟DOM(Virtual DOM)

    vdom 是实现 Vue 和 React 的重要基石
    为什么会有 vdom

    • 有了一定复杂度,想减少计算次数比较难
    • 能不能把计算,更多的转移到JS计算?因为JS执行速度很快
    • vdom 用 JS 模拟 DOM 结构,计算出最小的变更,操作DOM

    用JS模拟DOM结构

    image

    使用 snabbdom 操作虚拟 dom

    文档:https://github.com/snabbdom/snabbdom
    主要API h() && vnode && patch()

    Diff 算法

    • diff 算法是 vdom 中最核心、最关键的部分
    • diff 算法能在日常使用 Vue React 中体现出来(循环的 key)

    优化前 树 diff 的时间复杂度 (n^3)

    1. 遍历Tree1,遍历Tree2
    2. 排序
    3. 假设有1000个节点,就要计算1亿次,算法不可用

    优化后时间复杂度 (n^1)

    1. 只比较同一层级,不跨级比较
    2. tag 不相同,则直接删掉重建,不再深度比较
    3. tag 和 key,两者都相同,则认为是相同节点,不再深度比较

    React 原理

    数据驱动视图(MVVM, setState)

    • 数据驱动视图 - React this.setState()
    • 函数式编程:函数式式编程是一种编程范式,两个最重要的概念是 纯函数不可变值

    JSX 本质

    • JSX 等同于 Vue 模板
    • Vue 模板不是 html
    • JSX 也不是 JS

    讲JSX语法,通过 React.createElement()编译成Dom,BABEL 可以编译JSX
    流程:JSX => React.createElement() => 虚拟DOM (JS对象) => 真实DOM
    React 底层会通过 React.createElement() 这个方法,将 JSX 语法转成JS对象,React.createElement() 可以接收三个参数,第一个为标签名称,第二参数为属性,第三个参数为内容

    createElement() 根据首字母大小写来区分是组件还是HTML标签,React规定组件首字母必须大写,HTML规定标签首字母必须小写

    // 第一个参数为 标签(tag) 可为 'div'标签名 或 List组件
    // 第二个参数为:属性(props)
    // 第三个参数之后都为子节点(child),可以在第三个参数传一个数组,也可以在第三、四、五....参数中传入
    React.createElement('tag', null, [child1, chlild2, child3])
    或者
    React.createElement('tag', { className: 'class1' }, child1, chlild2, child3)
    

    事件合成机制

    • 所有事件挂载到 document 上
    • event 不是原生的,是SyntheticEvent合成事件对象
    • 与 Vue 事件不同,和 DOM 事件也不同
    image

    为什么要合成事件机制

    • 更好的兼容性和跨平台,摆脱传统DOM事件
    • 挂载到document,减少内存消耗,避免频繁解绑
    • 方便事件的统一管理,如:事务机制

    setState 和 batchUpdate(批处理)

    setState

    • 有时异步(普通使用),有时同步(setTimeout, DOM事件)
    • 有时合并(对象形式),有时不合并(函数形式),比较好理解(类似 Object.assign),函数无法合并

    核心要点

    • setState 主流程
    • batchUpdate 机制
    • transaction(事务)机制

    this.setState()是否是异步,看 isBatchingUpdates 的状态,为 true 就是异步,为 false 就是同步

    image
    image
    image

    哪些能命中 batchUpdate 机制

    • 生命周期(和它调用的函数)
    • React 中注册的事件(和它调用的函数)
    • React 可以“管理”的入口

    哪些不能命中 batchUpdate 机制

    • setTimeout setInterval等(和它调用的函数)
    • 自定义的DOM时间(和它调用的函数)
    • React“管不到”的入口

    transaction 事务机制

    image

    常见基础面试题

    1.组件之间如何通讯

    • 父子组件 props
    • 自定义事件
    • Redux 和 Context,简单数据用 Context

    2.JSX 本质

    JSX => React.createElement() => 虚拟DOM (JS对象) => 真实DOM

    3.shouldComponentUpdate 用途

    • 性能优化
    • 配合“不可变值”一起使用,否则会出错

    4.redux单项数据流

    Redux 单项数据流图

    image

    5.setState场景题

    image

    6.什么是纯函数

    • 返回一个新值,没有副作用(不会修改其它值)

    7.列表渲染为何要用key

    • 必须用 key,且不能是 index 和 random
    • diff 算法中通过 tag 和 key 判断,是否是同一个节点
    • 减少渲染次数,提升渲染性能

    8.函数组件 和 class 组件区别

    • 纯函数,输入 props,输出JSX
    • 没有实力,没有生命周期,没有state
    • 不能扩展其它方法

    9.如何使用异步组件

    • 加载大组件
    • React.lazy
    • React.Suspense

    10.多个组件有公共逻辑,如何抽离

    • 高阶组件 HOC
    • Render Props

    11.react-router 如何配置懒加载

    上文中有...

    12.PureComponent 有何区别

    • 实现了浅比较的 shouldComponentUpdate
    • 优化性能
    • 但要结合不可变值使用

    13.React事件和DOM事件的区别

    • 所有事件挂载到 document 上
    • event 不是原生的,是 SyntheticEvent 合成事件对象

    14.React性能优化

    • 渲染列表时加Key
    • 自定义事件、DOM事件及时销毁
    • 合理使用异步组件
    • 减少函数 bind this 的次数
    • 合理使用 shouldComponentUpdate、PureComponent 和 memo
    • 合理使用 ImmutableJS
    • webpack层面优化
    • 前端通用是能优化,如图片懒加载
    • 使用SSR

    React 和 Vue 的区别

    相同点

    • 都支持组件化
    • 都是数据驱动视图
    • 都是用 vdom 操作 DOM

    不同点

    • React 使用 JSX 拥抱JS,Vue使用模板拥抱 html
    • React 函数式编程,Vue声明式编程
    • React 更多需要自力更生,Vue把想要的都给你

    JS 基础 - 变量类型和计算

    typeof能判断哪些类型

    • 识别所有类型
    • 识别函数
    • 判断是否是引用类型,返回都为 object,不能再细分
    image
    image

    2. 何时使用===何时使用==

    image
    image

    3. 值类型和引用类型的区别

    引用类型的本质是相同的内存地址,出于性能问题考虑,所以JS对象使用引用类型,为了避免这种情况所以需要深拷贝

    常见值类型:undefined、String、Bool、Symbol('s')
    常见引用类型:Object、Array、null(指向空地址)
    特殊引用类型:function

    image

    4.变量计算

    字符串拼接


    image

    5. 手写深拷贝

    funciton deepClone ( obj = {}) {
        // 判断是否需要深拷贝,不是对象和数组
        if (typeof obj !== 'object' || obj == null) {
            return obj
        }
        let result
        // 判断是否为一个数组
        if (obj instanceof Array) {
            result = []
        } else {
            result = {}
        }
        // 遍历对象
        for (let key in obj) {
            // 保证 key 不是原型的属性
            if (obj.hasOwnProperty(key)) {
                // 递归【重点】
                result[key] = deepClone(obj[key])
            }
        }
        return result
    }
    

    JS 基础 - 原型和原型链

    JS本身是一个基于原型继承的语言,PS:class 的 extends 本质也是原型链继承

    1.如何准确判断一个变量是不是数组?

    a instanceof Array
    

    2.手写一个简易的jQuery,考虑插件和扩展性

    class jQuery {
        constructor (selector) {
            const result = document.querySelectorAll(selector)
            const length = result.length
            for (let i = 0; i < length; i++) {
                this.[i] = result[i]
            }
            this.length = length
        }
        get (index) {
            return this[index]
        }
        each (fn) {
            for (let i = 0; i < this.length; i++) {
                const elem = this[i]
                fn(elem)
            }
        }
        on (type, fn) {
            return this.each(elem => {
                elem.addEventListener(type, fn, false)
            })
        }
    }
    
    // 插件
    jQuery.prototype.dialog = function (info) {
        alert(info)
    }
    // 复写,造轮子
    class MyJQuery extends jQuery {
        constructor (selector) {
            super(selector)
        }
        ......
    }
    

    3.class 的原型本质,怎么理解?

    • 原型和原型链的图示
    • 属性和方法的执行规则

    补充知识 - 定义class

    // 父类
    class People {
        constructor (old) {
            this.old = old
        }
        eat () {
            consoloe.log('eating')
        }
    }
    // 继承
    class Student extends People {
        constructor(name, number,old) {
            super(old) // 变量传递给父类执行
            this.name = name
            this.number = number
        }
        sayHi () {
            console.log(this.name, this.number)
        }
    }
    const me = new Student('小明', 10, 20)  // 新建对象
    console.log(me.name)   // => 小明
    me.sayHi()             // => 小明 10
    
    // class 实际上是函数,可见是语法糖
    typeof People => 'function'
    typeof Student => 'function'
    
    // 隐式原型和显式原型
    me.__proto__ // 隐式原型           => People
    Student.prototype // 显式原型      => People
    me.__proto === Student.prototype   => true 全等通过的话,就说明引用的是同一个地址
    
    • 每个实例都有隐式原型__proto__
    • 每个 class 都有显式原型 prototype
    • 实例的隐式原型指向对应class的显式原型

    基于原型的执行规则

    • 优先在自身属性和自身方法中查找
    • 如果找不到则自动去 __proto__ 隐式原型中查找

    补充知识 - 类型判断 instanceof

    instanceof 工作原理:是顺着__proto__隐式原型一层层往上找

    // 根据上方定义的class
    me instanceof Student // true
    me instanceof People  // true
    me instanceof Object  // true,可以理解为 Object 是最上层的父类
    
    [] instanceof Array   // true
    [] instanceof Object  // true`
    
    {} instanceof Object  // true`
    

    原型链

    可以理解为,在 extend 继承时,对父类进行了一次实例化,所有拥有隐式原型__proto__

    // 根据上方定义的class
    Student.prototype.__proto__
    People.prototype
    console.log(People.prototype === Student.prototype.__proto__) ==> true
    
    image

    hasOwnProperty()就是继承于ObjecthasOwnProperty(functionName) => false无论继承还是自己的函数,均为falsehasOwnProperty()属性名只要是继承或者自己拥有的为true

    JS 基础 - 作用域和闭包

    1.this 的不同应用场景,如何取值?

    • 作为普通函数
    • 使用 call apply bind 改变 this 指向
    • 作为对象方法被调用
    • 在 class 方法中调用
    • 箭头函数,永远是取上级作用域的 this

    2.手写 bind 函数

    Function.prototype.bind1 = function () {
        // 将参数拆解为数组
        const args = Array.prototype.slice.call(arguments)
        // 获取 this (数组的第一项)
        const that = args.shift() // 删除并返回数组第一项
        // 获取 fn1.bind(...) 中的 fn1
        const self = this
        // 返回一个函数
        return function () {
            return self.apply(that, args)
        }
    }
    

    3.实际开发中闭包的应用场景,举例说明

    • 隐藏数据,只提供API,如做一个简单的 cache 工具


      image

    补充知识 - 作用域和自由变量

    作用域

    • 全局作用域
    • 函数作用域
    • 块级作用域(ES6新增)

    自由变量

    • 一个变量在当前作用域没有定义,但被使用
    • 向上级作用域,一层一层依次寻找,直至找到为止
    • 如果到全局作用域没找到,就会报错 xx is not defined

    补充知识 - 闭包

    作用域应用的特殊情况,有两种表现:

    • 函数作为参数被传递
    • 函数作为返回值
    • 函数自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方

    左右两张图都将打印 100

    image

    补充知识 - this

    this 在各个场景中取什么值,是在函数执行的时候确定的,不是在定义函数定义的时候决定的

    • 作为普通函数
    • 使用 call apply bind 改变 this 指向
    • 作为对象方法被调用
    • 在 class 方法中调用
    • 箭头函数,永远是取上级作用域的 this

    call 是直接执行,bind是返回一个新的函数去执行

    image
    image
    image

    JS 基础 - 事件

    手写一个通用绑定事件

    function bindEvent (elem, type, fn) {
        elem.addEventListener(type, fn)
    }
    

    Promise 图片懒加载

    function loadImg (src) {
        var promise = new Promise(function (resolve, reject) {
            var img = document.createElement('img')
            img.onload = function () {
                resolve(img)
            }
            img.onerror = function () {
                reject('图片加载失败')
            }
            img.scr = src
        })
        retrun promise
    }
    var result = loadImg('www.baidu.com')
    

    相关文章

      网友评论

        本文标题:2020 前端 React 面试

        本文链接:https://www.haomeiwen.com/subject/bivpuhtx.html