js基础面试题161-180道题目(js2021面试题)

161.JSON 的了解

参考答案:JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于 JavaScript 的一个子集。数据格式简单, 易于读写, 占用带宽小。

参与互动

162.事件代理怎么实现?

参考答案:在元素的父节点注册事件,通过事件冒泡,在父节点捕获事件

参与互动

163.什么是属性搜索原则?

参考答案:

1.首先会去查找对象本身上面有没有这个属性,有的话,就返回这个属性 2.如果对象本身上面没有这个属性,就到它的原型上面去查找,如果有,就返回 3.就到原型的原型上面去查找有没有这个属性,如果查找到最后一只没有找到,就返回一个undefined

参与互动

164.如何避免重绘或者重排?

参考答案:

1.分离读写操作


var curLeft=div.offsetLeft;
var curTop=div.offsetTop;
div.style.left=curLeft+1+'px';
div.style.top=curTop+1+'px';

2.样式集中改变


可以添加一个类,样式都在类中改变

3.可以使用absolute脱离文档流。

4.使用 display:none ,不使用 visibility,也不要改变 它的 z-index

5.能用css3实现的就用css3实现。

165.说下函数式编程的理解

参考答案:

1.什么是函数式编程?

函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。

2.优势特点

代码简洁、开发快速、命令式实现、函数式实现、易于理解,抽象度高、没有副作用,变量无状态

166.forEach,map和filter的区别(哔哩哔哩)

参考答案:

  • filter函数,顾名思义,它是一个用来过滤的函数。他可以通过指定的过滤条件,删选出数组中符合条件的元素,并返回。
  • map函数,这个函数与filter函数不同之处在于,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。而map则会返回传入函数return的值。
  • forEach函数,可以实现对数组的遍历,和map函数与filter函数不同的是它没有返回值。

167.delete 数组的 item,数组的 length 是否会 -1

参考答案:不会

解析:

delete Array[index]

const arr = ['a', 'b', 'c', 'd', 'e'];
let result = delete arr[1];
console.log(result); // true;
console.log(arr); // ['a', undefined, 'c', 'd', 'e']
console.log(arr.length); // 5
console.log(arr[1]); // undefined

使用delete删除元素,返回true和false, true表示删除成功,false表示删除失败。使用delete删除数组元素并不会改变原数组的长度,只是把被删除元素的值变为undefined。

168.给出 ['1', '3', '10'].map(parseInt) 执行结果

参考答案:[1, NaN, 2]

169.执行上下文

参考答案:

执行上下文可以简单理解为一个对象:

它包含三个部分:

  • 变量对象(VO)
  • 作用域链(词法作用域)
  • this指向

它的类型:

  • 全局执行上下文
  • 函数执行上下文
  • eval执行上下文

代码执行过程:

  • 创建 全局上下文 (global EC)
  • 全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被push到执行栈顶层
  • 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
  • 函数执行完后,callee 被pop移除出执行栈,控制权交还全局上下文 (caller),继续执行

170.怎样理解setTimeout 执行误差

参考答案:定时器是属于 宏任务(macrotask) 。如果当前 执行栈 所花费的时间大于 定时器 时间,那么定时器的回调在 宏任务(macrotask) 里,来不及去调用,所有这个时间会有误差。

解析:参考

171.数组降维

参考答案:

1.数组字符串化

let arr = [
    [222, 333, 444],
    [55, 66, 77], {
        a: 1
    }
]
arr += '';
arr = arr.split(',');

console.log(arr); // ["222", "333", "444", "55", "66", "77", "[object Object]"]

这也是比较简单的一种方式,从以上例子中也能看到问题,所有的元素会转换为字符串,且元素为对象类型会被转换为 "[object Object]" ,对于同一种类型数字或字符串还是可以的。

2.利用apply和concat转换

function reduceDimension(arr) {
    return Array.prototype.concat.apply([], arr);
}

console.log(reduceDimension([
    [123], 4, [7, 8],
    [9, [111]]
])); // [123, 4, 7, 8, 9, Array(1)]

3.递归

