面试整理

说说你都知道哪些排序的算法吧,稍微讲一下的每个算法的执行过程。

1、冒泡排序: 从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素较大则交换位置,重复操作到最后,这样最大值就被放到了数组末尾,下一次循环则只需要循环到length-2的位置

2、插入排序: 默认第一个元素是已排序元素,取出下一个元素和当前元素进行比较,如果当前元素大则就交换位置,则此时当前的最小值是第一个元素,然后下一次取出第三个值向前对比,重复操作

3、选择排序: 遍历数组,设置最小值的索引为0,如果取出的值比当前值小则替换最小值索引,遍历完成后将第一个元素和最小值索引上的值进行交换,如上操作后,下一次从索引1开始重复。

5、快排:快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置。然后将数组以基准值的位置分为两部分,继续递归以上操作。

Array.prototype.quickSort = function() {
    const l = this.length
    if(l < 2) return this
    const basic = this[0], left = [], right = []
    for(let i = 1; i < l; i++) {
      const iv = this[i]
      iv >= basic && right.push(iv) // to avoid repeatly element.
      iv < basic && left.push(iv)
    }
    return left.quickSort().concat(basic, right.quickSort())
}
const arr = [5, 3, 7, 4, 1, 9, 8, 6, 2];

面试官:看来基础掌握的很扎实啊,我看你简历上写的vue和react都用过是吧,那我再问问你框架的问题,来个vue的问题吧,

什么是v-model 它的原理是什么?

