iframe 有那些优点和缺点?
iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。
优点:
- 用来加载速度较慢的内容(如广告)
- 可以使脚本可以并行下载
- 可以实现跨子域通信
缺点:
- iframe 会阻塞主页面的 onload 事件
- 无法被一些搜索引擎索识别
- 会产生很多页面,不容易管理
compose
题目描述:实现一个 compose 函数
代码语言:javascript复制// 用法如下:
function fn1(x) {
return x 1;
}
function fn2(x) {
return x 2;
}
function fn3(x) {
return x 3;
}
function fn4(x) {
return x 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1 4 3 2 1=11
实现代码如下:
代码语言:javascript复制function compose(...fn) {
if (!fn.length) return (v) => v;
if (fn.length === 1) return fn[0];
return fn.reduce(
(pre, cur) =>
(...args) =>
pre(cur(...args))
);
}
如何避免回流与重绘?
减少回流与重绘的措施:
- 操作DOM时,尽量在低层级的DOM节点进行操作
- 不要使用
table
布局, 一个小的改动可能会使整个table
进行重新布局 - 使用CSS的表达式
- 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
- 避免频繁操作DOM,可以创建一个文档片段
documentFragment
,在它上面应用所有DOM操作,最后再把它添加到文档中 - 将元素先设置
display: none
,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。
上面,将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。
常见的HTTP请求头和响应头
HTTP Request Header 常见的请求头:
- Accept:浏览器能够处理的内容类型
- Accept-Charset:浏览器能够显示的字符集
- Accept-Encoding:浏览器能够处理的压缩编码
- Accept-Language:浏览器当前设置的语言
- Connection:浏览器与服务器之间连接的类型
- Cookie:当前页面设置的任何Cookie
- Host:发出请求的页面所在的域
- Referer:发出请求的页面的URL
- User-Agent:浏览器的用户代理字符串
HTTP Responses Header 常见的响应头:
- Date:表示消息发送的时间,时间的描述格式由rfc822定义
- server:服务器名称
- Connection:浏览器与服务器之间连接的类型
- Cache-Control:控制HTTP缓存
- content-type:表示后面的文档属于什么MIME类型
常见的 Content-Type 属性值有以下四种:
(1)application/x-www-form-urlencoded:浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL转码。
(2)multipart/form-data:该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。
(3)application/json:服务器消息主体是序列化后的 JSON 字符串。
(4)text/xml:该种方式主要用来提交 XML 格式的数据。
代码输出结果
代码语言:javascript复制f = function() {return true;};
g = function() {return false;};
(function() {
if (g() && [] == ![]) {
f = function f() {return false;};
function g() {return true;}
}
})();
console.log(f());
输出结果: false
这里首先定义了两个变量f和g,我们知道变量是可以重新赋值的。后面是一个匿名自执行函数,在 if 条件中调用了函数 g(),由于在匿名函数中,又重新定义了函数g,就覆盖了外部定义的变量g,所以,这里调用的是内部函数 g 方法,返回为 true。第一个条件通过,进入第二个条件。
第二个条件是[] == ![],先看 ![] ,在 JavaScript 中,当用于布尔运算时,比如在这里,对象的非空引用被视为 true,空引用 null 则被视为 false。由于这里不是一个 null, 而是一个没有元素的数组,所以 [] 被视为 true, 而 ![] 的结果就是 false 了。当一个布尔值参与到条件运算的时候,true 会被看作 1, 而 false 会被看作 0。现在条件变成了 [] == 0 的问题了,当一个对象参与条件比较的时候,它会被求值,求值的结果是数组成为一个字符串,[] 的结果就是 '' ,而 '' 会被当作 0 ,所以,条件成立。
两个条件都成立,所以会执行条件中的代码, f 在定义是没有使用var,所以他是一个全局变量。因此,这里会通过闭包访问到外部的变量 f, 重新赋值,现在执行 f 函数返回值已经成为 false 了。而 g 则不会有这个问题,这里是一个函数内定义的 g,不会影响到外部的 g 函数。所以最后的结果就是 false。
new 一个函数发生了什么
构造调用:
- 创造一个全新的对象
- 这个对象会被执行 [Prototype] 连接,将这个新对象的 [Prototype] 链接到这个构造函数.prototype 所指向的对象
- 这个新对象会绑定到函数调用的 this
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象
<script src=’xxx’ ’xxx’/>外部js文件先加载还是onload先执行,为什么?
onload 是所以加载完成之后执行的
实现一个三角形
CSS绘制三角形主要用到的是border属性,也就是边框。
平时在给盒子设置边框时,往往都设置很窄,就可能误以为边框是由矩形组成的。实际上,border属性是右三角形组成的,下面看一个例子:
代码语言:css复制div {
width: 0;
height: 0;
border: 100px solid;
border-color: orange blue red green;
}
将元素的长宽都设置为0
(1)三角1
代码语言:css复制div { width: 0; height: 0; border-top: 50px solid red; border-right: 50px solid transparent; border-left: 50px solid transparent;}
(2)三角2
代码语言:css复制div {
width: 0;
height: 0;
border-bottom: 50px solid red;
border-right: 50px solid transparent;
border-left: 50px solid transparent;
}
(3)三角3
代码语言:css复制div {
width: 0;
height: 0;
border-left: 50px solid red;
border-top: 50px solid transparent;
border-bottom: 50px solid transparent;
}
(4)三角4
代码语言:css复制div {
width: 0;
height: 0;
border-right: 50px solid red;
border-top: 50px solid transparent;
border-bottom: 50px solid transparent;
}
(5)三角5
代码语言:css复制div {
width: 0;
height: 0;
border-top: 100px solid red;
border-right: 100px solid transparent;
}
还有很多,就不一一实现了,总体的原则就是通过上下左右边框来控制三角形的方向,用边框的宽度比来控制三角形的角度。
类数组转化为数组的方法
题目描述:类数组拥有 length 属性 可以使用下标来访问元素 但是不能使用数组的方法 如何把类数组转化为数组?
实现代码如下:
代码语言:javascript复制const arrayLike=document.querySelectorAll('div')
// 1.扩展运算符
[...arrayLike]
// 2.Array.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.prototype.concat.apply([], arrayLike)
对Promise的理解
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
(1)Promise的实例有三个状态:
- Pending(进行中)
- Resolved(已完成)
- Rejected(已拒绝)
当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。
(2)Promise的实例有两个过程:
- pending -> fulfilled : Resolved(已完成)
- pending -> rejected:Rejected(已拒绝)
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
Promise的特点:
- 对象的状态不受外界影响。promise对象代表一个异步操作,有三种状态,
pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来——“承诺”; - 一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能:从
pending
变为fulfilled
,从pending
变为rejected
。这时就称为resolved
(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
Promise的缺点:
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
总结: Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。
状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。
注意: 在构造 Promise
的时候,构造函数内部的代码是立即执行的
关于原型的继承我们借助寄生组合继承
代码语言:javascript复制function Person(obj) {
this.name = obj.name
this.age = obj.age
}
Person.prototype.add = function(value){
console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})
function Person1(obj) {
Person.call(this, obj)
this.sex = obj.sex
}
// 这一步是继承的关键
Person1.prototype = Object.create(Person.prototype)
Person1.prototype.play = function(value){
console.log(value)
}
var p2 = new Person1({name:"鸡蛋", age: 118, sex: "男"})
深拷贝浅拷贝
代码语言:typescript复制浅拷贝:浅拷贝通过ES6新特性Object.assign()或者通过扩展运算法...来达到浅拷贝的目的,浅拷贝修改
副本,不会影响原数据,但缺点是浅拷贝只能拷贝第一层的数据,且都是值类型数据,如果有引用型数据,修改
副本会影响原数据。
深拷贝:通过利用JSON.parse(JSON.stringify())来实现深拷贝的目的,但利用JSON拷贝也是有缺点的,
当要拷贝的数据中含有undefined/function/symbol类型是无法进行拷贝的,当然我们想项目开发中需要
深拷贝的数据一般不会含有以上三种类型,如有需要可以自己在封装一个函数来实现。
常⽤的meta标签有哪些
meta
标签由 name
和 content
属性定义,用来描述网页文档的属性,比如网页的作者,网页描述,关键词等,除了HTTP标准固定了一些name
作为大家使用的共识,开发者还可以自定义name。
常用的meta标签:
(1)charset
,用来描述HTML文档的编码类型:
<meta charset="UTF-8" >
(2) keywords
,页面关键词:
<meta name="keywords" content="关键词" />
(3)description
,页面描述:
<meta name="description" content="页面描述内容" />
(4)refresh
,页面重定向和刷新:
<meta http-equiv="refresh" content="0;url=" />
(5)viewport
,适配移动端,可以控制视口的大小和比例:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
其中,content
参数有以下几种:
width viewport
:宽度(数值/device-width)height viewport
:高度(数值/device-height)initial-scale
:初始缩放比例maximum-scale
:最大缩放比例minimum-scale
:最小缩放比例user-scalable
:是否允许用户缩放(yes/no)
(6)搜索引擎索引方式:
代码语言:html复制<meta name="robots" content="index,follow" />
其中,content
参数有以下几种:
all
:文件将被检索,且页面上的链接可以被查询;none
:文件将不被检索,且页面上的链接不可以被查询;index
:文件将被检索;follow
:页面上的链接可以被查询;noindex
:文件将不被检索;nofollow
:页面上的链接不可以被查询。
CSS预处理器/后处理器是什么?为什么要使用它们?
预处理器, 如:less
,sass
,stylus
,用来预编译sass
或者less
,增加了css
代码的复用性。层级,mixin
, 变量,循环, 函数等对编写以及开发UI组件都极为方便。
后处理器, 如: postCss
,通常是在完成的样式表中根据css
规范处理css
,让其更加有效。目前最常做的是给css
属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。
css
预处理器为css
增加一些编程特性,无需考虑浏览器的兼容问题,可以在CSS
中使用变量,简单的逻辑程序,函数等在编程语言中的一些基本的性能,可以让css
更加的简洁,增加适应性以及可读性,可维护性等。
其它css
预处理器语言:Sass(Scss)
, Less
, Stylus
, Turbine
, Swithch css
, CSS Cacheer
, DT Css
。
使用原因:
- 结构清晰, 便于扩展
- 可以很方便的屏蔽浏览器私有语法的差异
- 可以轻松实现多重继承
- 完美的兼容了
CSS
代码,可以应用到老项目中
实现模板字符串解析功能
题目描述:
代码语言:text复制let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
实现代码如下:
代码语言:javascript复制function render(template, data) {
let computed = template.replace(/{{(w )}}/g, function (match, key) {
return data[key];
});
return computed;
}
浏览器渲染优化
(1)针对JavaScript: JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变,来进行优化:
(1)尽量将JavaScript文件放在body的最后
(2) body中间尽量不要写<script>
标签
(3)<script>
标签的引入资源方式有三种,有一种就是我们常用的直接引入,还有两种就是使用 async 属性和 defer 属性来异步引入,两者都是去异步加载外部的JS文件,不会阻塞DOM的解析(尽量使用异步加载)。三者的区别如下:
- script 立即停止页面渲染去加载资源文件,当资源加载完毕后立即执行js代码,js代码执行完毕后继续渲染页面;
- async 是在下载完成之后,立即异步加载,加载好后立即执行,多个带async属性的标签,不能保证加载的顺序;
- defer 是在下载完成之后,立即异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果DOM树已经准备好,则立即执行。多个带defer属性的标签,按照顺序执行。
(2)针对CSS:使用CSS有三种方式:使用link、@import、内联样式,其中link和@import都是导入外部样式。它们之间的区别:
- link:浏览器会派发一个新等线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码
- @import:GUI渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前不会继续渲染(阻碍浏览器渲染)
- style:GUI直接渲染
外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。所以CSS一般写在headr中,让浏览器尽快发送请求去获取css样式。
所以,在开发过程中,导入外部样式使用link,而不用@import。如果css少,尽可能采用内嵌样式,直接写在style标签中。
(3)针对DOM树、CSSOM树: 可以通过以下几种方式来减少渲染的时间:
- HTML文件的代码层级尽量不要太深
- 使用语义化的标签,来避免不标准语义化的特殊处理
- 减少CSSD代码的层级,因为选择器是从左向右进行解析的
(4)减少回流与重绘:
- 操作DOM时,尽量在低层级的DOM节点进行操作
- 不要使用
table
布局, 一个小的改动可能会使整个table
进行重新布局 - 使用CSS的表达式
- 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
- 避免频繁操作DOM,可以创建一个文档片段
documentFragment
,在它上面应用所有DOM操作,最后再把它添加到文档中 - 将元素先设置
display: none
,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。
将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。
对事件循环的理解
因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。
Event Loop 执行顺序如下所示:
- 首先执行同步代码,这属于宏任务
- 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
- 执行所有微任务
- 当执行完所有微任务后,如有必要会渲染页面
- 然后开始下一轮 Event Loop,执行宏任务中的异步代码
CDN的原理
CDN和DNS有着密不可分的联系,先来看一下DNS的解析域名过程,在浏览器输入的解析过程如下:
(1) 检查浏览器缓存
(2)检查操作系统缓存,常见的如hosts文件
(3)检查路由器缓存
(4)如果前几步都没没找到,会向ISP(网络服务提供商)的LDNS服务器查询
(5)如果LDNS服务器没找到,会向根域名服务器(Root Server)请求解析,分为以下几步:
- 根服务器返回顶级域名(TLD)服务器如
.com
,.cn
,.org
等的地址,该例子中会返回.com
的地址 - 接着向顶级域名服务器发送请求,然后会返回次级域名(SLD)服务器的地址,本例子会返回
.test
的地址 - 接着向次级域名服务器发送请求,然后会返回通过域名查询到的目标IP,本例子会返回
www.test.com
的地址 - Local DNS Server会缓存结果,并返回给用户,缓存在系统中
CDN的工作原理: (1)用户未使用CDN缓存资源的过程:
- 浏览器通过DNS对域名进行解析(就是上面的DNS解析过程),依次得到此域名对应的IP地址
- 浏览器根据得到的IP地址,向域名的服务主机发送数据请求
- 服务器向浏览器返回响应数据
(2)用户使用CDN缓存资源的过程:
- 对于点击的数据的URL,经过本地DNS系统的解析,发现该URL对应的是一个CDN专用的DNS服务器,DNS系统就会将域名解析权交给CNAME指向的CDN专用的DNS服务器。
- CND专用DNS服务器将CND的全局负载均衡设备IP地址返回给用户
- 用户向CDN的全局负载均衡设备发起数据请求
- CDN的全局负载均衡设备根据用户的IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求
- 区域负载均衡设备选择一台合适的缓存服务器来提供服务,将该缓存服务器的IP地址返回给全局负载均衡设备
- 全局负载均衡设备把服务器的IP地址返回给用户
- 用户向该缓存服务器发起请求,缓存服务器响应用户的请求,将用户所需内容发送至用户终端。
如果缓存服务器没有用户想要的内容,那么缓存服务器就会向它的上一级缓存服务器请求内容,以此类推,直到获取到需要的资源。最后如果还是没有,就会回到自己的服务器去获取资源。
CNAME(意为:别名):在域名解析中,实际上解析出来的指定域名对应的IP地址,或者该域名的一个CNAME,然后再根据这个CNAME来查找对应的IP地址。