1. 前言
主要汇总一些目前遇见过的跟前端有关的编程题(面试用),不是算法编程题。
有遗漏的和写错了的,欢迎评论区指正分享。
防抖与节流
防抖
- 用户触发事件过于频繁,只执行最后一次事件的操作。
- 实现:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后在200ms内没有再次触发滚动事件,那么就执行函数;如果在200ms内再次触发了滚动事件,那么当前的计时取消,重新开始计时。
window.onload = function() {
window.document.querySelector('#scroll-box').addEventListener('scroll', debounce(handle, 1000), true);
function handle(){
console.log("触发");
}
function debounce(fn, wait) {
var timer = null;
return function() {
if(timer != null){
clearTimeout(timer);
}
timer = setTimeout(fn, wait);
}
}
}
节流
- 如果在短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。(控制执行次数)
function throttle(fn, delay) {
var timer = null;
var startTime = Date.now();
return function() {
var context = this;
var args = arguments;
var cutTime = Date.now();
var remaining = delay - (curTime - startTime);
clearTimeout(timer);
if(remaining <= 0){
fn.apply(context, args);
startTime = Date.now();
}else {
timer = setTimeout(fn, remaining);
}
}
}
手写apply、call、bind
先讲述下三者的区别。
相同点
三者都可以改变方法this的指向。
不同点
.call()
:参数1为this指向的对象,参数2到参数n为传入方法的所有参数。
.apply()
:参数1为this指向的对象,参数2为一个数组,数组的内容是传入方法的所有参数。
.bind()
:参数与call方法相同。
call
方法和apply
方法会直接执行方法;bind
方法会返回修改this指向后的方法,不会立即执行。
const globalThis = window;
// call
Function.prototype.myCall = function(context = globalThis){
// 设置fn为调用myCall的方法
context.fn = this;
// 获取剩余的参数
const otherArg = Array.from(arguments).slice(1);
//调用这个方法,将剩余参数传递进去
context.fn(otherArg);
//将这个方法的执行结果传给result
let result = context.fn();
//删除这个变量
delete context.fn;
// 返回结果
return result;
}
//方法测试
let testJSON = {
value: 2
};
function getName(name) {
this.value = 1;
console.log("output: " + name + this.value);
}
getName.myCall(testJSON, "any");
// apply
Function.prototype.myApply = function(context = globalThis, arr) {
// 设置fn为调用myApply的方法
context.fn = this;
var result;
if(arr){
result = context.fn(arr);
}else{
result = context.fn();
}
delete context.fn;
return result;
}
// bind
Function.prototype.myBind = function(context = globalThis) {
// 设置fn为调用myBind的方法
context.fn = this;
//获取剩余参数
const otherArg = [...arguments].slice(1);
// 设置返回的新方法
const result = function() {
const resultArg = [...arguments];
//如果通过new 调用,则绑定this为实例对象
if(this instanceof result) {
fn.apply(this, otherArg.concat(resultArg));
}else {
// 否则,以普通形式绑定context
fn.apply(context, otherArg.concat(resultArg));
}
}
// 绑定原型链
result.prototype = Object.create(fn.prototype);
return result;
}
手写instanceof
a instanceof b
用于检查某个构造函数的原型对象b在不在对象a的原型链上。
那么我们就需要沿着对象a的原型链向上查找。
function MyInstanceof(a, b) {
let proto = a.__proto__;
let prototype = b.prototype;
if(proto == null) return false;
else if(proto === prototype) return true;
else return MyInstanceof(proto, b);
}
深拷贝与浅拷贝
深拷贝与浅拷贝的区别
假设源对象是A,拷贝后的对象是B。
总的来说,浅拷贝后的A和B两个对象指向同一个地址,深拷贝后两者指向不同的地址。
具体来说,浅拷贝后,修改B对象内的值后,A对象内对应的值也会改变;而深拷贝则不会。
- 浅拷贝只克隆了最外一层,而深拷贝需要递归拷贝每一层。这只是对于数组、json等对象来说存在区别;对于基本数据类型来说,两者意义相同。
浅拷贝:Object.assign({}, obj1);
是浅拷贝。
深拷贝
var newObj = Object.create(oldObj)
- 使用JSON方法转换
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1 === obj2);// false
console.log(obj1.body === obj2.body) // false
// 但是这种方法会抛弃对象的constructor。
// 也就是深拷贝后,不管这个对象原来的构造函数是什么,深拷贝之后都会变成Object
// 能正确处理的只有Number,String,Boolean,Array,扁平对象。
// RegExp对象无法通过这种方式深拷贝。
// function无法转换成JSON。
var func1 = { fun: function(){ console.log(123); }};
var func2 = JSON.parse(JSON.stringify(func1));
console.log(typeof func1.fun); // 'function'
console.log(typeof func2.fun); // 'undefined' // 没复制成功
- 递归拷贝
var array = [
{ number: 1 }, { number: 2 }, { number: 3 }
];
function myCopy(obj) {
var newObj = obj.constructor === Array ? [] : {};
if(typeof obj != 'object') return;
for(var i in obj){
newObj[i] = typeof obj[i] === 'object' ? myCopy(obj[i]) : obj[i];
}
return newObj;
}
// 方法测试
var copyArray = myCopy(array);
copyArray[0].number = 100;
console.log(array); // [{number:1}, {number:2}, {numer:3}]
console.log(copyArray);// [{number:100}, {number:2}, {numer:3}]
- 数组深拷贝
// slice()
var array = [1, 2, 3, 4];
var copyArray = array.slice();
// array.slice(start, end)返回一个从已有数组中截取一部分元素组成的新数组
// 不改变原数组,两个参数可不写
// concat()
var copyArray = [].concat(array);
var copyArray1 = array.concat([]);
// a.concat(arr1, arr2, ... , arrn);用于连接多个数组,不会修改原数组,返回一个新数组
- 类数组转换为数组
// 类数组定义
var alo = {0: "a", 1: "b", 2: "c", length: 3};
// 类数组是一个对象,拥有length属性,其他属性为非负数
// 例如arguments和DOM方法的返回结果,都是类数组
// 转换方法
var arr = Array.prototype.slice.call(alo);// es5方法
Array.isArray(alo); // false
Array.isArray(arr); // true
var arr1 = Array.from(alo); // es6方法
// 关于扩展运算符 ...
// ... 只适用于具有遍历器接口 Symbol.iterator 的对象
// 如果一个对象没有部署这个接口,就无法转换
合并两个有序数组并去重
function mergeArray() {
let arr = [...arguments];
let result = [];
for (item in arr) {
result = result.concat(arr[item]);
}
// es6方法 用Array.from和Set去重
result = Array.from(new Set(result));
// es5方法 遍历判断
let map = {};
result = result.filter((cur, idx, arr)=> {
const key = (typeof cur) + cur;
if(!map[key]){
map[key] = true;
return true;
}else {
return false;
}
})
return result.sort(function(a, b) {
return a - b;
});
}
mergeArray([1, 2, 7], [8, 3, 4]);
数组扁平化
function flat(array){
let newArray = [];
for(let item in array) {
if(array[item].constructor === Array) newArray = newArray.concat(flat(array[item]));
else newArray.push(array[item]);
}
return newArray;
}
console.log(flat([1, 2, 3, [4, 5, [6, 7]]]))
数组reduce方法实现map方法
Array.prototype.myMap = function (fn, thisArg) {
return this.reduce((acc, cur, index,array) => {
acc.push(fn.call(thisArg, cur,index,array));
return acc
}, [])
}
results = crr.myMap((item, index, array) => {
return item * 2;
})
console.log(results)
手写数组的reduce、map、filter
// reduce
crr = [1, 2, 3, 4];
Array.prototype.myReduce = function(fn){
let array = this;
let acc = arguments[1] === undefined ? array[0] : arguments[1];
for(let index = arguments[1] === undefined ? 1 : 0; index < array.length; index++) {
acc = fn(acc, array[index], index, array);
}
return acc;
}
results = crr.reduce((acc, cur, index, array) => {
return acc + cur;
}, 0);
console.log("ans", crr.myReduce((acc, cur, idx, array)=>{
return acc + cur
}, 0))
柯里化
// 柯里化
function curry(fn, arg) {
let args = arg || [];
return function() {
var _args = [...args, ...arguments];
return _args.length == args.length ? fn.apply(this, _args) : curry.call(this, fn, _args);
}
}
let fnn = curry(function(a, b, c) {
return a + b + c;
})
console.log(fnn("a", "b", "c")());// abc
console.log(fnn("a", "b")("c")());// abc
console.log(fnn("a")("b")("c")());// abc
对象数组转换为树形结构
let objArray = [
{ id: 1, value: 100, fatherNode: null}, { id: 2, value: 70, fatherNode: 1}, { id: 3, value: 30, fatherNode: 1}
];
// 树形结构
// let obj = {
// id: 1,
// value: 100,
// child:[
// {
// id: 2,
// value: 70
// },
// {
// id: 3,
// value: 30
// }
// ]
// };
function toTree(obj) {
let map = {};
let headNode = null;
for(let item of objArray) {
if(map[item.fatherNode] != undefined) {
if(map[item.fatherNode].child == undefined) map[item.fatherNode].child = [];
map[item.fatherNode].child.push(item);
}
if(map[item.id] == undefined) {
map[item.id] = item;
}
if(item.fatherNode == null) headNode = item;
}
return headNode;
}
console.log(toTree(objArray));
JSON的KEY改为驼峰命名
let json = {
"groupId": 1,
"last_change": "abc",
"session": "todo",
"group": {
"group_id": 2,
"last_change": "ac"
},
"array":[
[{"number_integer": 1, "string_count": 2}],
{"session_info": "backto", "back_code": 200},
]
};
function changeName(obj){
for(let item in obj) {
if(Array.isArray(obj[item])){
let arr = [];
for(let it in item) {
arr.push(changeName(it));
}
obj[item] = arr;
}else if(obj[item].constructor == Object) {
obj[item] = changeName(obj[item]);
console.log("changed", obj[item])
}
let wordArr = item.split("_");
for(let i = 1; i < wordArr.length; i++) {
wordArr[i] = wordArr[i].substr(0, 1).toUpperCase() + wordArr[i].slice(1);
}
if(wordArr.length > 1) {
obj[`${wordArr.join("")}`] = obj[item];
delete obj[item];
}
}
return obj;
}
console.log(changeName(json))
手写发布/订阅模式
function EventBus() {
this.events = {};
}
EventBus.prototype.$on = function (name, fn) {
if(typeof fn != 'function') return;
if(this.events[name] == undefined) this.events[name] = [];
this.events[name].push(fn);
}
EventBus.prototype.$emit = function (name, ...args) {
const events = this.events[name];
if(!events || events.length === 0) return;
events.forEach(fn => {
fn(...args);
});
}
let eventBus = new EventBus();
eventBus.$on("sum", function (a, b) {
console.log(a + b);
});
eventBus.$on("mix", function(a, b) {
console.log(a * b);
})
eventBus.$emit("sum", 1, 3)
eventBus.$emit("mix", 1, 3)