js面试题集

本文最后更新于:2022年5月31日 中午

  • 执行顺序
setTimeout(function () {
	console.log("setTimeout");
});

new Promise(function (resolve) {
	console.log("promishe");
}).then(function () {
	console.log("then");
});

console.log("console");

//执行结果
//promise
//console
//then
//setTimeout

setTimeout(function () {
	console.log("1");
});

new Promise(function (resolve) {
	console.log("2");
	resolve();
}).then(function () {
	console.log("3");
});

console.log("4");
//2
//4
//3
//1
//then是Promise的微任务,new Promise
  • 浅拷贝

    1. Object.assign()
    2. 展开运算符
  • 深拷贝

    1. JSON.parse(JSON.stringify(source))

      • 先 JSON.stringify 转成字符串,再 JSON.parse 把字符串转换成新的对象

      • 遇到 Symbol 和函数则报错

    2. 手动实现 deepClone

    const isObject = (obj) => obj !== null && typeof obj === "object";
    
    function deepClone(source, cache = new WeakSet()) {
    	// WeakSet只能保存对象引用!!且弱引用
    	if (!isObject(source) || cache.has(source)) return source;
    	const constructor = source.constructor;
    	const res = constructor === Object ? {} : new constructor(source);
    	// 用constructor判断处理其他复杂类型的对象
    	// eg:Date对象如果使用typeof 返回object
    
    	// 加入缓存中
    	// 处理循环引用
    	cache.add(source);
    
    	Reflect.ownKeys(source).forEach((k) => {
    		res[k] = deepClone(source[k], cache);
    	});
    
    	return res;
    }
  • for…in /for…of

    • for…in 语句以任意顺序遍历一个对象的除 Symbol 以外的可枚举属性,除了遍历对象中的元素之外,还回去遍历自定义的属性,甚至原型链上的属性
      javascript const o = {a:1,b:2} Object.defineProperty(o,'c',{enumerable:false, value:30}) for(let i in o){ console.log(i) } // a // b

    • for…of 则只调用可迭代对象的[Symbol.Iterator]接口遍历对象

    Array.prototype.extraFn = function () {
    	return "extraFn";
    };
    var myArray = ["1", { name: "mmm" }, 4, 5, 6, 7];
    myArray.name = "啦啦啦";
    
    for (var value of myArray) {
    	console.log(value);
    }
    //1 {name: "mmm"} 3 4 5 6 7
    for (var index in myArray) {
    	console.log(myArray[index]);
    }
    // 1
    // { name: 'mmm' }
    // 4
    // 5
    // 6
    // 7
    // 啦啦啦
    // [Function]
  • 阻止冒泡和默认事件

function stopBubble(e) {
	if (e && e.stopPropagation) {
		e.stopPropagation();
	} else window.event.cancelBubble = true;
}

