前端面试题-JS(五)

66 如何渲染⼏万条数据并不卡住界⾯

这道题考察了如何在不卡住⻚⾯的情况下渲染数据,也就是说不能⼀次性将⼏
万条都渲染出来,⽽应该⼀次渲染部分 DOM ,那么就可以通过requestAnimationFrame 来每 16 ms 刷新⼀次

var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
return Math.random() - 0.5;
})
console.log(arr);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>控件</ul>
<script>
setTimeout(() => {
// 插⼊⼗万条数据
const total = 100000
// ⼀次插⼊ 20 条,如果觉得性能不好就减少
const once = 20
// 渲染数据总共需要⼏次
const loopCount = total / once
let countOfRender = 0
let ul = document.querySelector("ul");
function add() {
// 优化性能,插⼊不会造成回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < once; i++) {
const li = document.createElement("li");
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();
}
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add);
}
}
loop();
}, 0);
</script>
</body>
</html>

67 希望获取到⻚⾯中所有的checkbox怎么做?

不使⽤第三⽅框架


var domList = document.getElementsByTagName(‘input’)
var checkBoxList = [];
var len = domList.length; //缓存到局部变量
while (len--) { //使⽤while的效率会⽐for循环更⾼
if (domList[len].type == ‘checkbox’) {
checkBoxList.push(domList[len]);
}
}

68 怎样添加、移除、移动、复制、创建和查找节点

创建新节点

  • createDocumentFragment() //创建⼀个DOM⽚段
  • createElement() //创建⼀个具体的元素
  • createTextNode() //创建⼀个⽂本节点

添加、移除、替换、插⼊

  • appendChild() //添加
  • removeChild() //移除
  • replaceChild() //替换
  • insertBefore() //插⼊

查找

  • getElementsByTagName() //通过标签名称
  • getElementsByName() //通过元素的Name属性的值
  • getElementById() //通过元素Id,唯⼀性

69 正则表达式

 正则表达式构造函数 var reg=new RegExp(“xxx”) 与正则表达字⾯量 var
 reg=// 有什么不同?匹配邮箱的正则表达式?
  • 当使⽤ RegExp() 构造函数的时候,不仅需要转义引号(即 \ ”表示”),并且还需要双反斜杠(即 \ 表示⼀个 \ )。使⽤正则表达字⾯量的效率更⾼

邮箱的正则匹配:

var regMail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2

70 Javascript中callee和caller的作⽤?

  • caller 是返回⼀个对函数的引⽤,该函数调⽤了当前函数;

  • callee 是返回正在被执⾏的 function 函数,也就是所指定的 function 对象的正⽂

    那么问题来了?如果⼀对兔⼦每⽉⽣⼀对兔⼦;⼀对新⽣兔,从第⼆个⽉起就开始⽣兔⼦;假定每对兔⼦
    都是⼀雌⼀雄,试问⼀对兔⼦,第n个⽉能繁殖成多少对兔⼦?(使⽤ callee 完成)

var result=[];
function fn(n){ //典型的斐波那契数列
if(n==1){
return 1;
}else if(n==2){
return 1;
}else{
if(result[n]){
return result[n];
}else{
//argument.callee()表示fn()
result[n]=arguments.callee(n-1)+arguments.callee(n-2);
return result[n];
}
}
}

71 window.onload和$(document).ready

原⽣ JS 的 window.onload 与 Jquery 的 $(document).ready(function(){}) 有什么不同?如何⽤原⽣JS实现Jq的
 ready ⽅法?
  • window.onload() ⽅法是必须等到⻚⾯内包括图⽚的所有元素加载完毕后才能执⾏。
  • $(document).ready() 是 DOM 结构绘制完毕后就执⾏,不必等到加载完毕
