前端方向优秀面经合集(下)


篇四:字节跳动架构部实习+秋招提前批共八轮面试(均已拿offer)

牛油:@洛霞

 

先进行说明一下,本人刚刚大三结束,去年十二月的时候是投递了字节的视频架构的实习,共三轮技术面+一轮hr面,成功拿到offer实习了五个月。

今年秋招提前批是投了抖音架构,共三轮技术面+一轮hr面,已经成功拿到offer。

由于去年实习面经没有在牛客上发,所以跟着本次提前批面经一共八轮面试一起发了。
面经更多的记录我当时面试的思考和一些相关问题回答的切入点,可能会跟其他面经直接给答案有所不同,其实掌握了切入点再进行扩展就是一份好的回答了~

 

实习一面(50min)

面试我的的是一个二十出头的小哥哥,第一次面试我还是比较紧张的,还好小哥哥待人和蔼可亲,针对不会的问题并没有刁难我,反而给我不少提示(给点个赞)

首先是自我介绍,问了下我学习前端的途径和方法啥的,算是暖了个开场吧,然后是具体的问题。

1. ES6新特性说一下?

我首先说的是let、const,学长就直接让我详细说一下这两个。let、const有三个方面可说:

1.1 let、const声明的变量只在其所在的块代码内有效,隐式截取了作用域,有了块级作用域的概念。

1.2 暂时性死区,变量声明提升但在不会被赋值为`undefined`,不能在声明之前使用。

1.3 不允许重复声明。

然后给了两个例子说一下:

const a = 1

const a = 2

console.log(a)

const b = []

b.push(1)

console.log(b)

 

2. ES6之前的模块引入方式和区别

ES6之前模块引入主要是CommonJS和AMD两种。

然后学长让说明一下CommonJs和ES6模块引入的区别:

2.1. 首先,CommonJS导出值是**浅拷贝**,一旦输出某个值,模块内部的变化就影响不到这个值。而ES6导出是采用实时绑定的方式,是将其内存地址导出,导入是动态加载     模块取值,并且变量总是绑定其所在的模块,不能重新赋值。

2.2. ES6模块化导入是异步导入,CommonJS导入是同步导入。这跟ES6模块通常用于web端,而CommonJS用于服务器端有关。

2.3. CommonJS导入支持动态导入require(`${path}/xx.js`),ES6模块化导入不支持,目前已有草案。

2.4. ES6模块化会编译成require/exports来执行的。

 

3. 一道数组遍历

实现`[1,2,3] => [2,4,6]`,这个太简单了:

function fn(arr) {

return arr.map(item => item * 2)

}

 

4. 字符串算法题

对输入的字符串,去除其中的字符'b'以及连续出现的'a'和'c',例如:

  'aacbd' -> 'ad'

  'aabcd' -> 'ad'

  'aaabbccc' -> ''

可以使用正则:

function fn(str) {

  let res = str.replace(/b+/g, '');

  while(res.match(/(ac)+/)) {

    res = res.replace(/ac/, '')

  }

  return res;

}

 

5. CSS9宫格

问题:创建出CSS9宫格,3*3宫格,每个宫格的长宽未知,要求达到自适应并且精确分配。

学长特意说要精确分配,所以用1/3肯定是不行的,于是使用flex布局:

<!--HTML-->

<div class="container">

  <div class="wrapper">

    <div class="item"></div>

    <div class="item"></div>

    <div class="item"></div>

  </div>

  <div class="wrapper">

    <div class="item"></div>

    <div class="item"></div>

    <div class="item"></div>

  </div>

  <div class="wrapper">

    <div class="item"></div>

    <div class="item"></div>

    <div class="item"></div>

  </div>

</div>

 

<!--CSS-->

.container {

  display: flex;

  flex-direction: column;

  flex-wrap: nowrap;

}

.wrapper {

  display: flex;

  flex: 1;

  flex-direction: row;

  flex-wrap: nowrap;

}

.item {

  flex: 1;

}

 

6. 说下React虚拟DOM

虚拟DOM是用JS模拟的DOM结构,将DOM变化的对比放在JS来做。可以从虚拟DOM的两个优势点来说:

6.1 函数式的UI编程,即UI取决于数据。

6.2 打开了跨平台的大门。提供了统一的JS层对象,根据不同平台制定不同的渲染方式,带来了跨平台渲染的能力。

 

7. 说一下diff算法

创建一个React元素树之后,在更新的时候将创建一个新的React元素树,React使用Diff算法对元素树进行比对,只更新发生了改变的部分,避免多余的性能消耗。

主要是三个思想,可以从这三个谈:

7.1. 永远只比较同层节点。

7.2. 不同的两个节点产生两个不同的树。

7.3. 通过key值指定哪些更新是相同的。

 

