2_JavaScript面试题

1. JS有几种数据类型?

八种:


2. 什么是闭包

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

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

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

    function func(){
    	var a = 1, b = 2;
    	fuction closure(){
    	return a + b;
    	}
    	return closure;
    }
    

    优点:

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

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

  • 避免全局变量的污染

    缺点:

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


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)

原理:

  • 防抖是维护一个计时器,规定在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)

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

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

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

区别:(三者的第一个参数都是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)
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);

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);

浅拷贝2:arr.slice()

const newArr = oldArr.slice();

浅拷贝3:arr.concat()

const newArr = oldArr.concat();

浅拷贝4:es6展开运算符

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

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

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

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

缺点:

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

解决: 可以通过lodash库解决

const newArr = _.cloneDeep(oldArr);

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表示执行完成且失败的状态。三个状态不可逆转
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值