1.多种方式实现数组去重、扁平化、对比优缺点
数组去重
- set和from
let arr = [12,43,23,43,68,12];
let item = new Set(arr);
console.log(item);//结果输出的是一个对象
//使用Array.from转成数组
let arr = [12,43,23,43,68,12];
let item = Array.from(new Set(arr));
console.log(item);// [12, 43, 23, 68]
- set和…
let arr = [12,43,23,43,68,12];
let item = [...new Set(arr)];
console.log(item);//[12, 43, 23, 68]
- indexOf
1 var arr = [2, 8, 5, 0, 5, 2, 6, 7, 2]
2 var newArr = []
3 for (var i = 0; i < arr.length; i++) {
4 if (newArr.indexOf(arr[i]) === -1) {
5 newArr.push(arr[i])
6 }
7 }
8 console.log(newArr) // 结果:[2, 8, 5, 0, 6, 7]
数组扁平化:是指将一个多维数组变为一维数组
[1, [2, 3, [4, 5]]] ------> [1, 2, 3, 4, 5]
**思路:**遍历数组arr,若arr[i]为数组则递归遍历,直至arr[i]不为数组然后与之前的结果concat。
- 遍历数组每一项,若值为数组则递归遍历,否则concat。
- reduce是数组的一种方法,它接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
2)调用数组的toString方法,将数组变为字符串然后再用split分割还原为数组
function flatten(arr) {
return arr.toString().split(',').map(function(item) {
return Number(item);
})
}
- 和上面的toString一样,join也可以将数组转换为字符串
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return parseInt(item);
})
}
- 递归
function flatten(arr) {
var res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
5) 扩展运算符:es6的扩展运算符能将二维数组变为一维
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
2.多种方式实现深拷贝、对比优缺点
数组浅拷贝:// 浅拷贝,拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用
- 还有浅拷贝数组的话可以利用,splice() 和 slice() 这两个方法。他们的区别一个是splice可以改变数组本身,slice不能改变数组本身。
- **Array.from() **方法从一个类似数组或可迭代对象中创建一个新的,浅拷贝的数组实例。
数组深拷贝:// 深拷贝,遍历到到每一项都是值类型时可以直接赋值.
1)
a =[1,2,3];
let b =[]
a.forEach(val => b.push(val));b.push('change');
console.log('a:'+a,'b:'+b)
// 结果VM167:5 a:1,2,3 b:1,2,3,change
- 通过 ** JSON.parse() 和 JSON.stringify() ** 实现对象的深拷贝。但对于值为 undefined 的值不进行拷贝
var test2 = JSON.parse(JSON.stringify(test));
- 递归函数
function deepClone(obj) {
var objClone = Array.isArray(obj) ? [] : {}
if (obj && typeof obj === 'object') {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
//判断obj的子元素是否为object对象,如果是则就递归拷贝
if (obj[key] && typeof obj[key] === 'object') {
objClone[key] = deepClone(obj[key])
} else {
//如果不为对象就直接拷贝
objClone[key] = obj[key]
}
}
}
}
return objClone
}
3.手写函数柯里化工具函数、并理解其应用场景和优势
- 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术以逻辑学家 Haskell Curry 命名的。
// 这是一个接受3个参数的函数
const add = function(x, y, z) {
return x + y + z
}
// 接收一个单一参数
const curryingAdd = function(x) {
// 并且返回接受余下的参数的函数
return function(y, z) {
return x + y + z
}
}
// 调用add
add(1, 2, 3)
// 调用curryingAdd
curryingAdd(1)(2, 3)
// 看得更清楚一点,等价于下面
const fn = curryingAdd(1)
fn(2, 3)
//手写柯里化工具函数
function curry(fn, ...args) {
// 判断参数个数是不是等于原函数参数个数
// 如果是,直接返回调用结果
if ([...args].length >= fn.length) {
return fn(...args);
} else {
// 如果不是,则返回一个函数
return (...params) => {
// 将前面传的全部参数传给curry,回到第一步的if判断,直到参数个数满足要求
return curry(fn, ...args, ...params);
};
}
}
4.手写防抖和节流工具函数、并理解其内部原理和应用场景
节流(throttle)
- 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
- **场景:**scroll滚动事件,每隔特定描述执行回调函数input输入框,每个特定时间发送请求或是展开下拉列表
- 节流重在加锁** flag = false**
function throttle(fn, delay) {
let flag = true,
timer = null
return function(...args) {
let context = this
if(!flag) return
flag = false
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(context,args)
flag = true
},delay)
}
}
防抖(debounce)
- 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
- **场景:**浏览器窗口大小resize避免次数过于频繁登录、发短信等按钮避免发送多次请求、文本编辑器实时保存
- 防抖重在清零 ** clearTimeout(timer)**
function debounce(fn, delay) {
let timer = null
return function(...args) {
let context = this
if(timer) clearTimeout(timer)
timer = setTimeout(function(){
fn.apply(context,args)
},delay)
}
}
总结
- 函数节流:是确保函数特定的时间内至多执行一次。 函数防抖:是函数在特定的时间内不被再调用后执行。
5.实现一个sleep函数
function sleep(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(time)
}, time)
})
}
sleep(5000).then(
(time) => {
console.log(`你睡了${time / 1000}s,然后做你的事情`)
}
)
6.call、apply、bind
call
// 不覆盖原生call方法,起个别名叫myCall,接收this上下文context和参数params
Function.prototype.myCall = function (context, ...params) {
// context必须是个对象并且不能为null,默认为window
const _this = typeof context === "object" ? context || window : window;
// 为了避免和原有属性冲突,定义一个Symbol类型的属性
const key = Symbol();
// call方法的目的是改变函数的this指向,函数的this指向它的调用者,也就是说我们的目标是改变函数的调用者。
// 下面的this就是函数本身,给_this增加一个名为[key]的方法指向this,就能用_this来调用this了
_this[key] = this;
const result = _this[key](...params);
// 获取函数执行结果后,删除以上添加的属性
delete _this[key];
return result;
};
apply:和call的区别在于第二个参数
Function.prototype.myApply = function (context, params) {
return this.myCall(context, ...params);
};
bind:和call的区别在于不立即执行,返回一个函数即可
Function.prototype.myBind = function (context, ...params) {
const _this = this;
// 返回的函数也能接收参数,但是是放在params后面
return function (...args) {
return _this.myCall(context, ...[...params, ...args]);
};
};