2019年9月面试记录
# 2019-9-9
# 常见的6种数据类型
- string 字符串
 - number 数值
 - object 对象
 - array 数组
 - null 空
 - undefined 未定义
 
# ajax和promise的区别
- ajax的数据是在回调函数里面返回的
 - promise是在then函数里面返回
 - promise是为了解决ajax回调地狱的问题
 - promise是链式写法
 - es7新增async await函数也可以解决ajax回调地狱和promise重复嵌套的问题
 
# 怎么解决0.1+0.2=0.33333的问题,只想要小数部分
- 利用(0.1+0.2).toFixed(1)
 - 可以先分别乘以10,然后再除以10
 
# promise有几种状态
- pending:进行中
 - fulfilled:已成功
 - rejected:已失败
 
# 为什么不建议在原型上拓展方法
- 容易造成全局污染,和其他库冲突
 - 出了Bug不太好定位问题
 - 有可能出现代码向上不兼容的情况
 
# array的some、every、filter、find有什么区别
- some只要数组中有一个满足条件,就返回true
 - every只有数组中所有的项满足条件,才返回true
 - filter返回一个满足条件的新数组
 - find返回数组中满足条件的第一个元素的值
 
# es6都用过哪些新增的特性
- let和const声明变量
 - 对象解构、数组解构、参数解构
 - 拓展运算符...
 - async await 函数
 - Promise 对象
 - Class类
 - Set和Map数据结构
 - for of 循环迭代 String、Array、Map、Set、
 - 字符串的拓展
- 字符串模板 ``
 - string.includes()
 
 - 数值的拓展
- Number.isNaN()
 - Number.isFinite()
 - Number.isInteger()
 - Math.trunc() 去除一个数的小数部分,返回整数部分
 - 指数运算符 2 ** 2 = 4
 
 - 函数的拓展
- 函数参数的默认值
 - 箭头函数
 - rest参数...rest
 - 函数的name属性,返回该函数的函数名
 
 - 数组的拓展
- Array.from转换伪数组或 Set 和 Map 数据结构为真数组
 - Array.of方法用于将一组值,转换为数组
 - copyWithin() 将指定位置的成员复制到其他位置
 - find()与findIndex(),find返回符合条件的成员,findIndex返回符合条件的索引值
 - fill()方法使用给定值,填充一个数组
 - 数组实例的 entries(),keys() 和 values()
 - 数组实例的 includes()
 - 数组实例的 flat() 将二维数组转换成一维数组,返回一个新数组
 
 - 对象的拓展
- Object.keys(),Object.values(),Object.entries()
 - Object.is()比较两个值是否相等,与===基本一致
 - Object.assign方法用于对象的合并,只是一级属性复制,比浅拷贝多深拷贝了一层而已
 
 
# reduce的常见用法
- 数组求和
 - 数组求最大值
 
// 数组求和
const arr = [12, 34, 23];
const sum = arr.reduce((total, num) => total + num);
// 数组最大值
const a = [23,123,342,12];
const max = a.reduce(function(pre,cur,index,arr){return pre>cur?pre:cur;}); 
// 将二维数组转为一维数组
const arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];
const res = arr.reduce((x, y) => x.concat(y), []);
# Map和Set数据结构有什么区别
- Set是没有重复元素的集合
 - Map是键值对的形式
 
# 箭头函数和普通函数有什么区别
- 箭头函数的this指向当前的执行环境,而普通函数指向自己
 - 箭头函数不可以当作构造函数,不能使用new命令
 - 箭头函数可以使用rest获取参数对象,普通函数可以用arguments获取参数对象
 - 箭头函数没有prototype(原型),所以箭头函数本身没有this
 - 不能直接修改箭头函数的this指向
 
# 命令式编程与声明式编程有什么区别
- 命令式编程:详细的告诉计算怎么一步一步的达到结果
 - 声明式编程:只告诉计算机需要做什么,计算机自己执行达到结果
 