8. NodeJS中的事件循环:

process.nextTick(function() {

  console.log('a')

})

 

process.nextTick(function() {

  console.log('b')

})

 

setImmediate(function() {

  console.log('c')

  process.nextTick(function() {

    console.log('d')

  })

})

 

setImmediate(function() {

  console.log('e')

})

 

console.log('f')

输出顺序应该是:f -> a -> b -> c -> d -> e

这个值得注意的是setImmediate只能占用一次,这块setImmediate当时就回答错了。

 

9. Promise实现网络超时判断

使用Promise实现网络请求超时判断,超过三秒视为超时。

借助的是Promise.race这个方法,网络请求和三秒定时器进行竞争:

const uploadFile = (url, params) => {

  return Promise.race([

    uploadFilePromise(url, params),

    uploadFileTimeout(3000)

  ])

}

function uploadFilePromise(url, params) {

  return new Promise((resolve, reject) => {

    axios.post(url, params, {

      headers: {'Content-Type': 'multipart/form-data'}, // 以formData形式上传文件

      withCredentials: true

    }).then(res => {

      if(res.status===200 && res.data.code===0) {

        resolve(res.data.result)

      }else {

        reject(res.data)

      }

    })

  })

}

function uploadFileTimeout(time) {

  return new Promise((resolve, reject) => {

    setTimeout(() => {

      reject({timeoutMsg: '上传超时'})

    }, time)

  })

}

实习二面(50min)

一面完等待10min,二面就开始了。

1. 进程和线程

这是操作系统的问题,可以从四方面思考:

1.1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位。

1.2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线。

1.3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间。

1.4. 调度和切换:线程上下文切换比进程上下文切换要快得多。

 

2. 说下操作系统中的死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,两羊过独木桥。

死锁的解除与预防,资源的合理分配。。

这一块答的不是很好。。。

 

3. 说一下链表类型

单向链表、双向链表、循环链表

 

4. 栈和队列的区别?

栈是后进先出的数据结构,常用的比如调用栈。

队列是先进先出的数据结构,常用的就是NodeJS和浏览器中的任务队列了。

 

5. 遍历一个二叉树所有节点,返回它们的和

function numSum(root) {

    if(!root) return 0;

    return root.val + numSum(root.left) + numSum(root.right);

}

6. 连续自串最大和

给出一个数组,求出连续元素组成子串和的最大值:

输入: [-2,1,-3,4,-1,2,1,-5,4],

输出: 6

双层循环的话时间复杂度是: O(n * n), 使用分治的话时间复杂度可以压缩到O(n * logn),问了下学长说直接用笨方法写一下,给出代码:

function numSum(arr) {

  if(!arr.length) return null;

  if(arr.length < 2) return arr[0];

  let sumMax = arr[0];

  let sumTemp;

  for(let i = 0; i < arr.length; i++) {

    sumTemp = arr[i];

    if(sumTemp > sumMax) sumMax = sumTemp;

    for(let j = i + 1; j < arr.length; j++) {

      sumTemp += arr[j];

      if(sumTemp > sumMax) sumMax = sumTemp;

    }

  }

  return sumMax;

}

7. 介绍一下你的项目?

略。。。

 

8. 微信小程序的原理说一下?

从微信小程序的四层说起:JS、WXML、WXSS、JSON。然后可以分析一下小程序的webview和jscore部分。

 

实习三面(1h)

然后紧接着就是三面压力面,也是我入职的部门leader。

1. 列举下ES6新特性并简要说明一波

可以列举一下以下几大块:

1.1 let、const

1.2 解构赋值

1.3 字符串、正则、数值、函数、数组、对象的扩展

1.4 symbol

1.5 Set、Map

1.6 Promise

1.7 Generator

1.8 async

1.9 class

1.10 修饰器

1.11 module

 

2. Generator实现一个自执行

Generator自执行就是著名的自执行模块co,可以使用递归思想,不停地的调用next方法。

 

3. 说一下微信小程序的生命周期

这个我没温习到,没有回答完整哈~

页面生命周期:

- onLoad // 页面创建时执行

- onShow // 页面出现在前台执行

- onReady // 页面首次渲染完毕时执行

- onHide // 页面从前台变为后台时执行

- onUnload // 页面销毁时执行

- onPullDownRefresh // 触发下拉刷新时执行

- onReachBottom // 页面触底时执行

- onPageScroll // 页面滚动时执行

- onResize // 页面尺寸变化时执行

- onTabItemTap // tab点击时执行

组件声明周期(在lifetimes字段内进行声明):

- created // 在组件实例刚刚被创建时执行

- attached // 在组件实例进入页面节点树时执行

- ready // 在组件在视图层布局完成后执行