function stopDefault(e) {
	if (e && e.preventDefault) e.preventDefault();
	else window.event.returnValue = false;
	return false;
}
  • for 循环实现遍历方法

    • for 循环实现 forEach

      Array.prototype._forEach = function (callback, context) {
      	if (this == null) {
      		throw new TypeError("this is null or undefined");
      	}
      	if (typeof callback !== "function") {
      		throw new TypeError(callback + "must be a function");
      	}
      	context = context || this;
      	for (let i = 0; i < this.length; i++) {
      		if (this.hasOwnProperty(i)) {
      			callback.call(context, this[i], i, this);
      		}
      	}
      };
    • for 循环实现 map

      Array.prototype._map = function (callback, thisArg) {
      	if (this == null) {
      		throw new TypeError("this is null or undefined");
      	}
      	if (typeof callback !== "function") {
      		throw new TypeError(callback + "must be a function");
      	}
      	const result = [];
      	thisArg = thisArg || this;
      	for (let i = 0; i < this.length; i++) {
      		if (this.hasOwnProperty(i)) {
      			result[i] = callback.call(thisArg, this[i], i, this);
      		}
      	}
      	return result;
      };
    • for 循环实现 filter

      Array.prototype._filter_ = function (callback, thisArg) {
      	if (this == null) {
      		throw new TypeError("this is null or undefined");
      	}
      	if (typeof callback !== "function") {
      		throw new TypeError(callback + "must be a function");
      	}
      	const result = [];
      	thisArg = thisArg || this;
      	for (let i = 0; i < this.length; i++) {
      		if (this.hasOwnProperty(i)) {
      			if (callback.call(thisArg, this[i], i, this)) {
      				result.push(this[i]);
      			}
      		}
      	}
      	return result;
      };
    • for 循环实现 some

      Array.prototype._some = function (callback, thisArg) {
      	if (this == null) {
      		throw new TypeError("this is null or undefined");
      	}
      	if (typeof callback !== "function") {
      		throw new TypeError(callback + "must be a function");
      	}
      	thisArg = thisArg || this;
      	for (let i = 0; i < this.length; i++) {
      		if (this.hasOwnProperty(i)) {
      			if (callback.call(thisArg, this[i], i, this)) {
      				return true;
      			}
      		}
      	}
      	return false;
      };
    • for 循环实现 every

      Array.prototype._every = function (callback, thisArg) {
      	if (this == null) {
      		throw new TypeError("this is null or undefined");
      	}
      	if (typeof callback !== "function") {
      		throw new TypeError(callback + "must be a function");
      	}
      	thisArg = thisArg || this;
      	for (let i = 0; i < this.length; i++) {
      		if (this.hasOwnProperty(i)) {
      			if (!callback.call(thisArg, this[i], i, this)) {
      				return false;
      			}
      		}
      	}
      	return true;
      };
    • for 循环实现 reduce

      Array.prototype._reduce = function (callback, initialValue) {
      	if (this == null) {
      		throw new TypeError("this is null or undefined");
      	}
      	if (typeof callback !== "function") {
      		throw new TypeError(callback + "must be a function");
      	}
      	let res;
      	let initialIndex = 0;
      	if (!initialValue) {
      		for (; initialIndex < this.length; initialIndex++) {
      			// 未指定初始值则自行查找第一个非空下标
      			if (this.hasOwnProperty(initialIndex)) {
      				res = this[i];
      				break;
      			}
      		}
      	} else {
      		res = initialValue;
      	}
      	for (let j = initialIndex + 1; j < this.length; j++) {
      		res = callback.call(null, res, this[j], j, this);
      	}
      	return res;
      };
  • reduce 实现遍历方法

    • reduce 实现 forEach
    Array.prototype._forEach = function (callback, thisArg) {
    	if (this == null) {
    		throw new TypeError("this is null or undefined");
    	}
    	if (typeof callback !== "function") {
    		throw new TypeError(callback + "must be a function");
    	}
    	/*无返回*/ this.reduce((pre, acc, index) => {
    		return [...pre, callback.call(thisArg, acc, index, this)];
    	}, []);
    };
    • reduce 实现 map
    Array.prototype._map = function (callback, thisArg) {
    	if (this == null) {
    		throw new TypeError("this is null or undefined");
    	}
    	if (typeof callback !== "function") {
    		throw new TypeError(callback + "must be a function");
    	}
    	/*有返回*/ return this.reduce((pre, acc, index) => {
    		return pre.concat(callback.call(thisArg, acc, index, this));
    	}, []);
    };
    • reduce 实现 filter
    Array.prototype._filter = function (callback, thisArg) {
    	if (this == null) {
    		throw new TypeError("this is null or undefined");
    	}
    	if (typeof callback !== "function") {
    		throw new TypeError(callback + "must be a function");
    	}
    	return this.reduce((pre, acc) => {
    		return callback.call(thisArg, acc, index, this) ? [...pre, cur] : [...pre];
    	}, []);
    };
  • 数组的所有排列组合

    function exchange(arr) {
    	//[[1,2,3],[4,5,6],[7,8,9]]
    	// 每个数组中取一个值组成新的数字
    	// 列出所有的排列可能性
    	let len = arr.length;
    	if (len >= 2) {
    		let len1 = arr[0].length;
    		let len2 = arr[1].length;
    		let lenBoth = len1 * len2;
    		let items = new Array(lenBoth);
    		let index = 0;
     // 每次选择前两个子数组做出所有组合,转化为一个数组
    		for (let i = 0; i < len1; i++) {
    			for (let j = 0; j < len2; j++) {
    				items[index] = [].concat(arr[0][i]).concat(arr[1][j]);
    				index++;
    			}
    		}
    		// 求出前两个数组的组合
    		// 接下来调整数组,递归调用
    
    		// 拼接剩余参数数组
    		return exchange([items, ...arr.slice(2)]);
    	} else {
    		return arr[0];
    	}
    }
  • 属性名表达式

