跳到主要内容

· 阅读需 11 分钟

01、前端存储:cookie、sessionStorage和localStorage的区别

相同点:

  1. cookie、sessionStorage和localStorage都属于浏览器本地存储;
  2. cookie、sessionStorage和localStorage存储遵循的同源策略。sessionStorage还限制是同一个页面。

区别是:

  1. cookie是由服务器端写入的,sessionStorage和localStorage是前端写入的;
  2. cookie的有效期,是在服务端设置好的。sessionStorage则是在页面关闭后自动清除,localStorage则可以长期保存,除非手动清除。
  3. cookie的存储空间比较小,每一个cookie大概是4kb。sessionStorage和localStorage存储空间比较大,大约为5M。
  4. 前端向后端发起请求的时候,会自动携带cookie。localStorage和sessionStorage则不会。

cookie是一般用于存储验证信息SessionID或者token;localStorage一般是用来存储一些不易变动的数据,这样减小服务器的压力。sessionStorage是用来检测用户是否是刷新进入页面的,比如音乐播放器进度条。

02、JavaScript数据类型

JavaScript数据类型分为两种:一种基本数据类型,一种是引用数据类型

基本数据类型:number、string、boolean、null、undefined、symbol和BigInt。 引用数据类型:object,包括了function、array、正则、date日期和Math数学函数。

基本数据类型和引用数据类型,它们在内存中的存储方式不同。基本数据类型是直接存储在栈内存中。引用数据类型是存储在堆内存中的,在栈内存中存储了指针,这个指针指向在堆内存中的实体。

基本数据类型中symbol是ES6新增的一种数据类型,它的特点就是不能有重复数据,可以用来作为对象的key。symbol创建的数据是唯一的,所以 Symbol() !== Symbol()。

BigInt数据类型,也是ES6新增,作用就是扩大了数据的范围,能够解决普通数据类型范围报错的问题。BigInt有两种使用方法:一种是直接在整数后面加n。另一种方式是调用BigInt构造函数。

03、对闭包的理解

函数和词法环境绑定在一起,这样的组合就是闭包,比如有一个函数A,它return一个函数B。函数B是可以访问到函数A内部定义的变量。函数A执行结束后,函数A中声明的变量并不会被销毁。

闭包的优点:让函数作用域中的变量不会因为函数执行结束而被销毁。也可以在外部访问到函数内部的局部变量; 闭包的缺点:垃圾回收器不会销毁闭包中的变量,这样就造成内存泄漏。

04、说一下Promise

Promise是实现异步的一种方式,解决了异步多层嵌套回调的问题,提升了代码的可读性,同时让我们所写的代码更利于维护。

Promise有三个状态:pendding、resolve和reject。在Promise的整个过程中,只发生一次状态转变。由pendding转为resolve或者reject。

Promise构造函数接收一个函数作为参数,这个函数有两个参数:resolve和reject,resolve和reject都是函数。

resolve函数的作用是把Promise由等待状态改为成功状态;reject是把Promise由等待状态转变为失败状态。

在Promise构造函数创建实例完成后,通过then函数来接收成功的回调函数,通过catch函数接受失败的回调函数,比如:

        const func = function (parma) {
return new Promise((resolve, reject) => {
if (parma > 2) {
resolve(parma)
}
reject(parma)
})
}

func(0).then(res => {
console.log("结果", res)
}).catch(err => console.log("报错", err))

Promise的特点:

  1. Promise的状态,不收到外界的影响,当Promise发生状态变化后,Promise的生命周期也就结束了。
  2. Promise只发生一次状态改变。
  3. resolve的参数是then方法中回调函数的参数;reject的参数是catch方法中回调函数的参数。

Promise的其他方法:

Promise.all()

把多个Promise包装成一个Promise对象,等参数里面所有的Promise都返回成功了,才触发成功,否则返回失败结果,比如:

        const func = new Promise((resolve, reject) => {
resolve(1)
})
const func2 = new Promise((resolve, reject) => {
reject(1)
})
const allPromise = Promise.all([func, func2])
allPromise.then(res => {
console.log("结果", res)
}).catch(err => {
console.log("报错", err)
})

这段代码的结果是失败状态:1

如果func2调用的resolve(1)的话,那么这段代码的结果为成功状态:[1,1]

Promise.any()

接收一个Promise对象集,只要有一个Promise返回成功,那么就返回这个Promise成功的值,比如:

        const func = new Promise((resolve, reject) => {
resolve(1)
})
const func2 = new Promise((resolve, reject) => {
reject(0)
})
const promise = Promise.any([func, func2])
promise.then(res => {
console.log("结果", res)
}).catch(err => {
console.log("报错", err)
})

这段代码的结果为:结果 1

Promise.race()

只要Promise集合里面,有一个子Promise返回成功或者失败,那么父Promise将子Promise的状态返回,然后结束Promise的生命周期。比如:

        const func = new Promise((resolve, reject) => {
reject(1)
})
const func2 = new Promise((resolve, reject) => {
resolve(0)
})
const promise = Promise.race([func, func2])
promise.then(res => {
console.log("结果", res)
}).catch(err => {
console.log("报错", err)
})

