前端常见编程题(一)

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);是浅拷贝。
深拷贝

  1. var newObj = Object.create(oldObj)
  2. 使用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' // 没复制成功
  1. 递归拷贝
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}]
  1. 数组深拷贝
// 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);用于连接多个数组,不会修改原数组,返回一个新数组
  1. 类数组转换为数组
// 类数组定义
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)
  • 0
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值