前端方向优秀面经合集(下)
篇四:字节跳动架构部实习+秋招提前批共八轮面试(均已拿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)
提前批一面(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
提前批二面(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
个人情况
★★★:高频,很重要
★★ :中频,最好掌握
JS
- 模拟实现函数的call、apply、bind方法 ★★
- 模拟实现函数节流、防抖方法 ★★★
- 模拟实现对象的深拷贝 ★★
- 嵌套数组指定层次展开 flat扁平化(多种方法,至少掌握两种)★★★
- 模拟实现 reduce 数组方法 ★
- 模拟实现数组map方法 ★★
- 模拟实现Array.fill()、Array.filter() ★★
- 模拟实现Array.find()、Array.findIndex()★★
- 模拟实现Promise.all方法(Promise.race也需要了解)★★
- 使用原生的JavaScript实现ajax请求(也可能让你说ajax实现原理,答案一样)★★★
- 模拟实现构造函数new的过程(也可能让你口述new的过程,答案一样)★★★
- 模拟实现Object.create方法 ★★
- 模拟实现instanceof的功能 ★★★
- 使用setTimeout实现setInterval方法 ★★
- 实现jsonp★★★
- promise 实现sleep函数★★
- 实现promise retry重试★
- js实现观察者模式★
数据结构和算法
- 排序算法(要会分析时间空间复杂度):冒泡、选择、插入、快排 ★★★ 归并 ★
- 二分查找(非递归递归)★★★
- 字符串逆序(翻转整数字符串)★★★
- 数组乱序(打乱数组,至少掌握两种方法)★★★
- 数组去重(至少掌握两种方法)★★★
- 两个栈来实现一个队列(两个队列实现栈可以了解一下)★
- 链表相关
入门:★★★
- 反转单链表
- 未排序链表去重O(n2)
- 排序链表去重O(n)
- 单链表删除节点
- 链表partition
- 寻找链表倒数第K个节点
- 删除链表倒数第N个节点
- 判断链表是否为回文链表
- 判断链表是否有环
- 环形链表第一个入环节点
- 两个链表的第一个公共节点
复杂:
- 合并两个排序的链表★★
- 合并K个排序的链表★★
- 奇偶链表★
- 复制带随机指针的单向链表★
- 有序链表转换二叉搜索树(BST)★
- 二叉树展开为链表★
- K 个一组翻转链表★
- 二叉树各种遍历(前中后序遍历,递归非递归,DFS,BFS)★★★
- 二叉树遍历涉及到的一些算法题
(好多题其实就是二叉树深度或者广度非递归遍历稍微改一下即可)
- 前序和中序重建二叉树★★
- BST第K大的数和第K小的数★
- 二叉树按层求和(层序遍历改进)★
- Z字型(之字形)遍历二叉树★
- 二叉树深度相关
- 二叉树深度★★★
- 二叉树最小深度★★
- 树找两(叶子)节点最长距离(相隔的最长路径)★
- 判断二叉树是否为平衡二叉树★★
- 二叉树右视图(左)★
- 二叉树路径相关
- 路径总和1★★★
- 路径总和2(回溯法)★
- DP
- 斐波那契数列★★★
- 最长公共子序列 LCS ★★
- 最长上升子序列 ★★
- 连续子数组(子串)的最大和★★
- 硬币找零(最少硬币个数)★★
- 0-1背包问题★
- 完全背包问题(了解即可)★
- 全排列(回溯法)★
CSS
- CSS画各种图形(等腰三角形、等腰梯形、扇形、圆、半圆) ★
- 三列布局(至少掌握三种方法) ★★★
- 垂直水平居中(至少掌握三种方法) ★★★
仅供参考:
JS
- 模拟实现函数的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);
- 模拟实现函数节流、防抖方法 ★★★
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); } } }
- 模拟实现对象的深拷贝 ★★
//第一种: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; }
- 嵌套数组指定层次展开 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() 方法用于把数组中的所有元素放入一个字符串 默认用,隔开 }
- 模拟实现 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; };
- 模拟实现数组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]
- 模拟实现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; };
- 模拟实现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 }
- 模拟实现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); } }) }
- 使用原生的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方式发送时候多使用这个方式
- 模拟实现构造函数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; }
- 模拟实现Object.create方法 ★★
// 用于创建一个新对象,被创建的对象继承另一个对象(o)的原型 function createObj(o) {//传入的参数o为返回实例的__porto__,也就是实例构造函数的显示原型 function F() {}//构造函数 F.prototype = o; return new F();//返回实例 }
- 模拟实现instanceof的功能 ★★★
function instanceofObj(a, b) { // 模拟 a instanceof b let prototypeB = b.prototype; let protoA = a.__proto__; let state
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专刊由牛客官方团队打造,互联网技术方向主流岗位简历特辑与面试干货。 保姆级简历教程:手把手教你打造网申优秀简历 主流岗位简历模板:学习大牛简历样本,使用模板增加面试邀请 20W字精选面经:牛客精选全网优质面经,专业面试问题、学习路径一网打尽
查看5道真题和解析