05、什么是跨域,怎么解决跨域

跨域:就是当前页的请求地址和当前页面的地址中,协议、域名、端口,其中一个不同,就造成了跨域。原因是浏览器为了保护网页安全做出的同原协议策略。

跨域的解决方式:

  1. cors;通过设置后端允许跨域访问
  2. node中间件、Nginx反向代理:跨域是浏览器限制不能跨域访问服务器,而node中间件和Nginx反向代理,是向代理服务器发起请求,代理服务器再向后端服务器发起请求。服务器和服务器之间不存在同源限制。

出现跨域的场景,一般是前后端分离开发、调用第三方接口。

06、什么是BFC

BFC是块级格式化上下文,是web页面中一个独立的渲染区域,内部元素的渲染不会影响到区域外面的其他元素。

BFC布局规则是: 内部元素会在垂直方向堆叠摆放,元素上下之间的距离,是由margin来决定的,而相邻的两元素的margin会发生重叠。

07、js判断数据类型的方式

JavaScript有3种方法判断数据类型:typeof、instanceof、Object.prototype.toString.call()。

typeof:用来判断基本数据类型的,如果使用typeof来判断引用数据类型的话,除了function会返回“function”,其他的都会返回“object”。

instanceof:用来区分引用数据类型、判断实例是否属于某一个构造函数。检测过程比较繁琐,而且对于undefined、null和symbol数据类型是无法检测的

Object.prototype.toString.call():可以用来检测所有的数据类型,返回的是该数据类型的字符串。

instanceof的原理是验证当前对象的原型prototype是不是出现在实例的原型链proto上,如果在,就返回true,否则返回false。

Object.prototype.toString.call()的原理是Object.prototype.toString表示一个返回对象数据类型的字符串,call()方法是改变this的指向,也就是把Object.prototype.toString()方法指向不同的数据类型上。

08、CSS样式优先级

在CSS样式中!important的优先级是最高的。其次是内联样式。

CSS选择器的优先级:id选择器 > (类选择器 | 伪类选择器|属性选择器) > (后代选择器 | 伪元素) > 子选择器或者相邻选择器 > 通配选择器。

09、JavaScript异步的方式

回调函数:是异步操作的基本方式,也是最简单,容易理解和实现的,比如常见的AJAX。但是回调函数不利于代码维护和阅读,代码结构混乱,流程难以追踪。

Promise、async/await

这三种是最常见的,实现异步操作的方式。

10、数组去重的方式

  1. 定义一个新数组,然后遍历数组过程中,每次判断新数组中是否存在元素,不存在的话,就将元素push进去。
  2. 利用Set数据类型的不重复特点,new一个Set,参数就是需要去重的数组,set会自动删除重复数据,然后将set转化为数组返回。
  3. reduce + includes:利用reduce遍历数组和传入一个空数组作为去重后的新数组,在内部再来判断新数组中是否存在当前元素,如果不存在,那么就将元素push进去。

· 阅读需 4 分钟

proxy代理,就是在目标对象的前面设置一个拦截层,外界在访问这个对象的时候,必须经过拦截层。 我们可以在拦截层做一些过滤或者是改写的操作。

const proxy = new Proxy({}, {
get: () => {
console.log("get");
},
set: () => {
console.log("set")
}
})

proxy.count = 1; // set
++proxy.count; // get

Proxy接受两个参数,第一个参数是拦截目标对象;第二个参数是设置拦截的操作,所谓的拦截,一般都是有一些操作行为的,如果在拦截层没有设置任何操作的话,就会直接访问目标对象。

Proxy支持的拦截操作

get(target,Prophet,receiver)

拦截对象的读取属性操作。

const proxy = new Proxy({ name: "duXin", count: 90 }, {
get: () => {
console.log("get");
return 100
},
})

console.log(proxy.name)

在访问目标对象的时候,无论是name属性还是count属性,都会返回100,因为在拦截层设置读取属性时都返回100。

set()

方法是设置属性值操作的捕获器。

const proxy_set = new Proxy({}, {
set: function (target, prop, value, receiver) {
target[prop] = value;
console.log('property set: ' + prop + ' = ' + value);
return true;
}
})

console.log("======proxy_set=======")
console.log('name' in proxy_set); // false
proxy_set.name = 'duXin'; // property set: name = duXin

console.log('name' in proxy_set); // true

has()

对in操作符的代理方法。

const targetObj = {
_secret: 'easily scared',
eyeCount: 4
};
const proxy_has = new Proxy(targetObj, {
has: (target, key) => {
console.log("key==",key)
if (key.includes('_')) {
return false;
}
return key in target;
}
})

console.log('eyeCount' in targetObj); // true
console.log('_secret' in proxy_has); // false

construct()

拦截new操作符,返回的是一个对象。

function proxy_construct_obj(disposition){
this.disposition = disposition;
}
const proxyConstruct = new Proxy(proxy_construct_obj, {
construct: function (target, args) {
return new target(...args);
}
})

console.log(new proxyConstruct("duXinYue").disposition)

apply()

拦截函数的调用。 语法:

var p = new Proxy(target, {
apply: function(target, thisArg, argumentsList) {
}
});

