Ajax、Es6面试题

345 阅读17分钟

Ajax

1、Ajax的原理是什么

  • 实现原理:
    • 其核心是 XMLHttpRequest对象
    • IE浏览器使用ActiveXObject
  • 常用属性:
    • onreadystatechange属性
      • 存有服务器响应的函数
xhr.onreadystatechange=function(){
    // 可以在这里做些操作
}
  • readyState属性
    • 存储服务器响应的状态信息,每当readyState改变时,onreadystatechange函数就会被执行,readyState属性可能有的值image.png
    • 向这个onreadystatechange函数中添加一个判断语句,测试响应是否成功
xhr.onreadystatechange=function(){
      // 可以在这里做些操作
      if(xhr.readyState===4){
          // 从服务的response获得数据
      }
  }
  • responseText
    • 可以通过responseText属性来获取服务器返回的数据
xhr.onreadystatechange=function(){
    // 可以在这里做些操作
    if(xhr.readyState===4){
        // 从服务的response获得数据
        document.myForm.time.value= xhr.reponseText
    }
}
  • 其它属性
    image.png
  • 常用方法:
    • open()方法
      • open()有三个参数,第一个参数定义发送请求使用的方法第二个参数规定服务器端脚本的URL,,第三个参数规定应对当前请求进行异步处理
xhr.opne('GET',test.php,true) // ture代表异步
  • send()方法
    • send()方法将请求发送服务器,假设HTML文件,和PHP文件位于相同目录,那么代码是以下形式
xhr.send(null)
  • 其它方法
  • image.png
  • 发送一个ajax-get请求
    • 创建XMLHttpRequest对象
    • 设置请求方式
    • 调用回调函数
    • 发送请求
// 1- 创建对象
let xhr =new  XMLHttpRequest()
var url = `http://location:8080/ajaxGet/login`
// 2- 设置请求参数
xhr.opne('GET',url,true)
// 3- 发送请求
xhr.send()
// 4- 注册事件
xhr.onreadystatechange=function(){
    if(xhr.readyState===4 && xhr.status===200){ 
            var obj = document.getElementById('id')
            obj.innerHTML= xml.responseText 
    } else{
            alert('ajax服务器返回错误')
        }
}
  • 发送一个ajax-post请求
    • 发送post请求,需要添加上请求头,否则会报错
// 1- 创建对象
let xhr =new  XMLHttpRequest()
var url = `http://location:8080/ajaxGet/login`
//2- post请求一定要添加请求头才行不然会报错
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
// 3- 设置请求参数
xhr.open('PSOT',url,true)
// 4- 发送请求
xhr.send()
// 5- 注册事件
xhr.onreadystatechange=function(){
    if(xhr.readyState===4 && xhr.status === 200){
            // 响应成功,做些什么
            var obj = document.getElementById('id')
            obj.innerHTML= xml.responseText 
    }  else{
            alert('ajax服务器返回错误')
        }
}

2、Get和post请求方式的区别有哪些

  • 用处
    • get常用于获取,post用于提交数据
  • 安全性
    • get比post相对安全
    • get直接把参数暴露在Url地址栏后,post是把请求参数放到请求体中,get参数直接暴露,浏览器会缓存,如果把密码和用户名缓存,那么就会造成泄漏,导致不安全
  • 请求参数
    • querystring是url的一部分,get,post都可以带上,get的querystring(仅支持urlencode编码),post参数,是放在Body(支持多种编码)
  • 请求参数长度限制
    • get请求长度最多1024kb,post请求对数据没有限制
    • 在http规范中,并没有对get请求方式进行大小限制,但是浏览器不同,对url长度有限制,对于Post理论上不受限制的
  • TCP数据包
    • get请求会产生一个TCP数据包,post请求会产生两个TCP数据包

3、请解释一下 JavaScript 的同源策略

  • 含义
    • 同源策略指的是,一下三者必须要统一,一者不同,就产生跨域
      • 域名
      • 协议
      • 端口image.png
  • 目的
    • 同源策略,为了保证用户信息安全,防止恶意网站窃取数据,假设用户在A网站登录,然后去其它网站浏览,但是B网站,可以获取A网站的cookie,那么此时就会造成用户信息泄露
  • 限制
    • cookie,LocalStorage和indexDB无法获取
    • DOM无法获得
    • Ajax请求不能发送
  • 规避限制
    • cookie
      • cookie是服务器写入浏览器的一段信息,只有同源的网页才能共享,但是两个网页一级域名相同,只是二级域名不同,浏览器允许设置document.domain共享cookie信息
      • 两个网页不同源: 无法获取对方的DOM,使用Iframewindow.opne方法打开窗口
    • LocalStorage
      • 通过postMessage()方法,允许跨窗口通信,无论是否两个是不是同源,该方法两个参数,参数1具体信息内容,参数2接收窗口的源(协议+域名+端口)也可以设置为* 表示不受限制,向所有窗口发送
    • ajax
      • jsonp
      • cors

