前端面试题(4)

目录

JavaScript

一、Promise的理解

1、Promise是什么?

2、Promise是为解决什么问题而产生的?

3、Promise的两个特点

        1)Promise对象的状态不受外界影响

        2)Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled 或者 由pending变成rejected

4、Promise的三个缺点

5、Promise在哪存放成功回调序列和失败回调序列?

二、箭头函数和普通函数的区别

三、ES6新特性

        跳转查看 ES6、7、8、9最常用的新特性_Joker_Yang*的博客-CSDN博客

四、实现继承的几种方式

1、原型链继承

2、借用构造函数继承(伪造对象、经典继承)

3、实例继承(原型式继承)

五、Null 和 undefined 的区别

六、call、bind、apply的区别

七、前端缓存的理解 或者 前端数据持久化的理解

八、防抖和节流

九、闭包

十、数组去重

1、利用ES6 Set去重(ES6中最常用)

2、利用for嵌套for,然后splice去重(ES5中最常用)

3、利用indexOf去重

4、利用sort()

5、利用includes

6、利用hasOwnProperty(PS:所有的都去重了)

7、利用filter

8、利用递归去重

9、利用Map数据结构去重


JavaScript

一、Promise的理解

        Promise 是一种为了避免回调地狱的异步解决方案。Promise 是一种状态机: pending(进行中)、fulfilled(已成功)和rejected(已失败)只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

        回调地狱:回调函数中嵌套回调函数的情况就叫做回调地狱。
        回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。

1、Promise是什么?

        Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大。

        ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
        ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

2、Promise是为解决什么问题而产生的?

        Promise是一种用于异步编程的JavaScript对象,它可以解决回调地狱的问题并提供更好的代码可读性和可维护性。

3、Promise的两个特点
        1)Promise对象的状态不受外界影响

                ① pending 初始状态

                ② fulfilled 成功状态

                ③ rejected 失败状态

        Promise 有以上三种状态,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态

        2)Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled 或者 由pending变成rejected
4、Promise的三个缺点

        1)无法取消Promise,一旦新建它就会立即执行,无法中途取消
        2)如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
        3)当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成

5、Promise在哪存放成功回调序列和失败回调序列?

        1)onResolvedCallbacks 成功后要执行的回调序列 是一个数组

        2)onRejectedCallbacks 失败后要执行的回调序列 是一个数组

        以上两个数组存放在Promise 创建实例时给Promise这个类传的函数中,默认都是空数组。
每次实例then的时候传入onFulfilled成功回调、onRejected 失败回调。如果此时的状态是pending,则将onFulfilled和onRejected push到对应的成功回调序列数组和失败回调序列数组中;如果此时的状态是fulfilled,则onFulfilled立即执行;如果此时的状态是rejected,则onRejected立即执行。

        上述序列中的回调函数执行的时候 是有顺序的,即按照顺序依次执行

二、箭头函数和普通函数的区别

        区别在于

                1、箭头函数没有this,所以需要通过查找作用域链来确定this的值,这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this;

                2、箭头函数没有自己的arguments对象,但是可以访问外围函数的arguments对象;

                3、不能通过new关键字调用,同样也没有new.target值和原型;

        1、语法更加简洁、清晰;
        2、箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this;
        3、箭头函数继承而来的this指向永远不变;
        4、.call()、.apply()、.bind() 无法改变箭头函数中this的指向;
        5、箭头函数不能作为构造函数使用;
        6、箭头函数没有自己的arguments,可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表;
        7、箭头函数没有原型 prototype;
        8、箭头函数不能用作Generator函数,不能使用yeild关键字;
        9、箭头函数不具有super,不具有new.target;

三、ES6新特性

        跳转查看 ES6、7、8、9最常用的新特性_Joker_Yang*的博客-CSDN博客

四、实现继承的几种方式

1、原型链继承

        父类的实例作为子类的原型

function Woman() {
}
Woman.prototype = new People();
Woman.prototype.name = '秋雅';
let womanObj = new Woman();

        优点:简单易于实现,父类的新增的实例与属性子类都能访问

        缺点:可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面;无法实现多继承;创建子类实例时,不能向父类构造函数中传参数

2、借用构造函数继承(伪造对象、经典继承)

        复制父类的实例属性给子类