- moved // 在组件实例被移动到节点树的另一个位置时执行

- detached // 在组件实例被从页面节点移除时执行

- error // 每当组件方法抛出错误时执行

 

4. 说一下你会的React技术栈

略。。。

 

5. React-Router实现简单路由

import { BroserRouter, Route, Switch, Redirect } from 'react-router-dom'

 

<BroserRouter>

  <Switch>

    <Route path='/login' exact component={Login} />

    <Route path='/' render={() => (

      <Switch>

        <Route paht='/home' component={Home} />

        <Redirect to='/home' />

      </Switch>

    )} />

  </Switch>

</BroserRouter>

6. 说一下Redux原理

Redux就是一个数据状态管理,React单向数据流带来的数据跨组件传递问题。

简要说下Redux数据流,store、action、reducer,搭配React的使用。

可以有空看下源码,内容不是很多。


7. 说一下你的项目

略。。。(遵循STAR原则)

 

8. 说一下Koa洋葱模型

Koa洋葱模型指的就是:一个请求从外到里一层一层地经过中间件,响应时从里到外一层一层地经过中间件。

观看Koa源码,你会发现:Koa中间件被存储在middleware数组中,实现洋葱模型关键依赖于koa-compose,分析一波koa-compose的源码:

'use strict'

 

module.exports = compose

 

function compose (middleware) {

  // 类型判断 middleware必需是一个函数数组

  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!');

  for (const fn of middleware) {

    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!');

  }

 

  // 返回一个函数,接受context和next参数,koa在调用koa-compose时只传入context,所以此处next为undefined

  return function (context, next) {

    // last called middleware #

    // 初始化index

    let index = -1;

    // 从第一个中间件开始执行

    return dispatch(0);

    function dispatch (i) {

      // 在一个中间件出现两次next函数时,抛出异常

      if (i <= index) return Promise.reject(new Error('next() called multiple times'));

      // 设置index,作用是判断在同一个中间件中是否调用多次next函数

      index = i;

      // 中间件函数

      let fn = middleware[i]

      // 跑完所有中间件时,fn=next,即fn=undefined,可以理解为终止条件

      if (i === middleware.length) fn = next

      // fn为空时,返回一个空值的promise对象

      if (!fn) return Promise.resolve();

      try {

        // 返回一个定值的promise对象,值为下一个中间件的返回值

        // 这里时最核心的逻辑,递归调用下一个中间件,并将返回值返回给上一个中间件

        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));

      } catch (err) {

        return Promise.reject(err)

      }

    }

  }

}

9. 实现一个Koa中间件

看懂洋葱模型的源码,中间就很简单了,中间件都遵循这个结构:

module.exports = function(options) {

  // 配置处理

 

  return async (ctx, next) => {

    // 中间件逻辑...

  }

}

中间件就是一个函数,接受两个函数:ctx(全局上下文)、next(调用下一个中间件的方法)

 

10. 参与比赛项目中,你自我感觉发挥比较重要的一个项目?

略。。。

 

实习hr面(40min)

四面是hr面,聊了一下选择前端的原因和对于前端发展的看法,平时的学习途径和学习方法,大学校园生活、参加的一些比赛和活动,对于考研和就业的一些看法,别人对你的看法,随后也是顺利拿到了字节的实习offer。

字节的面试官还是比较善解人意的,对于没有思路的问题会引导着你去思考而不是以难倒人为乐趣,算法题嘛整体也是比较简单的,多关注于实际实现和知识基础。

面试的节奏也是比较快的,三轮技术面基本都是连着面中间隔个15min。也可以看到字节对于基础知识是比较看重的,知识基础还是需要多看,还有一些框架的源码和原理看了的话也是加分很多的。

三面的效果并不是很理想,但最后还是成功拿到了实习offer,还是比较幸运的。

 

提前批一面(1h)

1. 跨域说一下

可以从跨域的原因和解决跨域的方法说。

1.1 跨域主要是由于浏览器的同源策略,是对跨域响应进行了拦截。

1.2 跨域的解决方法有JSONP、CORS、反向代理等等,具体说一说。

 

2. 输入URL发生了什么

寻找强缓存 -> 构建请求 -> DNS解析 -> 建立TCP连接 -> 发送HTTP请求 -> 响应(200/304协商缓存) -> 构建DOM树 -> 构建CSSOM树 -> 声称布局树 -> 建图层树 -> 生成绘制列表 -> 生成图块 -> 显示器显示

 

3. 重绘和回流说一下

重绘、回流的触发条件、过程,可以针对此做的优化策略。

 

4. new操作符进行的操作

以构造函数.prototype为原型创建空对象,将构造函数的this绑定到建立的空对象并执行,结果是引用数据类型则返回结果否则返回创建的对象。

 