function ready(fn){
if(document.addEventListener) { //标准浏览器
document.addEventListener('DOMContentLoaded', function() {
//注销事件, 避免反复触发
document.removeEventListener('DOMContentLoaded',arguments.cal
fn(); //执⾏函数
}, false);
}else if(document.attachEvent) { //IE
document.attachEvent('onreadystatechange', function() {
if(document.readyState == 'complete') {
document.detachEvent('onreadystatechange', arguments.calle
fn(); //函数执⾏
}
});
}
};

72 addEventListener()和attachEvent()的区别

  • addEventListener() 是符合W3C规范的标准⽅法; attachEvent() 是IE低版本的⾮标准⽅法
  • addEventListener() ⽀持事件冒泡和事件捕获; - ⽽ attachEvent() 只⽀持事件冒泡
  • addEventListener() 的第⼀个参数中,事件类型不需要添加 on ; attachEvent() 需要添加 ‘on’
  • 如果为同⼀个元素绑定多个事件, addEventListener() 会按照事件绑定的顺序依次执⾏,attachEvent() 会按照事件绑定的顺序倒序执⾏

73 获取⻚⾯所有的checkbox

var resultArr= [];
var input = document.querySelectorAll('input');
for( var i = 0; i < input.length; i++ ) {
if( input[i].type == 'checkbox' ) {
resultArr.push( input[i] );
}
}
//resultArr即中获取到了⻚⾯中的所有checkbox

74 数组去重⽅法总结

⽅法⼀、利⽤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,undefin
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}

⽅法⼆、利⽤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,undefin
console.log(unique(arr))
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}]
  • 双层循环,外层循环元素,内层循环时⽐较值。值相同时,则删去这个值。
  • 想快速学习更多常⽤的 ES6 语法

⽅法三、利⽤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,undefin
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a"
新建⼀个空的结果数组, for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则 push 进数组

⽅法四、利⽤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,undefin
console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", un
  • 利⽤ sort() 排序⽅法,然后根据排序后的结果进⾏遍历及相邻元素⽐对

⽅法五、利⽤对象的属性不能相同的特点进⾏去重


function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var arrry= [];
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
arrry.push(arr[i])
obj[arr[i]] = 1
} else {
obj[arr[i]]++
}
}
return arrry;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin
console.log(unique(arr))
//[1, "true", 15, false, undefined, null, NaN, 0, "a", {…}] //两个true直接

⽅法六、利⽤includes


function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}

⽅法七、利⽤hasOwnProperty

function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof
})
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,und
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]
利⽤ hasOwnProperty 判断是否存在对象属性

⽅法⼋、利⽤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,undefin
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]

⽅法九、利⽤递归去重


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(index - 1); //递归loop,然后数组去重
}
}
loop(len-1);
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a",

⽅法⼗、利⽤Map数据结构去重

function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组⽤于返回结果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有该key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果没有该key值
array .push(arr[i]);
}
}
return array ;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefi
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a",

⽅法⼗⼀、利⽤reduce+includes