function Woman(name) {
    //继承了People
    People.call(this); // People.call(this,'秋雅'); 
    this.name = name || 'renbo'
}
let womanObj = new Woman();

        优点:解决了子类构造函数向父类构造函数中传递参数;可以实现多继承(call或者apply多个父类)

        缺点:方法都在构造函数中定义,无法复用;不能继承原型属性/方法,只能继承父类的实例属性和方法

3、实例继承(原型式继承)
function Wonman(name) {
    let instance = new People();
    instance.name = name || '秋雅';
    return instance;
}
let wonmanObj = new Wonman();

        优点:不限制调用方式,简单,易实现

        缺点:不能多次继承

五、Null 和 undefined 的区别

        undefined 表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化),一个没有传入实参的形参变量的值为undefined,如果一个函数什么都不返回,则该函数默认返回undefined。

        null 则表示"什么都没有",即"空值"。Javascript将未赋值的变量默认值设为 undefined ;Javascript从来不会将变量设为 null 。它是用来让程序员表明某个用var声明的变量时没有值的;

六、call、bind、apply的区别

apply方法
        apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。

call方法
        call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。

bind方法
        bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。

七、前端缓存的理解 或者 前端数据持久化的理解

        前端缓存分为HTTP缓存和浏览器缓存。其中HTTP缓存是在HTTP请求传输时用到的缓存,主要在服务器代码上设置;而浏览器缓存则主要由前端开发在前端js上进行设置。

        缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。

​         对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种,如下:
        ①不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求

        ②存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存

        ③存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
        ①协商缓存生效,返回304

        ②协商缓存失效,返回200和请求结果

八、防抖和节流

防抖(debounce)
        所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。

节流(throttle)
        所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。**节流会稀释函数的执行频率。对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版

九、闭包

        (1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
        (2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值

十、数组去重

1、利用ES6 Set去重(ES6中最常用)
function unique(arr) {
    return Array.from(new Set(arr))
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]

不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。

2、利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr) {
    for (var i = 0; i < arr.length; i++) {
        for (var j = i + 1; j < arr.length; j++) {
            if (arr[i] == arr[j]) {         //第一个等同于第二个,splice方法删除第二个
                arr.splice(j, 1);
                j--;
            }
        }
    }
    return arr;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {}, {}]
// NaN和{}没有去重,两个null直接消失了

双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。

3、利用indexOf去重
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
        if (array.indexOf(arr[i]) === -1) {
            array.push(arr[i])
        }
    }
    return array;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {}, {}]
// NaN、{}没有去重

新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。

4、利用sort()
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return;
    }
    arr = arr.sort()
    var arrry = [arr[0]];
    for (var i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i - 1]) {
            arrry.push(arr[i]);
        }
    }
    return arrry;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {}, {}, "a", false, null, true, "true", undefined]
// NaN、{}没有去重

利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。

5、利用includes
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
        // includes 检测数组是否有某个值
        if (!array.includes(arr[i])) {
            array.push(arr[i]);
        }
    }
    return array
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
// {}没有去重
6、利用hasOwnProperty(PS:所有的都去重了)

        利用hasOwnProperty 判断是否存在对象属性

function unique(arr) {
    var obj = {};
    return arr.filter(function (item, index, arr) {
        return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}]
// 所有的都去重了
7、利用filter
function unique(arr) {
    return arr.filter(function (item, index, arr) {
        //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
        return arr.indexOf(item, 0) === index;
    });
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {}, {}]
8、利用递归去重
function unique(arr) {
    var array = arr;
    var len = array.length;

    array.sort(function (a, b) {   //排序后更加方便去重
        return a - b;
    })

    function loop(index) {
        if (index >= 1) {
            if (array[index] === array[index - 1]) {
                array.splice(index, 1);
            }
            //递归loop,然后数组去重
            loop(index - 1);
        }
    }
    loop(len - 1);
    return array;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {}, null, NaN, NaN, "NaN", 0, "a", {}, undefined]
9、利用Map数据结构去重
function arrayNonRepeatfy(arr) {
    let map = new Map();
    // 数组用于返回结果
    let array = new Array();
    for (let i = 0; i < arr.length; i++) {
        // 如果有该key值
        if (map.has(arr[i])) {
            map.set(arr[i], true);
        } else {
            // 如果没有该key值
            map.set(arr[i], false);
            array.push(arr[i]);
        }
    }
    return array;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "a", "true", true, 15, false, 1, {}, null, NaN, NaN, "NaN", 0, "a", {}, undefined]

创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。

原文摘录于 前端面试八股文(超详细)_前端八股文_小泽今天早睡的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值