JS和构建工具面试题

116 阅读18分钟

0.null和undefined区别

  • null表示"没有对象",即该处不应该有值,典型用法是: (1) 作为函数的参数,表示该函数的参数不是对象。 (2) 作为对象原型链的终点。
  • undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是: (1)变量被声明了,但没有赋值时,就等于undefined。 (2 ) 调用函数时,应该提供的参数没有提供,该参数等于undefined。 (3)对象没有赋值的属性,该属性的值为undefined。 (4)函数没有返回值时或者return后面什么也没有,返回undefined

1.闭包

  • 闭包就是能够读取其他函数内部变量的函数
  • 可以把闭包简单理解成“定义在一 个函数内部的函数”。
  • 不能滥用闭包,会造成网页的性能问题。 IE 中可能导致内存泄露

2.call和apply的区别

  • call改变 this 的指向,第一个参数是 this 的指向,从第二个参数开始是以 逗号分隔的不定参数,立即执行
  • apply 改变 this 的指向,第一个参数是 this 的指向,第二个参数是数组, 立即执行

3.cookies,sessionStorage,localStorage 的区别?

  • 存储大小限制也不同,cookie 数据不能超过 4K,同时因为每次 http 请求 都会携 带 cookie
  • 数据有效期不同sessionStorage当前浏览器窗口关闭之前有效; localStorage始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据; cookie:只在设置的 cookie 过期时间之前有效,即使窗口关闭或浏览器关闭
  • 作用域不同sessionStorage 不在不同的浏览器窗口中共享,即使是同一 个页面;localStorage所有同源窗口中都是共享的;cookie 也是在所有同源窗口中都是共享的

4.浏览器地址栏输入url 发生了什么

  1. URL 解析
  2. DNS 查询
  3. TCP 连接
  4. 处理请求
  5. 接受响应
  6. 渲染页面

5.构造函数和原型函数

[prototype]  zhuanlan.zhihu.com/p/213022502 

  • 通过new操作符来调用的,就是构造函数
  • 函数对象的prototype指向原型对象,原型对象的constructor指向函数对象
  • 实例对象的[Protoptype]属性指向原型对象

6.浏览器适配

  • CSS前缀:不同浏览器厂商可能对CSS特性的实现不一致,需要使用相应的CSS前缀来适配。例如,-webkit-前缀适用于Safari和Chrome-moz-前缀适用于Firefox-o-前缀适用于Opera
  • JavaScript特性:不同浏览器对JavaScript特性的实现也可能有差异,需要检测特性是否被支持并提供替代方案。例如,某些浏览器不支持ES6语法,需要使用Babel等工具将代码转换为ES5语法
  • 浏览器兼容性:需要考虑不同浏览器的版本,不同版本可能会有不同的特性支持和bug修复。可以使用Can I Use等网站查询特定浏览器版本的支持情况。

7.ES6 Map与Set

  • Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

  • Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

  • []: www.runoob.com/w3cnote/es6…

  • Map

    • key可以是字符串、对象、函数、NaN

      var myMap = new Map();
      var keyString = "a string"; 
       
      myMap.set(keyString, "和键'a string'关联的值");
       
      myMap.get(keyString);    // "和键'a string'关联的值"
      myMap.get("a string");   // "和键'a string'关联的值"
                               // 因为 keyString === 'a string'
      
    • Map的迭代

      • 1.for...of
      for (var [key, value] of myMap) {
        console.log(key + " = " + value);
      }
      for (var [key, value] of myMap.entries()) {
        console.log(key + " = " + value);
      }
      /* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */
      for (var key of myMap.keys()) {
        console.log(key);
      }
      for (var value of myMap.values()) {
        console.log(value);
      }
      
    • Map的迭代

      • forEach()

        myMap.forEach(function(value, key) {
            console.log(key + " = " + value);
        }, myMap)
        
  • Set
let mySet = new Set();
       
mySet.add(1); // Set(1) {1}
mySet.add(5); // Set(2) {1, 5}
mySet.add(5); // Set(2) {1, 5} 这里体现了值的唯一性
mySet.add("some text"); 
// Set(3) {1, 5, "some text"} 这里体现了类型的多样性
var o = {a: 1, b: 2}; 
mySet.add(o);
mySet.add({a: 1, b: 2}); 
// Set(5) {1, 5, "some text", {…}, {…}} 
// 这里体现了对象之间引用不同不恒等,即使值相同,Set 也能存储