let a = { a: 10 };
let b = { b: 10 };
let obj = {
  a: 10,
};
obj[b] = 20;
console.log(obj[a]); // 20

//obj.b和obj[b]的不同
obj[需要是已经声明的变量名]
obj.则不受限制

obj.c = 20 ===> obj:{c:20,a:10}
obj[c] //报错

//原题中obj[b]后打印得到
obj = {
  a:10,
  '[object:object]' : 20
  //b = { b: 10 } ===> '[object:object]'
}
后再访问obj[a]时
也就是访问obj['[object:object]'] = 20
  • this 的指向
var a = 10;
var obj = {
	a: 20,
	say: function () {
		console.log(this.a);
	},
};
obj.say();

// 打印出10的方法
// 1
var a = 10;
var obj = {
	a: 20,
	say: () => {
		console.log(this.a);
	},
};
obj.say();

// 2
var a = 10;
var obj = {
	a: 20,
	say: function () {
		console.log(this.a);
	},
};
let say = obj.say;
say();

// 3
var a = 10;
var obj = {
	a: 20,
	say: function () {
		console.log(this.a);
	},
};
obj.say.call(this);
  • js 执行顺序
// js执行顺序

// 1
console.log("script start"); // 1

setTimeout(function () {
	console.log("setTimeout"); // 4
}, 0);

Promise.resolve()
	.then(function () {
		console.log("promise1"); //2
	})
	.then(function () {
		console.log("promise2"); //3
	});

// 2
console.log("1"); //1

setTimeout(function () {
	console.log("2"); // 5
	process.nextTick(function () {
		console.log("3"); //7
	});
	new Promise(function (resolve) {
		console.log("4"); //6
		resolve();
	}).then(function () {
		console.log("5"); //8
	});
});
process.nextTick(function () {
	console.log("6"); //3
});
new Promise(function (resolve) {
	console.log("7"); //2
	resolve();
}).then(function () {
	console.log("8"); //4
});

setTimeout(function () {
	console.log("9"); //9
	process.nextTick(function () {
		console.log("10"); //11
	});
	new Promise(function (resolve) {
		console.log("11"); //10
		resolve();
	}).then(function () {
		console.log("12");
	}); //12
});

// 3
async function async1() {
	console.log(1); //1
	new Promise(() => {
		console.log(2); //2
	});
	Promise.resolve().then(() => {
		console.log("3-1"); //6
	});
	console.log("3-2"); //3
}

Promise.resolve().then(() => {
	console.log(4); //5
});

setTimeout(() => {
	console.log(5); //7
});

async1();

console.log(6); //4
  • js 执行优先级
function Foo() {
	getName = function () {
		console.log(1);
	};
	return this;
}
Foo.getName = function () {
	console.log(2);
};
Foo.prototype.getName = function () {
	console.log(3);
};
var getName = function () {
	console.log(4);
};
function getName() {
	console.log(5);
}

Foo.getName();
getName();
Foo().getName();
// function changeThis(){
//   return this
// }
// changeThis().fn666 = ()=>{
//   console.log(666);
// }
// global.fn666()
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
  • 实现一个 setTimeout
function _setTimeout(cb, timeout, ...args) {
	const start = +new Date();
	function loop() {
		const timer = window.requestAnimationFrame(loop);
		const now = +new Date();
		if (now - start >= timeout) {
			cb.apply(this, args);
			window.cancelAnimationFrame(timer);
		}
	}
	window.requestAnimationFrame(loop);
}
  • 使用 setTimeout 模拟 setInterval
// setInterval 的问题
// 定时往事件队列中添加回调函数,如果主线程运行时间过长到大量回调超时,则有可能同时执行大量异步回调,违背使用意图
function _setInterval(fn,timeout,...args){
  const controller = {
    stop = false
    // 控制器
  }
  function _interval(){
    if(!controller.stop){
      fn.apply(this,args)
      setTimeout(_interval,timeout)
    }
  }
  setTimeout(_interval,timeout)
  return controller
}
  • 模版字符串
const reg = /\{\{(w+)\}\}/g;
const template = "我是{{name}},年龄{{age}},性别{{sex}}";
const data = {
	name: "森下上士",
	age: 18,
};
function render(template, data) {
	if (reg.test(template)) {
		const tem = template.match(reg);
		tem.forEach((key) => {
			const p = key.slice(2, -2);
			// {{xxx}} ==> xxx
			template = template.replace(key, data[p]);
		});
	}
	return template;
}
  • js 获取文件扩展名
