面试备忘

190 阅读16分钟

Vue和React区别

  • 都使用虚拟 DOM Vue和React实现原理和流程基本一致,都是使用Virtual DOM + Diff算法 vue template/react jsx -> render函数 -> 生成VNode -> 当有变化时,新老VNode diff -> diff算法对比,并真正去更新真实DOM。 减少直接操作DOM。框架给我们提供了屏蔽底层dom书写的方式,减少频繁的整更新dom,同时也使得数据驱动视图
  • 组件化思想
  • react是mvc,vue是mvvm
  • vue 数据双向绑定,react 单向数据流
mvvm

- ViewModel 通过实现一套数据响应式机制自动响应Model中数据变化, 
  同时 Viewmodel 会实现一套更新策略自动将数据变化转换为视图更新; 

mvc

- mvc Model:负责保存应用数据,与后端数据进行同步。
- Controller:负责业务逻辑,根据用户行为对 Model 数据进行修改。
- View:负责视图展示,将 Model 中的数据可视化出来。

React生命周期

  • 描述
  1. 初始化阶段:由ReactDOM.render()触发 ---初次渲染

    • constructor()
    • getDerivedStateFromProps() 从Props获得派生状态
    • render()
    • componentDidMount() ====>常用
  2. 更新阶段:由组件内部的this.setState()或者父组件的render触发

    • getDerivedStateFromProps() 从Props获得state状态
    • shouldComponentUpdate() 组件应该更新
    • render()
    • getSnapshotBeforeUpdate() 在更新前获得快照
    • componentDidUpdate()
  3. 卸载组件:由ReactDOM.unmountComponentAtNode()触发

    • componentWillUnmount() ===>常用

    一般在这个钩子中做一些收尾的事情,如:关闭定时器、取消订阅消息

  • 重要的钩子
  1. render:初始化渲染或者更新渲染调用
  2. componentDidMount() :开启监听,发送ajax请求
  3. componentWillUnmount(): 做一些收尾工作,如:清理定时器

setState

  • 默认是异步更新,如果调用方可能会在任务队列里去setState,就会是同步的
  • setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
  • setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

如何在应用中处理Effect执行两次的问题

我们可以通过一个变量来控制请求的时候只发出去一次,也可以通过取消请求的方式来进行改善。

useEffect(() => {  
    let ignore = false;  
    async function startFetching() {  
        const json = await fetchTodos(userId);  
        if (!ignore) { 
            setTodos(json);  
        }  
}  

    startFetching();  
    return () => {  
       ignore = true;  
    };  
}, [userId]);

vue响应式原理

  • 通过Object.defineProperty劫持数据,循环遍历data对象的所有属性,为每个属性设置 getter、setter
  • 数据被读的时候,getter函数执行把当前的watcher(Watcher实例订阅一个或者多个数据)添加到dep数组中,完成依赖收集
  • 数据变化时触发setter函数,通知相关的watcher 去执行 update 方法派发更新

Vuex

  • 模块分别是 State、 Getter、Mutation 、Action、 Module
  • state => 基本数据(数据源存放地)
  • getters => 从基本数据派生出来的数据
  • mutations => 提交更改数据的方法,同步
  • actions => 像一个装饰器,包裹mutations,使之可以异步。
  • modules => 模块化Vuex
  • Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。
  • 每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

data为什么是一个函数而不是一个对象?

JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。而在Vue中,我们更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当我们每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。

如何一次性渲染10w条数据

10w按照每页数量limit分成总共Math.ceil(total / limit)页,然后利用setTimeout,每次渲染1页数据,这样的话,渲染出首页数据的时间大大缩减了

const renderList = async () => {
    console.time('列表时间')
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total / limit)
    
    const render = (page) => {
     if (page >= totalPage) return
     setTimeout(() => {
        for (let i = page * limit; i < page * limit + limit; i++) {
            const item = list[i]
            const div = document.createElement('div')
            div.className = 'sunshine'
            div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
            container.appendChild(div)
        }
        render(page + 1)
    }, 0)
}
render(page)
console.timeEnd('列表时间')
}

event-loop: 事件队列

  • 同步队列
    • 所有同步代码依次执行,根据代码书写行数从上到下执行
  • 异步队列
    • 等待所有同步代码执行完成后,再根据队列顺序依次执行
    • 宏任务:DOM 渲染后触发:定时器,异步ajax是宏任务,鼠标事件
    • 微任务:DOM 渲染前触发:promise.then;process.nextTick

Es6