// 命令式编程
let result;
for (var i = 0; i < user.length; i++) {
  if (user[i] === "Ben") {
    result = user[i];
    break;
  }
}
// 声明式编程
const result = user.find(item => item === "Ben");
# 为什么{} + [] === 0为true
{} 认定是语法块,这个放在前面,只有混淆作用,并不参与运算。
+[] 类型转换 0 ,0===0
# 什么是变量提升
参考:深入理解 js 变量提升 (opens new window)
JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)
# 2019-9-11
# socket怎么检测客户端长时间没有连接
这里面会有一个心跳机制,服务端定时向客户端发送一个数据包,然后启动一个低级别的线程,该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线,同样,如果客户端在一定时间内没 有收到服务器的心跳包,则认为连接不可用。
# 路由的hash模式和history模式有什么区别
参考:hash 和 history的区别 (opens new window)
- 原理不一样
- hash模式是通过在 window 对象 onhashchange 事件监听的地址栏的hash部分实现路由跳转
 - history模式则是通过在window对象上监听popState()事件,history api 提供 pushState、replaceState、go、back、forward,等方法实现路由跳转
 
 - 路由跳转表现不一样
 
- pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL
 - pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中
 
- 发送 http 请求的时表现不一样
- hash对地址栏的变化不会发送到后端,准确说只会发送#前半部分到后端,例如:https://yixiu.com
 - history则会将地址发送到后端,一旦后端没有对应的路由处理,会返回404错误,所以需要后台配置:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面
 
 
# 谈谈高阶组件在实际项目的运用
参考:如何理解 React 高阶组件(HOC)? (opens new window)
- 高阶组件就是接受组件为参数返回一个加强组件的函数
 - withRouter、connect这些都是高阶组件的应用
 - 用高阶组件做增加loading效果
 - 复用组件逻辑
 
# 深拷贝和浅拷贝
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
 - 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改变原对象
 
| / | 和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据包含子对象 | 
|---|---|---|---|
| 赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 | 
| 浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 | 
| 深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 | 
浅拷贝的实现方式
- 使用
Object.assign() - 使用es6的扩展运算符
... - Array.prototype.concat()
 - Array.prototype.slice()
 
深拷贝的实现方式
- JSON.parse(JSON.stringify()),这种方法虽然可以实现数组或对象深拷贝,但不能处理函数
 - 手写递归方法
 - 函数库lodash提供_.cloneDeep用来做 Deep Copy
 
//定义检测数据类型的功能函数
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}
//实现深度克隆---对象/数组
function clone(target) {
  //判断拷贝的数据类型
  //初始化变量result 成为最终克隆的数据
  let result, targetType = checkedType(target)
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else {
    return target
  }
  //遍历目标数据
  for (let i in target) {
    //获取遍历数据结构的每一项值。
    let value = target[i]
    //判断目标结构里的每一值是否存在对象/数组
    if (checkedType(value) === 'Object' ||
      checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
      //继续遍历获取到value值
      result[i] = clone(value)
    } else { //获取到value值是基本的数据类型或者是函数。
      result[i] = value;
    }
  }
  return result
}
# 为什么react和vue里面循环产生html时要绑定key
key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度
# 怎么控制video视频的宽高
设置width和height
# 为什么要发送一个option请求
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错
这和浏览器的 CORS 跨域机制有关,客户端将请求分为了两种:简单请求和非简单请求;当请求为非简单请求时,就会触发浏览器发送预检请求。
预检请求一般包括 request header
# 在项目中做过哪些优化
# 前端部署
# 2019-9-12
# 为何要在componentDidMount里面发送请求?
参考:为何要在componentDidMount里面发送请求? (opens new window)
- componentDidmount 是在组件完全挂载后才会执行,在此方法中调用setState 会触发重新渲染,最重要的是,这是官方推荐的!
 - componentWillMount、constructor 里进行网络请求会阻碍组件的渲染,会阻碍组件的实例化
 - 从React 16.3(2018年3月)开始componentWillMount会被弃用
 
# 你用过哪些react相关的UI框架
- Zent有赞
 - ant-design和ant-mobile
 - element-react
 - React Suite
 
