面经(一)

155 阅读7分钟

持续更新面试经历

一面

简单自我介绍

简单说了一下各个公司的任职工作内容,很久没有面试了,说得有点磕磕巴巴的,建议可以提前自己先写一段出来,这样不会思路混乱。

项目经历

说说你觉得最值得说的项目是哪些?

啊巴啊巴啊巴

八股文

1、从输入一个URL到页面呈现经历哪些?

解析浏览器地址 => 强缓存判断 => 协商缓存判断 => DNS寻址,先从本地查找,再逐级网上 => 建立TCP/IP连接 => 发送HTTP请求 => 服务端返回HTML => 浏览器解析HTML字符串 => 生成DOM树 => 生成styleSheets => 格式化Css => 根据DOM树与样式表生存布局树 => 根据层级关系生成分层树 => 绘制图层 => 图层栅格化,把图块转化为位图 => 渲染到浏览器

2、CDN加速原理

通过将源站内容分发至 最接近用户 的节点,从而 降低核心系统负载(系统、网络) ,使用户可就近取得所需内容,提高用户访问的响应速度

3、什么HTTP2,有哪些新特性

  • 传输数据量大、量减少
    • 二进制分帧
    • 标头压缩
  • 多路复用及相关功能
    • 多路复用
    • 优先级与依赖性
  • 服务器消息推送

4、script标签里defer、async有什么区别?

defer
在执行到加了defer的script时会推入下载进程,然后再去执行下一个标签。待到这个script的外链下载完毕时,需要看DOM是否渲染完毕了,如果渲染完毕了则执行defer script的内容,然后触发DOMContentLoaded 的回调。多个defer等待所有defer下载完依次执行。
async
在执行到加了async的script时会先下载,然后再去执行下一个标签。待到这个script的外链下载完毕时,如果DOM正在渲染则暂停,执行async script的内容。多个async先下载完的先执行

5、什么是闭包?

闭包的定义:指有权访问另一个函数作用域中的变量的函数,一般情况就是在一个函数中包含另一个函数。

闭包的作用:访问函数内部变量、保持函数在环境中一直存在,不会被垃圾回收机制处理 函数内部声明的变量是局部的,只能在函数内部访问到,但是函数外部的变量是对函数内部可见的。 子级可以向父级查找变量,逐级查找,直到找到为止或全局作用域查找完毕。因此我们可以在函数内部再创建一个函数,这样对内部的函数来说,外层函数的变量都是可见的,然后我们就可以访问到他的变量了。

function foo() {
  let value = 1;
  function bar() {
    console.log(value);
  }
  return bar;
}
const baz = foo();
// 这就是闭包的作用,调用 foo 函数,就会执行里面的 bar 函数,foo 函数这时就会访问函数外层的变量
baz();

6、模块化有哪些?

CMD、AMD、CommonJS、ES6 Module、UMD

CommonJS

function myModule() {
  this.hello = function() {
    return 'hello!';
  }

  this.goodbye = function() {
    return 'goodbye!';
  }
}
module.exports = myModule;

var myModule = require('myModule');

Node.js默认的模块化规范,CommonJS以服务器优先的方式来同步载入模块,如果引入多个模块,这些模块会被一个一个载入。在服务端用起来没有什么问题,但是在浏览器上就不太高效了。

AMD

define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
  console.log(myModule.hello());
});

AMD的诞生是为了弥补CommonJS同步载入模块所带来的问题,所以他的全称叫Asynchronous Moudle Defintion(异步模块定义规范)。

除了异步加载以外,AMD 的另一个优点是你可以在模块里使用对象、函数、构造函数、字符串、JSON 或者别的数据类型,而 CommonJS 只支持对象。

CMD

CMD专门服务于浏览器端,整合了CommonJS和AMD的优点,模块是异步加载的,同时模块使用时才会加载执行。

//定义没有依赖的模块
define(function(require, exports, module){ 
    exports.xxx = value
    module.exports = value
}) 
//定义有依赖的模块 
define(function(require, exports, module){
    //引入依赖模块(同步)
    var module2 = require('./module2')
    //引入依赖模块(异步) 
    require.async('./module3', function (m3) {
        console.log(m3)
    }) 
    //暴露模块 
    exports.xxx = value
})

ES6 Moudle

  • ESModule设计理念是希望在编译的时就确定模块的依赖关系及输入输出
  • CommonJS和AMD都只能在运行时才能确定依赖和输入、输出