function getExt(filename) {
	return filename.split(".").pop();
}
// 按.分割字符串为数组
// pop删除最后一项并返回

function getExt(filename) {
	return filename.replace(/.+\./, "");
}
  • 匿名函数自执行
// 直接在匿名函数之后加()无效
function() {
console.log(arguments[0]+arguments[1])
}(5,6)

//加号
+function(){
    console.log(arguments[0]+arguments[1]);
}(5,6);
//减号
-function(){
    console.log(arguments[0]+arguments[1]);
}(5,6);
//感叹号
!function(){
    console.log(arguments[0]+arguments[1]);
}(5,6);
//括号
(function(){
    console.log(arguments[0]+arguments[1]);
})(5,6);
(function(){
    console.log(arguments[0]+arguments[1]);
}(5,6));
  • 数组的全排列
function permute(arr) {
	if(arr.length < 2) return [arr]
	const res = [];
	// 回溯算法
	function recursion(path, set = new Set()) {
		if (path.length === arr.length) {
			return res.push([...path]); // 潜拷贝一次
		}
		// 使用path记录回溯的路径
		// [1,2,3,4]
		// path[1,2],set[1,2]
		// 1,2,已经用过
		// 分别遍历3,4
		// 添加[1,2,3,4],[1,2,4,3]进结果
		for (let num of arr) {
			if (set.has(num)) continue;
			path.push(num);
			set.add(num);
			recursion(path, set);
			path.pop();
			set.delete(num);
		}
	}
	recursion([], new Set());
	return res;
}
  • 二分查找元素

    试用于顺序数组

function binaryFind(arr, target, s = 0, e = arr.length - 1) {
	const i = Math.floor((s + e) / 2);
	if (arr[i] === target) {
		return i;
	} else if (arr[i] > target) {
		return binaryFind(arr, target, s, i - 1);
	} else if (arr[i] < target) {
		return binaryFind(arr, target, i + 1, e);
	}
	return -1;
}

function binaryFind2(arr, target) {
	let s = 0,
		e = arr.length - 1,
		mid;
	while (s <= e) {
		mid = Math.floor((s + e) / 2);
		if (arr[mid] === target) {
			return mid;
		} else if (arr[mid] > target) {
			e = mid - 1;
		} else if (arr[mid] < target) {
			s = mid + 1;
		}
	}
	return -1;
}
  • 对象扁平化(展开数组
const obj = {
	a: {
		a1: 123,
		a2: {
			a33: /\d+/gi,
		},
	},
	b: {
		b1: [{ xx: "xx" }, 2, 3],
		b2: null,
	},
};
{
  'a.a1': 123,
  'a.a2.a33': /\d+/gi,
  'b.b1[0].xx': 'xx',
  'b.b1[1]': 2,
  'b.b1[2]': 3,
  'b.b2': null
}
const isObject = (obj) => obj !== null && obj.constructor === Object;

function objectFlatten(obj){
	if(!isObject(obj)) return obj

	const res = {}

	function dfs(source,path){
		if(!isObject(source)) res[path] = source
		Object.entries(source).forEach(([key,val])=>{
			path = path ? `${path}.${key}` : key
			if(Array.isArray(val)){
				for(let i = 0 ; i< val.length ;i++){
					dfs(val[i],`${path}[${i}]`)
				}
			}else{
				dfs(val,key)
			}
		})
	}
	dfs(obj,'')
	return res
}
  • 变量连续赋值

    a = b = 3;
    // 一般来说执行顺序是
    b = 3;
    a = b;
    
    // 当遇到`.`操作符,操作优先级会高于赋值
    let a = { name: 666 };
    b = a;
    // b : {name:666}
    a.x = a = { name: 222 };
    // 先执行a.x = {name:222}
    // b : {name:666,x:{name:222}}
    
    // 后改变a指针 a = {name:222}
    
    a.x; // undefined
    b.x; // {name:222}
  • 0.1+0.2==0.3?

js 中一般数字精度有上限(双精度浮点),所以不能用==或者===来比较Number类型.
正确的比较方法:Math.abs(0.1+0.2-0.3)<=Number.EPSILON


js面试题集
http://yoursite.com/2022/02/24/js面试题集/
作者
tatekii
发布于
2022年2月24日
许可协议