系列文章目录
- HTML篇
- CSS篇
- JS篇
- VUE篇
- webpack篇
前言
汇总常见面试题
一、基础知识
(一)数据处理
- 判断数组(类型判定)
Object.prototype.tostring.call()【最佳】 | instanceOf | Array.isArray() 分析优劣 - 数组有哪些方法,哪些能改变原值
改变(7个):push pop shift unshift sort reverse splice
不改变:concat reduce join slice filter forEach… - 多维数组扁平化处理(flat)
将二维甚至多维数组转化为一维数组,例如var arrList = [[1,2,3],4,5,[6,7,[8,9]]]
转化为[1,2,3,4,5,6,7,8,9]
- 第一种:遍历 + 递归
let arrList = [[1, 2, 3], 4, 5, [6, 7, [8, 9]]];
function flat(arr) {
let result = []
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (Array.isArray(item)) {
result = result.concat(flat(item))
} else {
result.push(item)
}
}
return result
}
console.log(flat(arrList))
- 第二种:reduce + 递归
function flat2(arr) {
return arr.reduce((res, item) => {
return res.concat(Array.isArray(item) ? flat2(item) : item)
}, [])
}
console.log(flat2(arrList))
- 数组去重
- 第一种:ES6 Set
function unique(arr){
return Array.from(new Set(arr)) // 或者 return [...new Set(arr)]
}
- 第二种:利用includes 或者 indexOf方法
function unique2(arr) {
let list = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (!list.includes(item)) { // 或者 list.indexOf(item)===-1
list.push(item)
}
}
return list
}
- 第三种:sort 排序 + 相邻元素对比
function unique3(arr) {
arr = arr.sort();
let list = [arr[0]];
for (let i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
list.push(arr[i])
}
}
return list
}
- 第四种: filter 只保留数据在数组中第一次出现的位置
function unique4(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item, 0) === index // 只要item第一次出现的位置值
})
}
- 第五种:利用对象的key,还可统计出现次数
function unique5(arr) {
let obj = {};
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (!obj[item]) {
obj[item] = 1;
} else {
obj[item]++
}
};
return Object.keys(obj);
}
- 伪数组转化为数组
伪数组:不能调用数组的方法
Array.prototype.slice.call(arguments) // 利用数组的slice
Array.from(arguments) // ES6方法
- 实现对象深拷贝
- JSON.parse(JSON.stringify(obj)) 耗时较长,函数、正则、时间对象等无法拷贝。
- Object.assign({}, obj) 适用于一层深拷贝
- lodash cloneDeep(obj)方法
- 遍历+递归拷贝 相对完美
- 取得URL后面query的参数值
- window.location.search + split("&")分割
function getQueryVariable(variable)
{
var query = window.location.search.substring(1); // 获取?号后面的str
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
- 正则匹配
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
(二)原理
- 手写call apply bind实现??
Function.prototype.myBind = function () {
const args = Array.prototype.slice.call(arguments);
const ctx = args.shift(); // 把数组的第一个元素从其中删除,并返回第一个元素的值
const self = this;
return function () {
self.apply(ctx, args) // apply第二个参数是个数组形式
}
}
- new 的实现原理,若构造函数中有返回值怎么办
构造函数正常返回一个实例对象
// 构造函数 Person
function Person() {
this.name = 'haha';
this.age = 12;
}
let xiaoming = new Person();
console.log(xiaoming); // {name: "haha", age: 12}
当构造函数中有返回值时:(返回基本类型时,不影响结果;返回引用类型时,覆盖了原来的实例对象。)
function Person() {
this.name = 'haha';
this.age = 12;
let a =1;
let b = {id: 89};
let c = true;
let d = [4,5,6]
// return a ⇒ 不变,{name: "haha", age: 12}
// return b ⇒ 返回了b: {id: 89}
// return c ⇒ 不变,{name: "haha", age: 12}
// return d ⇒ 返回了d,[4,5,6]
}
let xiaoming = new Person();
console.log(xiaoming);
new 实现原理
根据MDN描述:new关键字会做如下操作:
1、创建一个空的简单JavaScript对象(即{});
2、链接该对象(设置该对象的constructor)到另一个对象 ;
3、将步骤1新创建的对象作为this的上下文 ;
4、如果该函数没有返回对象,则返回this。
function myNew(F) {
let obj = {}; // 第一步:创建空对象实例
obj.__proto__ = F.prototype; // 第二步:保证obj能访问到构造函数F的属性
F.apply(obj, Array.prototype.slice(arguments, 1)); // 第三步:绑定this
return obj instanceof Object ? obj : {}; // 第四步:返回实例对象
}
// 验证
console.log(myNew(Person)) // {name: "haha", age: 12}
- this指向方面,demo题 。this指向的几种情况,立即执行函数的this
函数直接调用(指向window) 对象方法(对象) 构造函数(实例化出来的对象) 计时器调用(全局) 匿名函数(window) 立即执行函数(window)
var fullname = 'John Doe';
var obj = {
fullname: 'Colin Ihrig',
prop: {
fullname: 'Aurelio De Rosa',
getFullname: function() { return this.fullname; }
}
};
console.log(obj.prop.getFullname()); // Aurelio De Rosa
var test = obj.prop.getFullname;
console.log(test()); // John Doe
var myobject={
foo:"bar",
func:function(){
var self=this;
console.log(this.foo); // bar
console.log(self.foo); // bar
(function(){
console.log(this.foo);// undefined
console.log(self.foo);// bar
})();
}
};
myobject.func();
- object.defineProperty可以同时定义get和value 吗?
不可以同时定义get 和value .使用Object.defineProperty() 定义对象属性时,如已设置 set
或 get
, 就不能设置 writable
和 value
中的任何一个了,不然会报如下TypeError
错误:
var i =0;
Object.defineProperty(user, 'age',{
// value: 13,
writable: true,
get: function() {
return i++
}
})
Object.defineProperty(user, 'age',{
^
TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符(value writable)是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
- Object.defineProperty对数组哪里支持不好?
对象数组监听:
function observe(obj) {
Object.keys(obj).forEach((key) => {
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get: () => {
console.log('get');
return value;
},
set: (newValue) => {
console.log('set')
return newValue;
}
})
}
var arr = [1, 2, 3];
observe(arr);
arr.push(4) // push方法无法通过set监听
arr.length = 8; // 更改length无法触发set监听
console.log(arr[0], arr)
// 输出结果:
get
1 [
[Getter/Setter],
[Getter/Setter],
[Getter/Setter],
4,
<4 empty items>
]
- eventloop、宏任务、微任务执行顺序。demo例题
题目一:setTimeout + Promise
console.log('start')
setTimeout(function () {
console.log('setTimeout')
}, 0);
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log('promise1')
}).then(function () {
console.log('promise2')
})
console.log('end')
// 结果:start end promise1 promise2 setTimeout
题目二:setTimeout + Promise
setTimeout(function () {
console.log(2)
}, 0)
new Promise(function (resolve) {
console.log('3')
resolve()
console.log(4)
}).then(function () {
console.log('5')
});
console.log(8)
// 结果:3,4,8,5,2
题目三:setTimeout + Promise + process.nextTick
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
// 第一轮(script宏任务): 1 7 6 8
// 第二轮(第一个setTimeout宏任务):2 4 3 5
// 第三轮(第二个setTimeout宏任务):9 11 10 12
题目四:setTimeout + Promise + async
console.log('script start')
async function async1() {
//async 语法糖 async2()执行完毕 才执行下面 会加入在微观任务里面
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise((resolve) => {
console.log('promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
// 第一轮:'script start' 'async2 end' 'promise' 'script end' 'async1 end' 'promise1' 'promise2'
// 第二轮:'setTimeout'
题目五:setTimeout + Promise + async
async function async1() {
console.log(1);
const result = await async2();
console.log(3);
}
async function async2() {
console.log(2);
}
Promise.resolve().then(() => {
console.log(4);
});
setTimeout(() => {
console.log(5);
});
async1();
console.log(6);
// 结果: 1 2 6 4 3 5 ?
二、ES6+
- es6中的箭头函数用ES5的方式实现
例1
// ES6
const func = (a, b) => a+b;
// ES5
var func = function func(a, b) {
return a + b;
};
例2
// ES6 f1定义时所处函数this是指的obj2, setTimeout中的箭头函数this继承自f1, 所以不管有多层嵌套,this都是obj2
var obj2 = {
say: function () {
var f1 = () => {
console.log(this); // obj2
setTimeout(() => {
console.log(this); // obj2
})
}
f1();
}
}
obj2.say()
// ES5
var obj2 = {
say: function say() {
var _this = this; // 关键步骤
var f1 = function f1() {
console.log(_this); // obj2
setTimeout(function () {
console.log(_this); // obj2
});
};
f1();
}
};
- promise()原理,手写实现。链式调用时返回的对象是一个对象吗?
const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected';
class myPromise {
constructor(executor) { // 传入的executor
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = []; // 存储所有的成功回调
this.onRejectedCallbacks = []; // 存储所有的失败回调
// 应该在 构造函数中添加resolve reject ,写在外面的方法会挂在prototype上
const resolve = (value) => {
if (this.status === PENDING) { // 只有pending状态下才能更改为fulfilled状态
this.status = FULFILLED;
this.value = value;
// 发布:通知所有的成功回调函数执行
this.onFulfilledCallbacks.forEach(fn => fn());
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 发布:通知所有的失败回调函数执行
this.onRejectedCallbacks.forEach(fn => fn());
}
}
// 捕捉执行器抛出的异常
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
onRejected = typeof onRejected === 'function' ? onRejected : ()=>{throw this.reason}
let promise2 = new myPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => { // 将onFulfilled变成异步,resolvePromise能拿到promise2
try {
let x = onFulfilled(this.value); // 这里有可能是一个普通值也可能是一个promise
resolvePromise(promise2, x, resolve, reject); // 外界拿不到promise2 resolve reject
} catch (e) {
reject(e);
}
},0)
}
if (this.status === REJECTED) {
setTimeout(() => { // 将onFulfilled变成异步,resolvePromise能拿到promise2
try {
let x = onRejected(this.reason);; // 这里有可能是一个普通值也可能是一个promise
resolvePromise(promise2, x, resolve, reject); // 外界拿不到promise2 resolve reject
} catch (e) {
reject(e);
}
},0)
}
if (this.status === PENDING) { // pending还没有结果的状态,主要考虑的是executor为异步情况。要使用发布订阅模式,收集回调
// 订阅的过程, 使用了发布订阅的开发模式
this.onFulfilledCallbacks.push(() => { // 等发布的时候,就遍历这个数组执行即可。
try {
let x = onFulfilled(this.value); // 这里有可能是一个普通值也可能是一个promise
resolvePromise(promise2, x, resolve, reject); // 外界拿不到promise2 resolve reject
} catch (e) {
reject(e);
}
})
this.onRejectedCallbacks.push(() => { // 等发布的时候,就遍历这个数组执行即可。
try {
let x = onRejected(this.reason); // 这里有可能是一个普通值也可能是一个promise
resolvePromise(promise2, x, resolve, reject); // 外界拿不到promise2 resolve reject
} catch (e) {
reject(e);
}
})
}
});
return promise2;
}
catch(errCallback) {
return this.then(null, errCallback);
}
}
// 2.3 promise resolution procedure
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) { // 防止循环引用
return reject(new TypeError('chaning cycle detected for promise #<myPromise>'));
}
let isCalled = false;
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try { // x.then可能会抛出一个异常,为了捕获异常并reject
// x 为一个object or function
let then = x.then;
if (typeof then === 'function') { // 认为x是promise对象
then.call(x, (y) => {
if (isCalled) return; // 避免重复调用成功or失败的回调
isCalled = true;
resolvePromise(promise2, y, resolve, reject); // 递归,promise多层嵌套问题
}, (r) => {
if (isCalled) return; // 避免重复调用成功or失败的回调
isCalled = true;
reject(r);
})
} else {
resolve(x)
}
} catch (e) {
if (isCalled) return; // 避免重复调用成功or失败的回调
isCalled = true;
reject(e)
}
} else { // 普通值
resolve(x);
}
}
module.exports = myPromise
- 实现一个普通对象能用for…of遍历
实现效果:
let obj = { name: 'wahaha', age: 12 };
for(let i of obj) {
console.log('i:', i)
}
// wahaha
// 12
给普通对象定义Symbol.iterator
属性,即可用for…of遍历
// 实现某个对象:
obj[Symbol.iterator] = function () {
const _this = this;
const keys = Object.keys(obj);
let index = 0;
return { // 返回迭代器对象
next() {
return {
value: _this[keys[index++]],
done: index>keys.length
}
}
}
}
// 实现全部对象:
Object.prototype[Symbol.iterator] = function () {
const _this = this;
const keys = Object.keys(this);
let index = 0;
return {
next() {
return {
value: _this[keys[index++]],
done: index > keys.length
}
}
}
}
三、其他
- commonjs和es6 import的区别,哪种可以条件引入if
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块 。 - CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 ES6 模块在引入时并不会立即执行,内核只是对其进行了引用,只有在真正用到时才会被执行,这就是“编译时”加载。
所以CommonJS可以和If 配合引入。