function unique(arr){
return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cu
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin
console.log(unique(arr));
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {

⽅法⼗⼆、[…new Set(arr)]

[...new Set(arr)]
//代码就是这么少----(其实,严格来说并不算是⼀种,相对于第⼀种⽅法来说只是简化了代码)

75 (设计题)想实现⼀个对⻚⾯某个节点的拖曳?如何做?(使⽤原⽣JS)

  • 给需要拖拽的节点绑定 mousedown , mousemove , mouseup 事件
  • mousedown 事件触发后,开始拖拽
  • mousemove 时,需要通过 event.clientX 和 clientY 获取拖拽位置,并实时更新位置
  • mouseup 时,拖拽结束
  • 需要注意浏览器边界的情况

76 Javascript全局函数和全局变量

全局变量

  • Infinity 代表正的⽆穷⼤的数值。
  • NaN 指示某个值是不是数字值。
  • undefined 指示未定义的值。

全局函数

  • decodeURI() 解码某个编码的 URI 。
  • decodeURIComponent() 解码⼀个编码的 URI 组件。
  • encodeURI() 把字符串编码为 URI。
  • encodeURIComponent() 把字符串编码为 URI 组件。
  • escape() 对字符串进⾏编码。
  • eval() 计算 JavaScript 字符串,并把它作为脚本代码来执⾏。
  • isFinite() 检查某个值是否为有穷⼤的数。
  • isNaN() 检查某个值是否是数字。
  • Number() 把对象的值转换为数字。
  • parseFloat() 解析⼀个字符串并返回⼀个浮点数。
  • parseInt() 解析⼀个字符串并返回⼀个整数。
  • String() 把对象的值转换为字符串。
  • unescape() 对由 escape() 编码的字符串进⾏解码

77 使⽤js实现⼀个持续的动画效果

定时器思路

var e = document.getElementById('e')
var flag = true;
var left = 0;
setInterval(() => {
left == 0 ? flag = true : left == 100 ? flag = false : ''
flag ? e.style.left = ` ${left++}px` : e.style.left = ` ${left--}px`
}, 1000 / 60)

requestAnimationFrame

//兼容性处理
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
var e = document.getElementById("e");
var flag = true;
var left = 0;
function render() {
left == 0 ? flag = true : left == 100 ? flag = false : '';
flag ? e.style.left = ` ${left++}px` :
e.style.left = ` ${left--}px`;
}
(function animloop() {
render();
requestAnimFrame(animloop);
})();

使⽤css实现⼀个持续的动画效果

animation:mymove 5s infinite;
@keyframes mymove {
from {top:0px;}
to {top:200px;}
}
  • animation-name 规定需要绑定到选择器的 keyframe 名称。
  • animation-duration 规定完成动画所花费的时间,以秒或毫秒计。
  • animation-timing-function 规定动画的速度曲线。
  • animation-delay 规定在动画开始之前的延迟。
  • animation-iteration-count 规定动画应该播放的次数。
  • animation-direction 规定是否应该轮流反向播放动画

78 封装⼀个函数,参数是定时器的时间,.then执⾏回调函数

function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}

79 怎么判断两个对象相等?


obj={
a:1,
b:2
}
obj2={
a:1,
b:2
}
obj3={
a:1,
b:'2'
}

可以转换为字符串来判断

JSON.stringify(obj)==JSON.stringify(obj2);//true
JSON.stringify(obj)==JSON.stringify(obj3);//false

80 项⽬做过哪些性能优化?

  • 减少 HTTP 请求数

  • 减少 DNS 查询

  • 使⽤ CDN

  • 避免重定向

  • 图⽚懒加载

  • 减少 DOM 元素数量

  • 减少 DOM 操作

  • 使⽤外部 JavaScript 和 CSS

  • 压缩 JavaScript 、 CSS 、字体、图⽚等

  • 优化 CSS Sprite

  • 使⽤ iconfont

  • 字体裁剪

  • 多域名分发划分内容到不同域名

  • 尽量减少 iframe 使⽤

  • 避免图⽚ src 为空

  • 把样式表放在 link 中

  • 把 JavaScript 放在⻚⾯底部

81 浏览器缓存

浏览器缓存分为强缓存和协商缓存。当客户端请求某个资源时,获取缓存的流程如下
  • 先根据这个资源的⼀些 http header 判断它是否命中强缓存,如果命中,则直接从本地获取缓存资源,不会发请求到服务器;
  • 当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另⼀些 request header验证这个资源是否命中协商缓存,称为 http 再验证,如果命中,服务器将请求返回,但不返回资源,⽽是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源;
  • 强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源; 区别是,强缓存不对发送请求到服务器,但协商缓存会。
  • 当协商缓存也没命中时,服务器就会将资源发送回客户端。
  • 当 ctrl+f5 强制刷新⽹⻚时,直接从服务器加载,跳过强缓存和协商缓存;
  • 当 f5 刷新⽹⻚时,跳过强缓存,但是会检查协商缓存;

强缓存

  • Expires (该字段是 http1.0 时的规范,值为⼀个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间)
  • Cache-Control:max-age (该字段是 http1.1 的规范,强缓存利⽤其 max-age 值来判断缓存资源的最⼤⽣命周期,它的值单位为秒)

协商缓存

  • Last-Modified (值为资源最后更新时间,随服务器response返回)
  • If-Modified-Since (通过⽐较两个时间来判断资源在两次请求期间是否有过修改,如果没有修改,则命中协商缓存)
  • ETag (表示资源内容的唯⼀标识,随服务器 response 返回)
  • If-None-Match (服务器通过⽐较请求头部的 If-None-Match 与当前资源的 ETag 是
    否⼀致来判断资源是否在两次请求之间有过修改,如果没有修改,则命中协商缓存)

82 WebSocket

由于 http 存在⼀个明显的弊端(消息只能有客户端推送到服务器端,⽽服务器端不能主动推送到客户端),导致如果服务器如果
有连续的变化,这时只能使⽤轮询,⽽轮询效率过低,并不适合。于是 WebSocket 被发明出来
 相⽐与 http 具有以下有点
  • ⽀持双向通信,实时性更强;
  • 可以发送⽂本,也可以⼆进制⽂件;
  • 协议标识符是 ws ,加密后是 wss ;
  • 较少的控制开销。连接创建后, ws 客户端、服务端进⾏数据交换时,协议控制的数据包头部较⼩。在不包含头部的情况下,服务端到客户端的包头只有 2~10 字节(取决于数据包⻓度),客户端到服务端的的话,需要加上额外的4字节的掩码。⽽ HTTP 协议每次通信都需要携带完整的头部;
  • ⽀持扩展。ws协议定义了扩展,⽤户可以扩展协议,或者实现⾃定义的⼦协议。(⽐如⽀持⾃定义压缩算法等)
  • ⽆跨域问题。
    • 实现⽐较简单,服务端库如 socket.io 、 ws ,可以很好的帮助我们⼊⻔。⽽客户端也只需要参照 api 实现即可

83 尽可能多的说出你对 Electron 的理解

最最重要的⼀点, electron 实际上是⼀个套了 Chrome 的 nodeJS 程序

所以应该是从两个⽅⾯说开来

  • Chrome (⽆各种兼容性问题);
  • NodeJS ( NodeJS 能做的它也能做)

84 深浅拷⻉

浅拷⻉

  • Object.assign
  • 或者展开运算符

深拷⻉

  • 可以通过 JSON.parse(JSON.stringify(object)) 来解决
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

该⽅法也是有局限性的

  • 会忽略 undefined
  • 不能序列化函数
  • 不能解决循环引⽤的对象

85 防抖/节流

防抖

在滚动事件中需要做个复杂计算或者实现⼀个按钮的防⼆次点击操作。可以通过函数防抖动来实现

// 使⽤ underscore 的源码来解释防抖动

/**
* underscore 防抖函数,返回函数连续调⽤时,空闲时间必须⼤于或等于 wait,func 才会执⾏
* @param {function} func 回调函数
* @param {number} wait 表示时间窗⼝的间隔
* @param {boolean} immediate 设置为ture时,是否⽴即调⽤函数
* @return {function} 返回客户调⽤函数
*/
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
// 现在和上⼀次时间戳⽐较
var last = _.now() - timestamp;
// 如果当前间隔时间少于设定时间且⼤于0就重新设置定时器
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
// 否则的话就是时间到了执⾏回调函数
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
// 获得时间戳
timestamp = _.now();
// 如果定时器不存在且⽴即执⾏函数
var callNow = immediate && !timeout;
// 如果定时器不存在就创建⼀个
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
// 如果需要⽴即执⾏函数的话 通过 apply 执⾏
result = func.apply(context, args);
context = args = null;
}
return result;
};
};