function reduceDimension(arr) {
    let ret = [];
    let toArr = function(arr) {
        arr.forEach(function(item) {
            item instanceof Array ? toArr(item) : ret.push(item);
        });
    }
    toArr(arr);
    return ret;
}

4.Array.prototype?.flat()

var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

//使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity);
// [1, 2, 3, 4, 5, 6]

5.使用 reduce、concat 和递归无限反嵌套多层嵌套的数组

var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];

function flattenDeep(arr1) {
    return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []);
}
flattenDeep(arr1);
// [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

解析:參考

172.为什么for循环嵌套顺序会影响性能?

参考答案:把循环次数大的放在内层,执行时间会比较短

var t1 = new Date().getTime()
for (let i = 0; i < 100; i++) {
    for (let j = 0; j < 1000; j++) {
        for (let k = 0; k < 10000; k++) {}
    }
}
var t2 = new Date().getTime()
console.log('first time', t2 - t1)

变量

实例化(次数)

初始化(次数)

比较(次数)

自增(次数)

i

1

1

10

10

j

10

10

10 * 100

10 * 100

k

10 * 100

10 * 100

10 * 100 * 1000

10 * 100 * 1000

for (let i = 0; i < 10000; i++) {
    for (let j = 0; j < 1000; j++) {
        for (let k = 0; k < 100; k++) {

        }
    }
}
var t3 = new Date().getTime()
console.log('two time', t3 - t2)

变量

实例化(次数)

初始化(次数)

比较(次数)

自增(次数)

i

1

1

1000

1000

j

1000

1000

1000 * 100

1000 * 100

k

1000 * 100

1000 * 100

1000 * 100 * 10

1000 * 100 * 10

解析:參考

173.轮播图实现原理

参考答案:


1.图片移动实现原理:
利用浮动将所有所有照片依次排成一行,给这一长串图片添加一个父级的遮罩,每次只显示一张图,其余的都隐藏起来。对图片添加绝对定位,通过控制left属性,实现照片的移动。

2.图片移动动画原理:
从a位置移动到b位置,需要先计算两点之间的差值,通过差值和时间间隔,计算出每次移动的步长,通过添加定时器,每次移动相同的步长,实现动画效果。

3.图片定位停止原理:
每一张照片都有相同的宽度,每张照片都有一个绝对的定位数值,通过检测定每次移动后,照片当前位置和需要到达位置之间的距离是否小于步长,如果小于,说明已经移动到位,可以将定时器清除,来停止动画。

4图片切换原理:
在全局设置一个变量,记录当前图片的位置,每次切换或跳转时,只需要将数值修改,并调用图片页数转像素位置函数,再调用像素运动函数即可。

5.自动轮播原理:
设置定时器,一定时间间隔后,将照片标记加1,然后开始切换。

6.左右点击切换原理:
修改当前位置标记,开始切换。这里需要注意与自动轮播之间的冲突。当点击事件触发之后,停止自动轮播计时器,开始切换。当动画结束后再次添加自动轮播计时器。

7.无缝衔接原理:
需要无缝衔接,难度在于最后一页向后翻到第一页,和第一页向前翻到最后一页。由于图片的基本移动原理。要想实现无缝衔接,两张图片就必须紧贴在一起。所以在第一张的前面需要添加最后一张,最后一张的后面需要添加第一张。

7.预防鬼畜原理:
始终保证轮播图的运动动画只有一个,从底层杜绝鬼畜。需要在每次动画开始之前,尝试停止动画定时器,然后开始为新的动画添加定时器。

8.预防暴力点击原理:
如果用户快速点击触发事件,会在短时间内多次调用切换函数,虽然动画函数可以保证,不会发生鬼畜,但在照片从最后一张到第一张的切换过程,不会按照正常的轮播,而是实现了跳转。所以需要通过添加口令的方式来,限制用户的点击。当用户点击完成后,口令销毁,动画结束后恢复口令。

9.小圆点的位置显示原理:
每次触发动画时,通过全局变量标记,获取当前页数,操作清除所有小圆点,然后指定一页添加样式。

10.点击触发跳转的原理:
类似于左右点击触发,只是这是将全局页面标记,直接修改,后执行动画。需要避免与自动轮播定时器的冲突。

解析:参考

174.如何设计一个轮播图组件

