前端面试题目——JS

系列文章目录

  1. HTML篇
  2. CSS篇
  3. JS篇
  4. VUE篇
  5. webpack篇


前言

汇总常见面试题


一、基础知识

(一)数据处理

  1. 判断数组(类型判定)
    Object.prototype.tostring.call()【最佳】 | instanceOf | Array.isArray() 分析优劣
  2. 数组有哪些方法,哪些能改变原值
    改变(7个):push pop shift unshift sort reverse splice
    不改变:concat reduce join slice filter forEach…
  3. 多维数组扁平化处理(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))
  1. 数组去重
  • 第一种: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);
}
  1. 伪数组转化为数组

伪数组:不能调用数组的方法

Array.prototype.slice.call(arguments)  // 利用数组的slice
Array.from(arguments)   // ES6方法
  1. 实现对象深拷贝
  • JSON.parse(JSON.stringify(obj)) 耗时较长,函数、正则、时间对象等无法拷贝。
  • Object.assign({}, obj) 适用于一层深拷贝
  • lodash cloneDeep(obj)方法
  • 遍历+递归拷贝 相对完美
  1. 取得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;
}

(二)原理

  1. 手写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第二个参数是个数组形式
    }
}
  1. 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}
  1. 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();
  1. object.defineProperty可以同时定义get和value 吗?

不可以同时定义get 和value .使用Object.defineProperty() 定义对象属性时,如已设置 setget, 就不能设置 writablevalue 中的任何一个了,不然会报如下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函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。

  1. 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>
]
  1. eventloop、宏任务、微任务执行顺序。demo例题

知识点:JS事件循环

题目一: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+

  1. 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();
  }
};
  1. 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
  1. 实现一个普通对象能用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
          }
        }
      }
}  

三、其他

  1. commonjs和es6 import的区别,哪种可以条件引入if
  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
    CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块 。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 ES6 模块在引入时并不会立即执行,内核只是对其进行了引用,只有在真正用到时才会被执行,这就是“编译时”加载。
    所以CommonJS可以和If 配合引入。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值