4、解决跨域的方式有哪些

  • jsonp
    • 基本定义
      • 通过添加一个script元素,向服务器请求json数据,这种做法不受同源策略限制,利用的是script中的src属性,服务器收到请求以后,将数据放到一个指定名字的回调函数中,传递回来
    • 具体实现
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
    
 //=============________服务器端代码
// 服务器收到请求后,会把数据放到这个函数的参数位置上    
foo({
  "ip": "8.8.8.8"
});
  • 缺点
    • 只能发送Get的请求方式,无法处理Post请求方式
    • 只支持跨域HTTP请求的清空,不能解决不同域的两个页面之间进行js调用的问题
    • jsonp在调用失败的时候,不会返回http状态码
    • 存在一定的安全漏洞
  • Cors
    • 基本定义
      • Cors是一个wc3认定的一个标准,跨域资源共享(Cross-origin resource sharing)解决了ajax只能同源使用的限制
      • 目前浏览器和服务器都支持该种方式,浏览器版本不能低于IE10
    • 实现方式(Cors分为简单请求和复杂请求)
      • 简单请求方式,只要满足一下两大条件就可以image.png
      • 简单请求,只需要添加一个origin字段即可,浏览器发现是一个简单请求,就自动会在请求头中添加一个origin字段
GET /cors HTTP/1.1
// origin字段用来说明,本次请求,来自哪个源(协议+域名+端口) 服务器根据这个值,决定是否同意这次请求
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
  - 如果`origin`指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应,浏览器发现这个回应头信息没有包含`Access-Control-Allow-Origin`字段,会抛出一个错误,被`XMLHttpRequest``onerror`回调函数捕获,这种错误,是无法通过状态码识别的,如果`Origin`指定的域名在许可范围内,服务器会返回响应,多出几个头信息字段
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
// 该请求头中,有三个与Cors请求相关的字段,都按照Access-Contronl开头
  - Access-Contronl-Allow-Origin
     - 该字段是必须的,它的值要么是请求时`origin`字段,要么是一个`*`,表示接受任意域名的请求,如果发送cookie,`Access-Contronl-Allow-Origin`不能设置为星号,必须指定明确的,与请求网页一致的域名,同时cookie依然遵循同源政策,只有服务器域名设置的cookie才会上传,其它域名的cookie并不会上传,且(跨域)原网页代码中的`document.cookie`也无法读取服务器域名下的cookie信息
  - Access-Control-Allow-Credentials
     - 该字段是一个可选字段,表示是否允许发送Cookie,默认情况下,Cookir不包含在Cors的请求之中,true代表服务器明确许可,Cookie可以包含在请求中,是一个单选项,如果不设置true,那么删除该字段即可
  - Access-Control-Ecpose-Headers
     - 该字段可选。CORS请求时,`XMLHttpRequest`对象的`getResponseHeader()`方法只能拿到6个基本字段:`Cache-Control``Content-Language``Content-Type``Expires``Last-Modified``Pragma`。如果想拿到其他字段,就必须在`Access-Control-Expose-Headers`里面指定。上面的例子指定,`getResponseHeader('FooBar')`可以返回`FooBar`字段的值
  - withCredentials属性
     - Cors默认是不会发送Cookie信息,和http认证信息,如果要报Cookie发送到服务器,一方面要服务器统一,指定`Access-Control-Allow-Credentials`字段
Access-Control-Allow-Credentials: true
     - 另一方面,开发者必须在Ajax请求中打开`withCredentials`属性,否则即使服务器统一发送cookie,浏览器也不会进行发送,如何省略`withCredentials`字段,有的浏览器还是会发送cookie信息,这个时候,可以显示的关闭`xhr.withCredentials = false;`
