2022面试题目总结

153 阅读6分钟

垂直水平居中的几种方式(简单列个4种)

/* flex布局 */
.content {
    diplay: flex;
    justify-content: center;
    align-items: center;
}
/* position定高定宽 */
.content {
    diplay: flex;
    justify-content: center;
    align-items: center;
    
    .inner {
          position: absolute;
          width: 100px;
          height: 60px;
          top: 50%;
          left: 50%;
          margin-top: -30px;
          margin-left: -50px;
    }
}

/* position */
.content {
    diplay: flex;
    justify-content: center;
    align-items: center;
    
    .inner {
        position: absolute;
        width: 100px;
        height: 60px;
        left: 0;
        top: 0;
        right: 0;
        bottom: 0;
        margin: auto;
    }
}
/* transform 没有限定高(有兼容问题) */
.content {
    diplay: flex;
    justify-content: center;
    align-items: center;
    
    .inner {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
    }
}
/* table-cell */
.content {
    position: relative;
    display: table-cell;
    vertical-align: middle;
    text-align: center;
}

在es5上实现const

var __const = function __const(data, value) {
  window.data = value // 把要定义的data挂载到window下,并赋值value
  Object.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持当前对象,并修改其属性描述符
    enumerable: false,
    configurable: false,
    get: function () {
      return value
    },
    set: function (data) {
      if (data !== value) {
      // 当要对当前属性进行赋值时,则抛出错误!
        throw new TypeError('Assignment to constant variable..')
      } else {
        return value
      }
    }
  })
}
__const('a', 10)
// delete a
for (let item in window) { // 因为const定义的属性在global下也是不存在的,所以用到了enumerable: false来模拟这一功能
  if (item === 'a') { // 因为不可枚举,所以不执行
    console.log(window[item])
  }
}
// Vue目前双向绑定的核心实现思路就是利用Object.defineProperty
// 对get跟set进行劫持,监听用户对属性进行调用以及赋值时的具体情况,从而实现的双向绑定~~
console.log("测试this上的const挂载--", this.a)
a = 20 // 报错 Assignment to constant variable..

不使用循环和递归,从一个数组里找出最大值和最小值,考察面试者对call apply的理解

传统答法Math.max.apply(null, arr)答对顺势就是一波call,apply
机智操作Math.max(...arr)

es6、es7、es8、es9、es10你用到的新特性

箭头函数普通函数对比

箭头函数没有原型链,不能作为构造函数 箭头函数没有自己的this对象,它的this是定义时上层作用域中的this 箭头函数的this不能改变,普通函数可以改变

// 模板字符串
console.log(`我的名字是:${name}`);
// 解构赋值
let foo = {
    name: "zhang"
    age: "11"
};
let {name: myname, age: myage} = foo;
conosle.log(myname);

let a = 1;
let b = 3;
[a, b] = [b, a]; 
console.log(a); // 3 
console.log(b); // 1
// 扩展运算符
let arr = [1, 2, 3], arr2 = [3, 4, 5, 6]
console.log([...arr, ...arr2]) // [1,2,3,3,4,5,6]
// set(类似于数组,但是成员的值都是唯一的,没有重复的值)和map(它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,是一种更完善的 Hash 结构实现)
// Set 实例的方法 add delete has clear
// Set 实例属性 Set.prototype.constructor:构造函数,默认就是`Set`函数;
// Set.prototype.size:返回`Set`实例的成员总数。
// set去重数组
console.log([...new Set(arr, arr2)]) // [1,2,3,4,5,6]
// set去重字符串
console.log([...new Set("aabcbc")].join("")) // "abc"

// Promise对象代表一个异步操作 三种状态

说一说js的原型原型链

构造函数new一个实例对象的过程

  • 新建一个空对象 let obj = {}
  • 链接到原型
// 获得构造函数
let con = arguments.__proto__.constructor
// 链接到原型
obj.__proto__ = con.prototype
  • 绑定this,执行构造函数 let res = con.apply(obj, arguments)
  • 返回新对象 typeof res === 'object' ? res : obj

js中栈、堆、队列

