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
浅拷贝
Object.assign()
- 展开运算符
深拷贝
JSON.parse(JSON.stringify(source))
先 JSON.stringify 转成字符串,再 JSON.parse 把字符串转换成新的对象
遇到 Symbol 和函数则报错
手动实现 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