前言
上一篇文章中详细介绍了JavaScript中重要的概念,涉及到闭包、原型、作用域、执行上下文等知识点,本篇文章将注重总结JavaScript中手写代码的一些面试题。
问题1:JavaScript中模块加载方案有哪些?
- CommonJS:通过require来引入模块,通过module.exports定义模块的输出接口。这种模块加载方案是服务端的解决方案,它是以同步的方式引入模块的,因为在服务端文件都是存储在本地磁盘,所以读取非常快,所以采用同步的方式加载是没有问题的,但是如果在浏览器端,由于模块的加载需要网络,所以采用异步加载方式更加合适。
- AMD:这种方案是采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的我遇见都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js实现了AMD规范。
- CMD:这种方案和AMD方案都是为了解决异步模块加载的问题,sea.js实现了CMD规范,他和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
- ES6提出的方案:使用import和export的形式来导入导出模块。这种方案和上面三种方案不同。
衍生问题1:AMD和CMD的区别是什么?
- 在模块定义时对依赖的处理不同。AMD推崇依赖前置,在定义模块的时候要声明其依赖的模块;CMD推崇就近依赖,只有在用在某个模块的时候才去require。
- 对依赖模块执行时机的处理不同。AMD在依赖模块加载完成后直接执行依赖模块,依赖的执行顺序和属性顺序不一定一致。CMD在依赖模块加载完成后并不执行,只是下载。等到所哦呦哦的依赖模块都加载好之后,进入回调的函数逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。
问题2:in和hasOwnProperty的区别
- in操作符:检测指定对象原型链上是否有对应的属性。
- hasOwnProperty:检测指定对象自身上是否有对应的属性值。
- 二者的区别在于in会查找原型链,而hasOwnProperty不会。
问题3:深度克隆
我之前专门写过一篇关于深度克隆的文章:面试官:能否用JavaScript实现深度克隆,大家可以去阅读这篇文章。
function DeepClone(source) {
// 判断目标是数组还是对象
const targetObj = source.constructor === Array ? [] : {}
for (let key in source) {
if (source.hasOwnProperty(key)) {
// 如果是对象就递归
if (source[key] && typeof source[key] === 'object') {
targetObj[key] = source[key].constructor === Array ? [] : {}
targetObj[key] = DeepClone(source[key])
} else {
targetObj[key] = source[key]
}
}
}
return targetObj
}
问题4:如何在ES5的环境下模拟const?
function _const(key, value) {
Object.defineProperty(window, key, {
enumerable: false,
configurable: false,
get() {
return value
},
set(data) {
if (data !== value) {
throw new TypeError('error')
}
return value
}
})
}
_const('a',2)
console.log(a)
问题5:模拟实现call函数
// ES6语法
Function.prototype.call2 = function(content, ...keys) {
content = content || window
content.fn = this
const result = content.fn(...keys)
delete content.fn
return result
}
// ES5语法
Function.prototype.call3 = function(content, ...keys) {
content = content || window
content.fn = this
let args = []
for (let i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
let result = eval('content.fn(' + args + ')')
delete content.fn
return result
}
问题6:模拟实现apply函数
// ES6语法
Function.prototype.apply2 = function(content, keys) {
content = content || window
content.fn = this
const result = content.fn(...keys)
delete content.fn
return result
}
// ES5语法
Function.prototype.apply3 = function(content, keys) {
content = content || window
content.fn = this
let result = eval('content.fn(' + keys + ')')
delete content.fn
return result
}
问题7:模拟实现bind函数
Function.prototype.bind2 = function(content, ...keys) {
content = content || window
const self = this
function fn() {}
let bound = function(...keys2) {
self.apply(
this instanceof self ? this : content,
[].concat(keys, keys2)
)
}
fn.prototype = this.prototype
bound.prototype = this.prototype
return bound
}
var a = '2'
var obj = { a: 1 }
function fn(b, c, d) {
console.log(this.a, b, c, d)
}
var fn2 = fn.bind2(obj, 1, 2)
fn2(4)
可以参考这篇文章:面试官问:能否模拟实现JS的bind方法
问题8:模拟实现new操作符
function newObj(ctor, ...keys) {
if (typeof ctor !== 'function')
throw new Error('ctor is not a function')
// new.target 指向构造函数
newOperator.target = ctor
const obj = Object.create(ctor.prototype)
const result = ctor.apply(obj, keys)
const isObject = result !== null && typeof result !== 'object'
const isFunction = typeof result !== 'function'
if (isObject || isFunction) {
return result
} else {
return obj
}
}
可以参考这篇文章:面试官问:能否模拟实现JS的new操作符
问题9:实现数组的扁平化
对于[1, [1, 2], [1, 2, [4, 4, 4]]]这样多层嵌套的数组,我们如何将其扁平化为[1, 1, 2, 1, 2, 4,4,4]这样的一维数组呢:
// 方法1
const arr = [1, [1, 2], [1, 2, [4, 4, 4]]]
const test = arr.flat(Infinity)
console.log(test) //[1, 1, 2, 1, 2, 4, 4, 4]
// 方法2
const test = JSON.stringify(arr).replace(/(\[|\])/g, '')
console.log('[' + test + ']')
// 方法3
function flat(target) {
return target.reduce(function(arr, item) {
return [].concat(arr, typeof item === 'object' ? flat(item) : item)
}, [])
}
console.log(flat(arr))
问题10:模拟实现promise
class MyPromise {
constructor(executor) {
// 初始化状态
this.state = 'pending'
// 保存成功的值
this.value = undefined
// 保存成功的回调函数
this.onFulfilledCallBacks = []
// 保存失败的值
this.reason = undefined
// 保存失败的回调函数
this.onRejectedCallBacks = []
// 成功后调用的函数
let resolve = value => {
if ((this.state = 'pending')) {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallBacks.forEach(item => item())
}
}
// 失败后调用的函数
let reject = value => {
if ((this.state = 'pending')) {
this.state = 'rejected'
this.reason = value
this.onRejectedCallBacks.forEach(item => item())
}
}
// 如果executor报错,直接reject
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
if (this.state === 'pending') {
this.onFulfilledCallBacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallBacks.push(() => {
onRejected(this.reason)
})
}
}
}
let promise = new MyPromise(function(resolve, reject) {
setTimeout(function() {
reject(213213123)
}, 2000)
})
promise.then(
function(value) {
console.log(value)
},
function(error) {
console.log(error)
}
)
推荐大家阅读这篇文章:BAT前端经典面试问题:史上最最最详细的手写Promise教程
问题11:ES5如何实现继承?
这里就不详细展开说了,本人之前总结过:JS中对象的继承模式
问题12:模拟实现instanceof
function myInstanceOf(leftValue, rightValue) {
if (typeof leftValue !== 'object' || rightValue === null) return false
let rightProto = rightValue.prototype
let leftProto =leftValue.__proto__
while(true){
if(leftProto === null){
return false
}
if(leftProto === rightProto){
return true
}
leftProto = leftProto.__proto__
}
}
具体可以参考这篇文章:聊一聊typeof instanceof 实现原理
最后
以上就是总结的前端面试时手写代码的部分题及答案,最后给大家总结了一些面试题集合,欢迎大家学习!祝大家都能拿到自己心仪的offer! 最近也会陆续总结css和vue相关知识点,欢迎大家关注!!
「面试」45 道牛客网 JavaScript 经典题总结(8500字)
70个JavaScript面试题集锦,内含解答,自测 JS 掌握程度