1. let、const 和 var 之间的区别:
  • var 声明的变量存在变量提升,而 let、const 不存在变量提升的问题。变量提升:变量是否可在声明前调用
console.log(h)//undefined,var在变量声明前就可以使用,提升到作用域顶端
console.log(f)//报错
  var h='xxx'
  let j='vvvv'
  • var 不存在块级作用域,let 和 const 存在块级作用域
{
  var g='xxx'
  let f='vvvv'
}
console.log(g)//可读取
console.log(f)//不可读取
  • var 可以重复声明变量,let 和 const 在一个作用域内不允许重复声明,并且 const 声明的是一个 只读 的变量,并且一定要赋值
2. 解构赋值
//数组
const D=['xx','vv']
const [a,b]=D
console.log(a)//xx
console.log(b)//vv
//对象
const G={
  name:'xl',
  age:'12'
}
const {name,age}=G
console.log(name)//xl
console.log(age)//12
3. 模板字符串
let a=字符串
let str=`我是${a}`
console.log(str)//我是字符串
4. 箭头函数
  • this始终指向上一级作用域
  function fn1() {
    console.log(this.name)
  }
  const fn2 = () => {
    console.log(this.name)
  }
  window.name = '全局'
  const obj = {
    name: '局部'
  }
​
  fn1.call(obj)//局部
  fn2.call(obj)//全局,fn2为箭头函数,this指向上级作用域
  • 不能作为构造函数
  • 没有arguments变量
5. 函数参数可以设置默认值
6. 扩展运算符[...]
7. rest参数
function date(...args){
     console.log(args);
  }
  date('1','2')//['1',2],返回数组形式,rest参数必须放到最后例如fn(a,b,...args)
8. Symbol
 // ES6中引入的原始数据类型,代表着独一无二的
 let a = Symbol();
 let b = Symbol();
 console.log(a === b); // false
9. Promise

...

10. async和await
const p = new Promise((resolve, reject) => {
    resolve('成功啦')
    reject('失败了')
  })
​
  async function fn() {
    try {
      const res = await p
      console.log(res);
    } catch (e) {
      console.log(e);
    }
  }
11. Map
  • 定义:类似于对象的数据结构,成员键是任何类型的值
  • 声明:const set = new Map(arr)
  • 方法:
  1. get():返回键值对
  2. set():添加键值对,返回实例
  3. delete():删除键值对,返回布尔
  4. has():检查键值对,返回布尔
  5. clear():清除所有成员
  6. keys():返回以键为遍历器的对象
  7. values():返回以值为遍历器的对象
  8. entries():返回以键和值为遍历器的对象
  9. forEach():使用回调函数遍历每个成员
let m = new Map(arr)
m.set('key','value')
...

H5,CSS3

H5

1、拖拽释放(drag and drop)API 2、语义化更好的内容标签(header footer nav aside article section) 3、音频、视频(audio video)API 4、画布(Canvas)API和svg绘制矢量图 5、地理(Geolocation)API 6、localstorage 和 sessionstorage 缓存方式 7、表单控件(calendar date time email ul search) 8、新技术(webworker websocket Geolocation)

css3新特性
  • border-radius 圆角效果
  • box-shadow 阴影
  • border-image 使用图片来绘制边框
  • RGBA 颜色
  • 渐变色彩,liner-gradient...,
  • transform 旋转rotate() 缩放scale() 位移translate() 倾斜transform()
  • flex 弹性布局
  • animation 动画
盒子模型类型
  • 标准盒模型(content-box):属性 width ,height 只包含内容 content,不包含 border 和 padding 。
  • IE 盒模型(border-box):属性 width,height 包含 content、border 和 padding,指的是 content + padding + border
Flex布局
  • 容器属性,容器包括水平的主轴和垂直的交叉轴
  1. flex-direction属性决定主轴的方向(即项目的排列方向)。
  2. flex-wrap属性定义,是否换行。
  3. justify-content属性定义了项目在主轴上的对齐方式。
  4. align-items属性定义项目在交叉轴上如何对齐。
  • 容器子类属性
  1. order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
  2. flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  3. flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  4. flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认 值为auto,即项目的本来大小。
  5. flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为01auto。。
  6. align-self设置单个子类私有属性,可覆盖align-items属性。
BFC
  • BFC格式化上下文,它是一个独立的渲染区域,独立隔离容器,容器内的元素和容器外的元素布局不会相互影响
  • 产生:display: inline-block;position: absolute/fixed; overflow 的值不为 visible