5. 说一下CommonJS和ES模块化的区别

这个实习问过,答案在上面就不说了。

 

接着算法题:

6. 版本号排序

输入 ['1.1.1.1.1.1', '6', '5.4.3', '2.3.1', '2.3.1.1'] 返回从大到小的版本号数组

我的写法是将每一个版本号以.分隔为数组,然后从第一位往后比较。

 

7. 判断一个链表是否有环。

快慢指针即可。

 

8. 实现一个类的add方法,使得同时的并行请求只有两个,并行请求完成后自动执行下一个任务直到全部执行完。

class Scheduler {

    async add(promiseFunc) {

    }

}

 

const scheduler = new Scheduler()

 

const timeout = (time) => {

    return new Promise(r => setTimeout(r, time))

}

 

const addTask = (time, order) => {

    scheduler.add(() => timeout(time)).then((res) => console.log(res))

}

 

addTask(1000, 1)

addTask(500, 2)

addTask(300, 3)

addTask(400, 4)

这个解法我详细总结过,可以查看:https://juejin.im/post/5f194ab1e51d45346d310b30

9. 使用Koa实现请求-响应的时间监听

提前批二面(1h)

上面先是两道题:

1. ES5实现数组去重,要求时间复杂度是O(n). [1, 2, 2, '1', 'test'] => [1, '1', 'test'] 要求可以区分到1和'1'
我问了是否可以使用额外空间,面试官回答是可以的。那就定义一个hash对象,遍历存入,key值的话需要添加typeof,用来区分1和''1'

2. Promise并行

[promiseGenerator]

promiseGenerator => Promise

 

dispatch(arr, n) {

 

}

// 实现dispatch方法

// 要求:并行限制2个

跟上一面的思路差不多


3. 简历上的实习项目

这个过程很长,问的东西也很多,所以多准备准备自己的项目吧。

 

4. HTTPS相对于HTTP做了些什么?

从SSL+数字签名这两个方面说。

 

5. HTTP2.0的优化?

头部压缩、多路复用、服务器推送这三个方面详细回答。

 

6. 说一下为什么HTTP1有队头阻塞的问题

 

7. HTTP2.0实现了服务器主动PUSH,那么如果服务端文件(html)没有发生改变,那么此时还会主动PUSH,如何解决这个问题?

这个我有点懵,因为面试官对于服务端主动推送问的很深,我不会,他回答是服务端可以主动释放。

 

8. React看你比较熟悉,说一下?

可以说一下解决的问题、优势。

 

9. 看过相关的源码没,说一下印象深刻的地方?

 

10. Virtual DOM的优势

函数式UI编程、跨平台。

 

11. webpack的原理说一下?

其实就是依赖关系图的构建过程

 

12. webpack自己实现过loader或plugin吗?

 

13. webpack loader和plugin的区别?

 

提前批三面(35min)

三面进行的时间比较短,首先是围绕项目进行的一些提问,这个持续的时间比较长。

然后其他没问,直接给了两道题:

1. 一个非常长的非递减数组,找出一个数出现的次数。比如:[1, 2, 3, 3, 4, ......, 45, 45, 69, ......, 1000]找出80出现的次数

使用二分查找,找出这个数字出现的索引,然后向左向右扩展。

 

2. 同花顺扑克牌问题

将扑克牌去除大小王,剩余1~13的黑桃、红桃、梅花、方块,1~13黑桃编号1~13,1~13红桃编号14~26,一次类推,求解取出5张牌是同花顺(同一颜色,顺子)

解决方法很简单,先排序,最大与最小和需要是4,然后判断最小或最大是否在指定区间。

const fn = arr => {

  if (!arr || arr.length !== 5) return false;

  arr.sort((a, b) => a - b);

  if (arr[4] - arr[0] !== 4) return false;

  if (arr[0] % 13 >= 9) return false;

  return true;

}

 

三面进行的很简单时间也很短,主要是项目,算法也很简单,应该是前两面反馈比较好就没过多问什么问题。

提前批hr面(40min)

三面完就约了当天进行hr面,不得不说字节的效率,hr面是个可爱的小姐姐~

主要沟通了一些工作、实习、学校、学习方面的事情吧。

 

为什么实习离职了,而不是留下来转正?

你觉的你的优势在哪里?

你觉得你的缺点在哪里?

你对薪资待遇有什么要求?

薪资在你心中的地位和分量?

实习期间对于公司的工作时间怎么看的?能接受吗?

对于加班怎么看待的呢?

你未来两三年的职业规划?

你是会学习一些后端、人工智能,还是专注于前端?

平时学习的方式?

 

大概是有这些问题,当时还有其他问题想不起来了,沟通了四十分钟过程还是挺OK的。

