小言_互联网的博客

史上最全常见前端大厂面试知识点汇总

389人阅读  评论(0)

网络,浏览器篇

tcp/ip协议有几层

tcp/ip协议包含4层,分别是:1、应用层,为应用进程提供服务的;2、运输层;3、网络层,可以进行网络连接的建立和终止;4、网络接口层,是传输数据的物理媒介。

网络七层协议内容

OSI的7层从上到下分别是 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层

浏览器进程和线程

进程是系统中正在运行的一个程序,程序一旦运行就是进程。进程可以看成程序执行的一个实例,可以视作为浏览器打开了多个tab页,每个tab页相当于独立的进程。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程与进程的一个主要区别是,统一进程内的一个主要区别是,同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。

进程通信的方式

程间通信的方式——信号、管道、消息队列、共享内存

https加密过程

验证证书 ,创建密钥,传上去验证

url到页面过程

域名解析(递归域名),三次握手,服务器端返回请求数据,渲染页面,四次挥手

http2和http1的区别

1、HTTP2使用的是二进制传送,HTTP1.X是文本(字符串)传送。二进制传送的单位是帧和流。帧组成了流,同时流还有流ID标示

2、HTTP2支持多路复用。因为有流ID,所以通过同一个http请求实现多个http请求传输变成了可能,可以通过流ID来标示究竟是哪个流从而定位到是哪个http请求

3、HTTP2头部压缩。HTTP2通过gzip和compress压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引Id就行,通过索引ID查询表头的值

4、HTTP2支持服务器推送。HTTP2支持在未经客户端许可的情况下,主动向客户端推送内容

如何实现有状态的http协议

HTTP是一种无状态协议,即服务器不保留与客户交易时的任何状态。
用户登录后,切换到其他界面,进行操作,服务器端是无法判断是哪个用户登录的。 每次进行页面跳转的时候,得重新登录。使用Cookie session来实现有状态的http协议

强缓存和协商缓存(浏览器缓存机制)

强缓存

  • cache-control: max-age=xxxx,public
    客户端和代理服务器都可以缓存该资源;
    客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,如果用户做了刷新操作,就向服务器发起http请求

  • cache-control: max-age=xxxx,private
    只让客户端可以缓存该资源;代理服务器不缓存
    客户端在xxx秒内直接读取缓存,statu code:200

  • cache-control: max-age=xxxx,immutable
    客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,即使用户做了刷新操作,也不向服务器发起http请求

  • cache-control: no-cache
    跳过设置强缓存,但是不妨碍设置协商缓存;一般如果你做了强缓存,只有在强缓存失效了才走协商缓存的,设置了no-cache就不会走强缓存了,每次请求都回询问服务端。

  • cache-control: no-store
    不缓存,这个会让客户端、服务器都不缓存,也就没有所谓的强缓存、协商缓存了

协商缓存

  • etag:每个文件有一个,改动文件了就变了,就是个文件hash,每个文件唯一。

  • last-modified:文件的修改时间,精确到秒

ETag和Last-Modified的作用和用法,他们的区别:

1.Etag要优于Last-Modified。Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;

2.在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值;

3.在优先级上,服务器校验优先考虑Etag。

浏览器缓存过程

1.浏览器第一次加载资源,服务器返回200,浏览器将资源文件从服务器上请求下载下来,并把response header及该请求的返回时间一并缓存;

2.下一次加载资源时,先比较当前时间和上一次返回200时的时间差,如果没有超过cache-control设置的max-age,则没有过期,命中强缓存,不发请求直接从本地缓存读取该文件(如果浏览器不支持HTTP1.1,则用expires判断是否过期);如果时间过期,则向服务器发送header带有If-None-Match和If-Modified-Since的请求

3.服务器收到请求后,优先根据Etag的值判断被请求的文件有没有做修改,Etag值一致则没有修改,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的Etag值并返回200;;

4.如果服务器收到的请求没有Etag值,则将If-Modified-Since和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回304;不一致则返回新的last-modified和文件并返回200;

跨域请求

协议域名ip。属于浏览器的同源策略,服务器可以接受到,同源策略判断的条件是协议,域名和端口号

js原理篇

js执行机制

单线程执行

”JS是单线程的”指的是JS 引擎线程
在浏览器环境中,有JS 引擎线程和渲染线程,且两个线程互斥。
Node环境中,只有JS 线程。

  • GUI渲染线程:负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。
  • (例如V8引擎)JS引擎线程负责解析Javascript脚本,运行代码。JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序

注意:GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

宿主

JS运行的环境。一般为浏览器或者Node