伪类和伪元素
  • 伪类以冒号(:)开头,用于选择处于特定状态的元素。
动态伪类::visited:focus:hover等
状态伪类::disabled:empty:required 等
结构伪类::first-child:nth-of-type等
其他伪类::target:lang:not()等
  • 伪元素:以双冒号(::)开头,用于在文档中插入虚构的元素。
::first-letter//选中块状元素中的首字母
::first-line//选中首行
::before//在之前创建一个不在文档树中的元素
::after//在之后创建一个不在文档树中的元素
::placeholder//选中表单元素的占位文本
::file-selector-button//选中类型为 file 的 input 里面的 button
css居中
  • flex布局设置居中
display: flex //写在父元素上这就是定义了一个伸缩容器
justify-content: center //主轴居中对齐方式,默认是横轴
align-items: center //纵轴居中对齐方式,默认是纵轴
  • 绝对定位
容器设置//
position: relative。
子元素设置// 
position: absolute; 
left: 50%;
top: 50%;
transfrom: translate(-50%, -50%);
  • 容器设置 display: flex; 子项设置 margin: auto;
.father {
  width: 200px;
  height: 200px;
  border: 1px solid;
  display: flex;
}
.son {
  background: red;
  margin: auto; // 水平垂直居中
} 
  • 子元素绝对定位
<!-- 需要给子元素设置 position: absolute; 设置固定宽度和高度;topleftbottomright都设置为0; margin设置为auto; -->
.box {
  width: 200px;
  height: 200px;
  border: 1px solid;
  position: relative;
}
.child {
  background: red;
  width: 100px;
  height: 40px;
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  margin: auto;
}  
  • tabel-cell实现垂直居中
.father {
  width: 200px;
  height: 200px;
  border: 1px solid;
  display: table-cell;
  vertical-align: middle;  // 设置元素在垂直方向上的对齐方式
  text-align: center;
}
.child {
  background: red;
  display: inline-block;
} 
​
  • grid设置居中(兼容性较差)
.box {
  width: 200px;
  height: 200px;
  border: 1px solid;
  display: grid;
  align-items: center;
  justify-content: center;
}
.child {
  background: red;
}  
  • 给容器加给伪元素(适合给文本设置水平垂直居中)
.box {
  width: 200px;
  height: 200px;
  border: 1px solid;
  text-align: center;
}
.box::after {
  content: "";
  line-height: 200px;
}
.child {
  display: inline-block;
  background: red;
}