参考答案:

1.轮播图功能实现 2.抽出需要传入的变量,如:背景图,文案描述等

175.script 引入方式

参考答案:

  • html 静态 <script> 引入
  • js 动态插入 <script>
  • <script defer> : 延迟加载,元素解析完成后执行
  • <script async> : 异步加载,但执行时会阻塞元素渲染

176.数组中的forEach和map的区别

参考答案:

相同点

  • 都是循环遍历数组中的每一项
  • forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项),index(索引值),arr(原数组)
  • 匿名函数中的this都是指向window
  • 只能遍历数组
  • 都不会改变原数组

区别

map方法


1.map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
2.map方法不会对空数组进行检测,map方法不会改变原始数组。
3.浏览器支持:chrome、Safari1.5+、opera都支持,IE9+,
array.map(function(item, index, arr) {}, thisValue)

var arr = [0, 2, 4, 6, 8];
var str = arr.map(function(item, index, arr) {
    console.log(this); //window
    console.log("原数组arr:", arr); //注意这里执行5次
    return item / 2;
}, this);
console.log(str); //[0,1,2,3,4]

若arr为空数组,则map方法返回的也是一个空数组。

forEach方法

1.forEach方法用来调用数组的每个元素,将元素传给回调函数 2.forEach对于空数组是不会调用回调函数的。

Array.forEach(function(item, index, arr) {}, this)
var arr = [0, 2, 4, 6, 8];
var sum = 0;
var str = arr.forEach(function(item, index, arr) {
    sum += item;
    console.log("sum的值为:", sum); //0 2 6 12 20
    console.log(this); //window
}, this)
console.log(sum); //20
console.log(str); //undefined

无论arr是不是空数组,forEach返回的都是undefined。这个方法只是将数组中的每一项作为callback的参数执行一次。

177.for in和for of的区别

参考答案:


简单总结就是,for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。

for-in总是得到对象的key或数组、字符串的下标。

for-of总是得到对象的value或数组、字符串的值,另外还可以用于遍历Map和Set。

178.typeof 与 instanceof 区别

参考答案:


1、typeof返回结果是该类型的字符串形式表示【6】(number、string、undefined、boolean、function、object)
2、instanceof是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型。 

179.微任务和宏任务

参考答案:

宏任务队列(大家称之为macrotask queue,即callback queue):按HTML标准严格来说,其实没有macrotask queue这种说法,它也就是ES5中的事件队列,该队列存放的是:DOM事件、AJAX事件、setTimeout事件等的回调。可以通过setTimeout(func)即可将func函数添加到宏任务队列中(使用场景:将计算耗时长的任务切分成小块,以便于浏览器有空处理用户事件,以及显示耗时进度)。

微任务队列(microtask queue):存放的是Promise事件、nextTick事件(Node.js)等。有一个特殊的函数queueMicrotask(func)可以将func函数添加到微任务队列中。

/*
 * 宏任务
 *   分类: setTimeout setInterval requrestAnimationFrame
 *   1.宏任务所处的队列就是宏任务队列
 *   2.第一个宏任务队列中只有一个任务: 执行主线程的js代码
 *   3.宏任务队列可以有多个
 *   4.当宏任务队列的中的任务全部执行完以后会查看是否有微任务队列如果有先执行微任务队列中的所有任务,如果没有就查看是否有宏任务队列
 *
 * 微任务
 *   分类: new Promise().then(回调) process.nextTick
 *   1.微任务所处的队列就是微任务队列
 *   2.只有一个微任务队列
 *   3.在上一个宏任务队列执行完毕后如果有微任务队列就会执行微任务队列中的所有任务
 * */

console.log('----------------- start -----------------');

setTimeout(() => {
    console.log('setTimeout');
}, 0)

new Promise((resolve, reject) => {
    for (var i = 0; i < 5; i++) {
        console.log(i);
    }
    resolve(); // 修改promise实例对象的状态为成功的状态
}).then(() => {
    console.log('promise实例成功回调执行');
})

console.log('----------------- end -----------------');

180.JavaScript 中 undefined 和 not defined 的区别

参考答案:undefined是没有初始化,not defined是没有声明

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注