基础用法
  • v-model 本质上不过是语法糖,可以用 v-model 指令在表单 、及元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model会忽略所有表单元素的valuecheckedselected` 特性的初始值而总是将 Vue 实例的数据作为数据来源。
  • v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
    • text 和 textarea 元素使用 value 属性和 input 事件;
    • checkbox 和 radio 使用 checked 属性和 change 事件;
    • select 字段将 value 作为 prop 并将 change 作为事件。
实现原理

它主要是有两个操作,一个是根据data属性值来改变表单的值和表单值改变修改data的属性

  • input输入后更新data:

    ​ vue的编译器会编译该html模板文件。将页面上的dom元素生成一个虚拟的dom树,然后递归遍历虚拟dom的每一个节点。匹配到元素的时候会继续遍历其属性,当编理到v-model这个属性的时候会为该节点添加一个input事件,当监听从页面输入值的时候,来更新vue实例中data对应的属性值。

  • data属性值赋值后变更input值:

    ​ 在初始化vue实例的时候,会遍历data的每一个属性,并且通过defineProperty来监听每一个属性的get,set方法,从而一旦某个属性重新赋值,则能监听到变化来操作相应的页面控制。

面试官:看来vue掌握的不错,问你个react的问题

在react中父子组件嵌套,父组件再调用render时会驱动子组件render一起被调用,我们应该如何解决这个问题呢?

poreComponent 纯组件:这种方法也可以通过extends poreComponent来创建一个纯组件,纯组件可以判断自身中是否依赖父元素的状态,如果有则跟着render如果没有依赖则不render,但是纯组件不会判断对象类型数据,如果对象没有变化,但是还会检测到不一致并触发,这个时候我们就需要用到shoudComponentUpdata这个钩子来判断。

shouldComponentUpdata:如下判断返回一个布尔值来控制。

shouldComponentUpdate(nextProps,nextState){
    return nextProps.msg !== this.props.msg
}

但是shouldComponentUpdata 可以检测到值变化,但是不会自动render,

在对于函数式组件的时候我们就需要用到React.memo,因为mome是高阶函数,可以通过包裹函数组件来控制渲染。

你对模块化开发有什么理解

我对模块的理解是,一个模块是实现一个特定功能的一组方法。可以提高代码复用率,方便进行代码的管理。通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前流行的js模块化规范有CommonJS、AMD、CMD以及ES6的模块系统。

可以简单的说一下这四个吗?

CommonJs:

​ 我们常见的node.js就是commonjs规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:moduleexportsrequireglobal。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。

​ commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。

Amd:

​ 这里amd采用的规范和commonJs不同的是它是采取了异步加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行,依赖模块的执行顺序和我们书写的顺序不一定一致。

​ 这里通过define()来定义模块,通过require来加载模块。

Cmd:

​ cmd和amd类似,不同点就是,amd推崇前置依赖、提前执行,cmd推崇就近依赖、延迟执行,只有在用到这个模块的是时候才去加载该模块,它会把模块变成字符串解析一遍,才知道有哪些模块被调用。这里和amd类似是通过define来定义模块,通过seajs来加载模块

ES6 模块化

​ 通过export 和 export default 来导出 ,通过import 来导入,导入的时候用户需要知道所要加载的变量名或函数名。

三种事件模型是什么?

原始事件模型 (DOM0级模型): ,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js属性来指定监听函数。这种方式是所有浏览器都兼容的。

IE 事件模型: 在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。

DOM2 级事件模型: 在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行,true是捕获阶段,false是冒泡阶段。

说一下什么是工厂模式,以及它的优缺点

定义

工厂模式是我们常见的设计模式之一,是属于创建型模式,提供了一种最佳的创建方式,在该模式中我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同接口来指向创新的创建对象。

优点

  • 降低代码的重复率
  • 构造函数和创建者分离, 符合“开闭原则”
  • 调用者创建对象时,不需要知道具体的创建过程,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。

缺点

  • 添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度
  • 考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度

面试题

你知道什么是原型吗 我们为什么要用原型呢 或者说原型为我们提供了什么呢

在es6出现之前并没有引入类的概念 所以之前创建实例的方法是通过构造函数
构造函数通过new来调用 通过new会创建一个实例对象
也就是说new一个构造函数会得到一个实例对象 并且每一次都会将相同的属性挂载到返回的实例上
既然这样为什么不将相同的属性放到同一个地方 让所有的实例都可以访问到呢 这就需要原型
原型为同一个构造函数new出来的实例对象提供了一个公共的区域来存放共同的属性和方法
这样就可以节省一定的内存 特别是需要构造多个实例的时候
举一个简单的例子 实例上有一个name属性和一个sayName方法 sayName的打印结果是根据name打印的 name是传入的参数
如果不使用原型的话就会在每一个构建的实例对象上都挂载这个sayName方法
如果将这个sayName方法挂载到原型上的话 那么每一个实例对象只需要挂载一个name属性即可
当调用sayName方法时会自动到原型上查找这个方法 实现的结果是一样的

那你能说一下构造函数在new的过程中都做了什么吗

首先会创建一个空对象 并将这个对象赋值给this 也就是创建一个this空对象
然后我们就可以通过this.的方式在this这个对象上挂载一些属性和方法
最后将这个this对象返回
注意 这个return是默认返回的 如果认为的return一个什么东西 那么将得不到想要的结果
  • 你了解原型链吗 你能说说prototype与proto的区别吗

    prototype是每个函数独有的属性 proto是每个对象都存在的属性
    prototype主要是为每个实例对象提供一个公共的存储属性和方法的区域 实例对象的proto指向的是构造函数的prototype属性
    proto的话和prototype的作用是一样的 我们平时定义的数组、对象、函数这些引用类型都有proto属性 proto指向的是父级对象上的一些属性
    当访问一个对象上没有的属性时 就会顺着proto这个属性向父级对象上查找 如果没有就会报错
    比如我们平时定义的数组 本身是没有froEach、push、reduce这些方法的 都是从Array身上拿到的
    还有自定义函数的call、apply、bind方法也是从Function这个对象上拿到的
    向这样像链式结构层层向上查找的结构就是原型链
    

看你基础不错 那问你几个关于框架的问题吧

我们为什么要使用vuex redux与vuex有什么区别

我们使用vue的组件在传参时一般是通过props和$emit的方式 当组件的层级很多时 这样的传参方式就显得很吃力
会给我们带来非常多的麻烦 比如多个组件依赖同一状态、不同组件的行为要改变同一状态、由于vue是响应式 依赖同一状态的组件会一起更新 vuex可以很好的解决这些问题
首先vuex是直接可以和vue搭配使用的 redux则需要react-redux这个插件将react和redux融合起来
其次他们的store的分支管理不一样 vuex通过new一个Vuex.Store方法将不同的分支合并到modules上 组件引入store通过store.的方式访问不同的分支数据 redux则是通过combindReducer方法合并分支
另外vue获取vuex里的数据和方法是通过从vuex里解构出mapState和mapAction 然后再用...扩展运算符扩展出来
或者是直接使用this.$store的方式 redux则是通过从react-redux中解构出connect connect是一个高阶函数 通过传入mapState和mapDispatch两个函数将store里面的数据和方法映射到被修饰组件的props中
他们最大的不同就是派发action的方式 vuex是把副作用操作放到了action中 等action的一系列操作结束以后再去提交mutation 这是vuex人性化的地方 而redux则不同 由于redux派发一个action必须返回一个扁平的对象 所以在redux中做一些副作用操作就得使用中间件

你平时用的redux异步处理中间件是什么 怎么运行的

我用过的中间件有thunk和saga 平时用的最多的是saga saga可以更好的统一管理action
saga使用的是es6的generator函数
首先要先引入redux-saga 然后创建一个sagaMiddleWare 在创建store之后调用run方法来检测全局的中间件
当组件派发一个action后 saga通过takeEvery进行拦截 根据action.type来判断执行哪一个副作用函数
可以从radux-saga/effects中解构出call和put方法 call方法可以进行异步请求数据 put方法则派发一个带有type属性的扁平对象给reducer处理 reducer再通过type属性来进行数据修改

你平时使用hook吗 什么情况下会使用hook 或者说我们为什么要使用hook

我们在写项目的时候喜欢将一个组件分成容器组件和展示组件
容器组件主要是获取store里面的数据和方法
展示组件是一个无状态的函数 通过props来接受容器组件传递的参数
这样可以更好的理解和维护代码 组件的可复用性也更高
但由于是一个函数组件 所以无法使用this的setState方法来修改自己独有的数据
hooks主要是解决这个问题
react提供了useState、useEffect两个hook函数来创建stack hook和effect hook
useState的参数为初始状态 可以用数组解构出一个状态以及对应的改变状态的方法
useEffect允许我们添加一些副作用逻辑 它有两个参数 第一个参数是异步函数 第二个参数是一个数组
通过数组里面值的变化来决定是否执行异步函数
如果不传第二个参数且没有修改状态 或者是第二个参数是一个空数组的话 那么函数只执行一次
如果不传第二个参数又修改了状态 则会死循环

原型与原型链

构造函数

在es6出现之前并没有引入类的概念 所以之前创建实例的方法是通过构造函数

写法

构造函数与普通函数并没有什么区别 所以我们会用大驼峰的方法创建构造函数 用来区分

new

构造函数通过new来调用 通过new会创建一个实例对象

this

通过new一个构造函数 在内部发生了以下过程

  • 创建一个this空对象

  • 人为的(this.name = ‘zhangsan’)将构造函数里的变量和方法放到this里

  • 最后返回this

也就是说new一个构造函数会得到一个实例对象 并且每一次都会将相同的属性挂载到返回的实例上

既然这样为什么不将相同的属性放到同一个地方 让所有的实例都可以访问到呢 这就需要原型

原型为同一个构造函数new出来的实例对象提供了一个公共的区域来存放共同的属性和方法

原型

prototype
  • 函数独有 每一个函数都有一个prototype属性
  • 作用是让该函数构造的实例对象可以找到公共的属性和方法
proto
  • proto 每一个对象都有proto属性

  • 当访问一个对象的属性时 如果这个对象没有这个属性就会到proto这个属性上找

  • proto属性指向的是父对象的prototype

    • 例如function的父对象是object object的父对象是null
    • null是原型链的顶端 null不存在proto属性
  • 像这样层层向上访问原型形成链式结构的就是原型链

redux vuex

当不同的组件需要共享数据的时候 我们就需要使用状态管理

vuex

  • 当不同组件共享状态时 单向数据流就会显得很吃力 多层嵌套的组件之间传参会非常繁琐

    • 多个组件依赖同一状态
    • 不同组件的行为要改变同一状态
    • 响应式 一个组件改变状态 其他组件也会跟着改变
  • 组件派发一个action

  • action提交一个mutation

    • action可以进行异步操作
  • mutation改变一个状态

    • mutation只能是同步操作
  • 状态改变更新渲染组件

    • state拥有module属性 可以创建子分支
  • 子组件通过从vuex中结构出mapState等映射到自己的方法中

redux

  • 组件派发一个action
  • action传到reducer
    • action必须是一个扁平的对象
    • 可以添加中间件来进行异步操作
      • redux-thunk
      • redux-saga
  • reducer更改状态
  • 状态改变更新渲染组件
  • redux并不能直接响应react 需要通过react-redux插件建立连接
    • 通过从react-redux中结构出connect将react与redux连接起来
    • connect是一个高阶函数 用于增强组件的功能

saga thunk

redux中的action仅支持原始对象(plain object)处理有副作用的action 需要使用中间件

中间件可以在发出action 到reducer函数接受action之间 执行具有副作用的操作

thunk

  • 其实thunk是一个三层函数 在thunk的第三层函数中判断action是一个函数还是一个扁平的对象

  • 如果是函数 返回这个函数(可以是执行异步操作)执行的结果 这个结果应该是一个扁平的对象

  • 如果是一个对象 直接next放走 交给reducer

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
缺点
  • action形式不统一
  • 异步操作分散在了各个action中

saga

  • saga使用的是es6的generator
import { call , put , takeEvery } from 'redux-saga/effects'
// generator函数
function * getProduct () {
  // 通过call方法执行异步操作
  const data = yield call()
  // 通过put方法提交给reducer一个扁平的action
  yield put({
    type: 'GET_PRODUCT',
    data
  })
}
function * sagas () {
	  // 拦截redux派发的action 
    yield takeEvery('SAGA_GET_PRODUCT' , getProduct)
}

export default sagas
  • 将所有的异步函数集中在了一起
  • action就是一个扁平的对象 带有type属性的对象
  • 每一个分支都可以有自己的saga 然后合并到主干的saga

hook

  • 为了结构清晰 react可以分为容器组件和函数组件(展示组件)

    • 通过分离可以更好的理解和维护代码
    • 可复用性更好 同一个函数组件可用于不同的数据源来展示不同的页面
  • 容器组件负责请求数据和数据更新 函数组件负责渲染数据 容器组件通过自定义属性的方式将数据传到函数组件中的props里

  • 容器组件通过react-redux的connect装饰器连接store 然后在componentDidMount这个生命周期里dispatch派发方法来更新store里的状态

  • 有时候函数组件里可能需要使用自己的一些状态 但由于函数组件没有this 无法使用setState这个方法 达不到响应的效果 所以我们就需要使用hooks

    • react提供了useState、useEffect两个hook函数来创建stack hook和effect hook

      • useState的参数为初始状态 可以用数组结构出一个状态以及改变状态的方法

        function Example() {
          const [count, setCount] = useState(0);
        
          return (
            <div>
              <p>You clicked {count} times</p>
        			// setCount里面可以直接写值 或者表达式 或者写一个函数
              <button onClick={() => setCount(count + 1)}>
                Click me
              </button>
            </div>
          );
        }
        
        

        由于react的状态管理机制是根据旧的状态改变新的状态 不会改变旧的状态

        所以 在使用set方法时最好是写一个回调函数 这样比较符合react的机制

      • useEffect可以给组件添加一些副作用逻辑

        • 接受两个参数 第一个参数是副作用函数 第二个是判断是否执行副作用函数的数组
          • 如果不传第二个参数
            • 并且不做修改状态的事 那么函数只会执行一次
            • 如果修改了状态 那么函数会无限循环
          • 如果有第二个参数
            • 如果参数是一个空数组 那么函数只会执行一次
            • 如果数组有值 那么函数会根据数组里面值的变化来判断是否执行函数
      • 同时我们也可以自定义一个hook函数

        • 自定义hook要用小驼峰命名 并且必须使用use开头

        • 其实自定义hook函数的内部是在使用了hook的基础上多增加了一些方法 最后将状态和方法返回

          • 这里的方法要用一个对象包裹

          • 最终的返回结果是一个数组 第一个参数是状态 第二个参数是一个对象 对象里面是改变状态的方法

            import { useState } from 'react'
            
            function useCount(param) {
              let [count, setCount] = useState(param)
            
              // 可以写一个参数
              function add(num) {
                // 或者不写参数 直接写死
                // setCount(count => count + 1)
                setCount(count => count + num)
              }
            
              function minus(num) {
                setCount(count => count - num)
              }
              
              return [count, { add, minus }]
            }
            
            export default useCount
            
            
            let [count, setCount] = useCount(1)
              let handleAddClick = useCallback(() => {
                setCount.add(1)
              }, [])
            
            
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值