// ES6模块 
import { stat, exists, readFile } from 'fs';

UMD UMD全称为Universal Module Definition,也就是通用模块定义,为什么叫通用呢,我们怎么描述一个模块是通用的呢?举个例子,假如现在我的项目使用的是AMD模块规范,那么现在我引入了一个用CommonJS规范写的模块,能正常运行吗?肯定不行的,而UMD就是解决了这个问题。

在模块中去判断全局是否存在exports和define,如果存在exports,那么以CommonJS的方式暴露模块,如果存在define那么以AMD的方式暴露模块。

7、什么是单例、发布订阅模式

单例模式 单例模式,又被称为单体模式,是只允许实例化一次的对象类。其实闭包就可以看作是一个单例模式,外部无法修改内部变量

const options = () => {
    let conf = {
        MAX_NUM: 100,
        MIN_NUM: 1,
        COUNT: 1000
    }
    return {
        get: (name) => { reutrn conf[name] ? config[name] : null },
        set: (name,val) => { conf[name] = val  }
    }
}
const instanceConf = options()
console.log(instanceConf.get('MAX_NUM'))

单例模式里还有一种叫惰性单例,惰性单例是指需要的时候才去创建。

var Singleton = function (name) {
    this.name = name
}
// 静态属性(类属性)
Singleton.instance = null
Singleton.prototype.getName = function () {
    console.log(this.name)
}
// 静态
Singleton.getInstance = function (name) {
    if(!this.instance){
        this.instance = new Singleton(name)
    }
    return this.instance
}
var a = Singleton.getInstance('wang')
var b = Singleton.getInstance('Yi')

console.log(a.name) // wang
console.log(b.name) // wang
console.log(a === b ) // true

发布订阅模式

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。

同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在。

class PubSub {
  constructor() {
    this.messages = {};
    this.listeners = {};
  }
  // 添加发布者
  publish(type, content) {
    const existContent = this.messages[type];
    if (!existContent) {
      this.messages[type] = [];
    }
    this.messages[type].push(content);
  }
  // 添加订阅者
  subscribe(type, cb) {
    const existListener = this.listeners[type];
    if (!existListener) {
      this.listeners[type] = [];
    }
    this.listeners[type].push(cb);
  }
  // 通知
  notify(type) {
    const messages = this.messages[type];
    const subscribers = this.listeners[type] || [];
    subscribers.forEach((cb, index) => cb(messages[index]));
  }
}

// 发布者
class Publisher {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  publish(type, content) {
    this.context.publish(type, content);
  }
}

// 订阅者
class Subscriber {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  subscribe(type, cb) {
    this.context.subscribe(type, cb);
  }
}

8、移动端适配

  1. 媒体查询
    通过CSS里的@media来根据不同设备宽度来设置不同的CSS样式
@media only screen and (max-width: 750px) {
    background: red
}
@media only screen and (max-width: 720px) {
    background: green
}
  1. 动态rem、em
    通过设置rem、em这种动态单位来自适应,单位由根元素字体大小决定。

  2. Viewport
    通过meta标签来固定布局视口宽度,与缩放比例

<meta name="viewport" content="width=720, user-scalable=no" />

9、Fiber架构

看这个吧,内容有点多:tsejx.github.io/react-guide…

10、页面加载优化

回答这个问题一定要理清楚思路,从开发 -> 打包 -> 发布 -> 请求 -> 访问 -> 呈现一步一步的说,不然说了几点过后就乱掉了忘了前面说的什么了

  1. 非页面初始化时的必要请求全部后置
  2. 大图片等资源文件可上OSS,CDN
  3. 使用React.memo lazy等懒加载方式
  4. 打包时可通过配置webpack 代码切割,代码按需加载
  5. 在移动端可以提取react等不常更新的包,打成dll先提前内置近宿主
  6. 开启Nginx的Gzip
  7. 把前端资源发布至CDN
  8. 开启浏览器缓存(强缓存、协商缓存)
  9. 在页面加载资源时,渲染时,最好能提前给用户呈现一个loading效果或者骨架屏,然后用户感官上觉得页面已经在加载中了。

11、react父子组件通信

  • props
  • Context
  • ref、forwardRef
  • useReducer

二面

G...

注:部分答案仅代表个人观点,可能存在一定的误差,若有错误各位大佬在评论区轻喷🙇,文章会持续更新