自行整理面试知识(JS篇)(三) [算法篇]

严格模式

主要的改变就是不允许其删除不能删除的属性,也不允许删除变量和函数,不能对只读的属性赋值,不能使用转义字符,不能重复定义,不能不定义(以前不定义的话会被绑到全局window上),不能把this绑到全局对象上,不允许使用with,保留eval和argument的关键字

作用就是为以后的js规范化做好铺垫,同时消除js中一些不合理的地方,提高编译器的效率(等于是用复杂一点的开发过程换了性能)

 

 

进入算法世界


数组去重

你可以使用indexOf或者includes,前者无法判断NaN,undefined,后者可以

	let a = [0,2,2,2,2,3,4,5,6,7,8,9];
	let newa = [];
	for (let i of a) {
		if(newa.indexOf(i) == -1)newa.push(i);
	}
	console.log(newa);

你应当知道,indexOf对大小写敏感,若是让你写个对大小写不敏感的去重且根据先后顺序保留大小写呢

	let a = "acjjisodajiACkKLSod";
	let newa = [];
	for (let i of a) {
		if(newa.indexOf(i.toUpperCase()) == -1 && newa.indexOf(i.toLowerCase()) == -1){
			newa.push(i);
		}
	}
	console.log(newa.join(""),a);

前两个的算法复杂度都比较高,最为推荐的是排序双指针(两种sort方式都可行)(两种都会返回数组副本,你可以稍微改动就能不改变原数组了),缺点也很明显,要排序

	let a = "acjjisodajiACkKLSod";
	let newa = [];
	a = a.split("").sort().join("");
	//a = [...a].sort();
	for(let i = 0,j = 0; i < a.length ; i++){
		if(a[i] != a[j]){
			newa.push(a[i]);
			j = i;
		}
	}
	console.log(newa.join(""),a);

不允许排序的情况下,最推荐的是,优化双层循环,对字符串肯定只能使其转化为数组,数组本身不用开辟新空间,除非要求修改副本,想法很简单,从i的第一个值开始检查其后所有值是否有重复的,把除第一个值之外的所有值咔嚓掉

	let a = "acjjisodajiACkKLSod";
	let newa = [...a];
	//a = [...a].sort();
	for(let i = 0; i < newa.length ; i++){
		for(let j = i+1 ; j < newa.length ; j++){
			if(newa[i] == newa[j]) newa.splice(j,1);
		}
	}
	console.log(newa.join(""));

还有一种是set,不谈了,复杂度不清楚,不过大概率我看是没人会让你用的

let newa = [...new Set(a)];

最后一种用空间换时间,标记数组,按照说法是时间复杂度最低的一个,就连判大小写重复都是最快的一个,但是很明显内存也是耗得最多的一个

	let a = "acjjjjjisodajiACkKLSod48115112";
	let newa = [];
	let amark = {};
	for(let i of a){
		if(!amark[i]){
			amark[i] = true;
			newa.push(i);
		}
	}
	console.log(newa.join(""));

深拷贝与浅拷贝

浅拷贝只拷贝第一层,不会进行递归拷贝,也就是修改拷贝对象里的数组,也会修改到原对象里的数组,因为是在第一层直接拿下来的,指针是一样的,内存地址也是一样的

(官方的话是 对于引用类型数据,复制后仍然会被修改)

深拷贝则是递归拷贝,将里面的引用类型数据打开进行深层复制,彻底断开指针。

浅拷贝的原生实现:

	let a = {b:1,c:2,d:3};
	let newa = {};
	for(let i in a){
		if(a.hasOwnProperty(i))newa[i] = a[i];
	}
	console.log(newa);

注意hasOwnProperty 判断该值是否是自身的属性而非来自于父类(继承)

深拷贝的原生实现:

	let a = {b:1,c:2,d:3,e:{f:1,j:2},p:[1,2,3]};
	let newa = {};
	function deepCopy(objold,objnew = {}){
		for(let i in objold){
			let checkType = typeof(objold[i]);
			let checkConstructor = objold[i].constructor ? objold[i].constructor : null ;
			if(!objold.hasOwnProperty(i))continue;
			if(checkType != "object")objnew[i] = objold[i];
			else{
				console.log(checkConstructor,checkType)
				if(checkConstructor === Array){
					objnew[i] = [];
					deepCopy(objold[i],objnew[i]);
				}else{
					objnew[i] = {};
					deepCopy(objold[i],objnew[i]);
				}
			}
		}
		return objnew;
	}
	deepCopy(a,newa);
	console.log(newa);

