天天看点

面试记录(2021-06)js篇React篇CSS篇其他

面试记录

  • js篇
    • 几种循环语句哪个最快
    • Set、Map、Object
      • ==Set==:类似于数组,但成员是唯一的
      • ==Map==:类似于对象,也是键值对的集合,但“键”的范围不局限于字符串,各种类型的值(包括对象)都可以当做键
      • ==Object==:本质上是键值对(Hash)结构,但只能用字符串或Symbol作为键
    • 箭头函数
    • Promise
    • 计时器
    • ES6中class与function的区别
  • React篇
    • 渲染优化
    • React.lazy(懒加载)
    • fiber基于链表形式
    • 为什么React官方推荐Hooks
    • React Router
    • useEffect和useLayoutEffect区别
    • useCallback和useMemo
  • CSS篇
    • canvas和svg的区别
    • 移动端适配方案
  • 其他
    • Nginx——[Nginx入门指南](https://juejin.cn/post/6844904129987526663)
    • HTTP 请求/响应的步骤——[HTTP协议超级详解](https://www.cnblogs.com/an-wen/p/11180076.html)
    • HTTPS原理
    • 对称加密和非对称加密
    • 本地存储
    • 浏览器缓存
    • 解决跨域
    • 浏览器渲染机制

js篇

几种循环语句哪个最快

for(倒序) > for > forEach > for...of
           
const million = 1000000; 
const arr = Array(million);

// 注:这是稀疏数组,应该为其指定内容,否则不同方式的循环对其的处理方式会不同:
// const arr = [...Array(million)]

console.time('⏳');
for (let i = arr.length; i > 0; i--) {} // for(倒序)  :- 1.5ms
for (let i = 0; i < arr.length; i++) {} // for          :- 1.6ms
arr.forEach(v => v)                     // foreach      :- 2.1ms
for (const v of arr) {}                 // for...of     :- 11.7ms
console.timeEnd('⏳');
           

原因:倒序只计算一次let i = arr.length,而 forEach 是 Array 原型的一个方法,与普通的 for 循环相比,forEach 和 for…of 需要花费更多的时间进行数组迭代

Set、Map、Object

Set:类似于数组,但成员是唯一的

一、Set的基本概念

1.Set本身是一个构造函数,用来生成Set数据结构
2.Set函数可以接收一个数组(或其他具有iterable接口的其他数据结构)作为参数,用来初始化
3.Set内部判断两个值是否相同,使用的算法叫“Same-value-zero equality”,类似于精确相等运算符(===)
注:Set中,两个NaN是重复的("==="预算符判断NaN不等),两个对象总是不重复的
           

二、Set的实例属性和方法(Set.prototype.)

属性:
	constructor——构造函数,即Set函数
	size——返回Set实例的成员总数
方法:
	add(value)——增,返回Set结构本身
	delete(value)——删,返回boolean,表示是否删除成功
	has(value)—— 查,返回boolean,表示是否为Set的成员
	clear()——删,删除所有成员,无返回值
遍历(Set中键名和键值是同一个值):
	keys()——返回键名的遍历器
	values()——返回键值的遍历器
	entries()——返回键值对的遍历器
	forEach()——使用回调函数遍历每个成员
Set转数组的两种方式:
	let set = new Set()
	通过扩展运算符:[...set]
	利用Array.from:Array.from(set[, function])
Set结构转数组后可更方便的进行遍历,如:map(),filte()……
           

Map:类似于对象,也是键值对的集合,但“键”的范围不局限于字符串,各种类型的值(包括对象)都可以当做键

一、Map的基本概念

1.不仅仅是数组,任何具有Iterator接口,且每个成员都是一个双元素的数组的数据结构,都可以当做Map构造函数的参数
2.如果对同一个键进行复制,则后一个的值覆盖前一个的值
3.取一个位置的键,返回undefined
4.Map的键实际上跟内存地址绑定的,内存地址不同,就视为两个键
           

二、Map的实例属性和方法(Map.prototype.)

属性:
	constructor——构造函数,即Map函数
	size——返回Map结构的成员总数
方法:
	set(key,value)——设置键名key对应的value值,返回整个Map结构,可采用链式写法
	get(key)——读取key对应的键值
	has(key)——返回boolean,表示当前键是否在Map对象中
	delete(key)——返回boolean,表示删除当前键是否成功
	clear()——清除所有成员,没有返回值
遍历:
	keys()——返回键名的遍历器
	values()——返回键值的遍历器
	entries()——返回键值对的遍历器
	forEach()——使用回调函数遍历每个成员
与其他数据结构的转化:
	数组->Map:new Map([[1,2],[3,4]])
	Map->数组:[...new Map()]
	对象->Map:let map = new Map(Object.entries(obj)); 或 for...of + Map.set()
	Map->对象:for...of
	JSON->Map:for...of + JSON.parse(jsonStr)
	Map->JSON: 
		键名全为字符串:JSON.stringify(for...of Map)
		键名不全是字符串:JSON.stringify([...Map])
           

Object:本质上是键值对(Hash)结构,但只能用字符串或Symbol作为键

一、super关键字

指向当前对象的原型对象
注:只能用在对象的方法中,目前只有对象方法的简写写法可以让JavaScript引擎确认,定义的是对象的方法
           

二、链式判断运算符(?.)和空值合并操作符(??)

?. 作用:在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined
?? 作用:左侧为null或undefined时,返回右侧操作
|| 作用:左侧为假值(包含null、undefined、false、''、0)时,返回右侧操作
           

三、对象的一些方法

Object.is(obj1,obj2):除判断+0和-0,NaN和NaN与“===”相反,其他一致
Object.assign():合并对象,浅拷贝
__proto__属性
Object.setPrototypeOf()、Object.getPrototypeOf()
Object.keys()、Object.values()、Object.entries()
Object.fromEntries():是Object.entries()的逆操作,转成对象
           

箭头函数

1.箭头函数没有this对象
	若内部有this使用,则该this指向函数定义时上层作用域中的this
	arguments、super、new.target同理
2.不可以当做构造函数
	即不可对箭头函数使用new命令
3.不可以使用arguments对象
	该对象在箭头函数体内不存在,可以使用rest参数(...变量名)替代
4.不可以使用yield命令
	箭头函数不能用作Generator函数,可以用async/await
5.箭头函数没有原型
           

Promise

一、什么是Promise

Promise 是异步编程的一种解决方案,遵循Promise/A+规范
           

二、Promise原理

1.Promise有三个状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
2.Promise对象的几个特点:
  (1)对象的状态不受外界影响。只有异步的操作结果,可以决定是哪一种状态,其他任何操作斗殴无法改变这个状态
  (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果
  		pending -> fulfilled  或 pending -> rejected,一旦状态从pending变更,便不能将该promise扭转到其他状态
  (3)Promise对象一旦创建就会立即执行,无法取消
3.Promise的链式调用
	Promise对像原型上有个then方法,then方法会返回一个新的Promise对象,并且将前一个Promise回调函数的结果作为参数传递给then的回调函数。
	若无参数,then的回调函数参数也可与Promise对像相同,为(resolve, reject)
	then 方法会在一个 promise 状态被扭转为 fulfilled 或 rejected 时被调用。then 方法的参数为两个函数,分别为 promise 对象的状态被扭转为 fulfilled 和 rejected 对应的回调函数。
           

三、Promise的常用方法

Promise.prototype.chtch():
	捕获Promise抛出的异常。
	如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应
Promise.prototype.finally():
	用于指定不管最终Promise对象状态如何,都会执行的操作
Promise.all():
	将多个 Promise 实例,包装成一个新的 Promise 实例。
	多个实例状态都成功才返回成功,返回结果放在数组中传递
Promise.race():
	同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
	谁先返回状态就先返回谁的状态
Promise.any():
	该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回
	与all()相反,只要有一个成功,就返回成功
           

四、手写Promis

参考文章

计时器

setInterval()

let time = 10
function timeDown() {
	console.log(time)
	time--
	if(time <= 0) {
		window.clearInterval(t)
	}
}
t = setInterval(timeDown, 1000)
           

setTimeout()

let time = 10
function timeDown() {
	console.log(time)
	time--
	if(time > 0) {
		setTimeout(timeDown, 1000)
	}
	// 或通过clearTimeout方法取消递归
	/**
	t = setTimeout(function(){
     	timeDown();
    },1000);
    if(num <= 0){
    	clearTimeout(t);
 	}
	*/
}
timeDown()
           

两种定时器在运行方面的差别:

上述两种计时器的间隔时间都是1s,假设函数运行时间为2s
	使用setInterval()时,由于javascript是单线程的,执行完这次之前,下次的循环就被阻塞了,处在排队状态,当此次操作执行完以后才会执行处在排队状态的操作。
但是排队的序列太多,阻塞结束以后只能执行一个,这样会造成性能的浪费
	使用setTimeout递归调用实现循环的方法便显得很方便,它不会发生阻塞的状况。整个一次循环3s钟。
	setTimeout又不如setInterval执行的精确,所以在不同情况下可以选择不同的定时器以达到最好的效果。
           

ES6中class与function的区别

1、function 可以用call apply bind 的方式来改变他的执行上下文,class不行

2、class声明的函数,内部采用的严格模式,function不是

3、class所有的方法是不可枚举的,而function声明的函数是可枚举的

4、class所有方法都没有原型对象prototype,不能通过new关键字实例化,function可以

5、必须使用new来调用class,不能直接调用

6、class内部不能重写类名

React篇

渲染优化

1.避免组件重新渲染
React.memo:可防止不必要的重新渲染函数式组件
	第一个参数接收函数组件
	第二个参数接收自定义的比较函数,比较props。返回值:true——相同,不更新,false——更新
React.PureComponent:可防止不必要的重新渲染类组件
	以浅层对比props和state的方式实现了shouldComponentUpdate()生命周期函数
	shouldComponentUpdate()返回值:true——更新,false——不更新
useMemo:函数式组件的HookAPI,返回一个memoized值
	把“创建”函数和依赖项数组作为参数传入 useMemo
	它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
           

React.lazy(懒加载)

用法:

React.lazy可以异步加载组件文件

React.lazy不可以单独使用,需配合React.Suspense.

Suspense是用来包裹异步组件,添加loading效果等

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}
           

此时,OtherComponent组件文件被拆分打包为一个新的包(bundle)文件,并且只会在OtherComponent组件渲染时才会下载到本地

原理:

1、import()

该函数是由TS39提出的一种动态加载模块的规范实现,其返回是一个promise
当 Webpack 解析到该import()语法时,会自动进行代码分割。
           

2、React.lazy

3、Suspense

总结:React利用React.lazy和import()实现了渲染时的动态加载,并利用Suspense来处理异步加载资源时页面应该如何显示的问题

fiber基于链表形式

React渲染更新机制

链表结构,如java中的LinkedList

1、单向链表:

单向链表的链表对象维护了一个first引用,该引用指向节点链表中的第一个节点对象,每个节点对象维护一个next引用,next引用指向下一个节点对象(节点对象包含存储的数据和next的引用)

面试记录(2021-06)js篇React篇CSS篇其他
面试记录(2021-06)js篇React篇CSS篇其他

2、双向链表

链表对象中维护一个first引用和last引用,分别指向链表中的首末节点对象

每个节点对象维护:存储的数据对象引用、prev引用(指向签节点对象)、next引用(指向后节点对象)

面试记录(2021-06)js篇React篇CSS篇其他
面试记录(2021-06)js篇React篇CSS篇其他

为什么React官方推荐Hooks

Hooks是React 16.8的新增特性,可以在不编写class的情况下使用state以及其他的的React特性

1、Hook可以在无需修改组件结构的情况下复用状态逻辑

类复用状态逻辑——render props(跟据状态动态渲染组件)、高阶组件

自定义Hook——以“use”开头,内部可以调用其他Hook

2、Hook将组件中相互关联的部分拆分成更小的函数(如:设置订阅或请求数据),而并非强制按照生命周期划分

生命周期——在componentDidMount订阅后,须在componentWillUnmount中取消订阅

Hook——useEffect中订阅,return取消订阅

3、Hook可以在不使用class的情况下使用更多的React特性

注:①只在最顶层使用Hook,不可在循环、条件或嵌套函数中使用

②在React函数组件中调用Hook,或自定义Hook函数中调用

原因:只有Hook的调用顺序在多次渲染中保持一致,React才能将内部的state和对应的Hook进行关联

React Router

React Router是一个基于React之上的强大路由库,可以向应用中快速地添加视图和数据流,同时保持页面与URL之间的同步

React Router原理

window.addEventListener(‘hashchange’, () => {

this.setState({ route: window.location.hash.substr(1) })

})

监听URL的hash部分,即"#"后面的部分变化

所以,React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。

  • browserHistory

    推荐的history,使用浏览器中的History API处理URL,创建一个真实的URL,即访问真实的路径资源,此时,服务器需做好处理对应URL的准备

    注:当我们采用browserHistory方案时,通常会遇到浏览器刷新404 的问题

    原因:browserHistory本质访问真实路径资源,而通过路由访问时,服务器下没有这个文件可供访问

    解:其本质的原理就是利用服务端将任何请求都指向index.html,而在React应用中index.html又刚好通过React-Router配置了相应的路由,我们让服务器返回index.html,后面就交给前端路由来实现无刷新加载对应页面。

    服务端配置

    #  Nginx设置:
      #	访问任何URI都指向index.html,浏览器上的path,会自动被React-router处理,进行无刷新跳转
      location / {
    		try_files $uri  /index.html;
      }
      
      // node设置
      //加载指定目录静态资源
      app.use(express.static(__dirname + '/dist'))
      //配置任何请求都转到index.html,而index.html会根据React-Router规则去匹配任何一个route
      app.get('*', function (request, response){
      	response.sendFile(path.resolve(__dirname, 'dist', 'index.html'))
      })
               
  • hashHistory

    Hash History会使用URL中的hash(#)部分去创建形如xxxx.com/#/path的路由

  • createMemoryHistory

    Memory History不会在地址栏被操作或读取,非常适合测试或非DOM环境(如:React Native)

    注:该history须创建出来

    const history = createMemoryHistory(location)
               

React Router配置

  • 1、添加首页——IndexRoute

    设置一个默认页面

  • 2、UI从URL中解耦——path使用绝对路径
    React.render((
      <Router>
      	<Route path="/" component={App}>
    			<IndexRoute component={Dashboard} />
    			<Route path="about" component={About} />
    			<Route path="inbox" component={Inbox}>
      			{/* 使用 /messages/:id 替换 messages/:id */}
      			<Route path="/messages/:id" component={Message} />
    			</Route>
      	</Route>
      </Router>), document.body)
               
URL 组件
/ App -> Dashboard
/about App -> About
/inbox App -> Inbox
/messages/:id App -> Inbox -> Message

注:绝对路径可能在动态路由无法使用

静态路由——应用启动时,所有路由模块都加载

动态路由——访问页面时才加载对应组件(懒加载)

  • 3、兼容旧的URL(重定向)——Redirect
    <Route path="inbox" component={Inbox}>
     	<Route path="/messages/:id" component={Message} />
     	{/* 跳转 /inbox/messages/:id 到 /messages/:id */}
     	<Redirect from="messages/:id" to="/messages/:id" />
      </Route>
               
  • 4、进入和离开的Hook——onEnter、onLeave

    这些Hook会在页面跳转确认时触发一次,可用于权限验证或在路由跳转前将一些数据持久化保存起来

    • onEnter:进入路由前执行,从最外层的父路由开始,到最内层的子路由结束
    • onLeave:离开路由前执行,从最内层的子路由开始,到最外层的父路由结束

useEffect和useLayoutEffect区别

相同点:二者函数签名相同

不同点:

  • class的componentDidMount 和componentDidUpdate是同步的在render结束后就执行
  • useEffect在浏览器完成布局与绘制之后,传给useEffect的回调函数会延迟调用(注:但会保证在任何新的渲染前执行)。
  • useLayoutEffect的回调函数会在DOM更新完成后立即执行,但会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制。

所以,90%的情况下使用useEffect,当需要操作DOM的时候,使用useEffect可能会出现闪屏,此时才需要使用useLayoutEffect。示例

useCallback和useMemo

相同点:依赖第二个参数的数据变化,才会重新计算结果,起到缓存作用

不同点:

  • useCallback:计算结果是函数,即主要缓存函数
  • useMemo:计算结果是回调函数return的值,即缓存计算结果的值

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

CSS篇

canvas和svg的区别

参考文档

canvas svg
依赖分辨率 不依赖分辨率
不支持事件处理器 支持事件处理器
文本渲染能力弱 适合带有大型渲染区域的应用程序(如谷歌地图)
能以.png或.jpg格式保存结果图像 复杂度高会减慢渲染速度(任何过度使用DOM的应用都不快)
最适合图像密集型游戏,其中的许多对象会被频繁重绘 不适合游戏应用

所以,canvas适合动态图像,svg适合静态图像

移动端适配方案

  • 通过媒体查询方式的响应式布局

    即CSS3的media queries,主要通过查询设备的宽度来执行不同的css代码,最终达到界面的配置。核心语法如下:

@media screen and (max-width: 600px) { /*当屏幕尺寸小于600px时,使用下面的样式*/
	/*css代码*/
} 
           

优点:做到设备像素比的判断,方法简单,成本低

缺点:代码量大,维护不便

  • flex弹性布局

固定viewport——做手机网站必加的一句头部(head)代码:

name=“viewport”——名称=视图

width=device-width——页面宽度=设备宽度

initial-scale——初始的缩放比例

minimum-scale——允许用户缩放到的最小比例

maximum-scale——允许用户缩放到的最大比例

user-scalable——用户是否可以手动缩放

  • rem/em弹性布局(也固定viewport)

    rem是相对于html元素的font-size大小而言的

    em是相对于其父元素的font-size大小而言

rem全称font size of the element(根元素的字体大小),根节点(html)的font-size决定了rem的尺寸,也就是说它是一个相对单位,相对于html

浏览器默认font-size是16px,则1rem = 16px

:root {
	font-size: 20px
}
此时,1rem = 20px
           

其他

Nginx——Nginx入门指南

Nginx:一种轻量级的web服务器,占用内存少、启动速度快、并发能力强

四大应用:

1.动静分离

将接收到的请求分为动态请求和静态请求

root——静态请求,获取对应的静态资源

proxy_pass——动态请求,转发给真实的后台(即服务器,如Tomcat),并将请求到的内容原封不动的返回

2.反向代理

即访问nginx,nginx再访问对应服务器

好处:保障应用服务器的安全;实现负载均衡;实现跨域

3.负载均衡

在服务器集群中,将收到的客户端请求“均匀地”(可设置权重)分配到这个集群中所有的服务器上

作用:分摊服务器集群压力;保证客户端访问的稳定性

4.正向代理

客户端知道访问的服务端地址,如vpn就是一个正向代理工具

# 负载均衡:设置domain
upstream domain {
    server localhost:8000;
    server localhost:8001;
}
server {  
        listen       8080;        
        server_name  localhost;

        location / {
            root   html; # Nginx默认值
            index  index.html index.htm;
        }
        
        # 静态化配置,所有静态请求都转发给 nginx 处理,存放目录为 my-project
        location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|js|css)$ {
            root /usr/local/var/www/my-project; # 静态请求所代理到的根目录
        }
        
        # 动态请求匹配到path为'node'的就转发到8002端口处理
        location /node/ {  
            proxy_pass http://localhost:8002; # 充当服务代理
        }
        
		location /domain {
            proxy_pass http://domain; # 负载均衡配置,请求会被平均分配到8000和8001端口
            proxy_set_header Host $host:$server_port;
        }
}
           

HTTP 请求/响应的步骤——HTTP协议超级详解

  1. 客户端连接到Web服务器

    一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.baidu.com。

  2. 发送HTTP请求

    通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。

  3. 服务器接受请求并返回HTTP响应

    Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。

  4. 释放连接TCP连接

    若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

  5. 客户端浏览器解析HTML内容

    客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。

    例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程:
     1.浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
     2.解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
     3.浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
     4.服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
     5.释放 TCP连接;
     6.浏览器将该 html 文本并显示内容; 
               

HTTPS原理

https在证书验证阶段使用的是非对称加密,而在内容传输的加密使用的是对称加密。

https的整体过程分为证书验证和数据传输阶段,具体交互如下图:

面试记录(2021-06)js篇React篇CSS篇其他

为什么数据传输使用对称加密?

1、非对称加密的加密解密效率低,而http的应用场景一般是端与端之间进行大量交互

2、https的场景中只有服务端保存了私钥,一对公私钥只能进行单向的加密解密

对称加密和非对称加密

对称加密

对称加密指的是加密解密使用同一个秘钥,只有一个私钥

非对称加密

加密解密使用不同的秘钥,公钥和私钥,公钥加密,私钥解密

比如,你向银行请求公钥,银行将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人–银行才能对你的消息解密。与对称加密不同的是,银行不需要将私钥通过网络发送出去,因此安全性大大提高。

总结

(1)对称加密使用一个秘钥,速度快,但需要将秘钥在网络中传输,安全性低

(2)非对称加密使用一对公钥私钥进行加密解密,安全性高,但加密解密慢

(3)将对称加密的密钥使用非对称加密的公钥加密进行发送,接收方使用私钥进行解密获取秘钥,再通过对称加密进行交互

本地存储

sessionStorage 、localStorage 和 cookie

1、作用域

sessionStorage:不在不同浏览器窗口共享

localStorage、cookie:在同源浏览器窗口共享

2、传递方式

cookie:始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递

sessionStorage、localStorage:仅在本地保存

3、数据大小

cookie:cookie有路径概念,可限制cookie只属于某个路径下,大小不能超过4K

sessionStorage、localStorage:也有大小限制,可达5M

4、数据有效期不同

sessionStorage:仅在当前浏览器窗口关闭前有效

localStorage:始终有效,浏览器或窗口关闭也一直保存,可用作持久数据

cookie:在设置cookie过期时间之前一直有效,即使关闭浏览器

浏览器缓存

面试记录(2021-06)js篇React篇CSS篇其他

三级缓存原理(访问缓存优先级):

1、现在内存中查找,如果有直接加载

2、内存中没有,则在硬盘中查找,如果有直接加载

3、硬盘中也没有,进行网络请求

4、请求获取的资源缓存的硬盘和内存

解决跨域

浏览器出于安全考虑,有同源策略。也就是说,协议、域名、端口有一个不同即为跨域

非同源站点的限制:

1、不能读取和修改对方的DOM

2、不能访问对方的Cookie、IndexDB和LocalStorage

3、限制XMLHttpRequest请求

解决跨域的四种方法

后两种为react在项目中的解决办法

JSONP

在HTMl一些标签里(如:script、img)是没有跨域限制的

JSONP是服务端与客户端跨源通信的常用方法,只支持get请求

CORS

普通跨域请求:在服务端设置Access-Control-Allow-Origin

带cookie跨域:前后端都进行设置(前端设置:根据xhr.withCredentials字段判断是否带有cookie)

proxy

在package.json文件中使用proxy配置可解决跨域问题

"proxy":{
    "/api":{
       "target":"http://xxx.xxx.com",
       "changeOrigin": true,
       "pathRewrite": {
        	"^/api": ""
    	}
     }
 }
           

target:接口的域名

changeOrigin:开启代理

pathRewrite:通过pathRewrite重写地址,将接口请求的时候前缀("/api")去除

nginx

通过nginx代理

浏览器渲染机制

获取资源步骤:

DNS查询 -> TCP连接 -> HTTP请求 -> 服务器响应 -> 客户端渲染

浏览器的线程:

1.GUI渲染线程

2.JavaScript引擎线程

3.定时器触发线程

4.事件触发线程

5.HTTP请求线程

构建DOM树过程:

字节 -> 字符 -> 令牌 -> 节点对象 -> 对象模型(DOM树)

绘制步骤:

1.处理HTML标记并构建DOM树

2.处理CSS标记并构建CSSOM树

3.将DOM树和CSSOM树合并成渲染树

4.根据渲染树来布局,计算每个节点的几何信息

5.将各个阶段绘制到屏幕上