8.async和await

  1. async/awaitpromise的语法糖,作用是为了让Promise更加完善,让代码看上去更加同步
  2. promise.allpromise.race是为了解决多个解决(异步的结果)
  3. async就是为了标记 function ,其它没啥用
  4. 所有async都会返回一个promise
  5. await只能放在async函数里面
  6. 错误捕获使用try...catch...

10.ES6的新特性

  • 1.不一样的变量声明:const和let
  • 2.模板字符串
  • 3.箭头函数(Arrow Functions)
  • 4.函数的参数默认值
  • 5.对象和数组解构

11.前端性能优化

  • 优化模块

    • 减少HTTP请求
    • 白屏时间做加载动画
  • 资源

    • 静态资源cdn

      静态css/js/img等资源可以做cdn缓存,这样把资源同步到全国全球各地,用户就能更快访问到

    • gzip压缩

      服务端配置,如nginx可配置支持gzip压缩资源传输的方式

    • 将CSS放在文件头部,JavaScript文件放在底部

      单线程js可能会阻滞文档加载

  • 图片

    • 字体图标代替图片图标

    • 精灵图

    • 图片懒加载

      为了首屏渲染更快,图片可设置一张加载图代替,当页面在可视区域内时在替换为正真的图片

    • 图片预加载

      可以在window.onload之后请求一些其他地方需要的图片资源

    • 小于10k的图片可以打包为base64格式

      可以使用webpack url-loader处理

  • 代码

    • 慎用全局变量

    • 缓存全局变量

      将使用中无法避免的全局变量缓存到局部

    • 节流、防抖

    • 少用闭包、减少内存泄漏

  • webpack优化

    • 减小代码体积
    • 按需加载
    • 提取第三方库

12.for in与for of的区别

  • 主要区别在于迭代对象迭代方式

    1.迭代对象:

  • for-in循环用于迭代对象的可枚属性。它会迭代对象的所有可举属性,包括继承的属性。它适用于迭代普通对象和数组等可迭代对象。但需要注意的是,for-in循环可能会遍历到一些非预期的属性,例如原型链上的属性和一些内置属性

  • for-of循环用于迭代可迭代对象(例如数组、字符串、Set、Map等)。它会迭代对象的迭代器的返回值。for-of循环不会迭代对象的属性,只能迭代实际的值。它遍历的是对象自身的属性,不包括继承的属性

    2.迭代方式

  • for-in循环通过遍历对象的键来进行迭代。在每次迭代中,变量会被赋值为当前迭代的键

  • for-of循环通过遍历对象的值来进行迭代。在每次迭代中,变量会被赋值为当前迭代的值

12.1 forEach和map的区别

  • 都是循环遍历数组中的每一项
  • 每一次执行匿名函数都支持三个参数,数组中的当前项item,当前项的索引index,原始数组input
  • 匿名函数中的this都是指window
  • map

    • 有返回值,可以return出来一个length和原数组一致的数组(内容可能包含undefined、null等)
  • forEach

    • 被调用时,不会改变原数组,也就是调用它的数组(尽管 callback 函数在被调用时可能会改变原数组)
    • forEach没有返回值,返回结果为undefined

13.什么是前端跨域问题以及如何解决跨域问题

  • 什么是前端的跨域问题

    • 浏览器的同源策略限制了网页不能加载其他源(域名、协议、端口)的资源
  • 同源策略的限制包括以下几个方面:

    1. Cookie、LocalStorage 和 IndexDB 等存储机制。不同站点的 Cookie、LocalStorage、IndexDB 是独立的,无法相互获取。
    2. DOM 和 JS 对象。JS脚本只能访问同源页面的对象和方法,而不能访问不同源的对象和方法。
    3. AJAX 请求。AJAX请求的限制也是基于同源策略的。使用XHR对象发送AJAX请求时,浏览器会根据请求URL的源与当前页面的源是否相同来决定是否发送该请求。
  • 如何解决跨域

    • 使用JSONP(JSON with Padding)技术。JSONP是一种跨域访问资源的方式,它利用了script标签不受同源策略限制的特性,将JSON数据作为参数传递到一个回调函数中,并将该函数作为一个脚本动态插入到页面中。
    • 使用CORS(Cross-origin Resource Sharing)技术。该技术通过在原生XHR对象上使用标准的HTTP头部来允许浏览器和服务器进行跨源通信,从而实现安全的跨域数据传输。
    • 使用代理。将跨域请求发送到同源的服务器上,由该服务器向目标服务器请求数据,并将数据返回给页面,从而避免了跨域问题。

