一些个面试题

114 阅读7分钟

闭包

定义:本质就是函数里面嵌套了别的函数,比如说函数A中return了一个函数B,在函数B中访问了函数A中的变量,那么函数B就是函数A的一个变量背包。

function A() {
    const a = 1
    return function b() {
        console.log(a)
        return a
    } 
}

console.log(A()())

原理:作用域链,当前作用域可以访问上级作用域中的变量。

解决的问题:

  • 函数作用域中的局部变量在函数执行完后不会被销毁
  • 可以在函数外部访问到函数内部的变量

出现的问题:

  • 由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。

应用:

  • 模拟块级作用域
function outputNumbers(count) {
  (function () {
    for (var i = 0; i < count; i++) {
      alert(i);
    }
  })();
  alert(i); //导致一个错误!因为上面是一个立即执行函数,函数执行完后,函数内部的变量就被销毁了,所以外部访问不到里面的变量了
}

  • 节流防抖中也有应用
//防抖
//通过setTimeout的方式,触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
//应用场景
-   登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
-   调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
-   文本编辑器实时保存,当无任何更改操作一秒后进行保存
function debounce(fn, timer, isFirst) {
            var t = null
            return function () {
                if (!t) {
                    clearTimeout(t);
                }
                if (isFirst) {
                    var temp = !t;
                    if (temp) {
                        fn();
                    } else {
                        t = setTimeout(() => {
                            t = null;
                        }, timer);
                    }

                } else {
                    t = setTimeout(() => {
                        fn();
                    }, timer)
                }
            }()
        }

//节流
//连续触发事件但是在 n 秒中只执行一次函数
//节流的应用场景
-   鼠标连续不断地触发某事件(如点击),单位时间内只触发一次;
-   监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断。例如:懒加载;
-   浏览器播放事件,每个一秒计算一次进度信息等
function throttle(fn, delay) {
            var begin = 0;
            return function() {
                //拿到时间搓
                var cur = new Date().getTime();
                console.log(cur - begin)
                if (cur - begin > delay) {
                    fn();
                    begin = cur;
                }
            }
        }

实现异步的方法

  • 回调函数:最基本的异步操作方法,比如说AJAX回调,回调函数的优点是简单易理解,缺点是代码不利于维护,耦合度高,会存在回调地狱的问题,而且每个任务只能指定一个回调函数
  • setTimeout:一个计时器,在对应指定时间之后再调用回调函数
  • promise:包装了一个异步调用并生成了一个promise实例,当异步调用返回结果的时候根据调用的结果分别调用实例化时传入的resolve 和 reject方法,then接收到对应的数据,做出相应的处理
  • generators/yield:Generator 函数是一个状态机,封装了多个内部状态,可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果
  • async/await:它们是基于promise封装的,在函数外面加上async就说明这是一个异步函数,返回的也是一个promise,await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 去写。(后续写一个详细的,是基于generators封装的)

数组去重

let arr = [1, 2, 3, 3, 3]
// 方法一
const set = new Set(arr)
arr = [...set]
console.log(arr)

//方法二
arr = arr.filter((item, index) => arr.indexOf(item) === index) 
console.log(arr)

es6的箭头函数

  • 它没有this,它的this是从外部获取的,是继承父级作用域中的this。
  • 不能使用new构造函数。
  • 没有arguments,没有原型和super。
  • 不能使用apply、call和bind去改变this指向。

call、apply和bind的作用和区别

它们的作用都是改变函数运行时this的指向问题

区别:

  • call的第一个参数是this的新指向,后面依次传入函数所需要的参数,会立即执行函数。
  • apply的第一个参数是this的新指向,第二个参数是函数执行所需要的参数的数组,会立即执行函数。
  • bind的第一个参数也是this的新指向,后面可以依次传入函数所需要的参数,也可以通过数组的形式传进去,不会立即执行函数,且只能改变一次this的指向,返回的是已经改变this指向的新函数。

this指向

this,函数执行上下文,可以通过apply、call和bind来改变this指向,匿名函数和立即指向函数的this指向全局对象,在浏览器中,全局对象是window,在node.js中,全局对象是global,es6中的箭头函数是没有this的,剩余函数的this,谁调用它,它就指向谁。声明函数时是无法确定this指向的,调用时才能确定。