target:目标对象,也就是函数 thisArg:被调用时的上下文对象 argumentsList:被调用时的参数,是一个类数组。

声明一个函数:

function sum(a, b) {
return a + b;
}

const proxy_apply = new Proxy(sum, {
apply: (target,thisArg, argumentsList) => {
console.log("target",target,thisArg, argumentsList)
return target(argumentsList[0], argumentsList[1]) * 10;
}
})

console.log(proxy_apply(1,2)); //30

defineProperty()

拦截对象的 Object.defineProperty() 操作符。 这是语法: defineProperty: function(target, property, descriptor) {} target:目标对象, property:被检索的属性名 descriptor:待定义或者修改的属性的描述符

defineProperty的返回值必须是Boolean值,表示对该属性操作是否成功。

var desc = { configurable: true, enumerable: true, value: 10 };
var defineProperty = new Proxy(desc, {
defineProperty: function (target, prop, descriptor) {
console.log('called: ' + prop);
Reflect.defineProperty(target, prop, descriptor);
}
});

defineProperty.name = "908"
console.log("obj", defineProperty); // { configurable: true, enumerable: true, value: 10, name: '908' }

· 阅读需 4 分钟

防抖

防抖函数,就是防止抖动,避免事件重复触发。比如监听输入框的输入,不应该在用户每输入一个字符就触发监听,而是在用户输入结束后再来监听。

流程为: 1、事件触发; 2、开启定时器; 3、当事件再次触发的时候,就会清除上个定时器,然后重新开启新的定时器; 4、时间到了以后,就开始处理事件操作。

现在有一个输入框,代码如下:

import React from "react";
const Child2 = () => {
return <input onChange={({ target: { value } }) => {
console.log(value)
}} />
};

效果如下图:

在这里插入图片描述 每输入一个字符,就触发监听事件。如果是搜索查询的话,那就不合适了。

现在在监听事件上设置防抖:

import React from "react";
const Child2 = () => {
const inputChange = debounce(({ target: { value } }) => {
console.log(value)
})
return <input onChange={inputChange} />
};
function debounce(fn) {
let timeout = null;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, 1000);
};
}

如果在定时器时间内,事件再次触发,那么就清空之前的定时器,然后重新开启新的定时器。

效果图:

在这里插入图片描述 这就是防抖。

节流

节流就是控制事件触发的频率。比如按钮点击,在短时间内多次点击,那么只需要触发一次即可。

比如:

import React from "react";
const Child2 = () => {
return <button onClick={() => {
console.log("点击")
}}>点击</button>
};

连续点击多次,效果如下图,如果是涉及到网络请求的话,那么得不偿失了。 在这里插入图片描述

现在定义一下节流函数:

// 节流
function throttle(fn) {
let timeout = null;
return function () {
if (timeout) return;
timeout = setTimeout(() => {
fn.apply(this, arguments);
timeout = null;
}, 1000);
}
}

完整代码:

import React from "react";
const Child2 = () => {
const click = throttle(() => {
console.log("点击")
})
return <button onClick={click}>点击</button>
};

// 节流
function throttle(fn) {
let timeout = null;
return function () {
if (timeout) return;
timeout = setTimeout(() => {
fn.apply(this, arguments);
timeout = null;
}, 1000);
}
}

在一秒内连续点击多次,最后只有一次有效。

在这里插入图片描述

· 阅读需 5 分钟

一个优秀的产品,需要有一个良好的错误处理策略可以让用户知道到底发生什么。不至于让用户自己不知道发生了什么,然后再重试或者是不至于然用户感到特别厌烦,然后直接离开。

try/catch语句

try/catch语句,在JavaScript中作为处理异常的一种方式,基本语句如下:

try{
// 可能出错的代码
} catch(error){
// 出错的时候做什么
}

如果try块中有代码发生错误,就会立即退出执行,跳到catch块中,这时候catch模块接受到一个对象,该对象包含了发生错误的相关信息,比如:

try{
window.someNonexistenFunction();
} catch (error){
console.log(error.message);
}

这个message属性是唯一一个在IE、Firefox、Safari、Chrome和Opera中都有的属性,尽管每个浏览器添加了其他属性。IE添加了description属性(其值始终等于message)和number属性(它包含内部错误号)。Firefox添加了fileName、lineNumber和stack(包含栈跟踪信息)属性。Safari添加了line(行号)、sourceId(内部错误号)和sourceURL属性。同样,为保证跨浏览器兼容,最好只依赖message属性。

finally子句

在try/catch语句中,可选的finally语句始终运行,意思就是try模块中代码执行结束后,就执行finally模块中的代码。如果出错并执行catch中的代码,finally模块中代码依旧在运行。try或者catch不能阻止finally块的执行,包括return。比如:

function test(){
try{
return 2;
}catch(err){
return 1;
}finally{
return 0;
}
}
console.log(test()); // 0

在这段代码中,如果去掉finally模块,则返回2;如果写上finally语句,就不管什么情况都会返回2,那么catch就是可选项了。

总结:只要代码中包含了finally语句,那么try或者catch的return就会被忽略。

错误类型