反问环节我问她怎么看待印度最近封杀了抖音海外版的情况,她笑了笑说说明了公司这方面的工作,然后通知我三个工作日offer会到。

 

如今意向书到了也快有一星期了,也祝愿大家都能拿到心仪的offer~

整理不易,求个点赞关注~

 

 

篇五:前端秋招面经

牛油:@bbln

 

个人情况

本硕211,通信专业,前端岗位,秋招拿到阿里本地生活,字节抖音互娱,美团,滴滴,快手,拼多多,贝壳找房等大厂offer,感觉自己还是比较幸运的,9月7号拿了阿里的意向书以后也就正式上岸了。😁

秋招期间牛客的各种帖子对自己帮助很大,因此自己也整理一份面经,回馈牛客,希望对大家有帮助。(后续也会发一个offer比较选择的帖子希望大家帮忙投个票给个建议😁)

前端基础知识由于太多啦,所以就不整理啦,不过在牛客上看到一篇比较好的帖子,介绍的也挺全的,分享给大家:https://www.nowcoder.com/discuss/258810

都可以参考一下~

由于好多公司都比较注重手撕代码,包括数据结构和一些js的底层实现,所以我把我面试碰到的和一些别人面试碰到的比较高频的 手撕题 整理一下,希望对大家有帮助。

注:后面的答案仅供参考,可以根据自己的代码风格自行整理,这样有助于记忆。

★★★:高频,很重要

★★ :中频,最好掌握

★ :了解即可

JS

  1. 模拟实现函数的call、apply、bind方法    ★★
  2. 模拟实现函数节流、防抖方法        ★★★
  3. 模拟实现对象的深拷贝        ★★
  4. 嵌套数组指定层次展开 flat扁平化(多种方法,至少掌握两种)★★★
  5. 模拟实现 reduce 数组方法    ★
  6. 模拟实现数组map方法    ★★
  7. 模拟实现Array.fill()、Array.filter()    ★★
  8. 模拟实现Array.find()、Array.findIndex()★★
  9. 模拟实现Promise.all方法(Promise.race也需要了解)★★
  10. 使用原生的JavaScript实现ajax请求(也可能让你说ajax实现原理,答案一样)★★★
  11. 模拟实现构造函数new的过程(也可能让你口述new的过程,答案一样)★★★
  12. 模拟实现Object.create方法    ★★
  13. 模拟实现instanceof的功能        ★★★
  14. 使用setTimeout实现setInterval方法 ★★
  15. 实现jsonp★★★
  16. promise 实现sleep函数★★
  17. 实现promise retry重试★
  18. js实现观察者模式★

数据结构和算法

  1. 排序算法(要会分析时间空间复杂度):冒泡、选择、插入、快排    ★★★ 归并     ★
  2. 二分查找(非递归递归)★★★
  3. 字符串逆序(翻转整数字符串)★★★
  4. 数组乱序(打乱数组,至少掌握两种方法)★★★
  5. 数组去重(至少掌握两种方法)★★★
  6. 两个栈来实现一个队列(两个队列实现栈可以了解一下)★
  7. 链表相关

入门:★★★

    • 反转单链表
    • 未排序链表去重O(n2)
    • 排序链表去重O(n)
    • 单链表删除节点
    • 链表partition
    • 寻找链表倒数第K个节点
    • 删除链表倒数第N个节点
    • 判断链表是否为回文链表
    • 判断链表是否有环
    • 环形链表第一个入环节点
    • 两个链表的第一个公共节点

复杂

    • 合并两个排序的链表★★
    • 合并K个排序的链表★★
    • 奇偶链表★
    • 复制带随机指针的单向链表★
    • 有序链表转换二叉搜索树(BST)★
    • 二叉树展开为链表★
    • K 个一组翻转链表★
  1. 二叉树各种遍历(前中后序遍历,递归非递归,DFS,BFS)★★★
  2. 二叉树遍历涉及到的一些算法题

(好多题其实就是二叉树深度或者广度非递归遍历稍微改一下即可)

    • 前序和中序重建二叉树★★
    • BST第K大的数和第K小的数★
    • 二叉树按层求和(层序遍历改进)★
    • Z字型(之字形)遍历二叉树★
  1. 二叉树深度相关
    • 二叉树深度★★★
    • 二叉树最小深度★★
    • 树找两(叶子)节点最长距离(相隔的最长路径)★
    • 判断二叉树是否为平衡二叉树★★
    • 二叉树右视图(左)★
  2. 二叉树路径相关
    • 路径总和1★★★
    • 路径总和2(回溯法)★
  3. DP
    • 斐波那契数列★★★
    • 最长公共子序列 LCS ★★
    • 最长上升子序列 ★★
    • 连续子数组(子串)的最大和★★
    • 硬币找零(最少硬币个数)★★
    • 0-1背包问题★
    • 完全背包问题(了解即可)★
  4. 全排列(回溯法)★