js变量提升

js变量声明(用var声明的)和函数声明在代码编译期会被提到最前面,但是只有声明被提升,赋值没有,这样的结果是:在变量初始化之前访问变量,返回的是undefined,可以在函数声明之前调用函数。

let、var和const的区别

  1. const 和 let存在块级作用域。
  2. const 和 let不能重复定义。
  3. const定义的变量只能赋值一次。
  4. var 定义的变量存在变量提升,其实const和let定义的变量也存在变量提升,但存在暂时性死区(TDZ)。var发生变量提升时,会为变量赋值为undefined,而const和let发生变量提升时,不会为变量赋任何值,仅仅发生了提升,因此在显式赋值前,任何对变量的操作都会报错,所以称从代码块开始到变量显式赋值之内的这块区域为暂时性死区。

map和forEach的区别

map: 返回一个新的数组,不会对原数组进行修改,速度比forEach快

forEach: 一般是引用数组的元素的操作,对每个元素提供一个可执行的函数进行操作,默认返回值为undefied

//map
const arr=[1,2,3,4,5,6]
const newArr= arr.map((item,index) => {
    return item+1
});
console.log(newArr);

//forEach
const arr=[1,2,3,4,5,6]
 arr.forEach((item,index) => {
    console.log(item,index);
});

new会发生什么

  1. 创建一个新的对象。
  2. 给这个对象添加原型"proto",将原型指向父级(构造函数)的prototype(原型对象)。
  3. 将这个对象作为this的执行上下文。
  4. 返回这个对象。

伪数组和数组的区别

伪数组的数据类型是Object,数组的数据类型是Array,伪数组可以用.length属性查看数组的长度,也可以用[index]来查看元素,但不能使用数组的其他方法,要用for in 来遍历,常用的伪数组情景是:函数内的arguments就是伪数组,在原生中获取DOM元素,获取到的数组也是伪数组,可以用Array.from(伪数组)来将伪数组转换为数组,方法中的参数就是伪数组的变量名。

事件扩展符

使用场景:

  1. 数组克隆。
  2. 数组合并。
  3. 函数传递参数时,不确定参数个数时使用。
  4. 类数组转成真正数组。

es6新特性

  1. symbol,值独一无二,不能new
  2. let和const
  3. 解构赋值
  4. 新增了map和set构造函数
  5. 新增的object.assign()方法可以实现浅复制
  6. 字符串新增includes()判断字符串是否包含参数字符串
  7. 函数传递参数时可以用...来不定长传参
  8. 箭头函数
  9. 可以使用Class类关键字 10.模块导入导出 11.promise和generator

es6的模块化

可以将一个大的并且包含多个功能的程序文件拆分成拥有具体代码功能的小文件,然后这些小文件可以轻松实现组合和相互引用

es6模块化的关键字有两个:

export:用于规定模块的对外接口

import: 用于输入其他模块

expost有三种导出方式:

  1. 分别暴露,就是写多个export
  2. 统一暴露,它会暴露一个对象,对象里面有需要暴露的方法或变量
  3. 默认暴露,export defalt,默认暴露一个对象或者一个方法或者一个变量等等

import也有三种导入方式:

  1. 通用的引入方式,就是导入整个模块,然后再调用模块中的一些方法或者变量
  2. 解构赋值方式的导入
  3. 只针对默认暴露的导入

0.1+0.2不等于0.3

记住,永远不要直接比较两个浮点的大小

解决:

  1. 将浮点运算转换成整数计算
  2. 用bignumber.js等浮点数运算库来进行计算
  let x = new BigNumber(0.1);
  let y = new BigNumber(0.2)
  let z = new BigNumber(0.3)

  console.log(z.equals(x.add(y))) // 0.3 === 0.1 + 0.2, true
  console.log(z.minus(x).equals(y)) // true
  console.log(z.minus(y).equals(x)) // true
  1. 设置一个误差范围值,如果相减的结果在这个范围值之内,则认为相等 对于Javascript来说,这个值通常是2^-52,而在ES6中, 已经为我们提供了这样一个属性:Number.EPSILON,而这个值正等于2^-52。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0,这个时候我们只要判断(0.1+0.2)-0.3小于Number.EPSILON,在这个误差的范围内就可以判定0.1+0.2===0.3为true。