14.GET和POST有什么区别

  • 数据传输方式不同:GET 请求通过 URL 传输数据,而 POST 的数据通 过请求体传输
  • 安全性不同:POST 的数据因为在请求主体内,所以有一定的安全性保 证,而 GET 的数据在 URL 中,通过历史记录,缓存很容易查到数据信息。
  • 数据类型不同:GET 只允许 ASCII 字符,而 POST 无限制
  • 特性不同:GET 是安全(这里的安全是指只读特性,就是使用这个方法 不会引起服务器状态变化)且幂等(幂等的概念是指同一个请求方法执行多次和 仅执行一次的效果完全相同),而 POST 是非安全非幂等

15.原型和原型链

  • 原型:所有的函数默认都有一个“prototype”这样公有且不可枚举的属性,它会指向另一个对象,这个对象就是原型
  • 原型链:当访问对象的属性或方法时,首先对象会从自身去找,找不到就会往原型中去找,也就是它构造函数的“prototype”中,如果原型中找不到,即构造函数中也没有该属性,就会往原型后面的原型上去找, 这样逐层深入直到顶层对象Object的原型对象 ,就形成了链式的结构,称为原型链(prototype chain)

16.JS事件循环

  • 同一层级同一个任务体里面,先执行同步代码然后微任务最后才是宏任务,但执行宏任务的时候需要观察其他任务体里面的同步代码和微任务,如果有要先执行
  • new Promise()属于宏任务,而new Promise().then则属于微任务;setTimeout属于宏任务
  • 1.JS是从上到下一行一行执行。
  • 2.如果某一行执行报错,则停止执行下面的代码。
  • 3.先执行同步代码,再执行异步代码
//下面的代码中  setTimeout受第二个参数(时间)的影响
<script>
    setTimeout(() => {
      console.log("定时器1");//执行顺序6  同步代码
      new Promise((resolve) => {
        console.log("promsie3"); //执行顺序7 宏任务
        resolve()
      }).then(() => {
        console.log("promise3.then");//执行顺序8 微任务
      })
      setTimeout(() => {
        console.log("定时器3"); //执行顺序10 宏任务
      }, 0)
    }, 0)
​
    new Promise((resolve) => {
      console.log("promise1"); //执行顺序1 同步代码
      resolve()
    }).then(() => {
      console.log("promise1.then");//执行顺序3 微任务
      setTimeout(() => {
        console.log("定时器2");//执行顺序9 宏任务
      }, 0)
      new Promise(resolve => {
        console.log("promise2");//执行顺序4 宏任务
        resolve()
      }).then(() => {
        console.log("promise2.then"); //执行顺序5 微任务
      })
    })
    console.log("some code");//执行顺序2 同步代码
</script>

17.JS的基本类型

  • Number 、String、Boolean、Null、Undefined、Symbol

18.箭头函数和普通函数的区别

  • 箭头函数没有原型 prototype
  • call、apply、bind 并不会影响其 this 的指向。
  • 箭头函数不能作为构造函数使用,也不能使用 new 关键字
  • 箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值,定义的时候就确定并固定了

19.new 操作符到底做了什么

  • 创建一个空的对象;
  • 让空对象的原型属性指向原型链;
  • 让构造函数的 this 指向 obj,并执行函数体;
  • 判断返回类型,如果是值就返回这个 obj,如果是引用类型,返回这个 引用对象。

20.一些JS数据类型的判断结果

typeof NaN  // number
typeof null //object
typeof undefined //undefined
typeof []  //object
typeof {} //object
typeof Function //function
null instanceof Object  //false
null == undefined //false
null === undefined //false
null == NaN //false
null === NaN //false
NaN == NaN //false
NaN === NaN //false
null == undefined  //true

21.HTTP状态码

  • 成功响应

    • 200 OK 请求成功
  • 重定向消息

    • 301 请求资源的URL已永久更改
    • 304 缓存
  • 客户端错误响应

    • 400 错误的请求(错误的请求语法、无效的请求消息帧)
    • 401 未授权
    • 403 无权限
    • 404 服务器知道请求方法,但目标资源不支持该方法
  • 服务器响应错误

    • 500 服务器遇到了不知道如何处理的情况
    • 502 表明服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应
    • 503 服务器未准备好处理请求
    • 504 网关超时
    • 505 服务器不支持请求中使用的HTTP版本

