前端程序员面试题分享

文章目录


前言

        汇总了前端程序员的常见面试题,仅供交流学习。

一、关于React生命周期的常见的坑,以及避免踩坑的方法

        要避免生命周期的坑,就需要先了解React有那些生命周期?在React的不同版本生命周期的钩子函数也大同小异。React的生命周期分为三个挂载、更新、销毁阶段,不同的阶段触发不用的钩子函数。

        首先附上一张 react生命周期的梳理图   供大家参考

        

      ·React 15生命周期 

                组件的初始化选软(挂载阶段)

                

                 组件更新阶段

        

                 组件销毁阶段        

     在这里给大家提供一个 大神制作的 在线查看react16.3及16.4生命周期的网站                         在线查看react生命周期

上面介绍了在不同版本的生命周期,那在生命周期中有那些坑了。开篇提到,出现坑就是在:

  • 在不恰当的时机调用了不合适的代码
  • 在需要调用的时候,没有调用

那避免这些坑就是

  • 不在恰当的时机调用不合适的代码
  • 在需要调用的时候,去正确调用。

函数组件的无效触发

函数组件是一种无状态的组件,无生命周期,它在任何情况下都会被触发

        下面进行代码演示

        

import React from "react";
import ReactDom from "react-dom";

function TestComponent(props) {
  console.log("我重新渲染了");
  return <div>函数组件:1</div>;
}

class LifeCycle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "这个子组件文本",
      message: "这个是子组件消息"
    };
  }
  changeText = () => {
    this.setState({
      text: "修改后的子组件文本"
    });
  };
  changeMessage = () => {
    this.setState({
      text: "修改后的子组件消息"
    });
  };
  render() {
    return (
      <div className="container">
        <button onClick={this.changeText} className="changeText">
          修改子组件文本内容
        </button>
        <button onClick={this.changeMessage} className="changeText">
          修改子组件消息
        </button>
        <p>{this.state.text}</p>
        <p>{this.state.message}</p>
        <p className="textContent">
          <TestComponent />
        </p>
      </div>
    );
  }
}

class LifeCycelContainer extends React.Component {
  state = {
    text: "父组件文本",
    message: "父组件消息"
  };
  changeText = () => {
    this.setState({
      text: "修改后的父组件文本"
    });
  };
  changeMessage = () => {
    this.setState({
      message: "修改后的父组件消息"
    });
  };
  render() {
    return (
      <div className="fatherContainer">
        <button onClick={this.changeText} className="changeText">
          修改父组件文本
        </button>
        <button onClick={this.changeText} className="changeText">
          修改父组件消息
        </button>
        <p>{this.state.text}</p>
        <p>{this.state.message}</p>
        <LifeCycle />
      </div>
    );
  }
}

ReactDom.render(<LifeCycelContainer />, document.getElementById("root"));
 

函数组件任何情况下都会重新渲染。它并没有生命周期,但官方提供了一种方式优化手段,那就是 React.memo。React.memo 并不是阻断渲染,而是跳过渲染组件的操作并直接复用最近一次渲染的结果,这与 shouldComponentUpdate 是完全不同的。 

React.Component的无效触发

        定义shouldComponentUpdate函数来避免无效的触发

React.PureComponent谨慎使用

        React.PureComponent 与 React.Component 几乎完全相同,但 React.PureComponent 通过prop和state的浅对比来实现 shouldComponentUpate()。

如果React组件的 render() 函数在给定相同的props和state下渲染为相同的结果,在某些场景下你可以使用 React.PureComponent 来提升性能。

React.PureComponent 的 shouldComponentUpdate() 只会对对象进行浅对比。如果对象包含复杂的数据结构,它可能会因深层的数据不一致而产生错误的否定判断(表现为对象深层的数据已改变视图却没有更新, 原文:false-negatives)。当你期望只拥有简单的props和state时,才去继承 PureComponent ,或者在你知道深层的数据结构已经发生改变时使用 forceUpate() 。或者,考虑使用 不可变对象 来促进嵌套数据的快速比较。

二、 React 中 setState做了那些事情?

        

  setState 是 React 框架最常用的命令,它是用来更新状态的,这也是 React 框架划时代的功能。 但是 setState 函数是 react 包导出的,他们又是如何与 react-dom react-native react-art 这些包结合的呢

  setState 函数是在 React.Component 组件中调用的,所以最自然的联想是,更新 DOM 的逻辑在 react 包中实现。

        但是 react 却可以和 react-dom react-native react-art 这些包打配合,甚至与 react-dom/server 配合在服务端运行,那可以肯定 react 包中不含有 DOM 更新逻辑。