CSS

  1. CSS画各种图形(等腰三角形、等腰梯形、扇形、圆、半圆)    ★
  2. 三列布局(至少掌握三种方法)    ★★★
  3. 垂直水平居中(至少掌握三种方法)    ★★★

仅供参考:

JS

  1. 模拟实现函数的call、apply、bind方法    ★★
    /* 先将传入的指定执行环境的对象 context 取到
    将需要执行的方法(调用call的对象) 作为 context 的一个属性方法fn
    处理传入的参数, 并执行 context的属性方法fn, 传入处理好的参数
    删除私自定义的 fn 属性
    返回执行的结果 */
    // 模拟 call 方法
    Function.prototype.defineCall = function (context) {
        context = context || window;
        context.fn = this; //this指向 sayName函数实例
        let args = [];
        for (let i = 1; i < arguments.length; i++) { //i从1开始 
            args.push(arguments[i]);
        } //或者args = [...arguments].slice(1);
        let result = context.fn(args.join(','));
        delete context.fn;
        return result;
    }
    let sayName = function (age) {
        console.log('current name: ' + this.name, "current age: " + age);
    }
    let obj1 = {
        name: "obj's name"
    }
    sayName.defineCall(obj1, 22); //this指向 sayName函数实例
    // current name: obj's name current age: 22
    
    
    // 模拟 apply 方法
    Function.prototype.defineApply = function (context, arr) {
        context = context || window;
        context.fn = this;
        let result;
        if (!arr) { // 第二个参数不传
            result = context.fn();
        } else { // 第二个参数是数组类型
            let args = [];
            for (let i = 0; i < arr.length; i++) { //i从0开始
                args.push(arr[i]);
            }
            result = context.fn(args.join(','));
        }
        delete context.fn;
        return result;
    }
    let obj2 = {
        name: ['Tom', 'Johy', 'Joe', 'David']
    }
    sayName.defineApply(obj2, [3, 4, 5, 6, 7]);
    // current name: Tom,Johy,Joe,David current age: 3,4,5,6,7
    
    
    //用call、apply模拟实现bind
    Function.prototype.bind = function (context) {
        let self = this; // 保存函数的引用
        return function () { // 返回一个新的函数
            // console.log(arguments);
            // return self.apply(context, arguments);
            return self.call(context, arguments);
        }
    };
    
    let obj = {
        name: 'seven'
    }
    
    let func = function () {
        console.log(this.name)
    }.bind(obj);
    func('zhangsan', 20);

  2. 模拟实现函数节流、防抖方法        ★★★
    function debounce(fn, delay) {//防抖
      // 维护一个 timer,用来记录当前执行函数状态
      let timer = null;
    
      return function() {
        // 通过 ‘this’ 和 ‘arguments’ 获取函数的作用域和变量
        let context = this;
        let args = arguments;
        // 清理掉正在执行的函数,并重新执行
        clearTimeout(timer);
        timer = setTimeout(function() {
          fn.apply(context, args);
        }, delay);
      }
    }
    let flag = 0; // 记录当前函数调用次数
    // 当用户滚动时被调用的函数
    function foo() {
      flag++;
      console.log('Number of calls: %d', flag);
    }
    
    // 在 debounce 中包装我们的函数,过 2 秒触发一次
    document.body.addEventListener('scroll', debounce(foo, 2000));
    
    function throttle(func, delay){//节流
      let timer = null;
    
      return function(){
        let context = this;
        let args    = arguments;
        if(!timer){
          timer = setTimeout(function(){
            func.apply(context, args);
            timer = null;
          }, delay);
        }
      }
    }

  3. 模拟实现对象的深拷贝        ★★
    //第一种:JSON.parse(JSON.stringify())方法实现深拷贝
    var obj={a:"hello",b:1,c:true,d:[1,2],e:{x:1,y:2},f:function(){console.log("copytest");},g:null,h:undefined};
    var copyobj=JSON.parse(JSON.stringify(obj));
    console.log(copyobj);
    copyobj.a="change";
    copyobj.d[0]=9;
    copyobj.e.x=8;
    console.log(obj);
    
    
    //第二种:递归的方法实现深拷贝
    function deepclone(obj,copyobj){
        var copyobj=copyobj||{};
        for (var keys in obj) { //使用for..in进行遍历   数组的话keys为0,1..... keys是string类型
            if(obj.hasOwnProperty(keys)){//剥离原型链的数据
                if((typeof(obj[keys])) ==='object' && obj[keys]!==null){//判断是否为引用数据类型
                    if (Object.prototype.toString.call(obj[keys]) === '[object Array]') { //Object原型方法得到类型
                        copyobj[keys]=[];
                    }else{
                        copyobj[keys]={};
                    }
                    deepclone(obj[keys],copyobj[keys]);
                }else{
                    copyobj[keys]=obj[keys];
                }
            }
        }
        return copyobj;
    }

  4. 嵌套数组指定层次展开 flat扁平化(多种方法,至少掌握两种)★★★
    // 嵌套数组指定层次展开   flat扁平化
    // 1. 普通方法 递归
    function flattenMd() {
        let result = []
        return function flatten(arr) {//闭包
            arr.forEach(item => {
                if (Array.isArray(item)) {
                    flatten(item)
                } else {
                    result.push(item)
                }
            })
            return result
        }
    }
    // var ary = [1, [2, [3, [4, 5]]], 6]
    // flattenMd()(ary);  函数柯里化  部分求值
    
    //2.concat  与方法1类似 没用闭包
    function flattenMd(arr) {
        let result = [] // 利用函数作用域保存result var result = []也可
        arr.forEach(item => {
            if (Array.isArray(item)) {
                result = result.concat(flattenMd(item))
            } else {
                result.push(item)
            }
        })
        return result
    }
    
    //3. reduce 
    function flattenMd(arr) { //.concat([3,4])和.concat(3,4)均可
        return arr.reduce((prev, item) => prev.concat(Array.isArray(item) ? flattenMd(item) : item), [])
    }
    
    // 4. 展开运算符
    function flattenMd(arr) {
        let flatten = arr => [].concat(...arr)//可去掉一层[]
        return flatten(arr.map(item => Array.isArray(item) ? flattenMd(item) : item))
    }
    
    // 5. join和split组合( 只适用字符串数组, 最简单粗暴)
    //[ '1', '2', '3', '4', '5', '6' ] 得到的是字符串数组  再转换一下才行
    function flattenMd(arr) {
        return arr.join().split(',')
        //join() 方法用于把数组中的所有元素放入一个字符串 默认用,隔开
    }

  5. 模拟实现 reduce 数组方法    ★
    //reduce()函数接受两个参数,一个函数一个累积变量的初始值。
    //函数有四个参数:累计变量初值(默认第一个成员),当前变量值(默认第二个成员),当前位置,数组自身。
    //arr.reduce(function(prev, cur, index, arr){}, initialValue)
    Array.prototype.myReduce=function(fn,base){
        if(typeof fn !== 'function'){
            throw new TypeError("arguments[0] is not a function");//TypeError是错误的类型之一:类型错误
        }
    
        var initialArr=this;//调用myReduce()函数的当前对象
        var arr=initialArr.concat();//目的是返回一个等于初始数组的新数组,后面的操作都基于arr,这样初始数组不会发生改动
        var index,newValue;
        
        if(arguments.length==2){
            arr.unshift(base);
            index = 0; //!!当前位置 指的是当前变量(第二个参数)针对调用该方法的数组位置即initialArr
        }else{
            index=1;
        }
    
        if(arr.length===1){//长度为1 直接返回
            newValue=arr[0];
        }
    
        while(arr.length>1){
            newValue=fn.call(null,arr[0],arr[1],index,initialArr);
            index++;
            arr.splice(0,2,newValue);//删除前两位 然后把累加值添加到第一位
        }
        return newValue;
    };

  6. 模拟实现数组map方法    ★★
    // 模拟实现map
    // arr.map(function (currentValue, index, arr) {
    // })
    // currentValue 必须。 当前元素的值
    // index 可选。 当期元素的索引值
    // arr 可选。 当期元素属于的数组对象
    
    Array.prototype.newMap = function (fn) { //写法一
        var newArr = [];
        for (var i = 0; i < this.length; i++) {
            newArr.push(fn(this[i], i, this)) //this指向调用newMap方法的数组
        }
        return newArr;
    }
    
    // arr.reduce((previousValue, currentValue, currentIndex, array) => {}, initialValue?)
    // reduce若不指定初始值, 那么第一次迭代的 previousValue 为 ar[[0], currentValue 为 arr[1], currentIndex 为 1,
    // 若指定初始值, 那么第一次迭代的 previousValue 为 initialValue, currentValue为 arr[0], currentIndex 为0.
    
    Array.prototype.newMap = function (fn, Arg) { ////写法二:用数组的reduce方法实现数组的map
        var res = [];
        this.reduce((prev, curr, index, array) => {
            res.push(fn.call(Arg, curr, index, array));
        }, 0) //指定初始值initialValue=0,所以从currentIndex=0开始,即第一个开始  不这样会缺第一项,结果为[3,4]
        return res;
    }
    
    
    let arr = [1, 2, 3];
    let res = arr.newMap((a) => a + 1);
    console.log(res); //[2,3,4]

  7. 模拟实现Array.fill()、Array.filter()    ★★
    array.fill(value, start, end)
    value 必需。填充的值。start 可选。开始填充位置。end 可选。停止填充位置 (默认为 array.length)
    Array.prototype.myFill = function (value, start = 0, end = this.length) {
        for (let i = start; i < end; i++) {
            this[i] = value;
        }
    }
    Array.filter()
    Array.prototype.myFilter = function myFilter(fn, context) {
        if (typeof fn !== "function") {
            throw new TypeError(`${fn} is not a function`);
        }
        let arr = this;
        let temp = [];
        for (let i = 0; i < arr.length; i++) {
            let result = fn.call(context, arr[i], i, arr);
            if (result) temp.push(arr[i]);
        }
        return temp;
    };

  8. 模拟实现Array.find()、Array.findIndex()★★
    Array.find()
    用于找出第一个符合条件的数组成员,参数为一个回调函数
    [1, 4, -5, 10].find((n) => n < 0) // -5
    Array.prototype.myFind = function (fn, start = 0, end = this.length) {
        for (let i = start; i < end; i++) {
            if (fn.call(this, this[i], i, this)) {
                return this[i]
            }
        }
    }
    Array.findIndex()
    Array.prototype.myFindIndex = function (fn, start = 0, end = this.length) {
        for (let i = start; i < end; i++) {
            if (fn.call(this, this[i], i, this)) {
                return i
            }
        }
        return -1
    }

  9. 模拟实现Promise.all方法(Promise.race也需要了解)★★
     //promise.all()
      function myall(proArr) {
        return new Promise((resolve, reject) => {
          let ret = []
          let count = 0
          let done = (i, data) => {
            ret[i] = data
            if(++count === proArr.length) resolve(ret)
          }
          for (let i = 0; i < proArr.length; i++) {
            proArr[i].then(data => done(i,data) , reject)
          }
        })
      }
      
      
    //promise.race();这么简单得益于promise的状态只能改变一次,即resolve和reject都只被能执行一次
     function myrace(proArr) {
        return new Promise(function (resolve, reject) {
          for(let i=0;i<proArr.length;i++){
            proArr[i].then(resolve,reject);
          }
        })
      }

  10. 使用原生的JavaScript实现ajax请求(也可能让你说ajax实现原理,答案一样)★★★
    //手写3遍以上
    var xhr = new XMLHttpRequest();// 创建XMLHttqRequest
    var url = 'https://bbin.com';
    xhr.onreadystatechange=function(){// 监听状态码的变化,每次变化 均执行
        if(xhr.readyState===4){
    
            if (xhr.status === 200) { // 服务端 状态码
                console.log(xhr.responseText); //服务器返回的响应文本
            }else{
                console.error(xhr.statusText); //状态码的文本描述,如200的statusText是ok
            }
        
        }
    }
    
    xhr.open('GET', url, true); // 初始化请求参数,还没发送请求   true表示异步
    xhr.send(null); // 向服务器发送请求,但是不带有数据发送过去,一般在get方式发送时候多使用这个方式

  11. 模拟实现构造函数new的过程(也可能让你口述new的过程,答案一样)★★★
    function myNew(constructor,params){
      var args = [].slice.call(arguments);
      var constructor = args.shift();
      var obj = new Object();
      obj.__proto__ = constructor.prototype;
      var res = constructor.apply(obj,args);
      return (typeof res === 'object' && typeof res !== null) ? res : obj;
    }

  12. 模拟实现Object.create方法    ★★
    // 用于创建一个新对象,被创建的对象继承另一个对象(o)的原型
    function createObj(o) {//传入的参数o为返回实例的__porto__,也就是实例构造函数的显示原型
        function F() {}//构造函数
        F.prototype = o;
        return new F();//返回实例
    }

  13. 模拟实现instanceof的功能        ★★★
    function instanceofObj(a, b) {
        // 模拟 a instanceof b
        let prototypeB = b.prototype;
        let protoA = a.__proto__;
        let state

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

互联网校招大礼包 文章被收录于专栏

本专刊由牛客官方团队打造,互联网技术方向主流岗位简历特辑与面试干货。 保姆级简历教程:手把手教你打造网申优秀简历 主流岗位简历模板:学习大牛简历样本,使用模板增加面试邀请 20W字精选面经:牛客精选全网优质面经,专业面试问题、学习路径一网打尽

全部评论

相关推荐

不愿透露姓名的神秘牛友
12-17 17:40
点赞 评论 收藏
分享
淬月星辉:专利是什么?至少描述一下吧,然后把什么计算机二级、普通话这种拉低格调的证书删掉,不然hr以为你没东西写
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务