你应当知道,typeof引用类型可都是object的噢,判断是数组还是对象要用其构造函数类型或者instanceof

神奇的解构赋值全是浅拷贝,包括Object.assign()或者concat

最后如果你学过immutable.js,你可以向面试官秀肌肉啦,immutable.js的本质其实就是深拷贝噢,只不过他特别神奇的把对象内所有的引用类型都拆开了,修改哪个引用类型,就拷贝谁,再把修改后对象的替换原对象并把整个对象返回,避免了对整个对象的拷贝和直接的在原对象上的修改,保证了原对象的不变性。

某些字符串处理/判断问题

回文判断:

两种做法,切半或者逆序(所以切半到底有什么屁用。)

	let str = "strrts";
	str.split("").reverse().join("") === str ? console.log(true) : console.log(false);
	let str = "strrts";
	str.split("").splice(0,str.length/2).reverse().join("") === str.split("").splice(str.length/2,str.length/2).join("") ? 
	console.log(true) : console.log(false);

中横线转驼峰

	let str = "str-rts-lll";
	let newStr = "";
	str.split('-').forEach((item,idx)=>{
		if(idx == 0)newStr = newStr + item;
		else{
			let strTemp = item.charAt(0).toUpperCase() + item.substring(1);
			newStr = newStr + strTemp;
		}
	})
	console.log(newStr);

重复计数(其实跟去重一样啦)

	let str = "str-rts-lll";
	let strmark = {};
	for(let i of str){
		if(!strmark[i]){
			strmark[i] = 1;
		}else{
			strmark[i]++;
		}
	}
	let maxAt,maxNum;
	for(let i in strmark){
		if(!maxAt)maxAt = i,maxNum = strmark[i];
		else{
			if(strmark[i] > maxNum){
				maxAt = i;
				maxNum = strmark[i];
			}
		}
	}
	console.log(maxAt,maxNum);

不借助外界交换变量值

	let a = 1;
	let b = 3;
	[a,b] = [b,a];
	console.log(a,b);
	let a = 3;
	let b = 1;
	a = a^b;
	b = a^b;
	a = a^b;
	console.log(a,b);

1-10000有几个零或者其他?

	let Reg = /0+/;
	let ansNum = 0;
	for(let i = 1 ; i < 10001 ; i++){
		let str = i + "";
		if(!Reg.test(str))continue;
		ansNum = str.split('0').length - 1 + ansNum;
	}
	console.log(ansNum);
	let Reg = /0+/;
	let b = new Array(10000).fill('')
	.map((item,idx) => {return idx+1})
	.filter((item) => {
		return Reg.test(item);
	})
	.reduce((count,item) => {
		return String(item).split('0').length - 1 + count;
	},0)
	console.log(b);

两种方法的速度按照说法第一种是要快一些的,毕竟第二种既要map(本身就比for慢)又要过滤,最后还要累加

去前后空格

	let str = "      hello     "
	let Reg = /(^\s*)|(\s*$)/g;
	console.log(str.replace(Reg,""));

大致就是匹配开头的任何空白字符。匹配结尾的任何空白字符,并全局匹配。最后进行替换

数组降维打击

	let a = [[1,3],[3,4]];
	[b,c] = a;
	console.log(b.concat(c));
	let a = [[1,3],[3,4]];
	let b = Array.prototype.concat.apply([],a);
	console.log(b);

当然你非要用call也可以,只是call不能接受数组参数,你要用循环迭代噢

重复字符串

	let str = "str";
	const repeatStr = (str = "",num = 0) => {
		return new Array(num+1).join(str);
	}
	console.log(repeatStr(str,3));

用str替代分隔符,所以要num+1,毕竟分隔符的个数是num-1;

最后一个,地址参数截取

	let html = "www.baidu.com/?xx=xxxx&xxxx=xxxxxxx&xxxxx=xxxxxx";
	//let url = window.location.href;
	let str = html.substring(html.lastIndexOf("?") + 1);
	let obj = {};
	str.split("&").forEach((item,idx)=>{
		let itemMap = item.split("=");
		let map,value = "";
		if(itemMap.length == 0)return;
		name = decodeURIComponent(itemMap[0]);
		if(itemMap.length != 1){
			value = decodeURIComponent(itemMap[1]);
		}
		obj[name] = value;
	})
	console.log(obj);

要嘛正则呢

/([^?&=]+)=([^?&=]*)/g

非要就长这样子,意思就是前面的name部分必须要,后面的value部分可有可无,但是都不能以?/&/=开头,在replace里面用$1,$2抽就行了

 

 

你可能感兴趣的:(前端,javascrpit)