代码在执行过程中,会发生各种各样的类型错误,每种类型都会对应一个错误发生时抛出的错误对象。

有8中错误类型:

  1. Error,基类型,其他类错误类型都是继承基类型。所以所有的错误类型都共享相同的属性;
  2. internalError,在底层JavaScript引擎抛出异常的时候,由浏览器抛出的错误类型。例如递归过多导致栈溢出;
  3. EvalError,通常在eval函数发生异常的时候抛出。基本上,只要不把eval()当成函数调用就会报告该错误;
  4. RangeError,在数值越界时抛出,比如定义数组时,设置了不支持的长度,就会这个类型的错误;
  5. ReferenceError,在找不到对象的时候发生,这种错误经常是由访问不存在的变量而导致的;
  6. SyntaxError,SyntaxError经常在给eval()传入的字符串包含JavaScript语法错误时发生,比如:eval("a ++ b"); // 抛出SyntaxError
  7. TypeError,JavaScript中很常见,主要发生在变量不是预期类型,或者访问不存在的方法时
let o = new 1100 ; // TypeError
console.log('name' in true) ; // TypeError
Function.prototype.toString.call("name"); // TypeError

在这里插入图片描述

  1. URIError,会在使用encodeURI()或decodeURI()但传入了格式错误的URI时发生

· 阅读需 4 分钟

用于客户端存储会话信息。在浏览器中会对cookie做一些限制: ❑ 不超过300个cookie; ❑ 每个cookie不超过4096字节; ❑ 每个域不超过20个cookie; ❑ 每个域不超过81920字节。

每个域能设置的cookie总数也是受限的,但不同浏览器的限制不同。例如: ❑ 最新版IE和Edge限制每个域不超过50个cookie; ❑ 最新版Firefox限制每个域不超过150个cookie; ❑ 最新版Opera限制每个域不超过180个cookie; ❑ Safari和Chrome对每个域的cookie数没有硬性限制。

Web Storage

Web Storage定义了两个对象:localStorage和sessionStorage。localStorage是永久存储机制,sessionStorage是跨会话的存储机制。这两种存储方式都不会因为网页刷新而影响存储数据的。

storage类型用于保存键值对数据,storage实例包含了以下几个方法:

  1. clear():删除所有的值
  2. getItem(name):获取name的值
  3. key(index):获取对应位置的名称
  4. removeItem(name):删除name的键值对
  5. setItem(name,value):设置name的键值对

sessionStorage对象

sessionStorage对象只存储会话数据,会话数据只会储存到浏览器关闭,浏览器关闭时数据就会清空。另外存储在sessionStorage对象中的数据只能在最初存储数据的页面使用,无法在多页面应用程序中使用。

sessionStorage对象是Storage的实例,所以可以通过setItem方法或者直接给属性赋值:

    // 使用方法存储数据
sessionStorage.setItem("name", "Nicholas");
// 使用属性存储数据
sessionStorage.book = "Professional JavaScript";

对于存储在sessionStorage对象上的数据,可以使用getItem方法或者直接访问属性名来获取值:

    // 使用方法取得数据
let name = sessionStorage.getItem("name");
// 使用属性取得数据
let book = sessionStorage.book;

localStorage对象

localStorage作为在客户端永久存储数据的机制,要访问同一个localStorage对象,页面必须来自同一个域(子域不可以)、在相同的端口上使用相同的协议。 localStorage是storage的实例,那么localStorage可以使用storage的方法:

    // 使用方法存储数据
localStorage.setItem("name", "Nicholas");
// 使用属性存储数据
localStorage.book = "Professional JavaScript";
// 使用方法取得数据
let name = localStorage.getItem("name");
// 使用属性取得数据
let book = localStorage.book;

存储事件

当Storage对象发生变化的时候。都会在文档上触发storage事件,比如使用属性或者setItem设置值、使用delete或者removeItem删除值,以及每次调用clear的时候都会发生这个事件。

这个事件对象有这几个属性:

  1. domain:存储变化对应的域。
  2. key:被设置或删除的键。
  3. newValue:键被设置的新值,若键被删除则为null。
  4. oldValue:键变化之前的值

storage事件不会区分sessionStorage还是localStorage。

        window.addEventListener("storage",(e)=>{
console.log("storage",e)
})

在这里插入图片描述

· 阅读需 4 分钟

WebSocket是一种双向、单套接字连接,使用WebSocket,那么请求就变成了打开WebSocket连接的单一请求,并且可以重用公客户端到服务器以及服务器到客户端的同一连接。

WebSock减少延迟,一旦建立起WebSocket连接,服务器可以在消息可用的时候发送。这是轮询和WebSocket的比较。

在这里插入图片描述

WebSocket让实时通信更加有效,可以节约带宽、CPU资源并减少延迟。

WebSocket连接通过在客户端和服务端之间第一次握手的时候将http协议升级到WebSocket协议来完成,这个过程在相同的底层TCP连接上进行。

创建WebSocket连接

首先调用WebSocket构造函数,创建一个WebSocket连接,构造函数返回的WebSocket对象实例。

建立客户端的WebSocket连接

使用WebSocket接口,通过指向一个表示所要连接端点的URL,实例化一个WebSocket对象。