从浏览器地址栏输入 url 到请求返回发生了什么

  1. 输入 URL 后解析出协议、主机、端口、路径等信息,并构造一个 HTTP 请求。
  • 强缓存。
  • 协商缓存。
  1. DNS 域名解析。(字节面试被虐后,是时候搞懂 DNS 了

  2. TCP 连接。

    总是要问:为什么需要三次握手,两次不行吗?其实这是由 TCP 的自身特点可靠传输决定的。客户端和服务端要进行可靠传输,那么就需要确认双方的接收和发送能力。第一次握手可以确认客服端的发送能力,第二次握手,确认了服务端的发送能力和接收能力,所以第三次握手才可以确认客户端的接收能力。不然容易出现丢包的现象。

  3. http 请求。

  4. 服务器处理请求并返回 HTTP 报文。

  5. 浏览器渲染页面。

image.png

  1. 断开 TCP 连接

react-Hook

  • Hook是一个特殊的函数,它可以让你 “钩入” React 的特性,包括state、生命周期等。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。
  • 目的是增加代码的可复用性、逻辑性,最主要的是解决了函数式组件无状态的问题,这样既保留了函数式的简单,又解决了没有数据管理状态的缺陷

高阶组件 HOC

  • 一个函数接受一个组件为参数,返回一个包装后的组件(对象)
  • 例子1承接上层的props,在混入自己的props,来强化组件。有状态组件(属性代理)
function classHOC(WrapComponent){
    return class  Idex extends React.Component{
        state={
            name:'alien'
        }
        componentDidMount(){
           console.log('HOC')
        }
        render(){
            return <WrapComponent { ...this.props }  { ...this.state }   />
        }
    }
}
function Index(props){
  const { name } = props
  useEffect(()=>{
     console.log( 'index' )
  },[])
  return <div>
    hello,world , my name is { name }
  </div>
}

export default classHOC(Index)
  • 无状态
function functionHoc(WrapComponent)
{
return
function Index(props){
const [ state , setState ] = useState({ name :'alien' }) 
return <WrapComponent { ...props } { ...state } />
} 
}

自定义hooks

自定义hooks是在react-hooks基础上的一个扩展,可以根据业务、需求去制定相应的hooks,将常用的逻辑进行封装,从而具备复用性

  • tips:自定义hooks的时候需要结合useMemouseCallback等Api,但我们控制变量的值用useState 有可能会导致拿到的是旧值,并且如果他们更新会带来整个组件重新执行,这种情况下,我们使用useRef(缓存数据)将会是一个非常不错的选择

useCallback useMemo memo

  • useCallback 返回的是函数
  • useMemo 返回的是函数返回值
  • memo React.memo(子组件),会对传入子组件的props进行一次对比,判断是否更新子组件

useRef

useRef 可以获取当前元素的所有属性,并且返回一个可变的ref对象,并且这个对象只有current属性,可设置initialValue

主要vue路由模式

  1. hash( 监听onhashchange , hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面 )
  2. history( 利用了HTML5 History Interface中的 pushState()和replaceState()方法,改变路由模式的方法 )

typescript(转载)

typescript 的数据类型主要有如下:

  • boolean(布尔类型)
  • number(数字类型)
  • string(字符串类型)
  • array(数组类型)
方式一:元素类型后面接上 []
 let arr:string[] = ['12', '23'];
 arr = ['45', '56'];
复制代码
方式二:使用数组泛型,Array<元素类型>:
let arr:Array<number> = [1, 2];
arr = ['45', '56'];
  • tuple(元组类型)
元祖类型,允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
let tupleArr:[number, string, boolean];
tupleArr = [12, '34', true]; //ok
  • enum(枚举类型)
enum Color {Red, Green, Blue}
let c: Color = Color.Green;

  • any(任意类型)
  • null 和 undefined 类型
  • void 类型
  • never 类型
  • object 对象类型

TypeScript 中 any、never、unknown、null & undefined 和 void 有什么区别?

  • any: 动态的变量类型(失去了类型检查的作用)。
  • never: 永不存在的值的类型。例如:never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
  • unknown: 任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。
  • null & undefined: 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。当你指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自。
  • void: 没有任何类型。例如:一个函数如果没有返回值,那么返回值可以定义为void。

TypeScript 中 type 和 interface 的区别?

  • 相同点:
  1. 都可以描述 '对象' 或者 '函数'
  2. 都允许拓展(extends)
  • 不同点:
  1. type 可以声明基本类型,联合类型,元组
  2. type 可以使用 typeof 获取实例的类型进行赋值
  3. 多个相同的 interface 声明可以自动合并 使用 interface 描述‘数据结构’,使用 type 描述‘类型关系’

泛型

是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

type Info<T> = { name?: T age?: T }

枚举类型的理解?应用场景?

  • 是什么
  1. 枚举是一个被命名的整型常数的集合,用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型
  2. 通俗来说,枚举就是一个对象的所有可能取值的集合
  3. 在日常生活中也很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就可以看成是一个枚举 枚举的说明与结构和联合相似,其形式为:
enum 枚举名{
    标识符①[=整型常数],
    标识符②[=整型常数],
    ...
    标识符N[=整型常数],
}枚举变量;
  • 类型可以分成:数字枚举,字符串枚举,异构枚举

  • 数字枚举

当我们声明一个枚举类型是,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从0开始依次累加:
enum Direction {
    Up,   // 值默认为 0
    Down, // 值默认为 1
    Left, // 值默认为 2
    Right // 值默认为 3
}
console.log(Direction.Up === 0); // true
console.log(Direction.Down === 1); // true
console.log(Direction.Left === 2); // true
console.log(Direction.Right === 3); // true

如果我们将第一个值进行赋值后,后面的值也会根据前一个值进行累加1:
enum Direction {
    Up = 10,
    Down,
    Left,
    Right
}
console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 10 11 12 13
  • 字符串枚举 枚举类型的值其实也可以是字符串类型:
enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}
console.log(Direction['Right'], Direction.Up); // Right Up
  • 异构枚举 即将数字枚举和字符串枚举结合起来混合起来使用,如下:
enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

通常情况下我们很少会使用异构枚举

  • 应用场景 就拿回生活的例子,后端返回的字段使用 0 - 6 标记对应的日期,这时候就可以使用枚举可提高代码可读性,如下:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

包括后端日常返回0、1 等等状态的时候,我们都可以通过枚举去定义,这样可以提高代码的可读性,便于后续的维护