目录
1、箭头函数和普通函数的区别
- 箭头函数没有
prototype
(原型),所以箭头函数本身没有this
- 箭头函数的
this
在定义的时候继承自外层第一个普通函数的this
- 如果箭头函数外层没有普通函数,严格模式和非严格模式下它的
this
都会指向window
(全局对象) - 箭头函数本身的
this
指向不能改变,但可以修改它要继承的对象的this
- 箭头函数的
this
指向全局,使用arguments
会报未声明的错误 - 箭头函数的
this
指向普通函数时,它的argumens
继承于该普通函数 - 使用
new
调用箭头函数会报错,因为箭头函数没有constructor
- 箭头函数不支持
new.target
- 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
- 箭头函数相对于普通函数语法更简洁优雅
2、一道JS综合面试题
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS综合面试题</title>
</head>
<body>
<script type="text/javascript">
// function Foo() {
// getName = function(){alert(1);};
// return this;
// }
// Foo.getName = function(){ alert(2);};
// Foo.prototype.getName = function(){ alert(3);};
// var getName = function(){ alert(4);};
// function getName(){ alert(5);};
function Foo() {
getName = function(){alert(1);};
return this;
}
var getName;
function getName(){ alert(5);};
// 上面是变量提升和函数提升,变量只是声明提升,函数是整个函数提升,当变量和函数的声明有冲突时,默认保留函数的定义
Foo.getName = function(){ alert(2);};
Foo.prototype.getName = function(){ alert(3);};
getName = function(){ alert(4);};
//请写出下列的输出结果
Foo.getName(); //2
getName(); //4
Foo().getName(); // (Foo()).getName() --> window.getName() 1
getName(); //1
new Foo.getName(); //new (Foo.getName)() --> new (function(){ alert(2);})() 2
new Foo().getName(); //(new Foo()).getName() --> foo.getName() 3
new new Foo().getName(); //new ((new Foo()).getName)() --> new (foo.getName)() --> new (function(){ alert(3);})() 3
</script>
</body>
</html>
3、CSS盒子模型和IE盒子模型
标准模型和IE模型的区别就在于宽和高的计算方式不同,标准模型的宽度和高度指的就是content的宽度和高度,它不包含padding和border,IE模型的宽度和高度是计算padding和border的,如果宽度都设置为200px,对于IE模型来说,200px包含了padding和border,如果IE模型的padding是10px,无border,那么对应的content只能是180px,高度亦是如此。
CSS如何设置这两种模型
通过CSS3中的属性box-sizing:content-box;设置成标准模型;box-sizing:border-box;设置成IE模型。浏览器默认的方式是content-box
JS如何设置获取盒子模型对应的宽和高
- DOM.style.width/height(并不能拿到所有的宽和高,只能拿到内联样式的宽和高,也就是在div标签内写style的写法,其他两种CSS写法是取不到宽高的)
- DOM.currentStyle.width/height(拿到浏览器渲染后的宽高,但这个属性只有IE支持)
- window.getComputedStyle(DOM).width/height(与上一种原理相似,但这种支持大多数浏览器,兼容性更好)
- DOM.getBoundingClientRect().width/height(经常适用的场景是计算一个元素的绝对位置,这个绝对位置是根据视窗的绝对位置,getBoundingClientRect()这个方法拿到的有4个元素:left,top,width,height)
BFC(边距重叠解决方案)
BFC的基本概念
块级格式化上下文
BFC的原理
- 在BFC这个元素的垂直方向的边距会发生重叠
- BFC的区域不会与浮动元素的box重叠(用来清除浮动和布局的)
- BFC在页面上是一个独立的容器,它外边的元素不会影响它里边的元素,里边的元素也不会影响它外边的元素
- 计算BFC高度的时候,浮动元素也不参与计算
如何创建BFC
- 浮动元素:float除none以外的值
- 定位元素:position除static、relative以外的值(absolute、fixed)
- display为inline-block、table-cells(跟table相关的那几个)、flex
- overflow除visible以外的值(hidden、auto、scroll)
4、DOM事件
DOM事件的级别
准确来说是DOM标准定义的级别。element.addEventListener('click', function(){}, false)其中function是响应函数,后面可以是false也可以是true,它其实是指定了冒泡还是捕获。DOM3其实还是DOM2这种事件的定义方式,只不过事件类型增加了很多,比如说鼠标事件、键盘事件等等。
DOM事件模型
事件模型是捕获和冒泡,捕获是从上往下,冒泡是从当前元素也就是目标元素往上,这是两个过程
DOM事件流
浏览器在当前页面与用户做交互的过程中,比如说点击鼠标左键,这个左键是怎么传到这个页面上,这就是事件流,它又是怎么响应的,一个完整的事件流分3个阶段,第一阶段是捕获,第二阶段也就是目标阶段,比如说我点了当前这个按钮,这个按钮就是目标阶段,也就是说事件通过捕获到达目标元素,这个时候就是目标阶段,第三阶段就是从目标元素再上传到window对象,也就是冒泡的过程
描述DOM事件捕获/冒泡的具体流程
第一个接收到事件的对象是window,window接收完以后给document,JS中关于BOM对象,两个最常用的,一个是window,一个是document,这两个是非常重要的对象,在事件捕获中也是它两优先得到的,第三个才是html标签(怎么用JS表示当前这个HTML节点?document.documentElement专门表示节点的对象),html完了以后就是body了,body完了以后就是按照普通的html结构一层一层往下传,比如说父级元素,然后是子级元素,最后到达目标元素。冒泡的具体流程,就是从目标元素,一层一层往上传,最后到window,完成了一次冒泡的流程。
Event对象的常见应用
自定义事件
5、HTTP协议
HTTP协议的主要特点
- 无连接(我连接一次,它就会断掉,不会保持连接)
- 无状态(客户端和服务端是两种身份,客户端向服务端请求一个图片,HTTP协议帮你建立连接,帮你中间传输,任务完成以后,连接就要断开了,下次客户端再过来,服务端是没法区分上次和这次连接是不是同一个人同一个身份,服务端单从HTTP协议上是不能区分两次连接者的身份的,这就是无状态)
- 简单快速(每个资源url,一个图片或者页面地址要统一资源符,这是固定的,所以在HTTP协议处理起来也是非常简单的,想访问某个资源只需输入这个url就可以了)
- 灵活(在每一个HTTP协议中有一个头部分会有一个数据类型,通过一个HTTP协议就可以完成不同数据类型的传输,所以是比较灵活的)
HTTP报文的组成部分
请求行包含什么
HTTP方法、页面地址、HTTP协议以及版本
请求头是什么
就是一些key、value值,来告诉服务端我要哪些内容,要注意什么类型
空行是什么
空行是告诉服务端往下就是按照请求体的内容解析了,而不是请求头了
请求头下面是空行和请求体,没显示出来
响应头下面是空行和响应体(文档部分),没显示出来
HTTP方法
GET:请求指定的页面信息,并返回实体主体。POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和已有资源的修改。HEAD:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头。PUT:从客户端向服务器传送的数据取代指定的文档的内容。DELETE:请求服务器删除指定的页面。
GET和POST的区别
红色的必须记住
HTTP状态码
状态码分类:
- 1XX- 信息型,服务器收到请求,需要请求者继续操作。
- 2XX- 成功型,请求成功收到,理解并处理。
- 3XX - 重定向,需要进一步的操作以完成请求。
- 4XX - 客户端错误,请求包含语法错误或无法完成请求。
- 5XX - 服务器错误,服务器在处理请求的过程中发生了错误。
常见状态码:
- 200 OK - 客户端请求成功301 - 资源(网页等)被永久转移到其它URL
- 302 - 临时跳转
- 400 Bad Request - 客户端请求有语法错误,不能被服务器所理解
- 401 Unauthorized - 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
- 404 - 请求资源不存在,可能是输入了错误的URL
- 500 - 服务器内部发生了不可预期的错误
- 503 Server Unavailable - 服务器当前不能处理客户端的请求,一段时间后可能恢复正常
持久连接
只有HTTP1.1版本才支持,HTTP1.0版本不支持,面试要答出这个和持久连接与非持久连接之间的区别
HTTP协议的缺点
- 请求信息明文传输,容易被窃听截取。数据的完整性未校验,容易被篡改没有验证对方身份,存在冒充危险
- HTTP协议不适合传输一些敏感信息,比如:各种账号、密码等信息,使用http协议传输隐私信息非常不安全
什么是HTTPS
HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):一般理解为HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密
HTTPS协议的优点
相比于http,https可以提供更加优质保密的信息,保证了用户数据的安全性,此外https同时也一定程度上保护了服务端,使用恶意攻击和伪装数据的成本大大提高。
HTTPS协议的缺点
HTTPS协议多次握手,导致页面的加载时间延长近50%;HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗;申请SSL证书需要钱,功能越强大的证书费用越高。SSL涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大。
HTTP和HTTPS的区别
- https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
CA证书是什么
CA证书就是CA机构颁发的数字证书,CA机构就是电子商务认证授权机构,是负责发放和管理数字证书的权威机构,是具有权威性和公正性的第三方信任机构,承担公钥体系中公钥的合法性检验的责任。
HTTP1.0 和 HTTP1.1 区别
- 缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
- 带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
- 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
- Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
- 长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点
HTTP/1.x现在存在的一些问题
- HTTP1.x 在传输数据时,每次都需要重新建立连接,增加了大量的延迟时间
- HTTP1.x 在传输数据时,所有的传输内容都是明文的,客户端和服务端无法验证对方的身份,在一定程度上无法保证数据的安全性
- HTTP1.x 在使用时,header里携带的内容过大,增加了传输的成本,在移动端增加用户流量
- HTTP1.x 虽然支持了keep-alive, 来减少多次创建连接产生的延迟,但是keep-alive 使用多了也会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务,因为文件被请求之后还保持了不必要的连接时间,keep-alive可能会极大的影响服务器的性能
- 虽然HTTP1.x可以让客户端向服务器并行发送多个请求,而且服务器也可以并行处理多个请求,但是HTTP/1.x 有严格的串行返回响应机制,通过 TCP 连接返回响应时,必须一个接一个,前一个响应没有完成,下一个响应就不能返回,如果第一个响应时间很长,那么后面的响应处理完了也无法发送,只能被缓存起来,占用服务器内存
HTTP2.0新特性
- 二进制分帧层
- 多向请求与响应
- 优先级和依赖性
- 首部压缩
- 服务器推送
HTTP2.0优点
-
多路复用(MultiPlexing):一次TCP握手,多个同域并行请求,请求和响应同时发送接受,然后再拼装组合,不阻塞;一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面
-
header压缩 : HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小;
-
优先级和依赖性(Priority):可以请求的时候告知服务器端,资源分配权重,优先加载重要资源;
-
服务器推送(Server Push):根据客户端需求,服务端主动推送资源,减少请求耗时;
Http2和Http1.X的区别
(1)HTTP2使用的是二进制传送,HTTP1.X是文本(字符串)传送。
二进制传送的单位是帧和流。帧组成了流,同时流还有流ID标示
(2)HTTP2支持多路复用
因为有流ID,所以通过同一个http请求实现多个http请求传输变成了可能,可以通过流ID来标示究竟是哪个流从而定位到是哪个http请求
(3)HTTP2头部压缩
HTTP2通过gzip和compress压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引Id就行,通过索引ID查询表头的值
(4)HTTP2支持服务器推送
HTTP2支持在未经客户端许可的情况下,主动向客户端推送内容
HTTPS什么时候用的对称加密,什么时候用的非对称加密
HTTPS在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。
6、原型和原型链
创建对象有几种方法
原型、构造函数、实例、原型链
构造函数M,它的实例o3,这些都为true,M.prototype.constructor === M;o3.__proto__ === M.prototype;
从一个实例对象往上找构造这个实例的相关联的对象,这个关联的对象再往上找,它又有创造它的上一级的原型对象,以此类推,一直到Object.prototype终止,这个链条就断了,也就是说Object.prototype属性是整个原型链的顶端,原型链是通过prototype这个原型和__proto__属性完成原型链的查找;
当我有多个实例的时候,有相同方法的时候,可以存到共同的东西上,共同的东西就是原型对象,通过原型链的方式找到原型对象,原型对象的方法是被不同的实例所共有的,这是原型链的一个工作原理;
按照JS引擎的分析方式,在访问一个实例的时候,实例有什么方法,在这个实例上没有找到这个方法和属性的话,它就会往它的上一级原型对象上找,如果在它的上一级原型对象还没有找到这个属性和方法,它会在它原型对象的基础上再通过原型对象这个__proto__再往上一级查找,以此类推,直到找到Object.prototype,如果到那个地方还没有找到,原路返回,告诉它这个方法或属性没有找到没有定义,如果在中间找到了,它就停止向上查找,直接返回这个方法的用处;
函数才有prototype,对象是没有prototype的;只有实例对象才有__proto__,函数也有__proto__,因为函数既是函数,也是一个对象(函数.__proto__ === Function.prototype返回true),说明了函数M的构造函数是Function(把M看成一个实例),可以理解为普通函数M是Function这个构造函数的一个实例;
实例明明是被构造函数生成的,那它是怎么跟这个原型对象产生关联的,也就是构造函数的prototype这个属性,也就是说你修改这个属性,也就修改了实例上一级的原型对象;
当我们创建一个函数的时候,每个函数会自动生成一个原型(prototype)属性;在函数中就只有这一个原型属性,而这个属性是一个指针,指向一个对象,称为原型对象,原型对象中含有一个constructor属性,通过这个属性又可指回函数。当我们向函数中添加属性时,实际上添加到了原型对象之中,当我们用new操作符创建新实例时,这个新实例是可以共享原型对象中的属性的。当我们向新实例中添加属性时,属性被保存到了新实例中,当向新实例中添加和原型中一模一样的属性时,这个属性会覆写原型中的属性
instanceof原理
7、JS面向对象
类的声明
类的声明有2种方式,一种是传统的用构造函数来模拟一个类的方式,第二种就是ES6中对class的声明。ES6中有增加了对类的声明,就是class这个语法,后面就是类名,constructor就是构造函数,构造函数里面就可以写它这个属性了,跟ES5中的写法是相同的
实例化类的对象
通过new就可以实例化一个类,虽然声明方式不一样,但是实例化的方式是一样的,如果构造函数没有参数的话,new Animal()的括号是可以不要的
如何实现继承
继承的本质就是原型链,第一种方式是借助构造函数实现继承,第二种方式是借助原型链实现继承,第三种方式是前两种的组合方式
第一种方式:
主要原理就在Parent1.call(this);这句话上(没有这句话实现不了继承):将父级构造函数那个类指向子构造函数的实例上去,导致你看到的父级构造函数的所有属性在子级中也有就是这个原理
缺点:Parent1的原型链上的东西并没有被Child1继承,这种方式只是实现了部分继承,如果父类的属性都在构造函数里面,那么完全可以实现继承,但如果父类的原型对象上还有方法的话,那么子类是拿不到这些方法的
第二种方式:
每个函数都有一个prototype属性,这个属性它是一个对象,这个对象是可以任意赋值的,现在赋值给父类一个实例对象,而上面两个红框内容是等同的,所以new Child2().__proto__引用的就是父类Parent2的一个实例对象。按照类似于作用域的寻找方式,先找new Child2()上面的属性,比如找name,通过Child2实例化的属性上没有name,然后这个作用域开始找__proto__属性,这个属性是一个对象,这个属性又被赋值为父类的一个实例,所以这个属性也就找到了父类的一个对象,父类的对象上已经有name了,所以这个对象也就能拿到这个name,也就实现了继承。这种方式完全就是原型链。new Child2().__proto__.name返回"parent2"
缺点:在一个类上我们实例了两个对象,修改第一个对象的属性play,第二个对象也跟着改变了,这个不是想要的效果。这个缺点造成的原因就是因为原型链中的原型对象它俩是共用的,因为s1.__proto__和s2.__proto__它俩引用的是同一个对象,也就是父类的实例对象,而那个父类方法是在它的实例对象上,而且是引用类型,所以,当你改变s1这个原型链对象的时候,改的就是s2原型对象的值,所以它俩会同时跟着变,同理,你改了s2,s1也会跟着变
第三种方式-组合方式:
通过组合方式轻松地实现了继承,把构造函数和原型它们的优点都结合起来,把它们的不足也弥补了,这是实现继承最通用的方式,但也有不足
缺点:我们在写子类的原型链的时候,就是实例化了这个父类,我们在实例化子类的时候,父类的构造函数在上面两处地方都执行了一次,也就是父类的构造函数执行了两次,然而这两次没有必要,因为构造函数函数体内的内容会自动执行,所以没有必要再把父类的实例对象再放到原型对象上去,因为在子类的构造函数里面已经有了这块内容
组合继承优化1:
通过两个构造函数组合能拿到所有的构造函数体里面的属性和方法,既然是想继承父类的原型对象,所以直接赋值给我当前这个原型对象就行了,这样的话就可以把父级的构造函数体内的属性和方法拿到,还能把它原型对象拿到,完成继承。这种方式父类的构造函数只执行了一次,也就是在实例化子类的时候执行一次,原型对象上只是简单的引用,Parent4.prototype是引用类型,所以它不会再执行父级的构造函数。
缺点:s5.constructor指向了父类,这不是想要的效果,prototype里面有一个属性constructor,而子类的原型对象和父类的原型对象是一个对象,这个对象的constructor就是父类的constructor,指向的当然是Parent4自己了,所以这就是问题的根源所在。将子类的原型对象和父类的原型对象划等号,也就是引用同一个对象,说白了,它俩是一个对象,它俩的构造函数指向的是一个,也就是constructor是一个,所以无法区分这个实例是由父类创造的还是由子类创造的
组合继承优化2:
这是组合最完美的方式。通过创建中间对象的方法,就把两个原型对象区分开,中间对象还具备的一个特性就是,它的原型对象是父类的原型对象,这样的话在原型链上又连起来了通过给Child5的原型对象的constructor做修改,就能正常地区分父类和子类的构造函数了。Object.create创建的对象原型对象就是参数Parent5.prototype。Child5的实例的原型对象等于Child5.prototype,它又等于Object.create这个方法创建的对象,这个中间对象原型对象又是父类的原型对象,一级的上一级的上一级,通过这样找就实现了继承,而且还达到了父类和子类原型对象的隔离,这就是原理。这时候Child5实例的constructor还是没有自己的constructor,它要找的话肯定也到原型对象找,到原型对象找的话contructor还是指向Parent5,作用域通过原型链向上找的特性,所以要覆盖,要给Child5的原型对象写一个自己的constructor,Child5.prototype.constructor = Child5
8、通信类
什么是同源策略及限制
比如http是协议,www.imooc.com是域名,默认的端口是80(如果没指定端口的话,默认的端口是80),这三个构成的叫一个源,三个中有一个不一样,就是源不一样,就是跨域了。
限制:不是一个源的文档,你没有权利去操作另一个源的文档,主要限制的上面几个方面,无法获取和操作另一个资源的DOM,任何DOM都拿不到,有限制。Ajax只适合同源的通信,跨域就不行了。
前后端如何通信
- Ajax
- WebSocket
- CORS
Ajax是同源的通信方式,跨域就不行了。WebSocket不受同源策略的限制。CORS既支持跨域通信也支持同源通信
如何创建Ajax
跨域通信的几种方式
JSONP
JSONP的原理是什么,怎么实现的
通过script标签的异步加载来实现的