22.前端的常见构建工具

  • 总结

    • webpack更适合打包项目
    • vite基于rollup实现了热更新也适合打包项目
    • rollup更适合打包库(没有webpack这么多的工具函数,rollup不支持热更新,不适合开发应用使用,因为需要使用第三方模块)
    • esbuild适合作为底层的模块构建工具(其编译阶段就已经将源码转译为了机器码)
  • webpack和vite(含有一个开发服务器、一套构建指令)两者比较:

    Vite启动更快是因为webpack启动时需要加载全部的资源文件再启动服务,而vite则会优先启动服务器再按需加载需要的资源

  • 详情介绍

  • webpack

    webpack是一个用于现代 JavaScript 应用程序的静态模块打包工具(将项目中用到的文件全部转换为JS文件)

    1、优势

    • 可以将各种资源文件视为模块进行处理和打包,并自动识别依赖关系
    • 拥有强大的插件系统、实现对代码压缩、分包chunk、模块热更新等
    • 支持自定义配置文件
    • 提供了各种插件和加载器处理各种资源文件

    2、劣势

    • 构建速度较慢
    • 体积较大
    • 配置复杂
  • rollup

    相比于Webpack,Rollup要小巧的多,打包生成的文件更小

    1、优势

    • 没有webpack那么多工具函数,因此打包产物比较干净,体积小
    • 插件机制设计得相对更干净简洁

    2、劣势

    • rollup不支持热更新
    • 不适合开发应用使用,因为需要使用第三方模块
  • vite

    一个开发服务器、一套构建指令(使用rollup打包代码)

    1、优势

    • 快速的冷启动:vite会直接启动开发服务器,不需要进行打包操作,所以不需要分析模块的依赖、不需要编译,因此启动速度非常快。
    • 即时的热更新:在热模块HMR方面,当修改一个模块的时候,仅需让浏览器重新请求该模块即可,无须像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。
    • 真正的按需编译:利用现代浏览器支持ES Module的特性,当浏览器请求某个模块的时候,再根据需要对模块的内容进行编译,这种方式大大缩短了编译时间
    • 更小的打包体积,vite在生产环境通过rollup进行打包,打包产物体积小。
    • 更加的简单易用,相对于 Webpack 来说,Vite 的配置更加简单明了,许多默认配置已经足够满足大多数前端项目的需求。

    2、劣势

    • 对于旧浏览器支持较差,由于 Vite 采用了 ES 模块化和原生浏览器 APIs,因此在旧版本的浏览器中可能会出现兼容性问题。
  • esbuild

    一个非常新的模块打包工具,拥有着超高的性能

    1、优势

    • 编译速度非常快,相比其他流行的 JavaScript 编译器和打包器,esbuild基于Go语言编写,在编译阶段就已经将源码转译为机器码,所以速度最多可以快 100 倍。
    • esbuild 支持多种模块格式,包括 CommonJS、ES6 模块、AMD 等,使得它适用于任何类型的 JavaScript 项目。
    • esbuild 的配置非常简单,只需要提供一个入口文件和输出目录即可。

    2、劣势

    • 支持不完善,提供的功能很基础,对代码分割和css处理等支持较弱。
    • 配置灵活的不高,侧重于快速且轻量级的构建,没有提供一些复杂的插件或高级配置选项。
    • 没有稳定版本,不适合直接用到生产环境,

23.数组方法sort

  • sort默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序

24.浏览器回收机制

  • 1.标记清理

    • 标记在上下文中的变量,若变量不在上下文中则标记不在上下文中
    • 思路:垃圾回收会周期性的运行,运行过程中会标记内存中所有的变量,然后再对被引用的变量移除标记,剩下的被标记的变量就是会被销毁回收的
  • 2.引用计数

    • 思路:对每个变量记录引用次数,引用一次(例如变量赋值、变量赋其他值)次数就会加一

      如果引用变量的值被覆盖,则引用数减1

  • 3.V8引擎的垃圾清理策略,分代垃圾回收

    • 新生代采用Scavenge算法,存储存活时间短点对象

      思路:新对象会在From空间分配内存,等到垃圾清理时,把还存活的对象复制到To空间,复制完成后,To空间变为From空间,From空间变为To空间,互相交换

    • 老生代采用标记整理算法,存储存活时间较长的对象。

      思路:标记和整理