变量存在哪里,对象存在哪里

用js实现拖拽,用js画一个动画

扁平化数组转为树

function toTreeData(data, parentId) {
  const result = [];
  let temp = [];
  for (let i = 0; i < data.length; i++) {
    mycount++;
    if (data[i].pid === parentId) {
      const obj = {
        'id': data[i].id,
        'name': data[i].name,
        'pid': data[i].pid,
        'children': []
      };
      temp = toTreeData(data.filter((itemlist) => itemlist.pid !== parentId), data[i].id);
      // temp = toTreeData(data, data[i].id);
      if (temp.length > 0) {
        obj.children = temp;
      }
      result.push(obj);
    }
  }
  return result;
}

// 优化
function toTreeData(data, parentId) {
  const result = [];   // 存放结果集
  const itemMap = {}; 
  for (const item of items) {
    const id = item.id;
    const pid = item.pid;

    if (!itemMap[id]) {
      itemMap[id] = {
        children: [],
      }
    }

    itemMap[id] = {
      ...item,
      children: itemMap[id]['children']
    }

    const treeItem =  itemMap[id];

    if (pid === 0) {
      result.push(treeItem);
    } else {
      if (!itemMap[pid]) {
        itemMap[pid] = {
          children: [],
        }
      }
      itemMap[pid].children.push(treeItem)
    }

  }
  return result;
}

说一说实时搜索(防抖)

闭包,原型链啊,作用域

处理异步操作的几种方式

说一说vue的发布订阅,再手写模拟一下

vue的发布订阅(远距离组件间的通讯)

// 创建一个EventBus (本质上是Vue的一个实例对象)
const EventBus = new Vue()
export default EventBus

// 将EventBus挂载到全局
import bus from "EventBus的文件路径" 
Vue.prototype.bus = bus

// 订阅事件
this.bus.$on('someEvent',func)

// 发布事件
this.bus.$emit('someEvent',params)

react远距离组件间的通讯
EventEmitter这个库,useContext

class EventEmitter {
  constructor() {
    // 单例模式
    if (!EventEmitter.instance) {
      EventEmitter.instance = this
      this.handleMap = {}
    }
    //map结构,用于储存事件与其对应的回调
    return EventEmitter.instance
  }

  //事件订阅,需要接收订阅事件名和对应的回调函数
  on(eventName, callback) {
    this.handleMap[eventName] = this.handleMap[eventName] ?? []
    this.handleMap[eventName].push(callback)
  }

  //事件发布,需要接收发布事件名和对应的参数
  emit(eventName, ...args) {
    if (this.handleMap[eventName]) {
      //这里需要浅拷贝一下handleMap[eventName]
      // 因为在once添加订阅时会修改this.handleMap,若once绑定在前就会导致后一个监听被移除
      const handlers = [...this.handleMap[eventName]]
      handlers.forEach(callback => callback(...args))
    }
  }

  //移除订阅,需要移除的订阅事件名及指定的回调函数
  remove(eventName, callback) {
    const callBacks = this.handleMap[eventName]
    const index = callBacks.indexOf(callback)
    if (index !== -1) {
      callBacks.splice(index, 1)
    }
  }

  //添加单次订阅,触发一次订阅事件后取消订阅,需要添加的订阅事件名及指定的回调函数
  once(eventName, callback) {
    const warpper = (...args) => {
      callback(...args)
      this.remove(eventName, warpper)
    }
    this.on(eventName, warpper)
  }
}

//基础测试
const eventBus = new EventEmitter()
eventBus.once("demo", (params) => { console.log(1, params) })
eventBus.on("demo", (params) => { console.log(2, params) })
eventBus.on("demo", (params) => { console.log(3, params) })
eventBus.emit("demo", "someData")

一个广泛使用的发布订阅类库
省流助手