执行栈

是一个存储函数调用的栈结构,遵循先进后出的原则。

Event Loop

JS引擎常驻于内存中,等待宿主将JS代码或函数传递给它。
也就是等待宿主环境分配宏观任务,反复等待 - 执行即为事件循环。
Event Loop中,每一次循环称为tick,每一次tick的任务如下:

  • 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
  • 检查是否存在微任务,有则会执行至微任务队列为空;
  • 如果宿主为浏览器,可能会渲染页面;
  • 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。

宏任务和微任务

ES6 规范中,microtask 称为 jobs,macrotask 称为 task
宏任务是由宿主发起的,而微任务由JavaScript自身发起。

js为什么设计成单线程语言

如果设计成多个线程,线程之间dom操作矛盾会导致浏览器不知道以哪个为主,web worker可以实现多个线程,但是不能操作dom,原理上是一样的

手写new

(1) 创建一个新对象;
(2) 将构造函数中的this指向该对象
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。

function _new(obj, ...rest){
   
    // 基于obj的原型创建一个新的对象
    const newObj = Object.create(obj.prototype);

    // 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
    const result = obj.apply(newObj, rest);

    // 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
    return typeof result === 'object' ? result : newObj;
}

ES6中class是语法还是语法糖

语法糖,js的继承还是基于原型的

原型的终点

null

手写实现promise,promise. all,promise. race

promise

class Promsie {
   
    constructor(fn) {
   
        //三个状态
        this.status = 'pending',
        this.resolve = undefined;
        this.reject = undefined;
        let resolve = value => {
   
            if (this.status === 'pending') {
   
                this.status = 'resolved';
                this.resolve = value;
            }
        };
        let reject = value => {
   
            if (this.status === 'pending') {
   
                this.status = 'rejected';
                this.reject = value;
            }
        }
        try {
   
            fn(resolve, reject)
        } catch (e) {
   
            reject(e)
        }
    }
    then(onResolved, onRejected) {
   
        switch (this.status) {
   
            case 'resolved': onResolved(this.resolve); break;
            case 'rejected': onRejected(this.resolve); break;
            default:
        }
    }

}

promise.all

   function promiseAll(promises) {
   
            if (!Array.isArray(promises)) {
   
                throw new Error("promises must be an array")
            }
            return new Promise(function (resolve, reject) {
   

                let promsieNum = promises.length;
                let resolvedCount = 0;
                let resolveValues = new Array(promsieNum);
                for (let i = 0; i < promsieNum; i++) {
   
                    Promise.resolve(promises[i].then(function (value) {
   
                        resolveValues[i] = value;
                        resolvedCount++;
                        if (resolvedCount === promsieNum) {
   
                            return resolve(resolveValues)
                        }
                    }, function (reason) {
   
                        return reject(reason);
                    }))

                }
            })
        }

promise.race

 function promiseRace(promises) {
   
            if (!Array.isArray(promises)) {
   
                throw new Error("promises must be an array")
            }
            return new Promise(function (resolve, reject) {
   
                promises.forEach(p =>
                    Promise.resolve(p).then(data => {
   
                        resolve(data)
                    }, err => {
   
                        reject(err)
                    })
                )
            })
        }

闭包优缺点

1.保护函数内的变量安全,加强了封装性 2.在内存中维持一个变量(用的太多就变成了缺点,占内存)
闭包之所以会占用资源是当函数a执行结束后, 变量i不会因为函数a的结束而销毁, 因为b的执行需要依赖a中的变量。
不适合场景:返回闭包的函数是个非常大的函数

function和object的关系

Function.prototype.__proto__ === Object.prototype;//true
Object.__proto__ === Function.prototype;//true

手写call

Function.prototype.myCall = function(context, ...args) {
   
  // 判断是否是undefined和null
  if (typeof context === 'undefined' || context === null) {
   
    context = window
  }
  let fnSymbol = Symbol()
  context[fnSymbol] = this
  let fn = context[fnSymbol] (...args)
  delete context[fnSymbol] 
  return fn
}
// context[fnSymbol]中的this指向的是context

异步执行顺序(易错)

由一道字节面试题进入主题:

async function async1() {
   
	console.log('async1 start')
	await async2()
	console.log('async1 end')
}
async function async2() {
   
	console.log('async2')
}
console.log('script start')
setTimeout(function() {
   
	console.log('settimeout')
}
async1()
new Promise(function(resolve) {
   
	console.log('promise1')
	resolve()
}).then(function() {
   
	console.log('promise2')
})
console.log('script end')

答案:

script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout

setTimeout

setTimeout的回调时一个task,它会被放到宏任务的队列中,即使是0ms的情况

promise

promise本身是同步的立即执行函数,当在executor中执行resolve或者reject的时候,此时是异步操作,会执行then、catch,这个才是异步的task,会放到微任务的栈中

async/await

async函数返回一个promise对象,当函数执行的时候,一旦遇到await就会先返回,可以理解成让出了线程,跳出了async函数体,每个await之后的操作是一个then,会被放入微任务队列中,刚开始的await之前的操作是同步的操作

async func1() {
   
	console.log(1);
	await func2(); // 让线程给func2
	console.log(2); // 这一步是异步的,会被放入微任务队列
}

this指向

箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

var name = 'window'; // 其实是window.name = 'window'

var A = {
   
   name: 'A',
   sayHello: function(){
   
      console.log(this.name)
   }
}

A.sayHello();// 输出A

var B = {
   
  name: 'B'
}

A.sayHello.call(B);//输出B

A.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window

箭头函数的this不指向调用的对象,而指向运行时的上下文环境

var name = 'window'; 

var A = {
   
   name: 'A',
   sayHello: () => {
   
      console.log(this.name)
   }
}

A.sayHello();// window

怎么把sayHello的this永远绑定A?只要把箭头函数再包在一个function里即可,因为箭头函数的this永远指向运行时的环境

var name = 'window'; 

var A = {
   
   name: 'A',
   sayHello: function(){
   
      var s = () => console.log(this.name)
      return s//返回箭头函数s
   }
}

var sayHello = A.sayHello();
sayHello();// 输出A 

var B = {
   
   name: 'B';
}

sayHello.call(B); //还是A
sayHello.call(); //还是A

css篇

三栏布局和二栏布局的实现

flex:1
第一个参数表示: flex-grow 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
第二个参数表示: flex-shrink 定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
第三个参数表示: flex-basis给上面两个属性分配多余空间之前, 计算项目是否有多余空间, 默认值为 auto, 即项目本身的大小

实现等比矩形

css实现宽高等比的矩形:用一个div包住一个div,子div设置width:100%, height: 0, padding-bottom: 对应比例,因为padding是根据宽度计算的,外层的div用来控制具体的i宽高是多少

框架篇

vue3的相关改动

1、组件式api的调整,将逻辑层集中了,并且抽离出了vue原先的生命周期和状态函数,可以更加灵活地调用,解决了mixin重名的问题
2、响应式实现上的调整,改用proxy去实现响应式,proxy相比defineProperty是针对一个对象的全部操作的,这样就不会有vue2不能监听到的问题
3、惰性响应式,vue2会把每一个对象数据都编程响应式的,vue3通过ref和reactive增加响应式,只需要给需要的对象或属性增加响应式
4、vue2的数据更新粒度是组件级的,对于一些静态节点,也会去遍历判断,而vue3是在编译阶段就对静态模板进行了分析,生成了block tree,这样就避免了对静态节点的不必要判断
5、vue2在父组件更新的时候,会强制使子组件更新,而vue3中优化了slot的生成,只有slot中的属性更新才会触发子组件更新

vue3哪些方面变快了

  • diff方法优化vue2.x中的虚拟dom是进行全量的对比。而vue3.0新增了静态标记。在与上次虚拟节点进行对比的时候,只对比带有patch flag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容。
  • hoistStatic(静态提升):vue2.x中无论元素是否参与更新,每次都会重新创建,然后再渲染。vue3.0中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可。
  • cacheHandlers(事件侦听器缓存):默认情况下,如onClick事件会被视为动态绑定,所以每次都会追踪它的变化,但是因为是同一个函数,所以不用追踪变化,直接缓存起来复用即可。

vue单向数据流

在vue文档组件模块里有介绍到单向数据流主要是避免子组件修改父组件的状态出现应用数据流混乱的状态,避免不必要的理解,维持父子组件正常的数据依赖关系。

mvc和mvvm

MVC数据传递的方式是单向的
MVVM数据传递的方式是双向的
MVC:M 指的是Model(模型)是后端传递的数据,V指的是View(视图)所看到的页面,
C指的是Controller是应用程序中处理用户交互的部分

父子组件执行顺序

父beforeCreate -> 父created -> 父beforeMount
-> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted
-> 父mounted

scope是怎么实现的

PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性,给css选择器额外添加一个对应的属性选择器,来选择组件中的dom,这种做法使得样式只作用于含有该属性的dom元素(组件内部的dom)


转载:https://blog.csdn.net/haofandedaima/article/details/117377989
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场