一些面经的小知识(二)
1、拷贝
当你把一个对象或数组作为值赋给一个变量时,实际上是将这个对象或数组的引用地址赋给了变量,当这个变量发生了改变对象或数组的操作时,原对象和数组也会受到影响跟着改变,所以我们想到通过拷贝的方式重新复制一份。
大家应该都知道JS拷贝有分浅拷贝和深拷贝~
浅拷贝:
(1)Object.assign
const obj1 = {
a: 1,
b: { c: 2 }
};
const obj2 = Object.assign({}, obj1);
(2)es6解构
const obj1 = {
a: 1,
b: { c: 2 }
};
const obj2 = { ...obj1 };
(3)遍历一次的key、value赋值
(4)数组的话还有slice
但是浅拷贝还是无法解决对象或数组嵌套的问题,这时我们需要考虑深拷贝。
深拷贝:
(1)JSON.stringify和JSON.parse
const obj1 = {
a: 1,
b: { c: 2 }
};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 3;
console.log(obj1);
console.log(obj2);
你会发现obj1和obj2已经独立互不影响了。
是不是用这种方法就可以完美解决问题呢?
其实这种方法,他是会忽略对象中值为undefined的属性的。
const obj1 = {
a: 1,
b: { c: undefined }
};
const obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1);
console.log(obj2);
(2)递归
function clone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === 'object') {
target[i] = clone(source[i]);
} else {
target[i] = source[i];
}
}
}
return target;
}
👆的方法欠缺参数检验、是否是对象的判断不严谨以及没有兼容数组。
其实判断是否是对象常用的方法是Object.prototype.toString.call(obj) === '[object Object]'
如果想了解完美的解决办法,可以看看这里:
其实这种深拷贝方法还有个问题,就是循环引用和层级过深会导致爆栈,这里有篇文章写的很好,大家可以看看:
2、数组去重
(1)利用对象
const arr = [1,2,3,4,2,5,4,6,7,6,8];
const obj = {};
const newArr = [];
arr.forEach((item) => {
if (!obj[item]) {
newArr.push(item);
obj[item] = 1;
}
});
console.log(newArr);
2、利用ES6的Set
const arr = [1,2,3,4,2,5,4,6,7,6,8];
const mySet = new Set(arr);
console.log(mySet);
这样虽然得出了去重后的结果,但还差一步,这还是一个Set,我们得把它转回数组,用什么方法呢?是的,Array.from。
const newArr = Array.from(mySet);
console.log(newArr);
当然数组去重还有其他方法,比如 原数组遍历,检查新数组是否存在即将插入的元素,不存在则插入,存在则不插入,但这会用到indexOf,其实本质也是把新数组遍历了,所以性能没有上面这两种好。
3、如果让你实现一个搜索框,你会考虑到什么?(这里的搜索框就像百度的搜索框,输入后在下面会展示搜索结果)
这道题应该大部分人会想到的是输入关键字触发请求,那用户每次输入都会触发请求的话,无疑对网络、性能、体验都是不好的,所以我们该用节流函数。
简单的节流函数:
function throttle(fn, duration) {
let flag = true;
return function() {
const context = this;
if (flag) {
flag = false;
setTimeout(() => {
fn && fn.apply(context, arguments)
flag = true;
}, duration);
}
}
}
节流函数一般可以用在onresize,onscroll,onmousemove,onmouseover等场景。
诶说到节流函数,那顺便提提防抖函数吧,这两者有什么不一样呢?
我觉得差别在于时间的控制吧,防抖函数也是在设置的时间内只会执行一次,但是如果第二次触发则是会重新计算时间。
function debounce(fn, duration) {
let timer = null;
return function() {
const context = this;
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn && fn.apply(context, arguments);
timer = null;
}, duration);
}
}
回到搜索框的问题上,除了设置节流以外,其实还有个问题。如果用户输入“abc”第一次触发,发出了请求,再继续输入“def”又触发一次,又发出一次请求,但是这两次请求返回顺序是不固定的,这样得到的搜索结果很可能并不是用户想要的,体验会很差。
所以这样需要用到XMLHttpRequest的中止请求abort,当你上一次请求的结果未返回时,先abort中止它,再进行下一次请求。
4、实现拖拽效果,用鼠标拖动一个东西,随意拖动,随意放下。
首先要理解这里不是让我们用html5的drag,然后自己画图想想这里要怎么利用各种坐标距离,再试着用代码实现一下看看。
<html>
<head>
<meta ...>
<title>test</title>
<style>
#box {
width: 100px;
height: 100px;
position: absolute;
top: 0px;
left: 0px;
background-color: #FF0000;
}
</style>
<script>
window.onload = function() {
const box = document.getElementById('box');
box.addEventListener('mousedown', mouseDown);
box.addEventListener('mouseup', mouseUp);
let boxLeft = 0; // 用来保存鼠标点击时 box绝对位置的left
let boxTop = 0; // 用来保存鼠标点击时 box绝对位置的top
let initX = 0; // 用来保存鼠标点击box时 的X坐标
let initY = 0; // 用来保存鼠标点击box时 的Y坐标
let distanceX = 0; // 用来保存box X轴上移动的距离
let distanceY = 0; // 用来保存box Y轴上移动的距离
function mouseDown(e) {
// 鼠标点击box触发
initX = e.pageX;
initY = e.pageY;
boxLeft = e.target.offsetLeft;
boxTop = e.target.offsetTop;
// 这里给body绑定鼠标移动事件,是因为body覆盖整个页面。
// 如果给box绑定鼠标移动事件,当鼠标快速滑动的时候,滑出了box,那事件就会中断了
document.body.addEventListener('mousemove', mouseMove);
}
function mouseUp() {
// 鼠标松开时触发
document.body.removeEventListener('mousemove' mouseMove);
}
function mouseMove(e) {
// 鼠标滑动时触发
distanceX = e.pageX - initX;
distanceY = e.pageY - initY;
box.style.left = boxLeft + distanceX;
box.style.top = boxTop + distanceY;
}
}
</script>
</head>
<body>
<div id="box"></div>
</body>
</html>
5、大数相加
javascript支持的数字范围是(-2^53, 2^53),超过了就会丢失精度。
如果后端返回了两个超大数字符串,我们要怎样将两者相加呢?因为如果转成数字相加,超大数字符串转成数字精度会丢失,相加的结果必然不准确;如果直接字符串相加,那也只是字符串拼接,并不是我们想要的结果。
我们来看一下,其实数字相加原理是每一位相加,然后满十进一。
12345
+ 56789
——————
69134
那我们是不是可以将字符串每一位相加,模拟数字的相加呢?
function add(str1, str2) {
const arr1 = str1.split('').reverse();
const arr2 = str2.split('').reverse();
const res = [];
let flag = 0;
while(arr1.length || arr2.length || flag) {
const num1 = arr1.shift() || 0;
const num2 = arr2.shift() || 0;
const sum = Number(num1) + Number(num2) + flag;
if (sum > 9) {
flag = 1;
res.push(sum % 10);
} else {
flag = 0;
res.push(sum);
}
}
return res.reverse().join('');
}
6、实现一个简单的模版引擎
现在有个模板字符串 '<p>hello,我是{{name}},年龄:{{info.age}}<p>,工作经历:{{info.experience.company}},工作时间:{{info.experience.time}}
',
数据data = {name: 'abc', info: {age: 24, experience: {company: 'abc', time: 'two years'}}}
,如何实现模板的数据替换?
这里关键是replace方法的使用,replace方法的第二个参数可以是个回调函数。
function compile(tpl, data) {
const regex = /\{\{([^}]*)\}\}/g; const string = tpl.trim().replace(regex, function(match, $1) {
if ($1) {
const arr = $1.split('.');
return getValue(data, arr);
} else {
return '';
}
});
console.log(string);
}
function getValue(data, arr) {
let attr;
if (arr.length) {
attr = arr.shift();
return getValue(data[attr], arr);
}
return data;
}
const tpl = '<p>hello,我是{{name}},年龄:{{info.age}}<p>,工作经历:{{info.experience.company}},工作时间:{{info.experience.time}}';
const data = {name: 'abc', info: {age: 24, experience: {company: 'def', time: 'two years'}}};
compile(tpl, data);
7、闭包的作用
闭包作用一是外部可以读取函数内部的变量;二是函数里声明的变量始终保持在内存中,不会在函数调用后被自动清除。
运用场景比如解决遍历索引,函数柯里化,节流防抖,bind等等。
未完待续~~
转载自:https://juejin.cn/post/6844904094050549767