var xhr = new XMLHttpRequest()
xhr.withCredentials= true
  - **非简单请求**
     - 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是`PUT``DELETE`,或者`Content-Type`字段的类型是`application/json`,非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight),浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段,只有得到肯定答复,浏览器才会发出正式的`XMLHttpRequest`请求,否则就报错
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
     - 上面代码中,HTTP的请求方式,是一个PUT,并且发送一个自定义头信息X-Custom-Header,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
     - "预检"请求用的请求方法是`OPTIONS`,表示这个请求是用来询问的。头信息里面,关键字段是`Origin`,表示请求来自哪个源,除了`Origin`字段,"预检"请求头中的信息包含两个特殊的字段
     - Access-Control-Request-Method
        - 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是`PUT`     - Access-Control-Request-Headers
        - 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是`X-Custom-Header`
     - 服务器通过了预检请求,每个浏览器都正常的CORS请求,就跟每个简单的请求一样,会产生一个`Origin`字段,服务器也会回应一个`Access-Control-Allow-Origin`头信息字段
// 预检 请求之后,浏览器的正常Cors请求, Origin字段的信息,是自动添加的
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
     - 服务器的正常反应('Access-Control-Allow-Origin'该字段每次回应都是必须包含的)
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

5、什么是同步什么是异步

  • 单线程
    • JavaScript是一门单线程语言,因此,JavaScript在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务,因为javaScript是一个单线程执行过程,同时间内处理多个任务,所有任务都会排队,前一个任务执行完毕,下一个任务才会执行,如果上一个任务执行时间很长(比如ajax请求),那么就会造成下一个任务等待,严重影响用户体验,所以才会有同步任务异步任务
// 同步代码
function fun1() {
  console.log(1);
}
function fun2() {
  console.log(2);
}
fun1();
fun2();
// 输出
1
2
// 如果fun1中是一个ajax操作,需要一定的时间,那么我们需要完全等待fun1()执行完毕后,才能执行fun2()吗?
  • image.png
  • 同步任务
    • 同步任务是指,主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,就是一个同步任务
// 同步代码
var num = 20;
console.log(num) // 马上输出结果

// 同步代码
var arr = [12,3224,34]
for(var i =0; i<arr.length;i++){
    console.log(arr[i])
}
// 同步代码.........还有非常多,除了callback() ajax和setTimeout以及Proimse中的.then()方法,都可以视为同步代码
  • 异步任务
    • 异步任务是指, 不进入主线程,而是进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,例如图片的加载,音乐的加载,就是一个异步任务
function fun1() {
  console.log(1);
}
function fun2() {
  console.log(2);
}
function fun3() {
  console.log(3);
}
fun1();
setTimeout(function(){
  fun2();
},0);
fun3();
 
// 输出
1
3
2
  • 异步机制实现原理(EventLoop是一码事)
    • javaScript中的异步如何实现的,需要知道回调和事件循环,异步任务是不会进入主线程中,会先进入到异步任务队列,任务队列其实就是一个遵循先进后出,后进先出的数据结构,也是一个事件队列,例如文件读取操作,是一个异步任务,该任务会先被添加到异步任务队列中,等到IO完成,就会在任务队列中,添加一个事件,表示异步任务已经完毕,可以进入执行栈了,但是此时,主线程未必有空处理,当主线程处理完其它任务后,才会读取异步任务队列中的代码,单线程从任务队列中获取任务,这个过程是不断循环的,每次执行栈清空以后,都会在任务队列中读取,如果没有任务,等到有新任务,如果存在任务,那么就会拿出来进行执行,这个循环的过程,就叫事件循环
    • 总结来说
      • 所有同步任务都在主线程上执行,行成一个执行栈
      • 主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
      • 一旦执行栈中所有异步任务执行完毕,系统就会读取任务队列,查看是否还存在异步任务,结束等待状态,进入执行栈,开始执行
      • 主线程不断重复以上三步

6、什么是HTTP协议  HTTP和HTTPS的区别(拓展需要掌握)

  • 什么是HTTP协议
    • 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础
    • HHTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)我们称这个客户端为用户代理程序(user agent)。应答的服务器上存储着一些资源,比如HTML文件和图像。我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道(tunnel)
    • HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口)的TCP连接。HTTP服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息
  • HTTP和HTTPS的区别
    • HTTP协议传输的数据都是未加密的,明文显示,因为使用HTTP传输的隐私信息,非常的不安全,为了保证信息能够加密进行传输,所以在HTTP基础上设计了SSL(Secure Sokets Layer)协议用于HTTP协议传输的数据进行加密处理,从而就诞生了HTTPS
    • HTTPS协议是有SSL+HTTP协议构建的可进行加密传输,身份认证的网络协议,比HTTP协议要安全
    • 区别如下
      • HTTPS协议需要到CA申请证书,一般免费证书较少,需要一定费用
      • HTTP是超文本传输协议,信息是明文显示,但是HTTPS具有安全性的SSL协议加密传输
      • HTTP和HTTPS使用完全不同的连接方式,默认端口也不一样,前者是80后者是443
      • HTTP的连接也很简单,是无状态的,HHTPS协议是有SSL+HTTP协议构建可进行加密传输,以身份认证的网络协议,比HTTP协议,更加安全

