2020前端

2020 前端 React 面试

2020-03-29  本文已影响0人  Actoress

性能优化

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

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

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 初始化

Mounting 挂载

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

Updation 组件更新

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

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

Unmounting 组件卸载

生命周期简单使用场景

  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

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>

组件公共逻辑的抽离

高阶组件

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

// 高阶组件 基本用法
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>
    )
}

路由跳转

路由配置懒加载

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模拟DOM结构

image

使用 snabbdom 操作虚拟 dom

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

Diff 算法

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

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

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

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

React 原理

数据驱动视图(MVVM, setState)

JSX 本质

讲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)

事件合成机制

image

为什么要合成事件机制

setState 和 batchUpdate(批处理)

setState

核心要点

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

image
image
image

哪些能命中 batchUpdate 机制

哪些不能命中 batchUpdate 机制

transaction 事务机制

image

常见基础面试题

1.组件之间如何通讯

2.JSX 本质

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

3.shouldComponentUpdate 用途

4.redux单项数据流

Redux 单项数据流图

image

5.setState场景题

image

6.什么是纯函数

7.列表渲染为何要用key

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

9.如何使用异步组件

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

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

上文中有...

12.PureComponent 有何区别

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

14.React性能优化

React 和 Vue 的区别

相同点

不同点

JS 基础 - 变量类型和计算

typeof能判断哪些类型

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 全等通过的话,就说明引用的是同一个地址

基于原型的执行规则

补充知识 - 类型判断 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 的不同应用场景,如何取值?

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.实际开发中闭包的应用场景,举例说明

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

作用域

自由变量

补充知识 - 闭包

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

左右两张图都将打印 100

image

补充知识 - this

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')
上一篇 下一篇

猜你喜欢

热点阅读