25.AJAX工作原理

  • 相当于在用户和服务器之间加了一个中间层(AJAX引擎),使用户操作与服务器响应异步化。

其核心通过XMLHttpRequest对象向服务器发异步请求,从服务器获得数据再用JavaScript来操 作Dom而更新页面

  • 具体步骤:

    • 1.创建XHR对象;
    • 2.调用open()方法创建请求;
    • 3.调用send()方法发送请求;
    • 4.OnreadyChange捕获状态码;
    • 5.判断状态码是否成功;
    • 6.调用ajax的response属性返回数据;

26.管理系统权限的控制

  • 路由权限

    • 在登录请求中会得到权限的数据。前端根据权限去动态生成路由,只允许用户访问权限内的路由,如果通过地址栏去访问权限外的路由会重定向至404页面(无论登录了还是没登录)。
  • 按钮权限

    • 同一页面还可能因为权限不同展示不同的按钮。
  • 请求和响应的控制

    • 如果用户通过非常规操作,比如通过浏览器调试工具将某些禁用的按钮变成启用状态。此时发的请求,也应该被前端所拦截。
  • 获取权限信息的方式

    • 方式1

      • 后端返回权限表或路由表(常见于管理员可以添加新角色)
      • 数据结构大概如下
      {
       "data": {
           "id": 1,
           "username": "admin",
           "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTY1MzM1NzIyNH0.1y-Ucq_MfFRloesg0eA9pfk-VA3pV_zAOSj3HFpnKak"
       },
       "rights": [
        {
            "id": 125,
            "authName": "用户管理",
            "icon": "icon-user",
            "children": [
                {
                    "id": 110,
                    "authName": "用户列表",
                    "path": "users",
                    "rights": ["view","edit","add","delete"]
                }
            ]
        },
        {
            "id": 103,
            "authName": "角色管理",
            "icon": "icon-juese",
            "rights": ["view","edit","add","delete"]
        }
       ]
      }
    • 方式2

      • 后端返回角色
      {
          "data": {
           "id": 1,
           "username": "admin",
           "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTY1MzM1NzIyNH0.1y-Ucq_MfFRloesg0eA9pfk-VA3pV_zAOSj3HFpnKak",
           "roles": ['admin','editr']
       }
      }
      
    • 方式3

      • 前端根据用户信息判断角色
      • 如利用level字段
      {
          "data": {
           "id": 1,
           "username": "admin",
           "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTY1MzM1NzIyNH0.1y-Ucq_MfFRloesg0eA9pfk-VA3pV_zAOSj3HFpnKak",
           "level": 1,
           "units": {
                   "id": 1,
                   "name": "部门1"
               }
       }
      }
      

27.sass中的循环语句

  • @for指令
    • @for 指令可以用于循环生成样式,@for 指令有两种类型,如下所示:
    • 其中 $i 表示变量,start 表示起始值,end 表示结束值。其实这两种方式的区别就在于,使用关键字 through 时会包括 end 这个数,而使用关键字 to 则不会包括 end 这个数。 (使用through会包含end,而to不会)
    • // 第一种
      @for $i from <start> through <end>
          
      // 第二种
      @for $i from <start> to <end>
      ​
      //示例:
      @for $i from 1 through 3{
          .width#{$i} {
              width: $i * 10px; 
          }
      }
      
  • @while指令
    • @while 指令也可以用于循环样式,后面接 SassScript 表达式,循环会一直到表达式的值为 false 时停止。
    • 示例:
      $num: 12;
      ​
      @while $num < 18 {
          .f-#{$num} { 
              font-size: #{$num}px; 
            }
          $num: $num + 2;
      }
      
  • @each 指令
    • @each 指令可以用于遍历一个列表,然后从列表中取出对应的值。
    • @each $i in <list>//示例:
      $list: 5 10 15 20 25;
      ​
      @each $i in $list {
          .p-#{$i}{
              padding: #{$i}px;
          }
      }
      ​
      

28.深拷贝和浅拷贝

  • 区别

    • 浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存。
    • 深拷贝:复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变。
  • 实现

    • 浅拷贝:通过 Object.assign() 拷贝

    • 深拷贝:

      • 采用递归的方式来封装实现深拷贝的函数

      • 利用JSON对象实现(无法实现对象里面的包含对象的深拷贝)

      • 通过jQuery的extend方法

        $.extend([deep ], target, object1 [, objectN ])
        
      • lodash函数实现lodash.cloneDeep()

      • 扩展运算符