整体函数实现

对于按钮防点击来说的实现

  • 开始⼀个定时器,只要我定时器还在,不管你怎么点击都不会执⾏回调函数。⼀旦定时器结束并设置为 null,就可以再次点击了
  • 对于延时执⾏函数来说的实现:每次调⽤防抖动函数都会判断本次调⽤和之前的时间间隔,如果⼩于需要的时间间隔,就会重新创建⼀个定时器,并且定时器的延时为设定时间减去之前的时间间隔。⼀旦时间到了,就会执⾏相应的回调函数

节流

防抖动和节流本质是不⼀样的。防抖动是将多次执⾏变为最后⼀次执⾏,节流是将多次执⾏变成每隔⼀段时间执⾏
/**
* underscore 节流函数,返回函数连续调⽤时,func 执⾏频率限定为 次 / wait
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗⼝的间隔
* @param {object} options 如果想忽略开始函数的的调⽤,传⼊{leading: false
* 如果想忽略结尾函数的调⽤,传⼊{trailing: false
* 两者不能共存,否则函数不能执⾏
* @return {function} 返回客户调⽤函数
*/
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的时间戳
var previous = 0;
// 如果 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function() {
// 如果设置了 leading,就将 previous 设为 0
// ⽤于下⾯函数的第⼀个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空⼀是为了防⽌内存泄漏,⼆是为了下⾯的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 获得当前时间戳
jsitjc8.com
var now = _.now();
// ⾸次进⼊前者肯定为 true
// 如果需要第⼀次不执⾏函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会⼤于0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果当前调⽤已经⼤于上次调⽤时间 + wait
// 或者⽤户⼿动调了时间
// 如果设置了 trailing,只会进⼊这个条件
// 如果没有设置 leading,那么第⼀次会进⼊这个条件
// 还有⼀点,你可能会觉得开启了定时器那么应该不会进⼊这个 if 条件了
// 其实还是会进⼊的,因为定时器的延时
// 并不是准确的时间,很可能你设置了2秒
// 但是他需要2.2秒才触发,这时候就会进⼊这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调⽤⼆次回调
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启⼀个定时器
// 并且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};

你可能感兴趣的:(前端,前端,javascript,开发语言)