WebSocket协议定义了两种URL:ws和wss。和HTTP、HTTPS类似,表示非加密和加密数据。wss表示使用传输层安全性的WebSocket连接。

WebSocket构造函数有一个必填的URL和可选参数protocols,URL是指向连接目标的URL;protocols是为了建立连接,服务器必须在它响应中包含一个或者一组协议名称,比如:

    <script>
let ws = new WebSocket("ws://localhost:3000/",['com.kaazing.echo']);
ws.onopen = () => {
ws.send("hello")
}
ws.onmessage =(ev) => {
console.log("ev", ev)
}

</script>

和所有的Web API事件一样,使用on+事件名称来处理属性监听这些事件。WebSocket对象调度4个不同的事件:

  1. open,当服务器响应了WebSocket连接请求,那么open事件触发并且建立一个连接,open事件对应的回调函数就是onopen。
  2. message,在接收到消息时候触发,对应事件的回调函数是onmessage,
  3. error,在响应意外故障的时候触发,对应的回调函数是onerror。错误还会导致WebSocket连接关闭。
  4. close,在WebSocket连接关闭的时候触发,对应的回调函数是onclose。一旦连接关闭,那么客户端或者服务器就不再能接收或者发送信息。

创建服务端的WebSocket

代码如下:

const express = require("express");
const http = require("http");
const Ws = require("ws").Server;
const app = express();

const server = http.createServer(app);

server.listen(3000);
app.use(express.static('www'));

let wsServer = new Ws({server});

wsServer.on("connection",function(socket){
console.log("连接成功");
socket.on("message",msg=>{
console.log("客户端信息",msg)
socket.send("客户端发送的信息:"+msg+","+new Date().getTime())
})
})

index.html代码:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<p id="mes"></p>

<script>
let pText = document.querySelector("p")
let ws = new WebSocket("ws://localhost:3000/", ['com.kaazing.echo']);
ws.onopen = (e) => {
console.log("WebSocket open", e);
setInterval(() => {
ws.send(new Date().getTime())
}, 2000)
}
ws.onmessage = ({ data }) => {
pText.innerText = data;
}

</script>
</body>

</html>

然后执行命令:node ws.js,在浏览器打开上面创建的页面,效果如下: 在这里插入图片描述

· 阅读需 7 分钟

Promise

Promise就是ES6新增的一个用于解决异步编程的方案。以前,我们在处理异步的时候,都是在回调函数内做处理的。比如Ajax请求,是在success属性里面做异步处理的,那么如果在一个请求中需要执行读个异步请求,第二个请求是依赖于第一个请求的结果,这样就导致代码嵌套很深,可读性差、很难维护并且难以复用。

那么Promise正好可以解决这样的问题,Promise有3种状态:pending、fulfilled和rejected。Promise在创建阶段是属于pending状态,接着状态只能有两种一个数fulfilled或者rejected,状态改变后就不能再发生变化了,例如:

const promise = new Promise((resolve, rejected) => {
// 异步请求处理
if (/异步请求/) {
resolve();
} else {
rejected();
}
})

异步请求成功,则执行resolve函数,否则执行rejected函数,resolve和rejected函数可以传参数,作为后面.then函数或者catch函数的数据源。

Promise在创建后立即调用,然后等待执行resolve或者是rejected函数来确定Promise的最终状态,比如下面代码:

const promise = new Promise((resolve, rejected) => {
console.log("Promise");
resolve();
})

promise.then(() => {
console.log("resolve")
});

console.log("hello")

结果就是Promise,hello,resolve。因为Promise创建后会立即执行,输出Promise;然后是执行resolve函数,这样就会触发then函数里面的回调函数,但是它需要等待当前线程代码执行完毕后再执行,缩回跳过,先执行后面的代码,输出hello,最后执行then的回调函数,输出resolve。

then函数,返回的是一个Promise对象实例,所以可以通过链式调用then函数,在上一个then函数内return的值是下一个then函数接收的数据,比如:

const promise = new Promise((resolve, rejected) => {
resolve(1);
})

promise.then((res) => {
console.log(res);
return 2;
}).then(res => {
console.log(res);
return 3;
}).then(res => {
console.log(res)
})

结果就是一次打印1,2,3。这样就是可以解决异步回调地狱的问题。

catch函数,是Promise执行失败的回调。如果我们在Promise中手动抛出一个异常,来测试catch函数,代码如下:

const promise1 = new Promise((resolve, rejected) => {
try {
throw new Error("测试");
} catch (error) {
rejected(error)
}
})

promise1.catch(err=>{
console.log(err); // Error: 测试
})

然而Promise在执行中,如果出现异常就会自动抛出,这样上面的代码可以改为:

const promise1 = new Promise((resolve, rejected) => {
throw new Error("测试");
})

promise1.catch(err=>{
console.log(err); // Error: 测试
})

但是需要注意,Promise的状态一旦变味fulfilled成功状态,然后再抛出异常,是不能触发catch的,因为前面已经说过了Promise的状态只要发生变化后就不能再次更改。

Promise静态方法

Promise.all()函数

