虾皮、OPPO、富途等十几家公司面经总结
最近朋友内推面试了几家公司(货拉拉
、虾皮
、有赞
、乐信
、Qtrade苹果树
、富途
、涂鸦
、OPPO
、微保
、微众
、元戎启行
),也收获了满意的offer。整理了下面试遇到的问题,作为记录。
JS相关
JS原型及原型链
function Person() {}
Person.prototype.name = 'Zaxlct';
Person.prototype.sayName = function() {
alert(this.name);
}
var person1 = new Person();
//JS 在创建对象的时候,都有一个__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。
//每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性
// 对象 person1 有一个 __proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype
console.log(person1.__proto__ == Person.prototype) //true
//所有函数对象的__proto__都指向Function.prototype
String.__proto__ === Function.prototype // true
String.constructor == Function //true
JS继承的几种方式
详解
原型继承
function Parent () {
this.name = 'Parent'
this.sex = 'boy'
}
function Child () {
this.name = 'child'
}
// 将子类的原型对象指向父类的实例
Child.prototype = new Parent()
//优:继承了父类的模板,又继承了父类的原型对象
//缺:1.无法实现多继承(因为已经指定了原型对象了)
// 2.创建子类时,无法向父类构造函数传参数
构造函数继承
在子类构造函数内部使用call或apply
来调用父类构造函数,复制父类的实例属性给子类。
function Parent (name) {
this.name = name
}
function Child () {
//用.call 来改变 Parent 构造函数内的指向
Parent.call(this, 'child')
}
//优:解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数
//缺:构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法
组合继承
组合继承就是将原型链继承与构造函数继承组合在一起。
使用原型链继承来保证子类能继承到父类原型中的属性和方法 使用构造继承来保证子类能继承到父类的实例属性和方法 寄生组合继承
class继承
在class
中继承主要是依靠两个东西:
extends
super
class Parent {
constructor (name) {
this.name = name
}
getName () {
console.log(this.name)
}
}
class Child extends Parent {
constructor (name) {
super(name)
this.sex = 'boy'
}
}
Event Loop 事件循环
同步与异步、宏任务和微任务分别是函数两个不同维度的描述。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入任务队列(task queue
)的任务,只有等主线程任务执行完毕,任务队列开始通知主线程,请求执行任务,该任务才会进入主线程执行。
当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务;如果没有,在执行环境栈中会读取宏任务队列中排在最前的任务;执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。
同步(Promise)>异步(微任务(process.nextTick ,Promises.then, Promise.catch ,resove,reject,MutationObserver)>宏任务(setTimeout,setInterval,setImmediate))
await阻塞 后面的代码执行,因此跳出async函数执行下一个微任务
Promise 与 Async/Await 区别
async/await是基于Promise实现的,看起来更像同步代码,
不需要写匿名函数处理Promise的resolve值 错误处理: Async/Await 让 try/catch 可以同时处理同步和异步错误。 条件语句也跟错误处理一样简洁一点 中间值处理(第一个方法返回值,用作第二个方法参数) 解决嵌套问题 调试方便
const makeRequest = () => {
try {
getJSON().then(result => {
// JSON.parse可能会出错
const data = JSON.parse(result)
console.log(data)
})
// 取消注释,处理异步代码的错误
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}
使用aync/await
的话,catch能处理JSON.parse
错误:
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}
promise怎么实现链式调用跟返回不同的状态
实现链式调用:使用.then()
或者.catch()
方法之后会返回一个promise
对象,可以继续用.then()
方法调用,再次调用所获取的参数是上个then
方法return
的内容
promise的三种状态是
fulfilled
(已成功)/pengding
(进行中)/rejected
(已拒绝)状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
Promise 中使用
resolve
和reject
两个函数来更改状态;then 方法内部做的事情就是状态判断:
如果状态是成功,调用成功回调函数 如果状态是失败,调用失败回调函数
函数柯里化
柯里化(Currying)
是把接收多个参数的原函数变换成接受一个单一参数(原来函数的第一个参数的函数)并返回一个新的函数,新的函数能够接受余下的参数,并返回和原函数相同的结果。
参数对复用 提高实用性 延迟执行 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。柯里化的函数可以延迟接收参数,就是比如一个函数需要接收的参数是两个,执行的时候必须接收两个参数,否则没法执行。但是柯里化后的函数,可以先接收一个参数
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
JS对象深克隆
递归遍历对象,解决循环引用问题
解决循环引用问题,我们需要一个存储容器存放当前对象和拷贝对象的对应关系(适合用key-value的数据结构进行存储,也就是map),当进行拷贝当前对象的时候,我们先查找存储容器是否已经拷贝过当前对象,如果已经拷贝过,那么直接把返回,没有的话则是继续拷贝。
function deepClone(target) {
const map = new Map()
function clone (target) {
if (isObject(target)) {
let cloneTarget = isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target)
}
map.set(target,cloneTarget)
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
}
return clone(target)
};
JS模块化
nodeJS
里面的模块是基于commonJS
规范实现的,原理是文件的读写,导出文件要使用exports
、module.exports
,引入文件用require
。每个文件就是一个模块;每个文件里面的代码会用默认写在一个闭包函数里面AMD
规范则是非同步加载模块,允许指定回调函数,AMD
是 RequireJS
在推广过程中对模块定义的规范化产出。
AMD
推崇依赖前置, CMD
推崇依赖就近。对于依赖的模块AMD是提前执行,CMD是延迟执行。
在ES6
中,我们可以使用 import
关键字引入模块,通过 exprot
关键字导出模块,但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel
将不被支持的import
编译为当前受到广泛支持的 require
。
CommonJs 和 ES6 模块化的区别:
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
前端模块化:CommonJS,AMD,CMD,ES6
import 和 require 导入的区别
import 的ES6 标准模块;require 是 AMD规范引入方式;
import是编译时调用,所以必须放在文件开头;是解构过程 require是运行时调用,所以require理论上可以运用在代码的任何地方;是赋值过程。其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
异步加载JS方式
匿名函数自调动态创建script标签加载js
(function(){
var scriptEle = document.createElement("script");
scriptEle.type = "text/javasctipt";
scriptEle.async = true;
scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js";
var x = document.getElementsByTagName("head")[0];
x.insertBefore(scriptEle, x.firstChild);
})();
async属性
// async属性规定一旦加载脚本可用,则会异步执行
等,提交后信息会存在服务器中 。
CSRF:跨站请求伪造 。引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。
A
站点img
的src=B
站点的请求接口,可以访问;解决:referer
携带请求来源
访问该页面后,表单自动提交, 模拟完成了一次POST操作,发送post请求
解决:后端注入一个随机串
到Cookie
,前端请求取出随机串添加传给后端。
http 劫持:电信运营商劫持
SQL注入
点击劫持:诱使用户点击看似无害的按钮(实则点击了透明 iframe
中的按钮) ,解决后端请求头加一个字段 X-Frame-Options
文件上传漏洞 :服务器未校验上传的文件
CSS 及 HTML
什么是BFC(块级格式化上下文)、IFC(内联格式化上下文 )、FFC(弹性盒模型)
BFC(Block formatting context)
,即块级格式化上下文
,它作为HTML页面上的一个独立渲染区域
,只有区域内元素参与渲染,且不会影响其外部元素。简单来说,可以将 BFC 看做是一个“围城”,外面的元素进不来,里面的元素出不去(互不干扰)。
一个决定如何渲染元素的容器 ,渲染规则 :
1、内部的块级元素会在垂直方向,一个接一个地放置。
2、块级元素垂直方向的距离由margin决定。属于同一个BFC的两个相邻块级元素的margin会发生重叠。
3、对于从左往右的格式化,每个元素(块级元素与行内元素)的左边缘,与包含块的左边缘相接触,(对于从右往左的格式化则相反)。即使包含块中的元素存在浮动也是如此,除非其中元素再生成一个BFC。
4、BFC的区域不会与浮动元素重叠。
5、BFC是一个隔离的独立容器,容器里面的子元素和外面的元素互不影响。
6、计算BFC容器的高度时,浮动元素也参与计算。
形成BFC的条件:
1、浮动元素,float 除 none 以外的值;
2、定位元素,position(absolute,fixed);
3、display 为以下其中之一的值 inline-block,table-cell,table-caption;
4、overflow 除了 visible 以外的值(hidden,auto,scroll);
BFC 一般用来解决以下几个问题
边距重叠问题 消除浮动问题 自适应布局问题
flex: 0 1 auto;
是什么意思?
元素会根据自身宽高设置尺寸。它会缩短自身以适应 flex
容器,但不会伸长并吸收 flex
容器中的额外自由空间来适应 flex
容器 。水平的主轴(main axis
)和垂直的交叉轴(cross axis
)几个属性决定按哪个轴的排列方向
flex-grow
:0
一个无单位数() : 它会被当作的值。 flex-shrink
:1
一个有效的**宽度(width)**值: 它会被当作的值。 flex-basis
:auto
关键字none
,auto
或initial
.
放大比例、缩小比例、分配多余空间之前占据的主轴空间。
避免CSS全局污染
scoped 属性 css in js
const styles = {
bar: {
backgroundColor: '#000'
}
}
const example = (props)=>{
}
CSS Modules 使用less,尽量少使用全局对选择器
// 选择器上>要记得写,免得污染所有ul下面的li
ul{
>li{
color:red;
}
}
CSS Modules
阮一峰 CSS Modules
CSS Modules是一种构建步骤中的一个进程。通过构建工具来使指定class
达到scope
的过程。
CSS Modules
允许使用::global(.className)
的语法,声明一个全局规则。凡是这样声明的class
,都不会被编译成哈希字符串
。:local(className)
: 做 localIdentName 规则处理,编译唯一哈希类名。
CSS Modules使用特点:
不使用选择器,只使用 class 名来定义样式 不层叠多个 class,只使用一个 class 把所有样式定义好 不嵌套class
盒子模型和 box-sizing
属性
width: 160px; padding: 20px; border: 8px solid orange;
标准 box-sizing: content-box;
元素的总宽度 = 160 + 202 + 82; IE的 border-box
:总宽度160
margin/padding
取百分比的值时 ,基于父元素的宽度和高度的。
css绘制三角形
通过border 处理
// border 处理
.class {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid red;
}
// 宽高+border
div {
width: 50px;
height: 50px;
border: 2px solid orange;
}
clip-path裁剪获得
div{
clip-path: polygon(0 100%, 50% 0, 100% 100%);
}
渐变linear-gradient 实现
div {
width: 200px;
height: 200px;
background:linear-gradient(to bottom right, #fff 0%, #fff 49.9%, rgba(148,88,255,1) 50%,rgba(185,88,255,1) 100%);
}
CSS实现了三角形后如何给三角形添加阴影
???
CSS两列布局的N种实现
两列布局分为两种,一种是左侧定宽、右侧自适应,另一种是两列都自适应(即左侧宽度由子元素决定,右侧补齐剩余空间)。
左侧定宽、右侧自适应如何实现
// 两个元素都设置dislpay:inline-block
.left {
display: inline-block;
width: 100px;
height: 200px;
background-color: red;
vertical-align: top;
}
.right {
display: inline-block;
width: calc(100% - 100px);
height: 400px;
background-color: blue;
vertical-align: top;
}
// 两个元素设置浮动,右侧自适应元素宽度使用calc函数计算
.left{
float: left;
width: 100px;
height: 200px;
background-color: red;
}
.right{
float: left;
width: calc(100% - 100px);
height: 400px;
background-color: blue;
}
// 父元素设置display:flex,自适应元素设置flex:1
.box{
height: 600px;
width: 100%;
display: flex;
}
.left{
width: 100px;
height: 200px;
background-color: red;
}
.right{
flex: 1;
height: 400px;
background-color: blue;
}
// 父元素相对定位,左侧元素绝对定位,右侧自适应元素设置margin-left的值大于定宽元素的宽度
.left{
position: absolute;
width: 100px;
height: 200px;
background-color: red;
}
.right{
margin-left: 100px;
height: 400px;
background-color: blue;
}
左右两侧元素都自适应
// flex布局 同上
// 父元素设置display:grid; grid-template-columns:auto 1fr;(这个属性定义列宽,auto关键字表示由浏览器自己决定长度。fr是一个相对尺寸单位,表示剩余空间做等分)grid-gap:20px(行间距)
.parent{
display:grid;
grid-template-columns:auto 1fr;
grid-gap:20px
}
.left{
background-color: red;
height: 200px;
}
.right{
height:300px;
background-color: blue;
}
// 浮动+BFC 父元素设置overflow:hidden,左侧定宽元素浮动,右侧自适应元素设置overflow:auto创建BFC
.box{
height: 600px;
width: 100%;
overflow: hidden;
}
.left{
float: left;
width: 100px;
height: 200px;
background-color: red;
}
.right{
overflow: auto;
height: 400px;
background-color: blue;
}
CSS三列布局
float布局:左边左浮动,右边右浮动,中间margin:0 100px;
Position布局: 左边left:0; 右边right:0; 中间left: 100px; right: 100px;
table布局: 父元素 display: table; 左右 width: 100px; 三个元素display: table-cell;
弹性(flex)布局:父元素 display: flex; 左右 width: 100px;
网格(gird)布局:
// gird提供了 gird-template-columns、grid-template-rows属性让我们设置行和列的高、宽
.div{
width: 100%;
display: grid;
grid-template-rows: 100px;
grid-template-columns: 300px auto 300px;
}
常见场景及问题
app与H5 如何通讯交互的?
// 兼容IOS和安卓
callMobile(parameters,messageHandlerName) {
//handlerInterface由iOS addScriptMessageHandler与andorid addJavascriptInterface 代码注入而来。
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
// alert('ios')
window.webkit.messageHandlers[messageHandlerName].postMessage(JSON.stringify(parameters))
} else {
// alert('安卓')
//安卓传输不了js json对象,只能传输string
window.webkit[messageHandlerName](JSON.stringify(parameters))
}
}
由app将原生方法注入到window上供js调用
messageHandlerName
约定的通信方法parameters
需要传入的参数
移动端适配方案
rem
是相对于HTML的根元素em
相对于父级元素的字体大小。VW,VH
屏幕宽度高度的高分比
//按照宽度375图算, 1rem = 100px;
(function (win, doc) {
function changeSize() {
doc.documentElement.style.fontSize = doc.documentElement.clientWidth / 3.75 + 'px';
console.log(100 * doc.documentElement.clientWidht / 3.75)
}
changeSize();
win.addEventListener('resize', changeSize, false);
})(window, document);
代码编程相关
实现发布订阅
/* Pubsub */
function Pubsub(){
//存放事件和对应的处理方法
this.handles = {};
}
Pubsub.prototype = {
//传入事件类型type和事件处理handle
on: function (type, handle) {
if(!this.handles[type]){
this.handles[type] = [];
}
this.handles[type].push(handle);
},
emit: function () {
//通过传入参数获取事件类型
//将arguments转为真数组
var type = Array.prototype.shift.call(arguments);
if(!this.handles[type]){
return false;
}
for (var i = 0; i < this.handles[type].length; i++) {
var handle = this.handles[type][i];
//执行事件
handle.apply(this, arguments);
}
},
off: function (type, handle) {
handles = this.handles[type];
if(handles){
if(!handle){
handles.length = 0;//清空数组
}else{
for (var i = 0; i < handles.length; i++) {
var _handle = handles[i];
if(_handle === handle){
//从数组中删除
handles.splice(i,1);
}
}
}
}
}
promise怎么实现链式调用跟返回不同的状态
// MyPromise.js
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
}
}
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
}
实现Promise.all
// Promise.all
function all(promises) {
let len = promises.length, res = []
if (len) {
return new Promise(function (resolve, reject) {
for(let i=0; i < len; i++){
let promise = promises[i];
promise.then(response => {
res[i] = response
// 当返回结果为最后一个时
if (res.length === len) {
resolve(res)
}
}, error => {
reject(error)
})
}
})
}
对象数组转换成tree数组
> 将entries 按照 level 转换成 result 数据结构
const entries = [
{
"province": "浙江", "city": "杭州", "name": "西湖"
}, {
"province": "四川", "city": "成都", "name": "锦里"
}, {
"province": "四川", "city": "成都", "name": "方所"
}, {
"province": "四川", "city": "阿坝", "name": "九寨沟"
}
];
const level = ["province", "city", "name"];
const result = [
{
value:'浙江',
children:[
{
value:'杭州',
children:[
{
value:'西湖'
}
]
}
]
},
{
value:'四川',
children:[
{
value:'成都',
children:[
{
value:'锦里'
},
{
value:'方所'
}
]
},
{
value:'阿坝',
children:[
{
value:'九寨沟'
}
]
}
]
},
]
思路:涉及到树形数组,采用递归遍历的方式
function transfrom(list, level) {
const res = [];
list.forEach(item => {
pushItem(res, item, 0);
});
function pushItem(arr, obj, i) {
const o = {
value: obj[level[i]],
children: [],
};
// 判断传入数组里是否有value等于要传入的项
const hasItem = arr.find(el => el.value === obj[level[i]]);
let nowArr;
if(hasItem) {
// 存在,则下一次遍历传入存在项的children
nowArr = hasItem.children;
}else{
// 不存在 压入arr,下一次遍历传入此项的children
arr.push(o);
nowArr = o.children;
}
if(i === level.length - 1) delete o.children;
i++;
if(i < level.length) {
// 递归进行层级的遍历
pushItem(nowArr, obj, i);
}
}
}
transfrom(entries, level);
JS instanceof 方法原生实现
简单用法
function Fn () {}
const fn = new Fn()
fn instanceof Fn // true
实现如下:
// left instanceof right
function _instanceof(left, right) {
// 构造函数原型
const prototype = right.prototype
// 实列对象属性,指向其构造函数原型
left = left.__proto__
// 查实原型链
while (true) {
// 如果为null,说明原型链已经查找到最顶层了,真接返回false
if (left === null) {
return false
}
// 查找到原型
if (prototype === left){
return true
}
// 继续向上查找
left = left.__proto__
}
}
const str = "abc"
_instanceof(str, String) // true
编程题
将有同样元素的数组进行合并
// 例如:
const arr = [
['a', 'b', 'c'],
['a', 'd'],
['d', 'e'],
['f', 'g'],
['h', 'g'],
['i']
]
// 运行后的返回结果是:
[
['a', 'b', 'c', 'd', 'e'],
['f', 'g', 'h'],
['i']
]
// 思路一:
const arr = [['a', 'b', 'c'], ['a', 'd'], ['d', 'e'], ['f', 'g'], ['h', 'g'], ['i']]
function transform(arr){
let res = []
arr = arr.map(el => el.sort()).sort()
const item = arr.reduce((pre, cur) => {
if (cur.some(el => pre && pre.includes(el))) {
pre = pre.concat(cur)
} else {
res.push(pre)
pre = cur
}
return [...new Set(pre)]
})
res.push(item)
return res;
}
transform(arr)
// console.log(transform(arr));
// 思路二:
function r (arr) {
const map = new Map()
arr.forEach((array, index) => {
const findAlp = array.find((v) => map.get(v))
if (findAlp) {
const set = map.get(findAlp)
array.forEach((alp) => {
set.add(alp)
const findAlp2 = map.get(alp)
if (findAlp2 && findAlp2 !== set) {
for(const v of findAlp2.values()){
set.add(v)
map.set(v, set)
}
}
map.set(alp, set)
})
} else {
const set = new Set(arr[index])
array.forEach((alp) => map.set(alp, set))
}
})
const set = new Set()
const ret = []
for (const [key, value] of map.entries()) {
if (set.has(value)) continue
set.add(value)
ret.push([...value])
}
return ret
}
源自:https://juejin.cn/post/6991724298197008421
声明:文章著作权归作者所有,如有侵权,请联系小编删除。