1. 写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?
知识点:key和就地复合
- 更准确 因为带key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。
- 更快 利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。(这个观点,就是我最初的那个观点。从这个角度看,map会比遍历更快。
2. ['1', '2', '3'].map(parseInt) what & why ?
知识点:map和parseInt
结果:[1,NaN,NaN]
-
parseInt()
-
作用:将一个字符串 string 转换为 radix 进制的整数,radix 为介于2-36之间的数
-
个人记忆:把二进制到36进制的数转换为十进制
-
参数: string:要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。, radix(可选):从 2 到 36,代表该进位系统的数字。例如说指定 10 就等于指定十进位(如果不写默认为十进制;为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理
-
例子
parseInt('111',2) // 7
111是一个2进制的数字转换成10进制为7
['1', '2', '3'].map(parseInt) ===>
['1', '2', '3'].map((item, index) => parseInt(item,index)) ===>
parseInt('1', 0) => 1 parseInt 的第二个参数是0,按照10为基础处理结果是1
parseInt('2', 1) => NaN 第二个参数为1,首先不可以为1,就算为1,sting为‘2’也不对,所以NaN
parseInt('3', 2) => NaN 同上2进制最大为2
再来几题
['10','10','10','10','10'].map(parseInt)
结果:[10,NaN,2,3,4]
3、函数的防抖与节流(juejin.im/post/684490…)
-
防抖
- 仅仅执行最后一次点击,如果持续点击10s,仅仅执行最后一次(节流会执行10次)
- 应用场景:一般可以使用在用户输入停止一段时间过后再去获取数据,而不是每次输入都去获取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>防抖</title>
</head>
<body>
<button id="debounce">点我防抖!</button>
<script>
window.onload = function() {
// 1、获取这个按钮,并绑定事件
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce));
}
// 2、防抖功能函数,接受传参
function debounce(fn, delay = 200) {
// 4、创建一个标记用来存放定时器的返回值
let timeout = null;
return function() {
// 5、每次当用户点击/输入的时候,把前一个定时器清除
clearTimeout(timeout);
// 6、然后创建一个新的 setTimeout,
// 这样就能保证点击按钮后的 interval 间隔内
// 如果用户还点击了的话,就不会执行 fn 函数
timeout = setTimeout(() => {
fn.call(this, arguments);
}, delay);
};
}
// 3、需要进行防抖的事件处理
function sayDebounce() {
// ... 有些需要防抖的工作,在这里执行
console.log("防抖成功!");
}
</script>
</body>
</html>
防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。
结合上面的代码,我们可以了解到,在触发点击事件后,如果用户再次点击了,我们会清空之前的定时器,重新生成一个定时器。意思就是:这件事儿需要等待,如果你反复催促,我就重新计时!
- 节流
- 时间间隔内仅仅执行第一次,持续点击10s会执行10次(防抖只执行1次)
- 比如懒加载时要监听计算滚动条的位置,但不必每次滑动都触发,可以降低计算的频率,而不必去浪费资源;另外还有做商品预览图的放大镜效果时,不必每次鼠标移动都计算位置。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>节流</title>
</head>
<body>
<button id="throttle">点我节流!</button>
<script>
window.onload = function() {
// 1、获取按钮,绑定点击事件
var myThrottle = document.getElementById("throttle");
myThrottle.addEventListener("click", throttle(sayThrottle));
}
// 2、节流函数体
function throttle(fn, delay = 200) {
// 4、通过闭包保存一个标记
let canRun = true;
return function() {
// 5、在函数开头判断标志是否为 true,不为 true 则中断函数
if(!canRun) {
return;
}
// 6、将 canRun 设置为 false,防止执行之前再被执行
canRun = false;
// 7、定时器
setTimeout( () => {
fn.call(this, arguments);
// 8、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
canRun = true;
}, delay);
};
}
// 3、需要节流的事件
function sayThrottle() {
console.log("节流成功!");
}
</script>
</body>
</html>
4. settimeout在async 之后执行
直接拿题练手
先做等会看答案
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
答案:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
再来一题
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
//async2做出如下更改:
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise3');
resolve();
}).then(function() {
console.log('promise4');
});
console.log('script end');
答案:
script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout
5. 下面数组打印结果是什么
for(var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
})
}
10个10
如何才能打印1-9
1、
for(let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
})
}
2、
for(var i = 0; i < 10; i++) {
(function(i) {
setTimeout(() => {
console.log(i)
}, 0)
})(i)
}
你还有多少方法?
6. 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
[...new Set(arr.flat(Infinity))].sort((a,b) => a-b)
知识点:sort和Set、Map flat直接去mdn查就好,讲的很专业
- sort
最开始我得出的答案是[...new Set(arr.flat(Infinity))].sort()发现答案不对
数字排序:sort((a, b) => a - b)升序 sort((a, b) => b - a)降序
字符串排序:如果sort()不写默认按照字符串升序排列所以11,会排在2前面
sort((a, b) => {
if (a > b) return 1
if (a < b) return -1
return 0
}) // 升序
sort((a, b) => {
if (a > b) return -1
if (a < b) return 1
return 0
}) // 降序
- Set、Map
- 持续更新中……
7. 闭包
定义:两个函数,嵌套关系,内部函数还访问了外部函数的变量,形成了一个闭包
作用:私有化数据(全局访问不到,全局不可以修改),保护数据安全,持久化维持数据
闭包与斐波那契的应用
闭包的弊端:
内存泄漏
内存管理
技术,被指像的次数,指向这块内存的数量为0的时候释放
标记清除算法,从window开始找(好,函数里面开辟的空间是找不到的),没有的话就清除,函数里面的,函数调用结束就可以清除,
ret = null;清除闭包(闭包用完不用就释放掉)
8. computed和watch的区别
computed:计算属性
计算属性是由data中的已知值,得到的一个新值。
这个新值只会根据已知值的变化而变化,其他不相关的数据的变化不会影响该新值。
计算属性不在data中,计算属性新值的相关已知值在data中。
别人变化影响我自己。
watch:监听数据的变化
监听data中数据的变化
监听的数据就是data中的已知值
我的变化影响别人
1.watch擅长处理的场景:一个数据影响多个数据
2.computed擅长处理的场景:一个数据受多个数据影响
9. reduce(感谢 www.jianshu.com/p/e375ba1cf… )
计算数组中每个元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
let nameNum = names.reduce((pre,cur)=>{
if(cur in pre){
pre[cur]++
}else{
pre[cur] = 1
}
return pre
},{})
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
数组去重
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
}else{
return pre
}
},[])
console.log(newArr);// [1, 2, 3, 4]
将二维数组转化为一维
let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre,cur)=>{
return pre.concat(cur)
},[])
console.log(newArr); // [0, 1, 2, 3, 4, 5]
将多维数组转化为一维
let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr){
return arr.reduce((pre,cur)=>pre.concat(Array.isArray(cur)?newArr(cur):cur),[])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]
对象里的属性求和
var result = [
{
subject: 'math',
score: 10
},
{
subject: 'chinese',
score: 20
},
{
subject: 'english',
score: 30
}
];
var sum = result.reduce(function(prev, cur) {
return cur.score + prev;
}, 0);
console.log(sum) //60
10、e.target与e.currentTarget的区别
- target指向被单击的对象
- currentTarget指向当前事件活动的对象 先看个例子
<body>
<div id="a">
<div id="b">
<div id="c">
<div id="d"></div>
</div>
</div>
</div>
<script>
document.getElementById('a').addEventListener('click', function ( e ) {
console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('b').addEventListener('click', function ( e ) {
console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('c').addEventListener('click', function ( e ) {
console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('d').addEventListener('click', function ( e ) {
console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
</script>
</body>
不必记什么时候e.currentTarget和e.target相等,什么时候不等,理解两者的究竟指向的是谁即可。
- e.target 指向触发事件监听的对象。
- e.currentTarget 指向添加监听事件的对象
给上面代码a注册事件,currentTarget始终指向了a,点击d的时候,target指向了d,current仍然指向a
11. let arr = ['aecb', 'dcbc', 'fcbg']返回最长的重复字符串cb
let arr = ['aecb', 'dcbc', 'fcbg']
function findStr(arr) {
let str = ''
for(let i = 0; i < arr[0].length; i++) {
str += arr[0][i]
let isTrue = arr.every(v => v.indexOf(str) > 0)
str = isTrue ? str : str.slice(0, str.length - 2)
}
return str
}
findStr(arr)
12 数字字符串补零
addZero: function (num) {
// return num + parseInt(num) < 10 ? '0' : ''
if (parseInt(num) < 10) {
num = '0' + num;
}
return num;
},
13. 用最精炼的代码实现数组非零非负最小值index
例如:[10,21,0,-7,35,7,9,23,18] 输出5, 7最小
(() => {
function getIndex(arr) {
let index;
let n = Infinity;
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (item > 0 && item < n) {
n = item;
index = i;
}
}
return index;
}
let arr = [-1, 21, 0, -7, 35, 7, 9, 23, 18];
console.log(getIndex(arr));
})();
14. 求目标值是由数组元素的哪两个值之和的值、下标(算法)
const arr = [3, 4, 6, 39, 6, 42, 76]
const tag = 81
// 求值:
// 思路:
// 1、遍历数组arr
// 2、给arr的每一项都做一个标注为 true
// 3、如果 target - 数组的这一项 以前存在过也就是为true
// 那就得出这两个值
普通方法:
function getTotal(arr, tag) {
return arr.filter(item => arr.includes(tag - item))
}
优化方法:
function getTotal(array, target) {
var a = {}
for (var i = 0; i < array.length; i++) {
var temp = target - array[i]
if (a[temp] != undefined) return [temp, array[i]]
a[array[i]] = true
}
}
// 求下标
// 求目标值是由数组元素的哪两个值之和的下标(算法)
普通方法:
function getIndex(arr, tag) {
return arr.reduce((acc, cur, index) => {
if (arr.includes(tag - cur)) {
acc.push(index)
}
return acc
}, [])
}
优化方法:
// 只要把原来优化方法里的的true改为i即可
function getTotal(array, target) {
var a = {}
for (var i = 0; i < array.length; i++) {
var temp = target - array[i]
if (a[temp] != undefined) return [a[temp], i]
a[array[i]] = i
}
}
15. 手写一个Promise.all
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('我是p1')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('我是p2')
}, 1000)
})
Promise.myAll = function (promiseArr) {
return new Promise((resolve, reject) => {
const ans = []
let index = 0
for (let i = 0; i < promiseArr.length; i++) {
promiseArr[i].then((res) => {
ans[i] = res
index++
if (index === promiseArr.length) {
resolve(ans)
}
}).catch((err) => reject(err))
}
})
}
Promise.myAll([p1, p2]).then((res) => {
console.log(res)
})
16. 手写一个Promise.race
Promise.myRace = function (promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach((p) => {
// 如果不是Promise实例需要转化为Promise实例
Promise.resolve(p).then(
(val) => resolve(val),
(err) => reject(err)
)
})
})
}
Promise.myRace([p1, p2]).then((res) => {
console.log(res)
})
17. 实现一个深拷贝
我们最常想到的是JSON.parse(JSON.stringify(obj))
但是我们要知道这种方法的弊端
- 如果obj里有时间对象,拷贝后的结果,时间将只是字符串的形式。而不是时间对象。
- 如果obj里的属性值为undefined或函数,拷贝后的结果将会丢失属性
- 如果obj里的属性值有RegExp、Error对象,则序列化的结果将只得到空对象
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
- JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor
- 如果对象中存在循环引用的情况也无法正确实现深拷贝
那么我们手写一个深拷贝(考虑简单类型,对象,数组)
function clone(target) {
if (typeof target !== 'object' || target === null) return target // 考虑简单类型和null
let cloneTarget = Array.isArray(target) ? [] : {} // 考虑数组还是对象
for (const key in target) {
cloneTarget[key] = clone(target[key]) // 考虑嵌套
}
return cloneTarget
}
这个已经基本可以满足我们最基本的要求了,如果感觉还不够,完整版提供给大家
const cloneDeep1 = (target, hash = new WeakMap()) => {
// 对于传入参数处理
if (typeof target !== 'object' || target === null) {
return target
}
// 哈希表中存在直接返回
if (hash.has(target)) return hash.get(target)
const cloneTarget = Array.isArray(target) ? [] : {}
hash.set(target, cloneTarget)
// 针对Symbol属性
const symKeys = Object.getOwnPropertySymbols(target)
if (symKeys.length) {
symKeys.forEach((symKey) => {
if (typeof target[symKey] === 'object' && target[symKey] !== null) {
cloneTarget[symKey] = cloneDeep1(target[symKey])
} else {
cloneTarget[symKey] = target[symKey]
}
})
}
for (const i in target) {
if (Object.prototype.hasOwnProperty.call(target, i)) {
cloneTarget[i] =
typeof target[i] === 'object' && target[i] !== null
? cloneDeep1(target[i], hash)
: target[i]
}
}
return cloneTarget
}
18.数据扁平化
将数据扁平化,例如下面的这个对象
let nestedObject = {
name: '文正',
class: {
name: '一班',
num: 99,
person: {
name: '张三',
gender: '男'
}
},
desc: '水城路'
}
转换成
{
"name": "文正",
"class.name": "一班",
"class.num": 99,
"class.person.name": "张三",
"class.person.gender": "男",
"desc": "水城路"
}
function flattenobject(obj, prefix = '', result = {}) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let newKey = prefix ? `${prefix}.${key}` : key
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
flattenobject(obj[key], newKey, result)
} else {
result[newKey] = obj[key]
}
}
}
return result
}
19 pc端滚动加载
<template>
<el-scrollbar>
<div class="list">
<div v-for="item in list" :key="item.id">
<!-- 列表内容 -->
</div>
<!-- 底部观察元素 -->
<div class="text-center gray-color" ref="bottomRef">{{emergencyList.length >= total ? '加载完成' : '加载更多……'}}</div>
</div>
</el-scrollbar>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const bottomRef = ref(null)
onMounted(() => {
getEmergency()
const observer = new IntersectionObserver((entries) => {
// 当底部元素可见时触发加载
if(entries[0].isIntersecting) {
console.log('触底了')
if (emergencyList.value.length >= total.value) return
console.log('加载数据')
current.value++
getEmergency() // 请求数据
}
})
if (bottomRef.value) {
observer.observe(bottomRef.value)
}
})
const getEmergency = async (clear = false) => {
loading.value = true
const { total: sum, records } = await getEmergencyPage({ current: current.value, size: size.value })
if (clear) {
current.value = 1
emergencyList.value = records
} else {
emergencyList.value = [...emergencyList.value, ...records]
}
total.value = sum
loading.value = false
}
</script>
- 找出字符串中出现次数最多的字母和出现次数
const str = 'abcdddabcdefsaadfdsfa'
let wordObj = {}
function findMaxChar(str) {
let wordObj = {}
for(let i = 0; i < str.length; i++) {
wordObj[str[i]] = (wordObj[str[i]] || 0) + 1
}
let maxChar = ''
let maxCount = 0
for (let char in wordObj) {
if (wordObj[char] > maxCount) {
maxCount = wordObj[char]
maxChar = char
}
}
return { maxChar, maxCount }
}
const result = findMaxChar('abcdddabcdefsaadfdsfa')
console.log(`出现次数最多的单词是 '${result.maxChar}',出现次数是 ${result.maxCount}`)