• 2_JavaScript面试题


    1. JS有几种数据类型?

    八种:


    2. 什么是闭包

    闭包就是能够读取其他函数内部变量的函数。例如在JS中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成 “定义在一个函数内部的函数” 。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

    • 举例:创建闭包最常见方式,就是在一个函数内部创建另一个函数。

      下面例子中的closure 就是一个闭包

      function func(){
      	var a = 1, b = 2;
      	fuction closure(){
      	return a + b;
      	}
      	return closure;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      优点:

    • 能够读取函数内部的变量;

    • 让这些变量一直存在于内存中,不会在调用结束后被垃圾回收机制回收;

    • 避免全局变量的污染

      缺点:

    • 由于闭包会使用函数中的变量存在在内存中,内存消耗很大,所以不能滥用闭包;解决的办法是退出函数之前,将不使用的局部变量删除;


    3. 防抖与节流

    核心:限制某一个方法被频繁触发,而一个方法之所以会被频繁触发,大多数情况下是因为 DOM 事件的监听回调。

    **共同点:**都为防止事件频繁触发

    不同点:

    • 节流:单位时间内,触发第一次事件;
    • 防抖:单位时间内,触发最后一次事件
    3.1 防抖 debounce

    指在一定时间内,函数被触发多次,只执行一次(第一次或最后一次),将多次执行变为最后一次执行

    应用场景:

    • 如(input框)用户触发时间过于频繁,只要最后一次事件的操作
    • 表单提交

    例:1s之内的不触发事件,超过的再触发

    function debounce(fn, delay) {
      let timer;
      return function() {
        if(timer !== null){
          clearTimeout(timer) //如果上一次有值,则清除上一次延时器
        }
        timer = setTimeout(function() {
            fn.apply(this,arguments)
        }, delay)
      }
    }
    let inp = document.querySelector('.username')
    inp.oninput = debounce(()=>{
      console.log(inp.value);
    },500)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    原理:

    • 防抖是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,都会清除当前的 timer 重新计时。这样一来,只有最后一次操作事件才被真正触发。
    • 节流是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。
    3.2 节流 throttle

    将多次执行变成每隔一段时间执行调用一次函数,而不是一触发事件就调用一次,这样就会减少资源浪费。

    应用场景:

    • 需要间隔一定时间触发回调来控制函数调用频率

    • 窗口调整、页面滚动、抢购和疯狂点击

    • DOM 元素的拖拽功能实现(mousemove)

    • 搜索联想(keyup)

    • 计算鼠标移动的距离(mousemove)

    • Canvas 模拟画板功能(mousemove)

    • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)

    • 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次

    function throttle(fn, delay) {
      let lastTime = 0;
      return function () {
        var nowTime = Date.now(); // 记录当前函数触发的时间
        if (nowTime - lastTime > delay) {
          fn.call(this); // 修正this指向问题
          lastTime = nowTime; // 同步时间
        }
      }
    }
    document.onscroll = throttle(function () {
      console.log('scroll事件被触发了' + Date.now())
    }, 1000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4. cookie&localstorge&sessionstorage | session&token

    cookie和token没可比性

    • cookie一般指存储方式 token属于校验规则

    • cookie是种客户端存储信息的方式,而token是信息本身,不是一个纬度

    • token本质上就是个验证身份的方式,应该和session这种方式比,一个不需要后端存储,一个需要;一个天然适合分布式,一个需要做额外工作。

    问:token能使用localstorage保存吗?

    答:一般都是这样做的token存在cookie里就按照cookie的方式处理,存在storage里就需要手动注入来携带

    多数情况,SessionId放到cookie里,但一样可以放到localstorage中

    [ 评论区 ]:

    • 我试过跨域拿cookie,有浏览器兼容性问题,后面都改成localstorage存了

    • 这问题我也常常喜欢问求职者,只要对方能回答出来,cookie是一种客户端存储介质,token是认证机制的生成物,就过关了。要是能解释出来为什么需要把token放在cookie中,还可以放在哪里,token是如何生效的,为什么要用token来携带认证信息(HTTP的无状态性),等等就是加分项。


    5. this的指向问题

    5.1 this指向

    this的最终指向的是那个调用它的对象。

    改变this指向的方法:

    1. 使用箭头函数;
    2. 在函数内部使用_this=this;
    3. 使用apply、call、bind;
    4. new实例化一个对象
    5.2 call、apply、bind的区别

    这三个方法的作用都是改变函数的执行上下文,换句话说就是改变函数体内部的this指向,以此来扩充函数依赖的作用域

    call:xxx.call(对象名,参数1,参数2,...)

    call可以改变this的指向,并且直接调用该函数,第一个参数是this的指向,第二个参数及其以后的参数都是想要传递的参数数据。

    function test(a,b){
        console.log(this);
        console.log(a + b);
    }
    test(1,2);  //  window  3
    var obj = { name:'lsj'};
    window.test.call(obj,3,5);  //  {name:'lsj'} 8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    apply:xxx.apply(对象名,[...])

    作用:和call方法一样是修改内部的 this 指向的,区别在于apply的第二个参数必须是一个数组(部署了Iterator接口的类数组对象也是可以的)

    function test(a,b){
        console.log(this);
        console.log(a + b);
    }
    test(1,2);  //  window  3
    var obj = {name:'lsj'};
    window.test.call(obj,[3,5]);  //  {name:'lsj'} 8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    bind: xxx.bind(对象名,参数1,参数2,...)

    作用:也是用于改变this的指向,传参与call一样

    例子:未使用bind方法前,foo()中的this指向window,使用后指向obj对象

    var obj = {key:"value"}
    var foo = function(){
        console.log(this)
    }
    foo.bind(obj)()  //  obj
    
    • 1
    • 2
    • 3
    • 4
    • 5

    区别:(三者的第一个参数都是this需要指向的对象)

    • 在参数上,只有apply是接收一个数组,call和bind用逗号分开
    • call和apply直接调用,返回的是一个值,而bind不直接调用,返回的是一个函数形式,执行:foo.bind(obj)()

    应用场景:

    • call通常用于对象的继承,真伪数组转换
    • apply用于找出数组中的最大值和最小值以及数组合并
    • bind用于vue和react中改变函数this指向

    6. 数组去重

    6.1 利用ES6中的Set方法 阮一峰-ES6-Set

    ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

    //Array.from(object)可以从具有length属性或可迭代对象的任何对象返回Array对象
    //...用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
    var arr = [1,2,2,2,3,4,4,5,6,6,7]
    let newArr = [...new Set(arr)]        //...ES6展开符
    let newArr = Array.from(new Set(arr)) //Array.from
    
    console.log(newArr)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    6.2 遍历 + 查找
    • forEach | map | filter等循环 + indexOF | includes 查找
    • 使用双重for循环,再利用数组的splice方法去重(ES5常用)
    let arr = [1, 2, 2, 2, 3, 4, 2, 7, 4, 5, 6, 6, 7]
    let newArr = []
    
    arr.forEach((item,index) => {
      if (newArr.indexOf(item) == -1) //indexOf - 新数组找不到的加进去
      if (arr.indexOf(item) == index  //indexOf - 老数组中找到第一个就加进去
      if (!newArr.includes(item))     //includes
      { 
        newArr.push(item)
      }
    })
    console.log(newArr);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    7.数组的深拷贝与浅拷贝(数组、函数都是对象类型)

    问题:要拷贝一个内容会变化的数组,使用了=赋值,slice(),concat()方法都不行,修改了原数组后拷贝数组也变了。

    原因是这个数组内容是object,而object是引用类型,需要使用深拷贝

    最后使用var newArr = JSON.parse(JSON.stringify(arr));解决

    string boolean number null undefined object(Array function data) bigInt symbol

    7.1 浅拷贝与深拷贝
    • 浅拷贝:如果数组元素是基本类型,就会拷贝一份,互不影响,但如果数组元素是对象或者数组,就会只拷贝对象和数组的引用,无论对新旧数组的哪一个进行了修改,两者都会发生变化。
    • 深拷贝:完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
    7.2 浅拷贝
    • 如果改变的值是基本类型(或是整个对象),就不会影响原数组

    • 但如果改变的值是引用类型,如obj.name,对其进行增删改,会影响原数组

    浅拷贝1:Object.assign

    const newObj = Object.assign({}, oldObj);
    
    • 1

    浅拷贝2:arr.slice()

    const newArr = oldArr.slice();
    
    • 1

    浅拷贝3:arr.concat()

    const newArr = oldArr.concat();
    
    • 1

    浅拷贝4:es6展开运算符

    const newObj = {...oldObj}
    const newArr = [...oldArr]
    
    • 1
    • 2
    7.3 深拷贝

    使用JSON.stringify和JSON.parse,通过JSON.stringify转化成字符串再通过JSON.parse()解析成原数组

    不仅可拷贝数组还能拷贝对象(但不能拷贝函数)

    const newObj = JSON.parse(JSON.stringify(oldObj));
    const newArr = JSON.parse(JSON.stringify(oldArr));
    
    • 1
    • 2

    缺点:

    JSON.stringify()有一些局限,比如对于RegExp类型和Function类型则无法完全满足,而且不支持有循环引用的对象。

    解决: 可以通过lodash库解决

    const newArr = _.cloneDeep(oldArr);
    
    • 1

    8. Eventloop:宏任务和微任务

    宏任务与微任务之间的执行顺序(同步任务->微任务->宏任务)

    • 宏任务是由宿主(浏览器)发起的,而微任务由JavaScript自身发起。

    • 在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了Promise,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。

    宏任务微任务
    发起者宿主(Node、浏览器)JS引擎
    具体事件script/ setTimeout/ setInterval / setImmediate等Promise.then

    PS.【ES6】

    1. var和let、const区别(变量提升)

    区别1. 变量提升

    • var 会使变量提升,let 和 const 不会使变量提升,提前使用会报错。

    变量声明和变量提升

    区别2. 作用域不同(let cosnt是块级作用域)

    • var 的作用域是整个执行上下文
      let 和 const 是块级作用域,只能在最近的一组花括号(function、if-else 代码块或 for 循环)中访问

    区别3. 重复声明

    • 用 var 重复声明变量,不会报错,但是 let 和 const 这样做会报错

    PS. let与const区别

    • let 和 const 的区别在于:let 允许多次赋值,而 const 只允许一次。

    2. 箭头函数和普通函数的区别
    • this,箭头函数没有自己的this,它的this与上下文有关,

    3. 说一下Promise

    Promise ,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

    从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

    Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

    Promise本身是同步,then的内容是异步:

    Promise很好地解决了回调地狱的问题,它包含三种状态:pending、fulfilled(resolved)、rejected

    • pending是promise的初始状态
    • resolved表示执行完成且成功的状态
    • rejected表示执行完成且失败的状态。三个状态不可逆转
  • 相关阅读:
    神经网络训练结果不稳定,神经网络越训练越慢
    Web3与物联网:探索区块链如何驱动智能设备的未来
    umich cv-4-2 经典卷积网络架构
    java多线程-线程间通信
    【Linux】第八章 基础IO(open+write+read+文件描述符+重定向+缓冲区+文件系统管理+软硬链接)
    短视频创作应该注意什么问题?
    【面试经典150 | 哈希表】赎金信
    MySql(46)事务的隔离级别
    LeetCode刷题第4周小结
    诡异的bug之dlopen
  • 原文地址:https://blog.csdn.net/linxwx/article/details/127759332