目录
面经汇总
浏览器渲染机制
浏览器的渲染机制可以分为五步:
第一步:解析html,构建DOM树 第二步:解析CSS,生成CSSOM树 第三步:合并dom树和css规则树,生成render渲染树 第四步:根据render渲染树进行布局 第五步:调用GPU对渲染树进行绘制,合成图层,显示在屏幕上
其中第四步和第五步合起来,就是我们常常说的浏览器渲染,并且第四步和第五步是浏览器渲染最耗时的部分(主要优化点)
关于渲染:
1.浏览器在生成网页的过程中,至少渲染一次 2.在用户浏览的过程中,还会不断重新渲染 (render = n+1) 3.在构建 CSSOM 树时,会阻塞渲染,直至 CSSOM树构建完成。并且构建 CSSOM 树是一个十分消耗性能的过程,所以应该尽量保证层级扁平,减少过度层叠,越是具体的 CSS 选择器,执行速度越慢 4.当 HTML 解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件。并且CSS也会影响 JS 的执行,只有当解析完样式表才会执行 JS,所以也可以认为这种情况下,CSS 也会暂停构建 DOM
第一步解析html标签,构建DOM树
在这个阶段,引擎开始解析html,解析出来的结果会成为一棵dom树(包含全节点,包括隐藏的节点和<head>标签) dom树构建完成以后主要作为下阶段渲染树状图的输入,并且成为网页和脚本的交互界面。(最常用的就是getElementById等等)
当解析器到达script标签的时候,发生下面四件事情
-
html解析器停止解析
-
如果是外部脚本,就从外部网络获取脚本代码
-
将控制权交给js引擎,执行js代码
-
恢复html解析器的控制权
结论:由于标签是阻塞解析的,将脚本放在网页尾部会加速代码渲染。
第二步:解析CSS标签,构建CSSOM树
js会阻塞解析,因为它会修改文档(document)。css不会修改文档的结构,如果这样的话,似乎看起来css样式不会阻塞浏览器html解析。但是事实上 css样式表是阻塞的。阻塞是指当cssom树建立好之后才会进行下一步的解析渲染
通过以下手段可以减轻cssom带来的影响
-
将script脚本放在页面底部
-
尽可能快的加载css样式表
-
将样式表按照media type和media query区分,这样有助于我们将css资源标记成非阻塞渲染的资源
-
非阻塞的资源还是会被浏览器下载,只是优先级较低
第三步:把DOM和CSSOM组合成渲染树(render tree)
这个阶段会让原本的dom树和cssom树相结合,成为一颗新的渲染树 render tree
值得注意的是:render tree不会将所有原本的dom树全部构建出来,一些不需要显示的元素则不会被构建到render tree中
渲染树不会添加 css中设置为display:none的dom元素 渲染树不会添加 <head>标签中的元素面试题中常见的问题会有:
Q: dom中有10个节点 渲染树中的节点会是10个吗? A: 不是的,渲染树不包括 head 和隐藏元素(更少的原因),大段文本的每一个行都是独立节点,每一个节点都有对应的 css 属性(更多的原因)
Q:CSS会阻塞DOM解析吗? A:对于一个HTML文档来说,不管是内联还是外链的css,都会阻碍后续的dom渲染,但是不会阻碍后续dom的解析。 当css文件放在中时,虽然css解析也会阻塞后续dom的渲染,但是在解析css的同时也在解析dom,所以等到css解析完毕就会逐步的渲染页面了。
第四步:在渲染树的基础上进行布局,计算每个节点的几何结构
布局渲染: 布局(layout):定位坐标和大小,是否换行,各种position, overflow, z-index属性
当js脚本中存在修改会影响dom的布局时,会产生回流(Reflow),回流会让浏览器重新执行第四步和第五步的操作
第五步,调用 GPU 绘制,合成图层,显示在屏幕上
将渲染树的各个节点绘制到屏幕上,这一步被称为绘制painting 当js脚本中存在修改会影响dom样式,但不影响布局时,会产生重绘(Repaint),重绘会让浏览器重新执行第五步的操作
优化浏览器渲染的性能
1.在合适的时机选择使用Load或者DOMContentLoaded Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。 DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载
2.使用不同的图层来渲染页面
tips: 一般来说,可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用。
通过以下几个常用属性可以生成新图层
-
3D 变换:translate3d、translateZ
-
will-change
-
video、iframe 标签
-
通过动画实现的 opacity 动画转换
-
position: fixed
3.通过优化重绘和回流来减少相应的页面渲染时间
重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大
tips:重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘 回流是布局或者几何属性需要改变就称为回流。 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流
常见引起回流属性和方法
-
添加或者删除可见的DOM元素;
-
元素尺寸改变——边距、填充、边框、宽度和高度
-
内容变化,比如用户在input框中输入文字
-
浏览器窗口尺寸改变——resize事件发生时
-
计算 offsetWidth 和 offsetHeight 属性
-
设置 style 属性的值
回流影响的范围
由于浏览器渲染界面是基于流失布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种
-
全局范围:从根节点html开始对整个渲染树进行重新布局。
<body> <div class="hello"> <h4>hello</h4> <p><strong>Name:</strong>BDing</p> <h5>male</h5> <ol> <li>coding</li> <li>loving</li> </ol> </div> </body>
当p节点上发生reflow时,hello和body也会重新渲染,甚至h5和ol都会收到影响
-
局部范围:对渲染树的某部分或某一个渲染对象进行重新布局
用局部布局来解释这种现象:把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界
原型对象,原型链,构造函数怎样互相转换
构造函数身上有protype属性指向它的原型也就是原型对象,原型对象上有constructor属性,指回生成它的构造函数,然后构造函数初始化实例对象,为它的实例对象添加属性和方法,实例对身上有proto指向原型对象,原型对象也是对象,也有proto属性,指向原型对象的原型对象,这样一层一层形成链式结构称为原型链,最顶层找不到返回null
html css js 生成页面底层过程
一个完整的渲染流程大致可总结为如下
渲染进程将HTML内容转换为能够读懂的DOM树结构。 渲染引擎将CSS样式表转化为浏览器可以理解的styleSheets,计算出DOM节点的样式。 创建布局树,并计算元素的布局信息。 对布局树进行分层,并生成分层树。 为每个图层生成绘制列表,并将其提交到合成线程。 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。 合成线程发送绘制图块命令DrawQuad给浏览器进程。 浏览器进程根据DrawQuad消息生成页面,并显示到显示器上
-
浏览器是多进程的,有浏览器主进程,网络进程,渲染进程,插件进程等,在将html,css,javascript解析成一个页面的时候,就需要多个进程的分工合作。 当浏览器接受到一个请求的响应数据,并且该数据的类型(content-type)为text/html,浏览器就会知道这是一个html页面,于是网络进程就会将收到的数据交给渲染进程后,就进入了渲染阶段,也就是在这一个阶段,浏览器会根据数据生成一个新的页面。
在执行的过程中,会被分为很多个子阶段,输入的HTML经过这些子阶段,最后会输出像素,这个处理过程叫做渲染流水线,按照渲染的时间顺序,流水线可分为如下几个子阶段:
1.构建dom树 2.样式计算 3.布局阶段 4.分层 5.绘制 6.分块 7.光栅化和合成
构建DOM树
网络进程交给渲染进程的字节流渲染进程是无法识别的,需要先转化为dom树,在渲染进程内部有个html解析器,就负责将字节流转化为dom树
解析器的工作过程 通过分词器将字节流转化为token,token分为tag token 和文本token,tag token又分为 starttag token 和 endtag token,比如 就是一个starttag token, 就是一个endtag token 接着需要将token解析为dom节点,并将dom节点添加到dom树中,这两个过程是同步进行的 html解析器维护了一个token的栈结构,该token栈主要用来计算节点之间的父子关系,在第一个阶段中生成的token会被按照顺序压入这个栈中,具体规则如下: 如果压入到栈中的是StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点 就是栈中相邻的那个元素生成的节点。 如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点。 如果分词器解析出来的是EndTag 标签,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。
样式计算
样式计算的目的是为了计算dom节点中每个元素的具体样式,具体可以分为以下三个步骤:
把css转化为浏览器能够理解的结构,可以用document.styleSheet查看
转换样式表中的属性值,使其标准化
属性标准化就是将一些属性值转化为渲染引擎容易理解的,标准化的计算值,比如一些字体的单位是em的就需要转化为px
算出dom树中每个节点的具体样式
样式计算的第一个规则就是css继承,第二个是层叠
总之,样式计算阶段的目的是为了计算出DOM节点中每个元素的具体样式,在计算过程中需要遵守CSS的继承和层叠两个规则。这个阶段最终输出的内容是每个DOM节点的样式,并被保存在ComputedStyle的结构内。
布局阶段
布局也就是计算出dom树中可见元素的几何位置,chrome在布局阶段需要完成两个任务:创建布局树和布局计算
创建布局树
在DOM树一般还会含有很多不可见的元素,比如head标签,还有使用了display:none属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树。
为了构建布局树,浏览器大体上完成了下面这些工作
遍历DOM树中的所有可见节点,并把这些节点加到布局中; 而不可见的节点会被布局树忽略掉,如head标签下面的全部内容,再比如body.p.span这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树 布局计算
现在我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了
分层
接下来,渲染引擎需要为特定的节点生成专用的图层,并生成一颗对应的图层树。图层叠加起来就是最终的页面图像
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。如上图中的span标签没有专属图层,那么它们就从属于它们的父节点图层。但不管怎样,最终每一个节点都会直接或者间接地从属于一个层。
满足以下条件才会被提升为单独的层
第一点,拥有层叠上下文的元素会被提升为单独的一层
页面是个二维平面,但是层叠上下文能够让HTML元素具有三维概念,这些HTML元素按照自身属性的优先级分布在垂直于这个二维平面的z轴上。你可以结合下图来直观感受下:
需要剪裁的地方也会被创建为图层
标签里面的内容超出了标签的宽度和高度,就会出现剪裁。出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层
图层绘制
在完成图层树的构建之后,渲染引擎会对图层树中的每一个图层进行绘制
绘制的过程就是把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表
绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。
栅格化操作
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。
当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程
通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做视口(viewport)。 在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。
基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是256x256或者512x512
合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示:
通常,栅格化过程都会使用GPU来加速使用,使用GPU生成位图的过程叫做快速栅格化,或者GPU栅格化,生成的位图会保存在GPU内存中
合成和显示
一旦所有的图块都被栅格化,合成线程就会生成一个绘制图块的命令—‘DrawQuad",然后将该命令提交给浏览器进程
浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面了。
history遇到过哪些问题,如何解决history404
路由的两种工作模式为:哈希(hash)路由和浏览器history)路由,大致区别如下: 1.对于一个url来说,什么是hash值? 及其后面的内容就是hash值 2.hash值不会包含在HTTP请求中,即:hash值不会带给服务器 3.hash模式 a.地址中永远带着#号,不美观 b.若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法 c.兼容性较好 4.history模式 a.地址干净,美观 b.兼容性和hash模式相比略差 c.应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
在项目打包上线时,如果采用的是哈希模式,不会出现404,原因是 url 中 # 号后面的内容不会发给后端当作资源路径请求服务器。
具体流程(哈希模式):
-
前端项目写完之后,把src件夹打包 (利用webpack、babel把项目里面的jsx / vue 、scss文件变异成js、css和html),命令如下
npm run build
该html文件需部署在服务器上才能正常显示(只有一个html文件,是因为项目本身是SPA),
新建static文件夹,将dist里面的文件放入static。
2.搭建服务器
新建一个文件夹用vscode打开后,
npm init // 然后设置 package name,回车 npm i express
接着新建 server.js文件:
// common.js 规范 // 引入 express const express = require('express') // 利用中间件 给服务器指定静态资源(打包后的项目文件) app.use(express.static(__dirname+'/static')) // 新建实例 const app = express() // 配置后端路由 app.get('/person',(request,response)=>{ // 给浏览器返回的信息 response.send({ name:'tom',age:19 }) }) // 开启监听 app.listen(5005,(err)=>{ if(!err) conslole.log('服务器启动成功!') })
3.启动服务器
history 模式下刷新出现 404解决
1. 根据前端发送的请求以及后端已有的资源路径做判断(正则)匹配,区分哪些是前端路由哪些是后端路由。 2. nginx 中间代理区分前后端路由 3. node.js提供的中间件Connect
// common.js 规范 // 引入 express const express = require('express') const history = require('connect-history-api-fallback'); // 新建实例 const app = express() app.use(history()); // 利用中间件 给服务器指定静态资源(打包后的项目文件) app.use(express.static(__dirname+'/static')) // 配置后端路由 app.get('/person',(request,response)=>{ // 给浏览器返回的信息 response.send({ name:'tom',age:19 }) }) // 开启监听 app.listen(5005,(err)=>{ if(!err) conslole.log('服务器启动成功!') })
cookie能改吗(cookie如何实现不在控制台中更改)如何解决项目登录后保持上一次登陆前状态
[]: https://blog.csdn.net/qq_44907926/article/details/120146746?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168113751416800211510722%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168113751416800211510722&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-120146746-null-null.142^v82^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E9%A1%B9%E7%9B%AE%E7%99%BB%E5%BD%95%E5%90%8E%E4%BF%9D%E6%8C%81%E4%B8%8A%E4%B8%80%E6%AC%A1%E7%99%BB%E9%99%86%E5%89%8D%E7%8A%B6%E6%80%81&spm=1018.2226.3001.4187
JSONP原理、中间件原理
假设在http://example1.com/index.php这个页面中向http://example2.com/getinfo.php提交GET请求,我们可以将下面的JavaScript代码放在http://example1.com/index.php这个页面中来实现
var eleScript= document.createElement("script"); eleScript.type = "text/javascript"; eleScript.src = "http://example2.com/getinfo.php"; document.getElementsByTagName("HEAD")[0].appendChild(eleScript);
当GET请求从http://example2.com/getinfo.php返回时,可以返回一段JavaScript代码,这段代码会自动执行,可以用来负责调用http://example1.com/index.php页面中的一个callback函数。
JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
JSONP的最基本的原理是:动态添加一个<script>标签,而script标签的src属性是没有跨域的限制的。这样说来,这种跨域方式其实与ajax XmlHttpRequest协议无关了
简述原理与过程:首先在客户端注册一个callback, 然后把callback的名字传给服务器。此时,服务器先生成 json 数据。 然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp。最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。
客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里。(动态执行回调函数)
[中间件原理] https://blog.csdn.net/Polaris_tl/article/details/117351423?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168113780516800197060840%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168113780516800197060840&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-117351423-null-null.142^v82^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%8E%9F%E7%90%86&spm=1018.2226.3001.4187
对象如何能转换成数组(不使用遍历)对象如何转换成字符串
Object.values(obj)
示例: var obj = { name: '小明', age: 22 }; console.log( Object.values(obj) ); // ['小明', 22]
for-in循环
示例: var obj = { name: '小明', age: 22 }; var arr = []; for(let i in obj) { arr.push (obj[i] ) // } console.log(arr); // ['小明', 22]
相同点
-
都可以将对象转化为数组;
-
返回值都可以是对象的属性值;
-
也都可以将字符串转化为数组。(示例1的最后一条打印)
区别
-
for-in 循环可以枚举原型链中的属性,而Object.values(obj)不可以。(示例1);
-
for- in 的返回值可以是对象的属性名(键名)和属性值,而Object.values(obj)只返回属性值。(示例2)
示例1: var obj = { name: '小明', age: 22 }; obj.__proto__.sex = '男'; //往原型链上插入一条属性 var arr = []; for(let i in obj) { arr.push (obj[i] ) // } console.log(arr); // ['小明', 22, '男'] cosole.log(Object.values(obj)); // ['小明', 22] cosole.log(Object.values(‘abc’)); // ['a, 'b', 'c']
示例2: var obj = { name: '小明', age: 22 }; var arr = []; for(let i in obj) { // arr.push (obj[i] ) //返回属性值 arr.push (i) //返回键名 } console.log(arr); // ['name', 22] cosole.log(Object.values(obj)); // ['小明', 22]
浏览器不能解析less怎么办
-
less无法在浏览器中直接使用,浏览器不能识别,因此需要使用js插件less.js进行识别。 但是less.js插件的原理是使用Ajax,
所以需要使用HTTP协议打开,而不是ftp协议。此时可以使用http-server,http-server基本使用 引入less.js时,需要放在less文件之后 原因:当加载less.js的时候,其会去查找可解析的less文件。 less文件的引入,需要加上type="text/less"
<link rel=“stylesheet” href="/css/test.less" type=“text/less”>
以上有一条不满足,less.js就无法解析less文件,所以慎重!!! 因此较完整的使用less如下:
<link rel="stylesheet" href="/css/test.less" type="text/less"> <script src="/js/less.js"></script>
如果需要加上监听,也即代码改动之后浏览器页面也跟着改动,可以加上如下代码:
<script>less.watch()</script>
怎么获取动态变量的值、怎么获取对象的value值
1、获取key值
获取对象所有key的方法,需要使用 Object.keys(obj) 方法,Object.keys(obj)方返回一个数组,这个数组包含obj对象中的所有key。
其中obj就是你写的对象,具体使用方法如下图:
正在上传…重新上传取消
2、获取value值
获取对象所有value的方法,需要使用 Object.values(obj) 方法,Object.value(obj)方返回一个数组,这个数组包含obj对象中的所有value。
其中obj就是你写的对象,具体使用方法如下图:
正在上传…重新上传取消
3、同时获取key值和value值
for in 循环遍历对象 (for in 和 for of 的区别请跳转至for in 和 for of)
如果想遍历对象obj中的所有键(key)、值(value),一般是用以下方式
for(var key in obj){ console.log(key,obj[key]) } 结果 age 20 name rose gender nv
new一个构造函数的底层原理
function Person(name) { this.name = name; } let zhangsan = new Person('张三');
上述代码中new了一个Person,这其中的过程如下:
第一步:创建一个空对象object,let obj = new Object()---创建对象新对象,就是指在栈内新建了一个obj,这个obj实际上是指的堆中对应的一个地址。
第二步:设置原型链---这里所说原型链,就是设置新建对象obj的隐式原型即proto属性指向构造函数Person的显示原型prototype对象,即
obj.proto = Person.prototype
第三步:改变构造函数Person的this绑定到新对象obj,并且利用call()或者是apply()来执行构造函数Person,如下
let result = Person.call(obj)
第四步:将第三步中初始化完成后的对象地址,保存到新对象中,同时要判断构造函数Person的返回值类型,为什么要判断值类型呢?因为如果构造函数中返回this或者是基本数据类型(number数值,string字符串,Boolean布尔,null,undefined)的值时,这个时候则返回新的实例对象,如果构造函数返回的值是引用类型的,则返回的值是引用类型,如下:
if (typeof (result) === "object") { func = result; } else { func = obj; // 默认返回 }
这里需要注意一点的就是JavaScript中的构造函数,是不需要返回值的,所以会默认返回一个新创建的空对象obj。
结合上面所说的new关键字执行过程,我们可以手写一个函数实现一下,提升一下理解:
function newObject(func) { return function () { let newObj = { __proto__: func.prototype // 新生成一个对象,且新对象的隐式原型对象继承自构造对象的显示原型对象 } var returnObj = func.call(obj, arguments) // 以第二次执行函数的参数,在obj作用域中执行func if ((typeof returnObj === "object" || typeof returnObj === "function") && returnObj !== null) { return returnObj; } // 同理,returnObj是 "对象" 类型,那么这个对象会取代newObj作为返回的对象 return newObj } }
js单线程多线程,遇到过script标签里加async吗?
概念
默认情况下js的脚本执行是同步和阻塞的,但是 <script> 标签有 defer 和 async 属性, 这可以改变脚本的执行方式,这些都是布尔类型了,没有值,只需要出现在 <script> 标签里即可。
如:
还要注意一点,html5说这些属性只在和src属性联合使用时才有效。
如果同时指定了两个属性,则会遵从async属性而忽略defer属性。
作用
defer 属性标注的脚本是延迟脚本,使得浏览器延迟脚本的执行,也就是说,脚本会被异步下载但是不会被执行,直到文档的载入和解析完成,并可以操作,脚本才会被执行。
async 属性标注的脚本是异步脚本,即异步下载脚本时,不会阻塞文档解析,但是一旦下载完成后,立即执行,阻塞文档解析。
区别
延迟脚本会按他们在文档里的出现顺序执行
异步脚本在它们载入后执行,但是不能保证执行顺序。
一张图足以说明区别:
使用async的意义就在于使得下载脚本时,不会阻塞文档的解析。因为async的脚本执行顺序是没有保证的,因此要确认脚本间没有依赖关系。
现在呢基本上都是在文档的最后写脚本,那么这和 defer 的区别在哪里呢?
第一点当然是异步下载脚本了,第二点就是 使用async或defer任何一个都意味着在脚本里不能出现 document.write。
fetch和axios区别
fecth与xios的定义 fecth是浏览器提供的一个api,而axios是社区封装的一个组件 fecth是一个低底层的api,是W3C的正式标准,使用起来不怎么舒服,所以需要封装,以便使用。
fetch和axios的区别 1.fetch是规范底层api 2.axios是封装 fetch的优点
1.语法简洁,更加语义化 2.基于标准Promise实现,支持async/await 3.更加底层,提供的API丰富 4.底层原生支持 5.是由whatwg组织提出,是w3c的规范 fetch的缺点 fetch是一个底层api,使用不便,需重新封装
1.fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回400,500错误码时并不会reject,只有网络错误导致这些请求不能完成时,fetch才会被reject。 2.fetch默认不会带cookie,需要添加配置项:fetch(url,{credentials:‘include’}) 3.fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止过程继续在后台运行,造成了流量的浪费 4.etch没有办法原生检测请求的进度,而XHR可以(进度条)
//fetch举例 fetch('httpp://example.com/movies.js') //第二个参数 指定get/post .then(function(response){ return response.json(); }) .then(function(myJson){ console.log(myJson); });
axios的优点 axios是一个基于Promise用于浏览器和nodejs的http客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,它本身具有以下特征:
1.从浏览器中创建XMLHttpRequest 2.支持Promise API 3.客户端支持防止CSRF 4.提供了一些并发请求的接口(!方便了很多操作) 5.从node.js创建http请求 6.拦截请求和响应 7.转换请求和响应数据(请求A服务器可以转换到B服务器) 8.中断请求 9.自动转换JSON数据(fetch API需要手动处理返回的数据)
//axios举例 xios.get('/user'{ params:{ ID:0512 } }) .then(funtion(response){ console.log(response); }) .catch(function(error){ console.log(error); });
fetch和原生ajax区别
AJAX
$.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function () {}, error: function () {} });
传统AJAX指的是XMLHttpRequest
,最早出现的发送后端请求的技术。
fetch
try { let response = await fetch(url); let data = response.json(); console.log(data); } catch(e) { console.log("Oops, error", e); }
fetch是基于Promise设计的,fetch不是AJAX的进一步封装,而是原生JS,没有使用XMLHttpRequest对象。
fetch和AJAX的主要区别 1.fetch返回错误的http状态码时不会reject fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
解决方案:
我们可以在第一层.then里面去手动抛出异常,之后就可以使用.then .catch了。
fetch( 'http://domain/service', { method: 'GET' } ) .then(response => { if (response.ok) { return response.json(); } throw new Error('Network response was not ok.'); }) .then(json => console.log(json)) .catch(error => console.error('error:', error));
2.默认情况下,fetch不会携带cookie 解决方案:
添加配置项: fetch(url, {credentials: 'include'})
3.fetch没有设置超时时间 解决方案: 使用一个promise来封装,通过setTimeout()来设置一个时间,如果超过该事件,就返回reject
return new Promise((resolve, reject) => { fetch(url, init) .then(resolve) .catch(reject); setTimeout(reject, timeout); }) }
在这里,fetch会和setTimeout同时执行,因为fetch是异步的,不会堵塞后面setTimeout的执行
九宫格怎么实现
js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> *{ padding: 0; margin: 0; list-style: none; } #box{ position: relative; } .smallbox { background-color: #ccc; position: absolute; } </style> </head> <body> <button></button> <div id="box"> </div> <script> window.οnlοad=function(){ var box = document.getElementById('box'); change(5,25,200,200,5) //修改第一个从来参数可以调整列数 function change(col,boxlength,width,height,margin){ for(var i =0;i<boxlength;i++){ var small = document.createElement("div"); small.className='smallbox'; box.appendChild(small) small.style.width=width + "px"; small.style.height=height+ 'px'; small.style.margin=margin+ 'px'; small.style.left = parseInt(i%col)*(width+margin) + "px"; small.style.top=parseInt(i/col)*(height+margin)+"px"; } } } </script> </body> </html>
Jq
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <style> *{ padding: 0; margin: 0; list-style: none; } #box{ position: relative; } .smallbox { background-color: #ccc; position: absolute; } </style> </head> <body> <button id="btn1">生成俩列</button> <div id="box"> </div> <script> $(function(){ var box = $('#box'); var width=100,height=100,margin=10,col=6; //改变col的值,可以改变列数 change(width,height,margin,col) //调用方法 function change(width,height,margin,col){ for(var i =0;i<30;i++){ var small = $('<div class="smallbox"></div>'); box.append(small); small.css({"left":parseInt(i%col)*(width+margin)+ 'px',"width":width,'height':height,"top":parseInt(i/col)*(height+margin) + "px"}); } }; }) </script> </body> </html>
vue实现九宫格布局
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> <style> *{ padding: 0; margin: 0; list-style: none; } #box{ position: relative; } .smallbox { background-color: #ccc; position: absolute; width: 100px; height: 100px; margin:100px; } </style> </head> <body> <div id="box"> <button @click="change(3)">3列</button> <button @click="change(4)">4列</button> <button @click="change(5)">5列</button> <button @click="change(6)">6列</button> <div v-for="po in position" class="smallbox" :style="{left:po.left+'px',top:po.top+'px'}">{{po}}</div> </div> <script> new Vue({ el:"#box", data:{ width:"100px", height:"100px", position:[], }, methods:{ change:function(col){ this.position=[] for(var i =0;i<100;i++){ var l = parseInt(i%col)*(100+10); var t = parseInt(i/col)*(100+10); po={'left':l,"top":t} console.log(po) this.position.push(po) } } } }) </script> </body> </html>
浏览器地址栏输入地址按下回车浏览器经过哪些过程
[一] https://blog.csdn.net/qq_45695853/article/details/123839094?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168113924016800213032580%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168113924016800213032580&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-123839094-null-null.142^v82^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=%E6%B5%8F%E8%A7%88%E5%99%A8%E5%9C%B0%E5%9D%80%E6%A0%8F%E8%BE%93%E5%85%A5%E5%9C%B0%E5%9D%80%E6%8C%89%E4%B8%8B%E5%9B%9E%E8%BD%A6%E6%B5%8F%E8%A7%88%E5%99%A8%E7%BB%8F%E8%BF%87%E5%93%AA%E4%BA%9B%E8%BF%87%E7%A8%8B&spm=1018.2226.3001.4187
[二] https://blog.csdn.net/MiemieWan/article/details/85708052?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168113924016800213032580%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168113924016800213032580&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-19-85708052-null-null.142^v82^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=%E6%B5%8F%E8%A7%88%E5%99%A8%E5%9C%B0%E5%9D%80%E6%A0%8F%E8%BE%93%E5%85%A5%E5%9C%B0%E5%9D%80%E6%8C%89%E4%B8%8B%E5%9B%9E%E8%BD%A6%E6%B5%8F%E8%A7%88%E5%99%A8%E7%BB%8F%E8%BF%87%E5%93%AA%E4%BA%9B%E8%BF%87%E7%A8%8B&spm=1018.2226.3001.4187
使用git时提交时发生冲突,你能解释冲突如何产生,如何解决
[]: https://blog.csdn.net/qq_16570607/article/details/121870014?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168113931816800211536534%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168113931816800211536534&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-121870014-null-null.142^v82^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=%E4%BD%BF%E7%94%A8git%E6%97%B6%E6%8F%90%E4%BA%A4%E6%97%B6%E5%8F%91%E7%94%9F%E5%86%B2%E7%AA%81%EF%BC%8C%E4%BD%A0%E8%83%BD%E8%A7%A3%E9%87%8A%E5%86%B2%E7%AA%81%E5%A6%82%E4%BD%95%E4%BA%A7%E7%94%9F%EF%BC%8C%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3&spm=1018.2226.3001.4187
git回滚命令、git原理、git分支,git如何使用
[]: https://blog.csdn.net/qq_51116747/article/details/129167832?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168113935416800182175238%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168113935416800182175238&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-129167832-null-null.142^v82^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=git%E5%9B%9E%E6%BB%9A%E5%91%BD%E4%BB%A4%E3%80%81git%E5%8E%9F%E7%90%86%E3%80%81git%E5%88%86%E6%94%AF%EF%BC%8Cgit%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8&spm=1018.2226.3001.4187
webpack怎么用
[]: https://blog.csdn.net/qq_59012240/article/details/127980314?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168113937816800222867577%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168113937816800222867577&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-3-127980314-null-null.142^v82^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=webpack%E6%80%8E%E4%B9%88%E7%94%A8&spm=1018.2226.3001.4187