# 怎么用css写出三角形
.trangle {
  width: 0;
  height: 0;
  border: 40px solid;  
  border-color: transparent transparent transparent red;
}
# react和vue有什么区别
区别:https://juejin.im/post/5b8b56e3f265da434c1f5f76 数据流动原理:https://recallhyx.github.io/2018/03/18/%E5%89%8D%E7%AB%AF%E4%B8%89%E5%A4%A7%E6%A1%86%E6%9E%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E5%8A%A8%E5%92%8C%E5%8E%9F%E7%90%86/
- 数据流向不一样:vue支持双向数据绑定,react是单向数据流
 - 监听数据变化的原理不一样:vue使用Object.defineProperty()实现数据劫持,组件能自动响应数据变化,react通过onChange/setState的方式
 
# MVVM的原理
参考:
MVC,MVP 和 MVVM 的图示 (opens new window)
 浅析 web 前端 MVVM (opens new window)
- View:视图
 - ViewModel:视图模型
 - Model:数据模型
 
它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然
Vue 的 MVVM
View:单文件里 <template> 标签的内容,展现给用户的内容,与 ViewModel 双向绑定,可以在其中插入 ViewModel 提供的数据。
ViewModel:Vue 实例整个都是 ViewModel,与 View 双向绑定,用户在 View 修改数据或发出 ajax 等指令时, ViewModel 会及时相应,接着向下修改 Model——至此可以看出 Model 和 View 是没有直接关系的。
Model:这一层或者有歧义。为了更好理解 Model 需要引入 Vuex,在有 Vuex 的情况下,Vuex 提供的数据就是 Model,这符合后端架构中 Model 包含业务逻辑的情况。但是在无 Vuex 的情况下,Model 应该就是 Vue 实例的 data 属性,也就是 JavaScript 数据对象本身。
# 实现垂直居中的方式有哪些
# 在项目中怎么做移动端适配
# react组件通信
# react导入组件的方式有哪些
- 通过传统import方式导入
 - 通过require引入
 
# HTTP请求头
- General 一般头
- Request URL 请求地址
 - Request Method 请求方式
 - Status Code 请求状态
 - Remote Address 远端地址
 
 - Request Header 请求头
- Accept 用户代理期望的 MIME(媒体类型) 类型列表
 - Origin 远端信息
 - User-Agent 用户代理信息
 - Content-Type 示服务器文档的MIME 类型
 - Host 域名
 - Authorization 用户凭证
 
 - Response Header 响应头
- Access-Control-Allow-Headers 允许请求头
 - Access-Control-Allow-Methods 允许的请求方式
 - Access-Control-Allow-Origin 是否允许跨域
 - Content-Type 示服务器文档的MIME 类型
 - Cache-Control 是否开启缓存
 - Expires 过期时间
 - Date 时间
 
 
# 按需引入
参考:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
# 对于redux的理解
用来做全局状态管理,组件可以共享到状态
- state:是一个js对象,存放应用状态
 - action:是一个包含
type和payload的普通obj,我们通常使用一个函数(action creator)返回action - reducer:是一个纯函数,描述state是如何改变的,它有两个参数state和action,我们在函数体内使用switch语句匹配到action的type,拿到payload然后对状态作出更改
 
数据流向
数据流向是这样的,组件dispath一个action,reducer匹配到actions的type对state作出修改,view接收到数据变化,视图响应变化。视图是怎么接收到变化到呢,当我们没有使用react-redux时,我们是通过store.subscrib()订阅了state到变化,从而响应到视图上,而在使用react-redux时,我们借助connect方法,它自动订阅了state到变化,并映射到视图上,组件可以使用connect到前提是,我们必须在根组件外层包裹Provider组件。
异步操作
异步操作需要借助redux-thunk这个中间件,它可以使我们可以在action中拿到dispatch对象,我们使用action creator函数返回一个promise对象,在then函数内再dispath一个action通知reducer对state做出改变,也就是说异步操作发起了2次dispatch。
# 同事对你的评价
热情、乐于助人、自律、做事认真、有潜力
 