7、常见的HTTP状态码有哪些,分别代表什么含义

  • 状态码种类
    • 1xx:指示信息--表示请求已接收,继续处理
    • 2xx:成功--表示请求已被成功接收、理解、接受
    • 3xx:重定向--要完成请求必须进行更进一步的操作
    • 4xx:客户端错误--请求有语法错误或请求无法实现
    • 5xx:服务器端错误--服务器未能实现合法的请求
  • 常见状态码及含义
200 OK                        	//客户端请求成功
400 Bad Request             //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized            //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 
403 Forbidden                 //服务器收到请求,但是拒绝提供服务
404 Not Found                //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error  //服务器发生不可预期的错误
503 Server Unavailable     //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

8、什么是TCP连接的三次握手(拓展需要掌握)

  • 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)
  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手

ES6

1、Array拓展

  • 扩展运算符
  • Arrar.from()
  • Arrar.of()
  • 实例copyWithin()
  • 实例find()和findIndex()
  • 实例fill()
  • 实例entrens()keys()values()
  • 实例inculecs()
  • 实例flat(),flatMap()

2、String拓展&方法

  • for of遍历
    • 可以使用下方的语法,进行遍历,同时,for of 可以遍历oxFFF的码点
for (let codeString of  'foo'){
    console.log(codeString)
}
// "f"
// "o"
// "o"
  • 模板字符串(template string)
    • 是一种增强版本的字符串,使用反引号(` )进行标识,可以当做普通的字符串使用,也可以定义多个字符串,同时也可以在字符串中嵌入变量
$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);
  • includes()
    • 返回一个布尔值,表示是否找到了参数字符串
let s = 'Hello world!';
s.includes('o') // true
// 第二个参数,代表查询起始位置
s.includes('o',6) // false
  • startsWith()
    • 返回布尔值,表示参数字符串是否在原字符串头部
let s = 'Hello Word!';
s.startsWith('Hello') // true
// 第二个参数,代表查询起始位置
s.endsWith('Hello', 5) // true
  • endsWith()
    • 返回布尔值,表示参数字符串是否在原字符串的尾部
let s = 'Hello world!';
// 第二个参数,针对前n个参数,其它两个方法,针对的是从第n个位置知道字符结束
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
  • repeat()
    • 返回新的字符串,表示将原字符串重复多少次
'x'.repeat(3) // "xxx"
'hello'.repeat(2)// "hellohello"

// 参数如果是小数,会被取整
'na'.repeat(2.9) // "nana"

// 如果repeat的参数是负数,或lfinity,会报错
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError

// 但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0
'na'.repeat(-0.9) // ""

// 参数是NaN等于与0
'na'.repeat(NaN) // ""

// 如果repeat的参数是字符串,则会先转换成数字
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

3、Obejct拓展&方法

  • 属性和方法简写
    • 属性简写
let obj = 'is object';
let objName = {obj}
objName // {obj:'is object'}

// 等价于
const objName = {obj:obj}
//  ***********************************************分割线*****************************************
function fn (x,y){
    return {x+y}
}
// 等同于
function fn (x,y){
    return {x:x,y:y}
}
fn(1,2) // Object {x:1,x:2}
  • 方法简写
const Person={
    sayHi(){
        console.log('我是Person对象中的sayHi方法')
    }
}

// 等价于
const Person={
    sayHi:function(){
        console.log('我是Person对象中的sayHi方法')
    }
}
//  ***********************************************分割线*****************************************
let url = 'htpps://www.baidu.com'
const ToUrl ={
    url, 
    erros: (){ console.log('url is errors')}
}

//等价于
let url = 'htpps://www.baidu.com'
const ToUrl ={
     // 等同于: url:url
    url,
    // 等价于:  erros:function() {}
    erros:  (){ console.log('url is errors')} 
}
  - 应用场景1(`commonJS中就可以采用这种方式`)
let ms = {};

function getItem (key) {
  return key in ms ? ms[key] : null;
}

function setItem (key, value) {
  ms[key] = value;
}

function clear () {
  ms = {};
}

module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
  getItem: getItem,
  setItem: setItem,
  clear: clear
};
  • 应用场景2(属性赋值器(getter)和属性取值期(setter)也可以采用此种方式)
const cart = {
  _wheels: 4,

  get wheels () {
    return this._wheels;
  },

  set wheels (value) {
    if (value < this._wheels) {
      throw new Error('数值太小了!');
    }
    this._wheels = value;
  }
}
  • 注意事项(不能在构造函数中使用简写的方式)
const obj = {
  f() {
    this.foo = 'bar';
  }
};

new obj.f() // 报错
// 因为f简写的是对象的方法,所以foo.f不能当做构造函数使用
  • 属性可枚举型
    • 对象的每个属性都有一个描述对象,用来控制该属性的行为,obj.geyOwnPropertyDescriptor方法,可以获取该属性的描述对象,描述对象的eumerable属性,称为可枚举性,如果该属性为false,就表示某些操作会忽略当前属性
let obj = { foo : 123}
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }
  • 属性的遍历
    • for in
      • for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
    • Object.keys
      • Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名
    • Object.getOwnPropertyNames(obj)
      • Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
    • Object.getOwnPropertySymbols(obj)
      • Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名
    • Reflect.ownKeys(obj)
      • Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
    • 注意事项:
      • 以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则
      • 首先遍历所有数值键,按照数值升序排列
      • 其次遍历所有字符串键,按照加入时间升序排列
      • 最后遍历所有 Symbol 键,按照加入时间升序排列
  • 对象的拓展运算符
    • 对象的拓展运算符,用于取出参数对象的所有可遍历属性,拷贝到当前对象之中
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
  • 由于数组是特殊的对象,所以对象的拓展运算符,也可以用于数组
let foo = {...['a','b','c']}
foo
//  {0: "a", 1: "b", 2: "c"}
  • 如果拓展运算符后面是一个空对象,那么不会有任何效果
{...{}, a: 1}
// { a: 1 }
  • 如果拓展运算符后面不是一个对象,那么会自动转换为对象
// 等同于 {...Object(1)}
{...1} // {}
  • 上面代码中,拓展运算符后面都是整数1,会自动转换为数值类型,由于该对象没有该属性,所以最后返回一个空对象
// 等同于 {...Object(true)}
{...true} // {}

// 等同于 {...Object(undefined)}
{...undefined} // {}

// 等同于 {...Object(null)}
{...null} // {}
  • 如果运算符后面是一个字符串,会自动转换为一个类数组的对象,因此返回的不是一个空对象
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
  • 对象的拓展运算符等同于使用Object.assign()方法
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
// 上面只是拷贝了对象实例的属性,如果想完整的克隆一个对象,还拷贝对象的原型属性,可以采用以下方式
// 写法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 写法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 写法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

4、解构赋值

  • 对象的解构赋值
    • 对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可以遍历,但尚未被读取的属性,分配到指定的对象上面,所有的键和他们的值,都会被拷贝到新对象上面
    • 无法对undefinednull进行转换,因为这两个无法转换为对象
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误
  • 解构赋值必须是最后一个参数,否则会报错
 let{ ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
  • 解构赋值中的拷贝是浅拷贝
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

5、Set和Map数据结构

  • set
    • 类似于数组的形式,但是成员都是唯一的,没有重复的值,Set本身是一个构造函数,用来生成Set数据结构
const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4
  • 可以进行的遍历操作
    • Set.prototype.keys():返回键名的遍历器
    • Set.prototype.values():返回键值的遍历器
    • Set.prototype.entries():返回键值对的遍历器
    • Set.prototype.forEach():使用回调函数遍历每个成员
  • map
    • 本质上是键值对的结合(Hash结构),但是传统上只能用字符串当做键,这给其使用方式带来了很大的限制
const data = {};
const element = document.getElementById('myDiv');

data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
  • 遍历方法
    • Map 结构原生提供三个遍历器生成函数和一个遍历方法
    • Map.prototype.keys():返回键名的遍历器
    • Map.prototype.values():返回键值的遍历器
    • Map.prototype.entries():返回所有成员的遍历器
    • Map.prototype.forEach():遍历 Map 的所有成员