将多个Promise对象实例包装成为一个Promise实例,参数是一个数组,这个Promise的状态是由所有传入Promise对象来决定,当所有的Promise的状态为fulfilled,这个新的Promise的状态才是fulfilled状态,如果有一个promise的状态为rejected,那么这个新Promise的状态就是reject。

现在使用一段代码来理解上面的描述:

const p1 = new Promise((resolve, reject) => {
resolve("success")
})

const p2 = new Promise((resolve, reject) => {
resolve(1)
})

const p = Promise.all([p1, p2]);
p.then(res => {
console.log(res);
}).catch(err => {
console.log(err)
})
// [ 'success', 1 ]

如果p1,p2中已经定义了对应的catch,当有一个promise状态变为reject的话,promise.all不会触发catch函数了,比如:

const p1 = new Promise((resolve, reject) => {
resolve("success")
}).then(res => res).catch(err => err)

const p2 = new Promise((resolve, reject) => {
throw new Error("报错")
}).then(res => res).catch(err => err)

const p = Promise.all([p1, p2]);
p.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
// [ 'success', Error: 报错 ]
Promise.race 函数

通过race方法来合并多个promise,那么这个promise状态是最先更新状态的promise的状态,比如:

const p1 = new Promise((resolve, reject) => {
}).then(res => res).catch(err => err)

const p2 = new Promise((resolve, reject) => {
reject("失败了")
}).then(res => res).catch(err => err)
const race = Promise.race([p1, p2]);
race.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
}) // 失败了
Promise.any函数

传入一个可迭代的参数,如数组。 结果为: 1、所有promise状态都为成功,返回的是第一个promise的成功状态

//1.获取轮播数据列表

function getBannerList() {
return new Promise((resolve, reject) => {
resolve('banner')
})
}

//2.获取店铺列表

function getStoreList() {
return new Promise((resolve, reject) => {
resolve('store')
})
}

//3.获取分类列表

function getCategoryList() {
return new Promise((resolve, reject) => {
resolve("失败了")
})
}

function initLoad() {

Promise.any([getBannerList(), getStoreList(), getCategoryList()])

.then(res => {

console.log(res)

}).catch(err => {

console.log(err)

})

}
initLoad(); // banner

2、有一个promise的状态为失败,那么就返回第一个状态为成功的promise:

//1.获取轮播数据列表

function getBannerList() {
return new Promise((resolve, reject) => {
reject('banner')
})
}

//2.获取店铺列表

function getStoreList() {
return new Promise((resolve, reject) => {
resolve('store')
})
}

//3.获取分类列表

function getCategoryList() {
return new Promise((resolve, reject) => {
resolve("失败了")
})
}

function initLoad() {

Promise.any([getBannerList(), getStoreList(), getCategoryList()])

.then(res => {

console.log(res)

}).catch(err => {

console.log(err)

})

}
initLoad(); // store

3、所有promise状态为失败的,那么返回的是[AggregateError: All promises were rejected]

//1.获取轮播数据列表

function getBannerList() {
return new Promise((resolve, reject) => {
reject('banner')
})
}

//2.获取店铺列表

function getStoreList() {
return new Promise((resolve, reject) => {
reject('store')
})
}

//3.获取分类列表

function getCategoryList() {
return new Promise((resolve, reject) => {
reject("失败了")
})
}

function initLoad() {

Promise.any([getBannerList(), getStoreList(), getCategoryList()])

.then(res => {

console.log(res)

}).catch(err => {

console.log(err)

})

}
initLoad(); // [AggregateError: All promises were rejected]

· 阅读需 5 分钟

web应用的痛点就是不能操作计算机上的文件。File API和Blob API可以安全访问到客户端上的文件。

File类型

现在我们可以在html表单中直接访问文件,比如:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="file">
</body>
</html>

效果如图:

在这里插入图片描述

当我们选择文件后,可以读出文件得到名称:

在这里插入图片描述

选中后的文件中,每一个File对象中都只有一些只读属性,比如name、size和type:

在这里插入图片描述

FileReader类型

FileReader类型是一种异步文件读取机制,提供了集中读取文件数据的方法:

readAsText(file,encoding):从文件中读取纯文本内容并且保存在result属性中,第二个参数是表示编码,可选参数。

readAsDataURL(file):读取文件并且将内容的数据URI保存在result属性中。

readAsBinaryString(file):读取文件并且把每一个字符的二进制数据保存在result属性。

readAsArrayBuffer(file):读取文件并且把内容以ArrayBuffer形式保存在result中。

在读取文件中,会触发几个事件:progress、error和load,表示进度、发生错误和读取完成。

progress事件每50毫秒就会触发一次,在这期间可以读取FileReader的result属性;

error事件,在无法读取文件的时候触发,error属性是一个对象,只包含一个属性:code。这个错误码的值可能是1(未找到文件)、2(安全错误)、3(读取被中断)、4(文件不可读)或5(编码错误)

load事件会在成功加载后触发,如果error事件触发,那么不会触发load事件 比如:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<input type="file" id="file">

<div type="text" id="output"></div>