const EmitterSubscription = require('EmitterSubscription');
    const EventSubscriptionVendor = require('EventSubscriptionVendor');

    const invariant = require('invariant');
    const emptyFunction = require('emptyFunction');

    /**
     * @class BaseEventEmitter
     * @description
     * An EventEmitter is responsible for managing a set of listeners and publishing
     * events to them when it is told that such events happened. In addition to the
     * data for the given event it also sends a event control object which allows
     * the listeners/handlers to prevent the default behavior of the given event.
     *
     * The emitter is designed to be generic enough to support all the different
     * contexts in which one might want to emit events. It is a simple multicast
     * mechanism on top of which extra functionality can be composed. For example, a
     * more advanced emitter may use an EventHolder and EventFactory.
     */
    class BaseEventEmitter {
      /**
       * @constructor
       */
      constructor() {
        this._subscriber = new EventSubscriptionVendor();
        this._currentSubscription = null;
      }

      /**
       * Adds a listener to be invoked when events of the specified type are
       * emitted. An optional calling context may be provided. The data arguments
       * emitted will be passed to the listener function.
       *
       * TODO: Annotate the listener arg's type. This is tricky because listeners
       *       can be invoked with varargs.
       *
       * @param {string} eventType - Name of the event to listen to
       * @param {function} listener - Function to invoke when the specified event is
       *   emitted
       * @param {*} context - Optional context object to use when invoking the
       *   listener
       */
      addListener(
        eventType: string,
        listener,
        context: ?Object,
      ): EmitterSubscription {
        return this._subscriber.addSubscription(
          eventType,
          new EmitterSubscription(this._subscriber, listener, context),
        );
      }

      /**
       * Similar to addListener, except that the listener is removed after it is
       * invoked once.
       *
       * @param {string} eventType - Name of the event to listen to
       * @param {function} listener - Function to invoke only once when the
       *   specified event is emitted
       * @param {*} context - Optional context object to use when invoking the
       *   listener
       */
      once(eventType: string, listener, context: ?Object): EmitterSubscription {
        var emitter = this;
        return this.addListener(eventType, function () {
          emitter.removeCurrentListener();
          listener.apply(context, arguments);
        });
      }

      /**
       * Removes all of the registered listeners, including those registered as
       * listener maps.
       *
       * @param {?string} eventType - Optional name of the event whose registered
       *   listeners to remove
       */
      removeAllListeners(eventType: ?string) {
        this._subscriber.removeAllSubscriptions(eventType);
      }

      /**
       * Provides an API that can be called during an eventing cycle to remove the
       * last listener that was invoked. This allows a developer to provide an event
       * object that can remove the listener (or listener map) during the
       * invocation.
       *
       * If it is called when not inside of an emitting cycle it will throw.
       *
       * @throws {Error} When called not during an eventing cycle
       *
       * @example
       *   var subscription = emitter.addListenerMap({
       *     someEvent: function(data, event) {
       *       console.log(data);
       *       emitter.removeCurrentListener();
       *     }
       *   });
       *
       *   emitter.emit('someEvent', 'abc'); // logs 'abc'
       *   emitter.emit('someEvent', 'def'); // does not log anything
       */
      removeCurrentListener() {
        invariant(
          !!this._currentSubscription,
          'Not in an emitting cycle; there is no current subscription',
        );
        this._subscriber.removeSubscription(this._currentSubscription);
      }

      /**
       * Returns an array of listeners that are currently registered for the given
       * event.
       *
       * @param {string} eventType - Name of the event to query
       * @return {array}
       */
      listeners(eventType: string): Array /* TODO: Array<EventSubscription> */ {
        var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
        return subscriptions
          ? subscriptions
            .filter(emptyFunction.thatReturnsTrue)
            .map(function (subscription) {
              return subscription.listener;
            })
          : [];
      }

      /**
       * Emits an event of the given type with the given data. All handlers of that
       * particular type will be notified.
       *
       * @param {string} eventType - Name of the event to emit
       * @param {*} Arbitrary arguments to be passed to each registered listener
       *
       * @example
       *   emitter.addListener('someEvent', function(message) {
       *     console.log(message);
       *   });
       *
       *   emitter.emit('someEvent', 'abc'); // logs 'abc'
       */
      emit(eventType) {
        var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
        if (subscriptions) {
          var keys = Object.keys(subscriptions);
          for (var ii = 0; ii < keys.length; ii++) {
            var key = keys[ii];
            var subscription = subscriptions[key];
            // The subscription may have been removed during this event loop.
            if (subscription) {
              this._currentSubscription = subscription;
              this.__emitToSubscription.apply(
                this,
                [subscription].concat(Array.prototype.slice.call(arguments)),
              );
            }
          }
          this._currentSubscription = null;
        }
      }

      /**
       * Provides a hook to override how the emitter emits an event to a specific
       * subscription. This allows you to set up logging and error boundaries
       * specific to your environment.
       *
       * @param {EmitterSubscription} subscription
       * @param {string} eventType
       * @param {*} Arbitrary arguments to be passed to each registered listener
       */
      __emitToSubscription(subscription, eventType) {
        var args = Array.prototype.slice.call(arguments, 2);
        subscription.listener.apply(subscription.context, args);
      }
    }

    module.exports = BaseEventEmitter;

