常见前端手写面试题 ## (一)
1.二分法查找(非递归)
function serch(arr,key){
let min = 0;
let max = arr.length-1;
while(min<=max){
let mid = Math.floor((min+max)/2)
if(arr[mid]==key){
return mid
}else if(arr[mid]>key){
max = mid -1
}else if(arr[mid]<key){
min = mid + 1
}else{
return -1
}
}
}
2.二分法查找(递归)
function search(min,max,arr,key){
let mid = parseInt((max+min)/2)
if(arr[mid]==key){
return mid
}else if(arr[mid]>key){
max = mid-1
search(min,max,arr,key)
}else if(arr[mid]<key){
min = mid+1
search(min,max,arr,key)
}else{
return -1
}
}
3.实现instanceof操作符的效果
function isInstanceOf(left,right){
let prototype = right.prototype
let _proto = left.__proto__
while(_proto){
if(_proto==prototype){
return true
}else{
_proto=_proto.__proto__
}
}
return false
}
4.实现new操作符的效果
function MyNew() {
let obj = {}
let Constructor = [].shift.call(arguments)
obj.__proto__ = Constructor.prototype
let result = Constructor.call(obj, arguments)
return typeof result === 'object'? result : obj
}
5.深拷贝
/* 封装函数deepCopy, 实现深拷贝, 支持的类型有:基本数据类型、对象、数组、日期、正则 */
function deepCopy(obj, cache = new WeakMap()) {
// 判断是否为引用类型
if(!obj instanceof Object) return obj;
// 避免循环引用
if(cache.get(obj)) return cache.get(obj)
// 支持函数
if(obj instanceof Function) {
return function() {
obj.call(this, ...arguments)
}
}
// 支持日期
if(obj instanceof Date) return new Date(obj)
// 支持正则
if(obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
/*
还可以继续添加其他支持的数据类型
*/
// 支持数组和对象
const res = Array.isArray(obj)? [] : {}
// 缓存遍历过的对象
cache.set(obj, res)
// 遍历对象或数组
Object.keys(obj).forEach(key => {
if(obj[key] instanceof Object) {
res[key] = deepCopy(obj[key], cache)
} else {
res[key] = obj[key]
}
})
// 返回最终结果
return res
}
6.节流
/* 封装函数throttle, 实现节流,动作稀释 */
function throttle(func, delay=500) {
let timer = null
let status = false//重在加开关锁
return function (...args) {
if(status) return;
status = true//改变开关的状态
timer = setTimeout(() => {
func.apply(this, args)
status = false
}, delay)
}
}
7.防抖
/* 封装函数debounce, 实现防抖,防止误触 */
function debounce(func, delay=500) {
let timer = null//重在消除时间间隔
return function(...args) {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
8.实现apply函数
/* 在函数原型上封装myApply函数 , 实现和原生apply函数一样的效果 */
Function.prototype.myApply = function(context) {
// 存储要转移的目标对象
_this = context? Object(context) : window
// 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
let key = Symbol('key')
_this[key] = this
// 将数组里存储的参数拆分开,作为参数调用函数
let res = arguments[1]? _this[key](...arguments[1]) : _this[key]()
// 删除
delete _this[key]
// 返回函数返回值
return res
}
// 测试代码
let obj = {
'name': '张三'
}
function showName(first, second, third) {
console.log(first, second, third);
console.log(this.name);
}
showName.myApply(obj, [7, 8, 9])
9.实现call函数
/* 在函数原型上封装myCall函数 , 实现和原生call函数一样的效果 */
Function.prototype.myCall = function(context) {
// 存储要转移的目标对象
let _this = context? Object(context) : window
// 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
let key = Symbol('key')
_this[key] = this
// 创建空数组,存储多个传入参数
let args = []
// 将所有传入的参数添加到新数组中
for(let i = 1; i < arguments.length; i++) {
args.push(arguments[i])
}
// 将新数组拆开作为多个参数传入,并调用函数
let res = _this[key](...args)
// 删除
delete _this[key]
// 返回函数返回值
return res
}
// 测试代码
let obj = {
'name': '张三'
}
function showName(first, second, third) {
console.log(first, second, third);
console.log(this.name);
}
showName.myCall(obj, 7, 8, 9)
9.实现bind函数
/* 在函数原型上封装myBind函数 , 实现和原生bind函数一样的效果 */
Function.prototype.myBind = function(context) {
// 存储要转移的目标对象
let _this = context? Object(context) : window
// 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
let key = Symbol('key')
_this[key] = this
// 创建函数闭包
return function() {
// 将所有参数先拆分开,再添加到新数组中,以此来支持多参数传入以及数组参数传入的需求
let args = [].concat(...arguments)
// 调用函数
let res = _this[key](...args)
// 删除
delete _this[key]
// 返回函数返回值
return res
}
}
// 测试代码
let obj = {
'name': '张三'
}
function showName(first, second, third) {
console.log(first, second, third);
console.log(this.name);
}
showName.myBind(obj)([7,8,9])
10.斐波那契数列(递归)
/* 递归实现斐波那契数列 */
function fibonacci1(n) {
if(n === 1 | n === 2) return 1;
return fibonacci1(n - 1) + fibonacci1(n - 2)
}
/* 高效地实现斐波那契数列 */
function fibonacci2(n) {
let arr = [1, 1]
for(let i = 2; i < n; i++) {
arr[i] = arr[i - 1] + arr[i - 2]
}
return arr[n - 1]
}
// 测试两种函数的效率
let start1 = Date.now()
console.log(fibonacci1(40))
let end1 = Date.now()
let start2 = Date.now()
console.log(fibonacci2(40));
let end2 = Date.now()
console.log(`
低效率所用时间:${end1 - start1} ms
高效率所用时间:${end2 - start2} ms
`);
10.发布订阅者模式
/* 简单实现一下发布订阅者模式 */
class Subect {
constructor(name) {
this.name = name // 被观察者的名字
this.message = '今天是晴天' // 存放一个值
this.observers = [] // 存放所有观察者
}
on(observer) {
this.observers.push(observer)
}
triggle(data) {
this.message = data
this.observers.forEach(o => o.update(data))
}
}
class Observer{
constructor(name) {
this.name = name
}
update(newDate) {
console.log(`我是观察者${this.name}:${newDate}`);
}
}
// 测试代码
let subject = new Subect('message')
let o1 = new Observer('小红')
let o2 = new Observer('小明')
subject.on(o1)
subject.on(o2)
subject.triggle('明天会下雨')
11.数组扁平化(四种方式)
/* 数组扁平化就是将多维数组转成一维数组 */
// 多维数组
let arr = [1, 2, [3, 4, [6, 7]]]
// 第一种方法:利用 flat() 函数
function flatArr1(arr) {
return arr.flat(Infinity)
}
// 第二种方法: 正则匹配
function flatArr2(arr) {
return JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']')
}
// 第三种方法:利用 reduce() 遍历所有的元素
function flatArr3(arr) {
return arr.reduce((i, j) => {
return i.concat(Array.isArray(j)? flatArr3(j) : j)
}, [])
}
// 第四种方法:直接使用递归函数
function flatArr4(arr) {
let new_arr = []
function innerArr(v) {
for(let i in v) {
let item = v[i]
if(Array.isArray(item)) {
innerArr(item)
} else {
new_arr.push(item)
}
}
}
innerArr(arr)
return new_arr
}
// 方法测试
console.log(flatArr1(arr));
console.log(flatArr2(arr));
console.log(flatArr3(arr));
console.log(flatArr4(arr));
12.数组去重(六种方式)
/* 数组去重:让数组所有元素都独一无二,没有重复元素 */
// 创建一个含有重复元素的数组
let arr = [1, 1, 2, 3, 3, 6, 7, 2, 9, 9]
// 第一种方法:利用 Set数据结构 + Array.from() 函数
function removeRepeat1(arr) {
return Array.from(new Set(arr))
}
// 第二种方法: 利用 Set数据结构 + ...扩展运算符
function removeRepeat2(arr) {
return [...new Set(arr)]
}
// 第三种方法: 利用 indexOf 函数
function removeRepeat3(arr) {
let new_arr = []
for(let i in arr) {
let item = arr[i]
if(new_arr.indexOf(item) === -1) {
new_arr.push(item)
}
}
return new_arr
}
// 第四种方法: 利用 includes 函数
function removeRepeat4(arr) {
let new_arr = []
for(let i in arr) {
let item = arr[i]
if(!new_arr.includes(item)) {
new_arr.push(item)
}
}
return new_arr
}
// 第五种方法: 利用 filter 函数
function removeRepeat5(arr) {
return arr.filter((value, index) => {
return arr.indexOf(value) === index
})
}
// 第六种方法: 利用 Map 数据结构
function removeRepeat6(arr) {
let map = new Map()
let new_arr = []
for(let i in arr) {
let item = arr[i]
if(!map.has(item)) {
map.set(item, true)
new_arr.push(item)
}
}
return new_arr
}
// 测试方法
console.log(removeRepeat1(arr));
console.log(removeRepeat2(arr));
console.log(removeRepeat3(arr));
console.log(removeRepeat4(arr));
console.log(removeRepeat5(arr));
console.log(removeRepeat6(arr));
13.获取对象指定层次的所有键值
/* 返回对象obj中第level层次的所有键值 */
let obj = {
name: {
a: 1,
b: 2,
c: {
o: 9,
p: 10,
q: 11
}
},
age: {
m: 3,
n: 4
}
}
function key(o,level){
let arr = []
function from(ob,l){
Object.keys(ob).forEach(key=>{
if(arr[l]) arr[l].push(key);
else{
arr[l] = [key]
}
if(l!==level-1){
from(ob[key],l+1)
}
})
}
from(o,0)
return arr[level-1]
}
// 测试代码
// 第一层:['name', 'age']
// 第二层:['a', 'b', 'c', 'm', 'n']
// 第三层:['o', 'p', 'q']
console.log(key(obj, 1));
console.log(key(obj, 2));
console.log(key(obj, 3));
14.函数柯里化(三种方式)
/* 函数柯里化:将一个接收多个参数的函数变为接收任意参数返回一个函数的形式,便于之后继续调用,直到最后一次调用,才返回结果值
例子:有一个add函数,用于返回所有参数的和,add(1, 2, 3, 4, 5) 返回的是15
现在要将其变为类似 add(1)(2)(3)(4)(5) 或者 add(1)(2, 3, 4)(5) 的形式,并且功能相同
*/
// 普通的 add() 函数
function add() {
let sum = 0
let args = [...arguments]
for(let i in args) {
sum += args[i]
}
return sum
}
/* 第一种add()函数柯里化方式
缺点:最后返回的结果是函数类型,但会被隐式转化为字符串,调用toString方法
*/
function add1() {
// 创建数组,用于存放之后接收的所有参数
let args = [...arguments]
function getArgs() {
args.push(...arguments)
return getArgs
}
getArgs.toString = function() {
return args.reduce((a, b) => {
return a + b
})
}
return getArgs
}
/* 第二种add()函数柯里化方式
缺点:需要在最后再自调用一次,即不传参调用表示已没有参数了
*/
function add2() {
let args = [...arguments]
return function() {
if(arguments.length == 0) {
return args.reduce((a, b) => {
return a + b
})
} else {
let _args = [...arguments]
for(let i = 0; i < _args.length; i++) {
args.push(_args[i])
}
return arguments.callee
}
}
}
/* 第三种add()函数柯里化方式
缺点:在刚开始传参之前,设定总共需要传入参数的个数
*/
function curry(length) {
let args = [...arguments].slice(1)
return function() {
args = args.concat([...arguments])
if(arguments.length < length) {
return curry.apply(this, [length - arguments.length].concat(args))
} else {
return args.reduce((a, b) => a + b)
}
}
}
// 测试代码
let res = add(1, 2, 3, 4, 5)
let res1 = add1(1)(2)(3)(4)(5)
let res2 = add2(1)(2, 3, 4)(5)()
let res3 = curry(5)
console.log(res);
console.log(res1);
console.log(res2);
console.log(res3(1)(2, 3)(4)(5));
15.排序
/*冒泡排序 */
//存在循环嵌套,时间复杂度为O(n^2)
Array.prototype.bubbleSort = function(){
for(let i = 0;i<this.length-1;i++){
for(let j=0;j<=this.length-1-i;j++){
if(this[j]<this[j+1]){
const temp = this[j+1]
this[j] = this[j+1]
this[j+1] = temp
}
}
}
}
/*
插入排序:从数组第二系那个开始选取,与前面的值进行比较,
若选取的元素比前面某一个元素小,则查到这个元素前面,较大的
元素放到后面去
*/
Array.prototype.insertionSort = function(){
for(let i = 0;i<this.length;i++){
const temp = this[i]
let j = i
while(j>0){
if(this[j-1]>temp){
this[j] = this[j-1]
}else{
break;
}
j-=1
}
this[j] = temp
}
}
//时间复杂度同样是O(n^2)
/*
归并排序的思路:分:将数组分成两半,在递归对子数组进行“分”操作
直到分成一个个单独的数
合:把两个数合并成有序数组,再对有序数组进行合并
直到全部子数组合并为一个完整数组
*/
Array.prototype.mergeSort = function(){
const rec = (arr) =>{
if(arr.length==1){return arr;}
const mid = Math.floor(arr.length/2)
const left = arr.slice(0,mid);
const right = arr.slice(mid,arr.length)
const orderLeft = rec(left)
const orderRight = rec(right)
const res= []
while(orderLeft.length||orderRight.length){
if(orderLeft.length&&orderRight.length){
res.push(orderLeft[0]<orderRight[0]?orderLeft.shift():orderRight.shift())
}else if(orderLeft.length){
res.push(orderLeft.shift())
}else if(orderRight){
res.push(orderRight.shift())
}
}
return res
};
const res = rec(this)
res.forEach((n,i) => {
this[i] = n
});
}
//分的时间复杂度为O(logN)
//合的时间复杂度为O(n)
//因此总时间复杂度为O(n*logN)
/*
快排思路:分区:从数组中任意选择一个基准,所有比基准小的放在基准前面
比基准大的放在基准后面
递归:递归地对基准前后的子数组进行分区
*/
Array.prototype.quickSort = function(){
const rec = (arr)=>{
if(arr.length===1) {return arr;}
const left = []
const right = []
const mid = arr[0]
for(let i = 0;i<this.length;i++){
if(arr[i]<mid){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return [...rec(left),mid,...rec(right)]
}
const res = rec(this)
//将排好序的结果数组按下标放回原数组,相当于复制
res.forEach((n,i) => {
this[i] = n
});
}
//递归时间复杂度为O(logN)
//分区操作时间复杂度为O(n)
//整体时间复杂度为O(n*logN)
/*
选择排序的思路:找到数组中最小值,选中并将其放置在第一位
接着找到第二小的值放在第二位,以此类推,执行n-1轮
*/
Array.prototype.selectSort = function(){
for(let i =0;i<this.length-1;i++){
let indexMin = i
for(let j = i;j<this.length;j++){
if(this[j]<this[indexMin]){
indexMin = j
}
}
if(indexMin!==i){
const temp = this[0]
this[0] = this[indexMin]
this[indexMin] = temp
}
}
}
//时间复杂度和冒泡排序一样,性能欠佳
15.数组的map方法
Array.prototype.myMap = function(fn, thisValue) {
let res = []
thisValue = thisValue||[]
let arr = this
for(let i in arr) {
res.push(fn(arr[i]))
}
return res
}
16.手写ajax
**步骤
创建 XMLHttpRequest 实例
发出 HTTP 请求
服务器返回 XML 格式的字符串
JS 解析 XML,并更新局部页面
不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。
了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。**
function ajax() {
let xhr = new XMLHttpRequest() //实例化,以调用方法
xhr.open('get', 'https://www.google.com') //参数2,url。参数三:异步
xhr.onreadystatechange = () => { //每当 readyState 属性改变时,就会调用该函数。
if (xhr.readyState === 4) { //XMLHttpRequest 代理当前所处状态。
if (xhr.status >= 200 && xhr.status < 300) { //200-300请求成功
let string = request.responseText
//JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
let object = JSON.parse(string)
}
}
}
request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
}
promise 版本:
function ajax(url) {
const p = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status <= 300) {
resolve(JSON.parse(xhr.responseText))
} else {
reject('请求出错')
}
}
}
xhr.send() //发送hppt请求
})
return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
.catch(reason => console.log(reason))