所以可以推断,平台相关的 UI 更新逻辑分布在平台相关的包里,react 包只做了代理。

        在react中,setstate是用于更新组件状态state的方法;setState()将对组件state的更改排入队列,并通知React需要使用更新后的state重新渲染此组件及其子组件,语法为“setState(对象,[回调函数])”

        setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式.

将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。

三、redux的实现原理是什么?

        为什么使用redux?

                我们都知道在React中,数据在组件中是单项绑定的。父组件向子组件传递数据可以通过props,但是兄弟组件之间传递数据就比较麻烦。redux 就可以解决这一问题。

         redux的设计理念

                redux 是将整个应用的state存储在一个公共的store文件当中,组件可以通过分发(dispatch)一个动作或者是行为(action)给这个公用的store,而不是直接去通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。这里我个人对的理解是,在我们的组件内部有个类似于监听器的东西,一旦监听到store中的值发生了改变就会刷新我们的页面。

        三大原则

                唯一数据源

                        整个应用的数据存储在一个统一的状态树中,也就是我们前面所说的公共的store 文件。在组件都会从这个store中获取数据。

                保持只读状态

                        state是只读的,唯一改变state的方法就是触发action,action是一个用于描述以发生时间的普通对象。

                数据改变只能通过纯函数来执行

                        使用纯函数来执行修改,为了描述action如何改变state的,你需要编写reducers。

        原理

                store

                        store就是保存数据的地方,你可以把它看成一个数据,而且整个应用能有一个store。redux提供了createStore这个函数,用来生成store。

                state

                        state就是store中存储的数据,store里面可以拥有多个state,Redux规定一个state对应一个View,只要state相同,view就是一样的,反过来也是一样的,可以通过store.getState( )获取。

                action

                        state的改变会导致View的变化,上面我们说过redux中不能直接通过this.setState()修改state,为了使state发生改变,在redux中提供了一个对象Action,我们可以理解为一个行为或者是动作,也就是说这个action可以改变state,而且也是改变state的唯一方法。

                store.dispatch()

                        store.dispatch( )是view发出Action的唯一办法,这里解释一下,在view中,用户触发一个行为或者简单理解点击某一个按钮,这时候如果需要修改state值,就需要触发action,而store.dispatch接收一个Action作为参数,将它发送给store通知store来改变state。

                reducer

                        Store收到Action以后,必须给出一个新的state,这样view才会发生变化。这种state的计算过程就叫做Reducer。

                        Reducer是一个纯函数,他接收Action和当前state作为参数,返回一个新的state。

四、React合成事件的原理?  

  • react自身实现了一套事件机制,包括事件的注册、事件的存储、事件的合成及执行等。
  • react 的所有事件并没有绑定到具体的dom节点上而是绑定在了document 上,然后由统一的事件处理程序来派发执行。
  • 通过这种处理,减少了事件注册的次数,另外react还在事件合成过程中,对不同浏览器的事件进行了封装处理,抹平浏览器之间的事件差异。

        对合成事件的理解

                1.对原生事件的封装

                        react会根据原生事件类型来使用不同的合成事件对象,比如: 聚焦合成事件对象SyntheticFoucsEvent(合成事件对象:SyntheticEvent是react合成事件的基类,定义了合成事件的基础公共属性和方法。合成事件对象就是在该基类上创建的)

                2.不同浏览器事件兼容的处理

                        在对事件进行合成时,react针对不同的浏览器,也进行了事件的兼容处理

       合成事件、原生事件之间的冒泡执行关系

                原生事件阻止冒泡肯定会阻止合成事件的触发。   

                合成事件的阻止冒泡不会影响原生事件。

                原因

                        浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段

                        节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡,所以原生事件阻止冒泡会阻止合成事件的触发,而合成事件的阻止冒泡不会影响原生事件。