v-if和v-for的区别

说一说vue的数据劫持

  • 定义一个监听函数,对对象的每一个属性进行监听
  • 通过Object.defineProperty对监听的每一个属性设置get 和 set 方法。
  • 对对象实行监听
  • 对对象内嵌对象进行处理
  • 对数组对象进行处理

参考:海明月
链接:juejin.cn/post/684490…

什么是JSX

JSX其实是javascript的语法扩展,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可以生成 React “元素”。
使用目的:React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。

说说react的生命周期

讲讲虚拟dom和真实dom的区别

用react的hooks来模拟一下生命周期

react的setState的异步和同步

  • setState不是立马更新的,而是把更改之后的state排入一个队列,在要渲染的时候,批量处理,也就是拿队列里面最后进来的那个state
  • 出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

umi的dva你平时怎么处理数据的

  • 约定式的 model 组织方式,不用手动注册 model
  • 文件名即 namespace,model 内如果没有声明 namespace,会以文件名作为 namespace
  • 内置 dva-loading,直接 connect loading 字段使用即可
  • 支持 immer,通过配置 immer 开启

怎么处理的react优化和组件报错

优化:

  • 避免不必要的渲染,shouldComponentUpdate、React.memo、React.useMemo、React.useCallback。
  • 代码分割,React.lazy 动态加载组件
  • 使用 react-query,对请求响应进行缓存、重发等,避免多次请求,减少网络 IO 消耗及优化渲染次数
  • 使用 useDebounce,对值及事件处理函数进行防抖,避免状态频繁变动,优化渲染次数
  • 使用 useImmer

组件报错:错误边界(Error Boundaries)是一种react组件,可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,不会渲染发生那些发生崩溃的子组件树。错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。

注意
错误边界无法捕获以下场景中产生的错误:

  • 事件处理
  • 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
  • 服务端渲染
  • 它自身抛出来的错误(并非它的子组件)

工作方式:

  • 类似catch{},但是只针对于React组件。只有class组件才可以成为错误边界组件。
  • 错误边界仅能够捕获其子组件的错误,如果没有渲染错误信息,那么它会冒泡到最近的上层错误边界

错误边界组件class

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

性能优化(代码分装,模块化,团队协作的代码注释,cdn,异步加载组件)

讲讲登录的实现过程

讲讲git的常用命令

// 文件1 文件2… 把有变化的文件(新增的、修改的、删除的) 添加到git暂存区里
git add
//  将所有改变的文件统一 加入到暂存区里
git add .
// 文件1 文件2 把暂存区中的文件从暂存区移除
git rm --cached 
//  提价描述
git commit -m
// 文件1 文件 2 … 放弃文件的改变
git restore
// 查看提交历史
git log
// 恢复到指定版本
git reset --hard commitid 

跨域是怎么处理的(json,cors,nginx)

webpack的配置

如何高效的按照顺序打印多个异步操作

说一说你项目中使用的视频打点技术

说一说你项目中用到的流程控制的第三方库

说一说你项目中封装的上传操作

在浏览器地址栏中输入url(建立n次握手,挥手,页面的渲染,重绘重构)