写点什么

一天梳理完 React 所有面试考察知识点

作者:beifeng1996
  • 2022-11-08
    浙江
  • 本文字数:13870 字

    阅读完需:约 46 分钟

性能优化

性能优化,永远是面试的重点,性能优化对于 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面试题详细解答

常见基础面试考题

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 的自带函数,需要重定向 thisbindClcik1 () { 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 + 1this.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 + 1this.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 = 0this.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 = 0this.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
复制代码

组件生命周期

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 单项数据流图


React-router

路由模式

  • hash 模式(默认),如:baidu.com/#/user/10

  • H5 history 模式,如:baidu.com/user/20


后者需要 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 )


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 结构

使用 snabbdom 操作虚拟 dom

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 事件也不同


为什么要合成事件机制

  • 更好的兼容性和跨平台,摆脱传统 DOM 事件

  • 挂载到 document,减少内存消耗,避免频繁解绑

  • 方便事件的统一管理,如:事务机制

setState 和 batchUpdate(批处理)

setState

  • 有时异步(普通使用),有时同步(setTimeout, DOM 事件)

  • 有时合并(对象形式),有时不合并(函数形式),比较好理解(类似 Object.assign),函数无法合并

核心要点

  • setState 主流程

  • batchUpdate 机制

  • transaction(事务)机制


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




哪些能命中 batchUpdate 机制

  • 生命周期(和它调用的函数)

  • React 中注册的事件(和它调用的函数)

  • React 可以“管理”的入口

哪些不能命中 batchUpdate 机制

  • setTimeout setInterval 等(和它调用的函数)

  • 自定义的 DOM 时间(和它调用的函数)

  • React“管不到”的入口

transaction 事务机制

常见基础面试题

1.组件之间如何通讯

  • 父子组件 props

  • 自定义事件

  • Redux 和 Context,简单数据用 Context

2.JSX 本质

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

3.shouldComponentUpdate 用途

  • 性能优化

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

4.redux 单项数据流

Redux 单项数据流图


5.setState 场景题

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,不能再细分



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


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

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


常见值类型:undefined、String、Bool、Symbol('s')

常见引用类型:Object、Array、null(指向空地址)

特殊引用类型:function


4.变量计算

字符串拼接


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__ // 隐式原型 => PeopleStudent.prototype // 显式原型 => Peopleme.__proto === Student.prototype => true 全等通过的话,就说明引用的是同一个地址
复制代码


  • 每个实例都有隐式原型__proto__

  • 每个 class 都有显式原型 prototype

  • 实例的隐式原型指向对应 class 的显式原型


基于原型的执行规则


  • 优先在自身属性和自身方法中查找

  • 如果找不到则自动去 __proto__ 隐式原型中查找

补充知识 - 类型判断 instanceof

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


// 根据上方定义的classme instanceof Student // trueme instanceof People  // trueme instanceof Object  // true,可以理解为 Object 是最上层的父类
[] instanceof Array // true[] instanceof Object // true`
{} instanceof Object // true`
复制代码

原型链

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


// 根据上方定义的classStudent.prototype.__proto__People.prototypeconsole.log(People.prototype === Student.prototype.__proto__) ==> true
复制代码



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 工具


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

作用域


  • 全局作用域

  • 函数作用域

  • 块级作用域(ES6 新增)


自由变量


  • 一个变量在当前作用域没有定义,但被使用

  • 向上级作用域,一层一层依次寻找,直至找到为止

  • 如果到全局作用域没找到,就会报错 xx is not defined

补充知识 - 闭包

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


  • 函数作为参数被传递

  • 函数作为返回值

  • 函数自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方


左右两张图都将打印 100


补充知识 - this

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


  • 作为普通函数

  • 使用 call apply bind 改变 this 指向

  • 作为对象方法被调用

  • 在 class 方法中调用

  • 箭头函数,永远是取上级作用域的 this


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




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')
复制代码


用户头像

beifeng1996

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
一天梳理完React所有面试考察知识点_React_beifeng1996_InfoQ写作社区