<div id="progress"></div>
<script>
let filesList = document.getElementById("file");
filesList.addEventListener("change", (event) => {
let info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = event.target.files,
type = "default",
reader = new FileReader();
if (/image/.test(files[0].type)) {
reader.readAsDataURL(files[0]);
type = "image";
} else {
reader.readAsText(files[0]);
type = "text";
}
reader.onerror = function() {
output.innerHTML = "Could not read file, error code is " +
reader.error.code;
};
reader.onprogress = function(event) {
if (event.lengthComputable) {
console.log(`${event.loaded}/${event.total}`)
progress.innerHTML = `${event.loaded}/${event.total}`;
}
};
reader.onload = function() {
let html = "";
switch(type) {
case "image":
html = `<img src="${reader.result}">`;
break;
case "text":
html = reader.result;
break;
}
output.innerHTML = html;
};
});
</script>
</body>

</html>

Blob

Blob是表示二进制大对象,是JavaScript对不可修改的二进制数据的封装类型,包含字符串的数组、ArrayBuffers、ArrayBufferViews,

Blob对象有一个size属性和type属性,slice方法用于进一步切分数据,另外也可以使用FileReader从Blob中读取数据。只读取一部分文件可以节省时间,比如现在只读取文件的前10个字节:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<input type="file" id="file">

<div type="text" id="output"></div>

<div id="progress"></div>
<script>
let filesList = document.getElementById("files-list");
filesList.addEventListener("change", (event) => {
let info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = event.target.files,
reader = new FileReader(),
blob = blobSlice(files[0], 0, 10);
if (blob) {
reader.readAsText(blob);
reader.onerror = function () {
output.innerHTML = "Could not read file, error code is " +
reader.error.code;
};
reader.onload = function () {
output.innerHTML = reader.result;
};
} else {
console.log("Your browser doesn't support slice().");
}
});
</script>
</body>

</html>

Blob URL

是引用存储在File或者Blob中数据的URL,有点就是不用把文件内容读取到JavaScript中可以使用文件,只需要在对应位置提供对象的URL即可。创建对象URL,使用window.URL.createObjectURL()方法,传入File或者Blob对象,它返回一个指向内存中地址的字符串。比如:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<input type="file" id="file" multiple>

<div type="text" id="output"></div>

<div id="progress"></div>
<script>
let filesList = document.getElementById("file");
filesList.addEventListener("change", (event) => {
let info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = event.target.files,
reader = new FileReader(),
url=window.URL.createObjectURL(files[0]);
console.log("url",url)
if (url) {
if (/image/.test(files[0].type)) {
output.innerHTML=`<img src="${url}">`;
} else {
output.innerHTML = "Not an image.";
}
} else {
output.innerHTML = "Your browser doesn't support object URLs.";
}
});
</script>
</body>

</html>

在这里插入图片描述

· 阅读需 5 分钟

fetch能够执行XMLHttpRequest对象的所有任务,接口更加现代化。XMLHttpRequest可以选择异步,而fetch必须是异步的。

基本用法

fetch方法是暴露在全局作用域中的。在使用的时候,需要传入一个必选项URL,这个参数是获取资源的URL。

在读取响应的时候,根据需要获取对应的格式内容,比如常见的json方法:

fetch("url").then(res=>res.json()).then(res=>{
console.log(res);
})

fetch支持通过response的status和statusText属性检查响应状态,成功获取响应的请求通常会产生值为200的状态码:

fetch("url")
.then(res=>{
console.log(res.status); // 200
console.log(res.statusText); // ok
})

fetch请求模式

  1. 发送JSON数据格式:
    let payload = JSON.stringify({
foo: 'bar'
});
let jsonHeaders = new Headers({
'Content-Type': 'application/json'
});
fetch('/send-me-json', {
method: 'POST', // 发送请求体时必须使用一种HTTP方法
body: payload,
headers: jsonHeaders
});
  1. 在请求体中发送参数:
    let payload = 'foo=bar&baz=qux';
let paramHeaders = new Headers({
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
});
fetch('/send-me-params', {
method: 'POST', // 发送请求体时必须使用一种HTTP方法
body: payload,
headers: paramHeaders
});
  1. 发送文件,因为请求体支持FormData,fetch可以序列化并且发送文件字段中的文件:
    let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file']");
imageFormData.append('image', imageInput.files[0]);
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});

还可以发送多个文件:

    let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file'][multiple]");
for(leti=0;i<imageInput.files.length;++i){
imageFormData.append('image', imageInput.files[i]);
}
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});
  1. 加载Blob文件,fetch也提供了Blob类型的响应。比如加载图片,将图片加载到内存中,然后添加到HTML图片元素,这样就可以使用响应对象上暴露的blob方法,得到一个Blob实例,然后,可以将这个实例传给URL.createObjectUrl()以生成可以添加给图片元素src属性的值:
    const imageElement = document.querySelector('img');
fetch('my-image.png')
.then((response) => response.blob())
.then((blob) => {
imageElement.src = URL.createObjectURL(blob);
});
  1. 跨域请求,从不同的源请求资源,响应要包含CORS头部才能保证浏览器收到响应;
  2. 中断请求,Fetch API支持通过AbortController/AbortSignal对中断请求。调用AbortController. abort()会中断所有网络传输,特别适合希望停止传输大型负载的情况。中断进行中的fetch()请求会导致包含错误的拒绝。
    let abortController = new AbortController();
