共计 14062 个字符,预计需要花费 36 分钟才能阅读完成。
前言
2019 年 6 月中旬,实在厌倦了之前平平淡淡的工作和毫不起眼的薪资,不顾亲人的反对,毅然决然地决定只身前往沿海城市,想着找到一份更加具有挑战性的工作,来彻彻底底地重新打磨自己,同时去追求更好的薪资待遇。当然在此之前,自己每天下班后都会利用业余时间抓紧复习巩固刷题等等,大概从 3 月份开始的吧,持续了 3 个多月。而后从 6 月中旬面试一直到 6 月底,中间大概两个星期,其实我的学历和背景并不突出,但是我个人感觉可能是因为自己简历做的稍微还行 ( 后面我可能会单独出一篇文章,来聊聊我做简历时的一点点心得 ),让大厂的 HR 能够多看几眼,中间面过的公司包括 喜马拉雅、携程、哔哩哔哩、流利说、蜻蜓 FM、爱回收 等,陆陆续续拿到 4,5 个 Offer 吧,如今已经转正,所以在这里记录下之前的部分面试题,和大家一起分享交流。
正文
1. 烈熊网络
这家公司其实我也没有太了解过,是我前同事推荐的,说里面的薪资待遇不错,然后我当时也有空闲时间,所以就去试试了,虽然公司名气没有上面提到的公司大,但是他的面试题我觉得还是挺有分量的。
1.1 请说出下面代码的执行顺序
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]。这道题目主要考对 JS 宏任务 和微任务 的理解程度,JS 的事件循环中每个宏任务称为一个 Tick(标记),在每个标记的末尾会追加一个微任务队列,一个宏任务执行完后会执行所有的微任务,直到队列清空。上题中我觉得稍微复杂点的在于 async1 函数,async1 函数本身会返回一个 Promise,同时 await 后面紧跟着 async2 函数返回的 Promise,console.log(3)
其实是在 async2 函数返回的 Promise 的 then 语句中执行的,then 语句本身也会返回一个 Promise 然后追加到微任务队列中,所以在微任务队列中 console.log(3)
在console.log(4)
后面,不太清楚的同学可以网上查下资料或者关注我的公众号「前端之境」,我们可以一起交流学习。
1.2 手动实现 Promise,写出伪代码
幸运的是在面试前刚好查阅了下这部分的资料,所以回答过程中还算得心应手,主要是需要遵循 Promise/A+ 规范:
(1) 一个 promise 必须具备三种状态 (pending|fulfilled(resolved)|rejected),当处于 pending 状态时,可以转移到 fulfilled(resolved) 状态或 rejected 状态,处于 fulfilled(resolved)状态或 rejected 状态时,状态不再可变;
(2) 一个 promise 必须有 then 方法,then 方法必须接受两个参数:
// onFulfilled 在状态由 pending -> fulfilled(resolved) 时执行,参数为 resolve()中传递的值
// onRejected 在状态由 pending -> rejected 时执行,参数为 reject()中传递的值
promise.then(onFulfilled,onRejected)
(3) then 方法必须返回一个 promise:
promise2 = promise1.then(onFulfilled, onRejected);
实现代码直接贴出来吧:
参考自:实现一个完美符合 Promise/A+ 规范的 Promise
function myPromise(constructor){
let self=this;
self.status="pending" // 定义状态改变前的初始状态
self.value=undefined;// 定义状态为 resolved 的时候的状态
self.reason=undefined;// 定义状态为 rejected 的时候的状态
self.onFullfilledArray=[];
self.onRejectedArray=[];
function resolve(value){if(self.status==="pending"){
self.value=value;
self.status="resolved";
self.onFullfilledArray.forEach(function(f){f(self.value);
// 如果状态从 pending 变为 resolved,// 那么就遍历执行里面的异步方法
});
}
}
function reject(reason){if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
self.onRejectedArray.forEach(function(f){f(self.reason);
// 如果状态从 pending 变为 rejected,// 那么就遍历执行里面的异步方法
})
}
}
// 捕获构造异常
try{constructor(resolve,reject);
}catch(e){reject(e);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
let promise2;
switch(self.status){
case "pending":
promise2 = new myPromise(function(resolve,reject){self.onFullfilledArray.push(function(){setTimeout(function(){
try{let temple=onFullfilled(self.value);
resolvePromise(temple)
}catch(e){reject(e) //error catch
}
})
});
self.onRejectedArray.push(function(){setTimeout(function(){
try{let temple=onRejected(self.reason);
resolvePromise(temple)
}catch(e){reject(e)// error catch
}
})
});
})
case "resolved":
promise2=new myPromise(function(resolve,reject){setTimeout(function(){
try{let temple=onFullfilled(self.value);
// 将上次一 then 里面的方法传递进下一个 Promise 状态
resolvePromise(temple);
}catch(e){reject(e);//error catch
}
})
})
break;
case "rejected":
promise2=new myPromise(function(resolve,reject){setTimeout(function(){
try{let temple=onRejected(self.reason);
// 将 then 里面的方法传递到下一个 Promise 的状态里
resolvePromise(temple);
}catch(e){reject(e);
}
})
})
break;
default:
}
return promise2;
}
function resolvePromise(promise,x,resolve,reject){if(promise===x){throw new TypeError("type error")
}
let isUsed;
if(x!==null&&(typeof x==="object"||typeof x==="function")){
try{
let then=x.then;
if(typeof then==="function"){
// 是一个 promise 的情况
then.call(x,function(y){if(isUsed)return;
isUsed=true;
resolvePromise(promise,y,resolve,reject);
},function(e){if(isUsed)return;
isUsed=true;
reject(e);
})
}else{
// 仅仅是一个函数或者是对象
resolve(x)
}
}catch(e){if(isUsed)return;
isUsed=true;
reject(e);
}
}else{
// 返回的基本类型,直接 resolve
resolve(x)
}
}
1.3 请说出以下打印结果
let a = {a: 10};
let b = {b: 10};
let obj = {a: 10};
obj[b] = 20;
console.log(obj[a]);
我的回答是:20。这道题目主要考对 JS 数据类型的熟练度以及对 ES6 中 属性名表达式 的理解。在上题中 obj[b] = 20
的赋值操作后,obj
其实已经变成了 {a: 10, [object Object]: 20}
,这是因为如果属性名表达式是一个对象的话,那么默认情况下会自动将对象转为字符串[object Object]
,最后一步获取obj[a]
时,a 本身也是一个对象,所以会被转换为获取 obj[object Object]
也就是上一步赋值的 20。
1.4 说出几种数组去重的方式
这个其实网上已经有大把大把的实现方案了,我也就大概给出了以下几种:
let originalArray = [1,2,3,4,5,3,2,4,1];
// 方式 1
const result = Array.from(new Set(originalArray));
console.log(result); // -> [1, 2, 3, 4, 5]
// 方式 2
const result = [];
const map = new Map();
for (let v of originalArray) {if (!map.has(v)) {map.set(v, true);
result.push(v);
}
}
console.log(result); // -> [1, 2, 3, 4, 5]
// 方式 3
const result = [];
for (let v of originalArray) {if (!result.includes(v)) {result.push(v);
}
}
console.log(result); // -> [1, 2, 3, 4, 5]
// 方式 4
for (let i = 0; i < originalArray.length; i++) {for (let j = i + 1; j < originalArray.length; j++) {if (originalArray[i] === originalArray[j]) {originalArray.splice(j, 1);
j--;
}
}
}
console.log(originalArray); // -> [1, 2, 3, 4, 5]
// 方式 5
const obj = {};
const result = originalArray.filter(item => obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true));
console.log(result); // -> [1, 2, 3, 4, 5]
1.5 对象数组如何去重?
这个题目不只一家公司问到了,开始的时候一脸懵逼,心里想着每个对象的内存地址本身就不一样,去重的意义何在,非要去重的话,那只能通过 JSON.stringify
序列化成字符串 (这个方法有一定的缺陷) 后进行对比,或者递归的方式进行键 - 值对比,但是对于大型嵌套对象来说还是比较耗时的,所以还是没有答好,后来面试官跟我说是根据每个对象的某一个具体属性来进行去重,因为考虑到服务端返回的数据中可能存在 id 重复的情况,需要前端进行过滤,如下:
const responseList = [{ id: 1, a: 1},
{id: 2, a: 2},
{id: 3, a: 3},
{id: 1, a: 4},
];
const result = responseList.reduce((acc, cur) => {const ids = acc.map(item => item.id);
return ids.includes(cur.id) ? acc : [...acc, cur];
}, []);
console.log(result); // -> [{ id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 3} ]
2. 携程
当时是前一天进行了一次电面,然后第二天现场面,两个面试官轮流问,大概持续了一个半小时吧,问的问题还是比较多的,有些问题时间久了还是不太记得了,多多见谅!
2.1 理解深拷贝和浅拷贝吗?
浅拷贝 是指创建一个对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,那么拷贝的就是基本类型的值,如果属性是引用类型,那么拷贝的就是内存地址,所以如果其中一个对象修改了某些属性,那么另一个对象就会受到影响。
深拷贝 是指从内存中完整地拷贝一个对象出来,并在堆内存中为其分配一个新的内存区域来存放,并且修改该对象的属性不会影响到原来的对象。
2.2 深拷贝和浅拷贝的实现方式分别有哪些?
浅拷贝:(1) Object.assign 的方式 (2) 通过对象扩展运算符 (3) 通过数组的 slice 方法 (4) 通过数组的 concat 方法。
深拷贝:(1) 通过 JSON.stringify 来序列化对象 (2) 手动实现递归的方式。
2.3 大概说下实现无缝轮播的思路?
先简单说了下实现轮播的思路,多张图片从左至右依次排列,点击左右侧按钮切换图片的时候,让图片的父级容器的 left 偏移值增加或减少单张图片的宽度大小,同时配合 CSS3 transition 过渡或者手写一个动画函数,这样可以实现一个比较平滑的动画效果。对于无缝轮播,我当时的思路是再拷贝一个图片的父级容器出来,例如原来一个 <ul><li></li><li></li></ul>
对应两张图片,现在变为两个 ul
对应 4 张图片,同时 ul
的父容器监听自身的 scrollLeft
,如果值已经大于等于一个ul
的宽度,则立即将自身的 scrollLeft
值重置为 0,这样就又可以从起点开始轮播,实现无缝的效果。
2.3 说出以下代码的执行结果
var a = 10;
var obj = {
a: 20,
say: function () {console.log(this.a);
}
};
obj.say();
这个是被我简化后的版本,具体题目记不太清了,反正就是考的 this 的指向问题,上题中答案为 20。然后面试官继续追问,如何才能打印出 10,给出如下方式:
// 方式 1
var a = 10;
var obj = {
a: 20,
say: () => { // 此处改为箭头函数
console.log(this.a);
}
};
obj.say(); // -> 10
// 方式 2
var a = 10;
var obj = {
a: 20,
say: function () {console.log(this.a);
}
};
obj.say.call(this); // 此处显示绑定 this 为全局 window 对象
// 方式 3
var a = 10;
var obj = {
a: 20,
say: function () {console.log(this.a);
}
};
var say = obj.say; // 此处先创建一个临时变量存放函数定义,然后单独调用
say();
2.4 Vue 的生命周期有哪些?
创建:beforeCreate,created;
载入:beforeMount,mounted;
更新:beforeUpdate,updated;
销毁:beforeDestroy,destroyed;
2.5 移动端如何设计一个比较友好的 Header 组件?
当时的思路是头部 (Header) 一般分为左、中、右三个部分,分为三个区域来设计,中间为主标题,每个页面的标题肯定不同,所以可以通过 vue props 的方式做成可配置对外进行暴露,左侧大部分页面可能都是回退按钮,但是样式和内容不尽相同,右侧一般都是具有功能性的操作按钮,所以左右两侧可以通过 vue slot 插槽的方式对外暴露以实现多样化,同时也可以提供 default slot 默认插槽来统一页面风格。
2.6 说出 space-between 和 space-around 的区别?
这个是 flex 布局的内容,其实就是一个边距的区别,按水平布局来说,space-between
在左右两侧没有边距,而 space-around
在左右两侧会留下边距,垂直布局同理,如下图所示:
2.7 你所知道的前端性能优化方案
这个其实方案还是比较多的,可以从 DOM 层面,CSS 样式层面 和JS 逻辑层面 分别入手,大概给出以下几种:
(1) 减少 DOM 的访问次数,可以将 DOM 缓存到变量中;
(2) 减少 重绘 和回流 ,任何会导致 重绘 和回流 的操作都应减少执行,可将 多次操作合并为一次 ;
(3) 尽量采用 事件委托 的方式进行事件绑定,避免大量绑定导致内存占用过多;
(4) css 层级尽量 扁平化 ,避免过多的层级嵌套,尽量使用 特定的选择器 来区分;
(5) 动画尽量使用 CSS3动画属性 来实现,开启 GPU 硬件加速;
(6) 图片在加载前提前 指定宽高 或者 脱离文档流 ,可避免加载后的重新计算导致的页面回流;
(7) css 文件在<head>
标签中引入,js 文件在 <body>
标签中引入,优化 关键渲染路径 ;
(8) 加速或者减少 HTTP 请求,使用CDN 加载静态资源,合理使用浏览器 强缓存 和协商缓存 ,小图片可以使用Base64 来代替,合理使用浏览器的 预取指令 prefetch和 预加载指令 preload;
(9) 压缩混淆代码 , 删除无用代码 , 代码拆分 来减少文件体积;
(10) 小图片使用雪碧图 ,图片选择合适的 质量 、 尺寸 和格式,避免流量浪费。
2.8 git 多人协作时如何解决冲突
冲突主要是出现在多人在修改同一个文件的同一部分内容时,对方当你之前 push
,然后你后push
的时候 git 检测到两次提交内容不匹配,提示你 Conflict
,然后你pull
下来的代码会在冲突的地方使用 =====
隔开,此时你需要找到对应的开发人员商量代码的取舍,切 不可随意修改并强制提交 ,解决冲突后再次push
即可。
3. 喜马拉雅
当时是两轮技术面,一次电面,一次现场面,电面有部分题目还是答得很模糊,现场面自我感觉还可以吧。
3.1 手动实现一个 bind 方法
代码如下:
Function.prototype.bind = function(context, ...args1) {if (typeof this !== 'function') {throw new Error('not a function');
}
let fn = this;
let resFn = function(...args2) {return fn.apply(this instanceof resFn ? this : context, args1.concat(args2));
};
const DumpFunction = function DumpFunction() {};
DumpFunction.prototype = this.prototype;
resFn.prototype = new DumpFunction();
return resFn;
}
3.2 说说对 React Hooks 的理解
在 React 中我们一般有两种方式来创建组件,类定义 或者 函数定义 ;在类定义中我们可以使用许多 React 的特性,比如 state 或者各种生命周期钩子,但是在函数定义中却无法使用。所以在 React 16.8 版本中新推出了 React Hooks 的功能,通过 React Hooks 我们就可以在函数定义中来使用类定义当中才能使用的特性。当然 React Hooks 的出现本身也是为了组件复用,以及相比于类定义当中的生命周期钩子,React Hooks 中提供的useEffect
将多个生命周期钩子进行结合,使得原先在类定义中分散的逻辑变得更加集中,方便维护和管理。
3.3 React Hooks 当中的 useEffect 是如何区分生命周期钩子的
useEffect
可以看成是 componentDidMount
,componentDidUpdate
和componentWillUnmount
三者的结合。useEffect(callback,)
接收两个参数,调用方式如下:
useEffect(() => {console.log('mounted');
return () => {console.log('willUnmount');
}
}, );
生命周期函数的调用主要是通过第二个参数 来进行控制,有如下几种情况:
(1) 参数不传时,则每次都会优先调用上次保存的函数中返回的那个函数,然后再调用外部那个函数;
(2) 参数传
[]
时,则外部的函数只会在初始化时调用一次,返回的那个函数也只会最终在组件卸载时调用一次;
(3) 参数有值时,则只会监听到数组中的值发生变化后才优先调用返回的那个函数,再调用外部的函数。
3.4 什么是高阶组件(HOC)
高阶组件 (Higher Order Componennt) 本身其实不是组件,而是一个函数,这个函数接收一个 元组件 作为参数,然后返回一个新的 增强组件,高阶组件的出现本身也是为了逻辑复用,举个例子:
function withLoginAuth(WrappedComponent) {
return class extends React.Component {constructor(props) {super(props);
this.state = {isLogin: false};
}
async componentDidMount() {const isLogin = await getLoginStatus();
this.setState({isLogin});
}
render() {if (this.state.isLogin) {return <WrappedComponent {...this.props} />;
}
return (<div> 您还未登录...</div>);
}
}
}
3.5 说出以下代码的执行结果
parseInt('2017-07-01') // -> 2017
parseInt('2017abcdef') // -> 2017
parseInt('abcdef2017') // -> NaN
3.6 React 实现的移动应用中,如果出现卡顿,有哪些可以考虑的优化方案
(1) 增加 shouldComponentUpdate
钩子对新旧 props
进行比较,如果值相同则阻止更新,避免不必要的渲染,或者使用 PureReactComponent
替代 Component
,其内部已经封装了shouldComponentUpdate
的浅比较逻辑;
(2) 对于列表或其他结构相同的节点,为其中的每一项增加唯一 key
属性,以方便 React 的 diff
算法中对该节点的复用,减少节点的创建和删除操作;
(3) render
函数中减少类似 onClick={() => {doSomething()}}
的写法,每次调用 render
函数时均会创建一个新的函数,即使内容没有发生任何变化,也会导致节点没必要的重渲染,建议将函数保存在组件的成员对象中,这样只会创建一次;
(4) 组件的 props
如果需要经过一系列运算后才能拿到最终结果,则可以考虑使用 reselect
库对结果进行缓存,如果 props
值未发生变化,则结果直接从缓存中拿,避免高昂的运算代价;
(5) webpack-bundle-analyzer
分析当前页面的依赖包,是否存在不合理性,如果存在,找到优化点并进行优化。
3.7 (算法题) 如何从 10000 个数中找到最大的 10 个数
这题没答好,两个字形容:稀烂!一碰到算法题就容易紧张蒙圈,来个正解吧。
创建一个最小堆结构,初始值为 10000 个数的前 10 个,堆顶为 10 个数里的最小数。然后遍历剩下的 9990 个数,如果数字小于堆顶的数,则把堆顶的数删除,将遍历的数插入堆中。堆结构会自动进行调整,所以可以保证堆顶的数一定是 10 个数里最小的。遍历完毕后,堆里的 10 个数就是这 10000 个数里面最大的 10 个。
4. 流利说
当时是提前有一次电面,然后过了几天才去现场面,现场两轮技术面,公司很注重底层原理,所以答得不是很好。
4.1 React 实现一个防抖的模糊查询输入框
代码如下:
// 防抖函数
function debounce(fn, wait, immediate) {
let timer = null;
return function (...args) {
let context = this;
if (immediate && !timer) {fn.apply(context, args);
}
if (timer) clearTimeout(timer);
timer = setTimeout(() => {fn.apply(context, args);
}, wait);
}
}
class SearchInput extends React.Component {constructor(props) {super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.callAjax = debounce(this.callAjax, 500, true);
}
handleChange(e) {
this.setState({value: e.target.value});
this.callAjax();}
callAjax() {
// 此处根据输入值调用服务端接口
console.log(this.state.value);
}
render() {return (<input type="text" value={this.state.value} onChange={this.handleChange} />);
}
}
4.2 手动封装一个请求函数,可以设置最大请求次数,请求成功则不再请求,请求失败则继续请求直到超过最大次数
代码如下:
function request(url, body, successCallback, errorCallback, maxCount = 3) {return fetch(url, body)
.then(response => successCallback(response)
.catch(err => {if (maxCount <= 0) return errorCallback('请求超时');
return request(url, body, successCallback, errorCallback, --maxCount);
});
}
// 调用
request('https://some/path', { method: 'GET', headers: {} }, (response) => {console.log(response.json());
}, (err) => console.error(err));
4.3 JS 中 == 和 === 的区别
==
表示 抽象相等 ,两边值类型不同的时候,会先做 隐式类型转换 ,再对值进行比较;===
表示 严格相等,不会做类型转换,两边的类型不同一定不相等。
4.4 GET 和 POST 的区别
(1) GET 请求在浏览器回退和刷新时是无害的,而 POST 请求会告知用户数据会被重新提交;
(2) GET 请求可以收藏为书签,POST 请求不可以收藏为书签;
(3) GET 请求可以被缓存,POST 请求不可以被缓存;
(4) GET 请求只能进行 url 编码,而 POST 请求支持多种编码方式。
(5) GET 请求的参数可以被保留在浏览器的历史中,POST 请求不会被保留;
(6) GET 请求长度有限制,发送数据时,GET 请求向 URL 添加数据,URL 长度是有限制的,最大长度是 2048 个字符,POST 请求无长度限制;
(7) GET 请求只允许 ASCII 字符,POST 请求无限制,支持二进制数据;
(8) GET 请求的安全性较差,数据被暴露在浏览器的 URL 中,所以不能用来传递敏感信息,POST 请求的安全性较好,数据不会暴露在 URL 中;
(9) GET 请求具有幂等性(多次请求不会对资源造成影响),POST 请求不幂等;
(10) GET 请求会产生一个 TCP 数据包,POST 请求会产生两个 TCP 数据包,因为 GET 请求会将 http header 和 data 数据一并发送出去,而 POST 请求会先发送 http header 数据,服务端响应 100(continue),然后 POST 请求再发送 http data 数据,服务端再响应 200 返回数据。
4.5 说下浏览器的缓存机制
浏览器的缓存机制可分为 强缓存 和协商缓存 ,服务端可以在响应头中增加Cache-Control/Expires 来为当前资源设置缓存有效期 (Cache-Control 的 max-age 的优先级高于 Expires),浏览器再次发送请求时,会先判断缓存是否过期,如果未过期则命中强缓存,直接使用浏览器的本地缓存资源,如果已过期则使用协商缓存,协商缓存大致有以下两种方案:
(1) 唯一标识:Etag(服务端响应携带) & If-None-Match(客户端请求携带);
(2) 最后修改时间:Last-Modified(服务端响应携带) & If-Modified-Since (客户端请求携带),其优先级低于 Etag。
服务端判断值是否一致,如果一致,则直接返回 304 通知浏览器使用本地缓存,如果不一致则返回新的资源。
5. 哔哩哔哩
现场两轮技术面,问了很多考验基础知识的题目,整体来说回答的还算比较满意吧。
5.1 CSS3 中 transition 和 animation 的属性分别有哪些
transition
过渡动画:
(1) transition-property
:属性名称
(2) transition-duration
: 间隔时间
(3) transition-timing-function
: 动画曲线
(4) transition-delay
: 延迟
animation
关键帧动画:
(1) animation-name
:动画名称
(2) animation-duration
: 间隔时间
(3) animation-timing-function
: 动画曲线
(4) animation-delay
: 延迟
(5) animation-iteration-count
:动画次数
(6) animation-direction
: 方向
(7) animation-fill-mode
: 禁止模式
5.2 盒模型
指的是页面在渲染时,DOM 元素所采用的布局模型,一个元素占用的空间大小由几个部分组成,内容 (content)、内边距(padding),边框(border) 和外边距 (margin)。可以通过box-sizing
来进行设置,其中 IE 盒模型的 content 包含了 padding 和 border,这是区别于 W3C 标准盒模型的地方。
5.3 选择器优先级
!important > 行内样式 > id 选择器 > class 选择器 > 标签选择器 > * > 继承 > 默认
5.4 forEach,map 和 filter 的区别
forEach
遍历数组,参数为一个回调函数,回调函数接收三个参数,当前元素,元素索引,整个数组;map
与 forEach
类似,遍历数组,但其回调函数的返回值会组成一个新数组,新数组的索引结构和原数组一致,原数组不变;filter
会返回原数组的一个子集,回调函数用于逻辑判断,返回 true
则将当前元素添加到返回数组中,否则排除当前元素,原数组不变。
5.5 实现函数柯里化
代码如下:
const curry = (fn, ...args1) => (...args2) => (arg => arg.length === fn.length ? fn(...arg) : curry(fn, ...arg)
)([...args1, ...args2]);
// 调用
const foo = (a, b, c) => a * b * c;
curry(foo)(2, 3, 4); // -> 24
curry(foo, 2)(3, 4); // -> 24
curry(foo, 2, 3)(4); // -> 24
curry(foo, 2, 3, 4)(); // -> 24
5.6 跨标签页的通讯方式有哪些
(1) BroadCast Channel
(2) Service Worker
(3) LocalStorage + window.onstorage 监听
(4) Shared Worker + 定时器轮询(setInterval)
(5) IndexedDB + 定时器轮询(setInterval)
(6) cookie + 定时器轮询(setInterval)
(7) window.open + window.postMessage
(8) Websocket
5.7 实现一个函数判断数据类型
代码如下:
function getType(obj) {if (obj === null) return String(obj);
return typeof obj === 'object'
? Object.prototype.toString.call(obj).replace('[object', '').replace(']','').toLowerCase()
: typeof obj;
}
// 调用
getType(null); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date
总结
有些面试题实在是想不起来了,上面的题目其实大部分还是比较基础的,问到的频率也比较高,这里只是做一个简单的分享,希望对大家多多少少有点帮助,也希望能和大家一起交流学习,如果有疑惑欢迎留言讨论。
交流
今天先分享到这里,笔者刚新开公众号,如果大家有兴趣和笔者一起学习,相互讨论技术,可以关注咱们的公众号,一起见证公众号的成长。
文章已同步更新至 Github 博客,若觉文章尚可,欢迎前往 star!
你的一个点赞,值得让我付出更多的努力!
逆境中成长,只有不断地学习,才能成为更好的自己,与君共勉!