五、为什么react元素有一个$$type属性?

        这个属性的引入,其实要从一个安全漏洞说起。

        因为如果我们让用户输入评论等操作  让用户拥有了一个可操纵的变量,这时候用户就已经有能力进行xss攻击,将一些代码放到我们程序中,影响程序的正常执行

        但其实,在react 0.13 时就存在这个问题,0.14版本进行了修复,修复方式就是通过引入$$typeof属性,并且用Symbol来作为它的值。

        这是一个有效的方法,因为JSON是不支持Symbol类型的。所以,即使用户提交了如上的message信息,到最后服务端也不会保存$$typeof属性。而在渲染的时候,React 会检测是否有$$typeof属性。如果没有这个属性,则拒绝处理该元素。

        那么如果浏览器不支持Symbol怎么办?

        是的,那这种保护方案就没用了。React 依然会加上$$typeof字段,并且将其值设置为0xeac7。(为什么是这个数字呢,因为这个数字看起来有点像React)。

六、说说对fiber架构的理解?它解决了什么问题?

             首先先提起js执行的一些问题

      JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待

                如果 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿

                而这也正是 React 15 的 Stack Reconciler所面临的问题,当 React在渲染组件时,从开始到渲染完成整个过程是一气呵成的,无法中断

                如果组件较大,那么js线程会一直执行,然后等到整棵VDOM树计算完成后,才会交给渲染的线程

这就会导致一些用户交互、动画等任务无法立即得到处理,导致卡顿的情况

             React Fiber

                        

                      React Fiber 是 Facebook 花费两年余时间对 React 做出的一个重大改变与优化,是对 React 核心算法的一次重新实现。从Facebook在 React Conf 2017 会议上确认,React Fiber 在React 16 版本发布

react中,主要做了以下的操作:

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
  • dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行

                从架构角度来看,Fiber 是对 React核心算法(即调和过程)的重写

                从编码角度来看,Fiber是 React内部所定义的一种数据结构,它是 Fiber树结构的节点单位,也就是 React 16 新架构下的虚拟DOM

     Fiber把渲染更新过程拆分成多个子任务,每次只做一小部分,做完看是否还有剩余时间,如果有继续下一个任务;如果没有,挂起当前任务,将时间控制权交给主线程,等主线程不忙的时候在继续执行

                即可以中断与恢复,恢复后也可以复用之前的中间状态,并给不同的任务赋予不同的优先级,其中每个任务更新单元为 React Element 对应的 Fiber节点

                实现的上述方式的是requestIdleCallback方法

      window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应

                首先 React 中任务切割为多个步骤,分批完成。在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间再进行页面的渲染。等浏览器忙完之后有剩余时间,再继续之前 React 未完成的任务,是一种合作式调度。

                该实现过程是基于 Fiber节点实现,作为静态的数据结构来说,每个 Fiber 节点对应一个 React element,保存了该组件的类型(函数组件/类组件/原生组件等等)、对应的 DOM 节点等信息。

                作为动态的工作单元来说,每个 Fiber 节点保存了本次更新中该组件改变的状态、要执行的工作。

七、说说你对事件循环event loop的理解?

                事件循环,是指浏览器或Node的一种解决JavaScript单线程运行时不会阻塞的一种机制,也就是实现异步的原理。作为一种单线程语言,JavaScript本身是没有异步这一说法的,是由其宿主环境提供的。

                JavaScript代码运行时,任务分为宏任务和微任务两种,Event Loop也将任务队列分为宏队列和微队列分别管理宏任务和微任务,宏队列和微队列也具备队列特性:先进先出。

    事件循环模型           

  1. 选择当前要执行的宏任务队列,选择一个最先进任务队列的宏任务,如果没有宏任务,则会跳转至微任务的执行步骤。
  2. 将事件循环的当前运行宏任务设置为已选择的宏任务。
  3. 运行宏任务。
  4. 将事件循环的当前运行任务设置为null。
  5. 将运行完的宏任务从宏任务队列中移除。
  6. 微任务步骤:进入微任务检查点。
  7. 更新界面渲染。
  8. 返回第一步。
    只要主线程空了,就会读取任务队列,这就是js的运行机制,也被称为EventLoop机制。
    在同一个上下文中,总的执行顺序:同步代码→微任务→宏任务

八、前端跨域的解决方案

                1、JSONP跨域

                  jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

                        

 <script>
    var script = document.createElement('script');
    script.type = 'text/javascript';
 
    // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);
 
    // 回调执行函数
    function handleCallback(res) {
        alert(JSON.stringify(res));
    }
 </script>

                2 jquery Ajax实现:

$.ajax({
    url: 'http://www.domain2.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "handleCallback",  // 自定义回调函数名
    data: {}
});

                        

               3.接口代理 :

        


const {createProxyMiddleware} = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(createProxyMiddleware('/api', { 
    target: 'http://xxx', //转发到的域名或者ip地址
    pathRewrite: {
      '^/api': '', //接口地址里没有"/api",将其重写置空
    },
    changeOrigin: true, //必须设置为true
    secure: false //是否验证https的安全证书,如果域名是https需要配置此项
  }));
};

九、for...in循环和for...of循环的区别

        for...infor...of都是JavaScript遍历数据的方法,让我们来了解一下他们的区别。

        for...in:

                for...in是为遍历对象属性而构建的,它以任意顺序遍历一个对象的除Symbol以外的可枚举属性,可用break或者throw跳出

                在JavaScript中,数组也是对象的一种,所以数组也是可以使用for...in遍历

        for...of:

                for...of语句在可迭代对象上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句(包括ArrayMapSetStringTypedArrayarguments等等,不包括Object),可用break或者throw跳出。

       区别:

                

                无论是for...in还是for...of都是迭代一些东西。它们之间的主要区别在于它们的迭代方式

                for...in语句以任意顺序迭代对象的可枚举属性

      for...of语句遍历可迭代对象定义要迭代的数据

十、Js数据类型判断都有哪几种方式?它们的区别是什么?

        

JavaScript有五种数据判断类型方法:

  • typeof
  • instanceof
  • constructor
  • Object.prototype.toString.call()
  • jquery.type()

        

一、typeof方法:

        1、可以判断数据类型,它返回表示数据类型的字符串(返回结果只能包括number,boolean,string,function,object,undefined);
        2、可以使用typeof判断变量是否存在(如if(typeof a!=“undefined”){…});
        3、Typeof 运算符的问题是无论引用的对象是什么类型 它都返回object

二、instanceof方法:

        一般用来检测引用数据类型,表达式为:A instanceof B,判断A是否是B的实例,如果 A 是 B 的实例,则返回 true,否则返回 false,由构造类型判断出数据类型

三、constructor方法:

        constructor是prototype对象上的属性,指向构造函数,

        注意:除了undefined和null之外,其他类型都可以通过constructor属性来判断类型

四、Object.prototype.toString 方法:
        用来检测对象类型

        所有数据类型的父类都是Object, toString为Object的原型prototype的方法,返回格式为[object xxx],其中Object对象返回的是[Object object],其他类型需通过call/apply来调用
Object.prototype.tostring.call();

        使用Object.prototype.toString.call()的方式来判断一个变量的类型是最准确的方法。

五、无敌万能的方法:jquery.type()
        如果对象是undefined或null,则返回相应的“undefined”或“null”。如果对象有一个内部的[[Class]]和一个浏览器的内置对象的 [[Class]] 相同,我们返回相应的 [[Class]] 名字其他一切都将返回它的类型“object”。

十一、说说你对Object.defineProperty()的理解?

        

        Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

        该方法接受三个参数:
         第一个参数是 obj:要定义属性的对象、
         第二个参数是 prop:要定义或修改的属性的名称或 Symbol
         第三个参数是 descriptor:要定义或修改的属性描述符。

        

        虽然可以直接添加属性和值,但是使用这种方式,可以进行更多的配置。

        函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符。
        数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的;
        存取描述符是由 getter 函数和 setter 函数所描述的属性。
        一个描述符只能是这两者其中之一,不能同时是两者。

        

这两种同时拥有下列两种键值:

  • configurable:是否可以删除目标属性或是否可以再次修改属性的特性(writable、configurable、enumerable)。设置为 true 可以被删除或可以重新设置特性;设置为 false,不能被删除或不可以重新设置特性。默认值为 false
  • enumerable:当且仅当该属性的 enumerable 键值为 true 时,该属性才会在对象的枚举属性中。默认为 false。

数据描述符还具有以下可选键值:

  • value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为undefined。
  • writable:当且仅当该属性的 writable 键值为true时,属性的值,也就是value,才能被赋值运算符改变。默认为false。

存取描述符还具有以下可选键值:

  • get:属性的 getter 函数,如果没有 getter,则为 undefined。 当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的 this 并不一定是定义该属性的对象)。该属性的返回值会被用作属性的值。默认为 undefined。
  • set:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。刚方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值