fetch('wikipedia.zip', { signal: abortController.signal})
.catch(() => console.log('aborted! ');
// 10 毫秒后中断请求
setTimeout(() => abortController.abort(), 10);
// 已经中断

Headers对象

Headers对象是所有外发请求和接收响应头部的容器,每发一次request实例都包含了一个空的Header实例。根据需要设置不同的header参数。

Request对象

Request对象是获取资源请求的接口。这个接口暴露了请求的相关信息,也暴露了使用请求体的不同方式。

  1. 创建Request对象:
    let r = new Request('https://foo.com');
console.log(r);
// Request {...}

Request对象构造函数也接收第二个参数,这个参数就是一些常规的设置,比如method。 2. 克隆Request对象,Fetch提供两种方式来创建Request对象的副本:使用Request构造函数和使用clone方法。clone方法的代码如下:

    let r1 = new Request('https://foo.com', { method: 'POST', body: 'foobar' });
let r2 = r1.clone();
console.log(r1.url); // https://foo.com/
console.log(r2.url); // https://foo.com/
console.log(r1.bodyUsed); // false
console.log(r2.bodyUsed); // false

· 阅读需 5 分钟

Encoding 是用于实现字符串和定型数组之间的转换,有四个用于执行转换的全局类:TextEncoder、TextEncoderStream、TextDecoder和TextDecoderStream。

文本编码

Encoding提供了两种字符串转换为定型数组二进制格式的方法:批量编码和流编码。把字符串转换为定型数组的时候,编码器始终使用的是UTF-8。

批量编码

批量编码,指的是JavaScript引擎会同步编码整个字符串,对很长的字符串,花费时间比较长。

批量编码是通过TextEncoding的实例来完成的:const textEncoder = new TextEncoder()

const textEncoder = new TextEncoder();
const text = textEncoder.encode("foo");

console.log('====================================');
console.log(text);
console.log('====================================');

在这里插入图片描述

其中f的UTF-8编码是0x66,十进制为102。

编码器还有一个方法是encodeInto()方法,接收一个字符串和目标Unit8Array,返回一个对象,该对象含有read和writen属性,表示成功从源字符串中读取多少字符和向目标数组写入多少字符。如果空间不够,编码就会提前终止,比如:

const textEncoder = new TextEncoder();
const fooArr = new Uint8Array(3);
const barArr = new Uint8Array(2);
const fooResult = textEncoder.encodeInto('foo', fooArr);
const barResult = textEncoder.encodeInto('bar', barArr);
console.log(fooArr); // Uint8Array(3) [102, 111, 111]
console.log(fooResult); // {read: 3, written: 3}
console.log(barArr); // Uint8Array(2) [98, 97]
console.log(barResult); // {read: 2, written: 2}

在这里插入图片描述

文本解码

Encoding 提供了两种定型数组转换为字符串的方式:批量解码和流解码。

批量解码

JavaScript引擎会同步解码整个字符串,对于很长的字符串,也是会花费很长时间的。批量解码是通过TextDecoder的实例来完成的。比如:

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const encodeText = textEncoder.encode("foo");
const decoderText = textDecoder.decode(encodeText);
console.log("解码:",decoderText); // 解码:foo

拖放事件

当一个元素被拖放的时候,会触发以下几个事件:dragstart、drag、dragend。按住鼠标键不放开,被拖动的元素上就会触发dragstart事件;之后只要目标元素还被拖动就会一直触发的drag事件;拖动停止后,就触发dragend事件。

在把元素放到一个有效的放置目标上的时候,会依次发生以下事件:dragenter、dragover、dragleave或者drop。

元素拖动到放置目标上的时候,就触发dragenter事件,之后立即触发dragover事件,并且元素在放置区域范围内被拖动的过程中dragover事件一直在触发,只有在元素被拖动到放置区域外面的时候,dragover才会停止触发,然后触发dragleave事件。如果被拖动元素被放到目标上,那么就会触发drop事件而不是dragleave。

定义放置区域

当我们把一个元素拖动到无效放置区域上的时候,就会看到一个特殊的光标,表示不能放下。通过dragenter和dragover事件的默认行为,就可以把认可元素转换为有效的放置区域。代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生拖放事件</title>
<style>
#droptarget {
width: 100px;
height: 100px;
border: 1px solid red;
}
</style>
</head>

<body>
<div id="droptarget"></div>

<script>
let dropTarget = document.getElementById("droptarget");
dropTarget.addEventListener("dragover", (event) => {
event.preventDefault();
});

dropTarget.addEventListener("dragenter", (event) => {
event.preventDefault();
})
</script>
</body>
</html>

可拖动能力

一般情况下,图片、链接和文本是可以拖动的。其中文本是在被选中后才可以拖动。如果想让其他元素变得可以拖动,那么就需要在该元素上定义一个draggable,表示是可以拖动。

如果要设置图片或者链接禁止拖动,则直接在元素的draggable属性设置为false即可。

效果如图:

在这里插入图片描述 在这里插入图片描述

这样我们就可以在不同的事件中,编写对应的逻辑。