跳到主要内容

· 阅读需 5 分钟

浏览器的重排和重绘是比较消耗性能的操作,所以网页性能优化的手段就是减少重排和重绘的操作。比如:

多次更改样式的操作合并为一次操作

我们在JavaScript中修改样式,比如:

const changeDiv = document.querySelect("#div");
changeDiv.style.width = '190px';
changeDiv.style.height = '190px';
changeDiv.style.background = 'red';
changeDiv.style.marginLeft = '20px';

这种方式修改样式,实际上是多次操作修改style属性,引起多次重绘或者是重排。其实这种操作,我们就可以合并为一次操作即可:

.changDiv{
width:'190px';
height:'190px';
background:'red';
margin-left:'20px';
}
const changDiv = document.querySelect('.div');
changeDiv.className = 'div';

这样就是在最后修改div的className的时候才发生重排或者重绘。

把多次发生重排的元素设置为绝对定位

一般情况下元素是处在正常的文档流中,如果这个元素发生重排就会影响到其他周边元素。要是这个元素脱离文档流,那么它的变化就不会影响到其他元素的变化,从而不会引起重排的操作。需要设置position属性为absolute或者是fixed。

比如有一个动画元素,它频繁改动位置、宽高这些属性,这样的元素可以考虑将它设置为绝对定位或者固定定位。

在内存中多次操作节点、完成后再添加到文档树中

比如我们通过异步请求拿到一个表格数据,然后渲染到页面上。现在在渲染的时候有两种方式:

  1. 每次构造一行数据就添加一次,也就是append到表格中
  2. 一次性构造数据,最后只需要添append一次 就好了。

方法一就是每添加一次就引起浏览器重排和重绘的操作,如果数据很大的话,渲染时可能发生卡顿。

方法二只会引起一次重排和重绘,在性能上有很大的提升。

在处理复杂交互的元素时候,将设置它的display属性值为none,出来完交互后在显示

因为display属性设置为none的时候,就不会出现在渲染树上,所以在对它进行处理时不会引起其他元素的重排,等待处理完成后在显示出来,这样前后只发生两次重排或者重绘的操作。

尽量使用table布局

因为table布局的中任何一个元素发生变化,都会导致整个table发生重排。尤其是在table内容比较庞大的时候。

使用事件委托

在对多个同一个层级元素做事件绑定的时候,使用事件委托机制来处理,这样可以减少事件处理程序的数量,从而提高性能

利用DocumentFragment操作DOM节点

DocumentFragment是一个没有父级节点的最小文档对象,它可以用于存储已经排好版或者尚未确定格式的HTML片段。DocumentFragment最核心的知识点在于它不是真实DOM树的一部分,它的变化不会引起DOM树重新渲染的操作,也就不会引起浏览器重排和重绘的操作,从而带来性能上的提升。

这就DOM操作的几点优化

· 阅读需 8 分钟

很多时候需要在页面初始化阶段执行一些特定的操作,页面初始化的操作就是文档加载完成后的执行的操作。在DOM中,文档加载完成有两个事件,一个load事件,另一个是jQuery提供的ready事件。

ready事件的触发表示文档结构已经加载完成,不包含图片、flash这些非文字媒体内容。

onload事件的触发表示页面图片、flash这些所有元素已经加载完成。

load事件

load事件,在页面、脚本或者图片加载完成后触发的,支持onload事件的标签元素有body、frame、frameset、iframe、img、link和script。

这个load事件的使用,有两种方式: 1、在标签上使用onload属性,类似于onclick事件属性一样,比如:

<body onload="bodyLoad()">
<script>
function bodyLoad(){
console.log("文档加载完成,执行load事件")
}
</script>
</body>

2、设置window对象的onload属性,属性值为一个函数,比如:

       window.onload = function(){
console.log("文档加载完成,执行load事件2222")
}

如果这两种方式同时设置的话,只有第二种方式生效,因为在JavaScript里面声明的事件优先级高于在标签元素内声明的属性事件。如下代码:

<body onload="bodyLoad()">
<script>
function bodyLoad(){
console.log("文档加载完成,执行load事件")
}

window.onload = function(){
console.log("文档加载完成,执行load事件2222")
}
</script>
</body>

在这里插入图片描述 load事件的使用,很多情况下是因为我们把JavaScript单独写在一个文件中,然后在head内引入或者是直接在head使用script标签便携JavaScript代码。

然而head标签是优先于body标签进行解析的,如果这时候JavaScript代码中含有对body内其他标签的处理,就会出现代码中操作的对象未被加载的情况,例如: 在这里插入图片描述 源码:

<!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>
<script>
document.querySelector("#div").innerText = "页面加载完成"
</script>
</head>

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

</html>

想要解决这个问题就是使用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>
<script>
window.onload = function () {
document.querySelector("#div").innerText = "页面加载完成"
}
</script>
</head>

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

</html>

在这里插入图片描述

浏览器的重排、重绘

浏览器渲染HTML的过程大致分为4步: 1、HTML文件被解析为对应的DOM树,css文件也被解析为对应的样式规则集合 2、DOM树和css样式附在一起形成一个渲染树 3、节点信息计算,根据渲染树计算每一个节点的几何信息 4、根据计算好的几何信息,绘制整个页面。

那么重绘和重排是发生在第三步和第四步。

重排

在对某一个DOM节点信息进行修改的时候,需要对该DOM结构进行重新运算,会改动周边其他DOM的位置,重排就是一种明显修改页面布局的操作。常见引起重排的操作:

  1. 页面首次渲染,html页面中的元素位置、大小这些信息都是位置,需要和css样式规则集才能确定元素的信息,这个过程会产生很多元素的几何运算过程,所以发生重排
  2. 浏览器窗口大小发生变化的时候,渲染树会从根元素html标签开始的所有元素都会重新计算几何信息,产生重排;
  3. 元素尺寸或者位置发生改变
  4. 元素内容发生变化
  5. 元素字体发生变化,3、4、5种情况会导致渲染树相关的节点失效,浏览器会根据DOM元素的变化,重新构建渲染树中失效的节点,从而产生重排;
  6. 添加或者删除可见元素,因为浏览器采用的流式布局模型,是从上到下、左到右遍历元素的,如果我们删除或者添加一个元素,那么对之前的元素是没有影响,而后面的元素就会重新计算几何信息,渲染树也需要重新构建修改后的节点,这就产生了重排;
  7. 获取一些特殊的属性时,会引起重排,比如:
· padding:内边距。
· display:元素显示方式。
· border:边框。
· position:元素定位方式。
· overflow:元素溢出处理方式。
· clientWidth:元素可视区宽度。
· clientHeight:元素可视区高度。
· clientLeft:元素边框宽度。
· clientTop:元素边框高度。
· offsetWidth:元素水平方向占据的宽度。
· offsetHeight:元素水平方向占据的高度。
· offsetLeft:元素左外边框至父元素左内边框的距离。
· offsetTop:元素上外边框至父元素上内边框的距离。
· scrollWidth:元素内容占据的宽度。
· scrollHeight:元素内容占据的高度。
· scrollLeft:元素横向滚动的距离。
· scrollTop:元素纵向滚动的距离。
· scrollIntoView():元素滚动至可视区的函数。
· scrollTo():元素滚动至指定坐标的函数。
· getComputedStyle():获取元素的CSS样式的函数。
· getBoundingClientRect():获取元素相对于视窗的位置集合的函数。

重绘

重绘就是改变元素在页面中的展现样式,不会引起元素在文档流中位置的变化。比如文字颜色,背景和透明度等等。

· 阅读需 4 分钟

对Promise有一定的了解后,但是在面对一些关于Promise的面试,还是感觉力不从心。现在整理一下Promise的使用场景:

1、Promise和同步代码一起执行

看一下这段代码的结果是什么?

const promise = new Promise((resolve,reject)=>{
console.log(1);
resolve();
console.log(2)
})
promise.then(()=>{
console.log(3)
})

console.log(4)

promise创建后就立即执行,那就或输出1,2,4。 resolve或者reject函数会在同步代码之后执行,等到resolve或者reject执行后,就进入了then或者catch函数。所以最好才输出3。

2、同一个Promise实例内resolve和reject执行先后

代码如下:

const promise = new Promise((resolve,reject)=>{
resolve(1);
reject(2);
resolve(3)
})
promise.then((res)=>{
console.log("then",res)
}).catch(err=>{
console.log("catch",err)
})

会输出什么样的结果呢? 答案是then 1。

要记住,promise状态的变更只有一次,当执行resolve或者reject,就完成一次状态变更。

3、promise实例重复执行

代码如下:

const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1);
resolve(2)
}, 1000)
})
const start = Date.now();


promise.then((res) => {
console.log(res, Date.now() - start)
})

promise.then(res => {
console.log(res, Date.now() - start)
})

答案是一秒后输出1, 2 1017,2 1017

当promise实例变更状态后,就会触发所有的then或者catch函数,所以上面的代码中第一个then和第二个then函数执行的结果是一样的。

4、在then函数返回一个异常

代码如下:

const promise = new Promise((resolve, reject) => {
resolve(20)
})
promise.then((res) => {
console.log(res);
return new Error("Error")
}).then(res => {
console.log(22)
console.log('then', res)
}).catch(err => {
console.log("Error", err)
})

答案是: 20 22 then Error: Error

其实在then中return的异常,和promise没有联系,所以不会触发catch函数。而是按照正常流程执行下去,把then中return的异常作为下一个then函数的的数据源。

注意:返回异常和抛出异常是不同概念的,如果是抛出异常就会被catch函数捕获,代码如下:

const promise = new Promise((resolve, reject) => {
resolve(20)
})
promise.then((res) => {
console.log(res);
throw new Error("Error")
}).then(res => {
console.log(22)
console.log('then', res)
}).catch(err => {
console.log("Error", err)
})

输出结果: 20 Error Error: Error

5、then的参数不是函数

代码如下:

const promise = new Promise((resolve, reject) => {
resolve(20)
})
promise.then(21)
.then(res => {
console.log('then', res)
})
.then(Promise.resolve(34))
.catch(err => {
console.log("Error", err)
})

答案是20。

因为promise的then函数或者catch函数接受的是一个函数,如果传入其他值,那么就会产生穿透现象。也就是传入非函数的值会被忽略掉,执行后面的函数。

暂时到这里吧,以后遇到了再说哈

· 阅读需 4 分钟

这是之前写的,关于Proxy的文章:JavaScript的Proxy代理

Reflect

Reflect是一个全局对象,内置的,提供了拦截JavaScript操作的方法,另外Reflect不是一个函数对象,所以它不是构造函数。它就像Math对象一样,所有的属性和方法都是静态的,比如

Reflect.apply(target,thisArgument,argumentsList)

对一个函数进行调用操作,同时传入一个数组作为调用参数。 target:目标函数 thisArgument:目标函数调用时绑定的this对象 argumentsList:目标函数调用时传入的参数列表,是一个类数组。

比如

function fn(name){
console.log("Name:",name)
}
Reflect.apply(fn,undefined,["duxin"]); // Name: duxin
Reflect.construct(target,argumentList[, newTarget])

和new 操作符一样,执行的是new target(...args),这里的target是目标构造函数,比如:

function MyClass(name){
this.class = name;
}
const obj = Reflect.construct(MyClass,['duxinyues'])

console.log(obj); // MyClass { class: 'duxinyues' }
console.log(obj instanceof MyClass) ; // true
Reflect.defineProperty(target, propertyKey, attributes)

在目标对象中添加一个属性或者是修改一个属性值,比如:

let obj1 = {}
Reflect.defineProperty(obj1, 'x', { value: 7 }) // true
console.log(obj1.x) // 7
console.log(Reflect.defineProperty(obj1, "name",{value:"duxin"}))
console.log(obj1.name)

如果Reflect.defineProperty用来检测一个对象的属性是否已经定义,如果是,则返回true。

Reflect.deleteProperty(target,propertyKey)

和delete操作符一样,是删除对象的一个属性。比如:

let obj ={
name:"duxin"
}
console.log(obj);//{ name: 'duxin' }
Reflect.deleteProperty(obj,"name")
console.log(obj);//{}
Reflect.get(target,propertyKey)

获取对象的某一个属性值,比如:

let obj ={
name:"duxin"
}
console.log(Reflect.get(obj,"name"));// duxin
Reflect.getOwnPropertyDescriptor(target, propertyKey)

判断对象中是否存在一个属性,如果存在,那么返回该属性描述符,比如属性值,是否可读写等等信息,如果不存在该属性,那么返回undefined。

示例代码:

let obj ={
name:"duxin"
}
console.log(Reflect.getOwnPropertyDescriptor(obj, "name"))

输出内容如下:

{
value: 'duxin',
writable: true,
enumerable: true,
configurable: true
}
Reflect.getPrototypeOf(obj)

和Object.getPrototypeOf(obj)一样,返回目标对象的原型

let obj ={
name:"duxin"
}
console.log(Reflect.getPrototypeOf(obj)); // [Object: null prototype] {}
Reflect.has(target, propertyKey)

判断一个对象是含有某一个属性,比如

let obj ={
name:"duxin"
}
console.log(Reflect.has(obj,"x")); // false
Reflect.isExtensible()

判断一个对象是否可扩展。

let obj ={
name:"duxin"
}
console.log(Reflect.isExtensible(obj)); //true
Reflect.ownKeys(obj)

返回对象自身的所有属性,和Object.keys()一样。

let obj ={
name:"duxin"
}
console.log(Reflect.ownKeys(obj)); //[ 'name' ]
Reflect.preventExtensions(obj)

将一个对象设置为不可扩展,不能添加新属性

let obj = {
name: "duxin"
}
console.log(Reflect.isExtensible(obj)); // true
Reflect.preventExtensions(obj);
console.log(Reflect.isExtensible(obj)); //false
Reflect.set(target, propertyKey, value[, receiver])和

修改对象属性的属性值,

Reflect.setPrototypeOf(target, prototype)

设置对象原型的函数。

let obj = {
name: "duxin"
}

console.log(Reflect.setPrototypeOf(obj, Object.prototype));// true

· 阅读需 5 分钟

sidebar_position: 0 title: 理解虚拟DOM、innerHTML和JavaScript操作DOM slug: 虚拟DOM、innerHTML和JavaScript操作事件

tags: [JavaScript]

UI就是用户界面,需要处理两件事情:

  1. 呈现内容、反馈
  2. 用户交互

先了解一下声明式UI和命令式UI,这两个概念。

声明式,倾向的是现实表达,比如react或者vue; 命令式,倾向的是执行过程,比如JQuery,就是典型的命令式UI框架;

比如现在有一个div标签,id为app,文本内容为text。需要点击div修改文本内容为“hello world”。

jQuery:

$("#app").text("hello world");

原生JavaScript:

        let app = document.querySelector('#app')
app.addEventListener("click", function () {
app.innerText = "hello world";
})

React:

import React from "react";
function WordClassification() {
const [text,setText] = React.useState('text');

return <div onClick={()=>{
setText("hello world")
}}>{text}</div>;
}

export default WordClassification;

通过代码的比较,可以看出,命令式UI代码指出了每一个步骤是做什么;而声明式UI代码呢,只是给出:“我要点击div,修改内容”,这样的结果。

在设计UI框架的时候,我们需要从性能和可维护性的角度来权衡。对于命令式代码来说,已经是做到极致的性能优化了,因为我们明确知道哪些发生了变化,只需要做出修改就行。

而声明式代码就不一定了,因为它所描述的是一个结果。对于声明式UI框架来说,在更新的时候,性能达到最优,就需要找到前后的差异并且只更新变化的部分,查找前后差异也是消耗性能的。和命令式UI框架相比较,声明式UI框架就多出了查找差异这部分的性能。但是,声明式代码更利于维护。在声明式代码中,将性能调到最优,更接近接近命令式代码的性能,就是将查找差异这部分的性能降到最低,就可以了。

那么虚拟DOM,就是为了将查找差异部分的性能最小化。

虚拟DOM

虚拟DOM在转化为真实DOM的过程中分为两部分:

  1. 先创建JavaScript对象,也就是虚拟DOM【因为虚拟DOM就是描述真实DOM】;
  2. 遍历虚拟DOM来创建真实DOM。

需要更新的时候,重新创建一个JavaScript对象,然后比较前后虚拟DOM,找到变化的元素并且更新它。

除此之外,我们更新页面还有两种方式:innerHTML和原生js【比如createElement之类的DOM操作方法】

innerHTML模板

innerHTML是用来或者或者设置HTML语法表示的元素的后代。在创建一个页面的时候,需要构造一段HTML字符串、再将这段HTML字符串赋值给DOM元素的innerHTML属性。

为了将这段html字符串渲染成页面,首先需要把字符串解析为DOM树,这个过程涉及到DOM的计算,性能远远比JavaScript计算的性能差。

使用innerHTML更新页面,其实就是重新构建HTML字符串,再重新设置DOM元素的innerHTML属性,也就是需要销毁所有旧的DOM元素,然后再全量创建新的DOM元素。

对于虚拟DOM来说,不管页面有多大,都只会更新变化的内容;对于innerHTML来说,页面越大,更新时性能消耗就越大。

原生JavaScript操作DOM

性能高,但是可维护性极差。因为需要手动创建、删除、修改大量的DOM元素。

如下图所示,三种方式的比较 在这里插入图片描述

· 阅读需 0 分钟

· 阅读需 9 分钟

Ajax是前端好后端数据交互的方式,通过异步请求就可以在不需要刷新页面的情况下,达到局部刷新的效果。

Ajax的原理就是通过XMLHttpRequest对象向服务器发送异步请求,获取数据后利用DOM操作来更新页面。 流程如图:

在这里插入图片描述

XMLHttpRequest对象,支持异步请求。访问服务器的并且不阻塞用户,达到不用刷新页面的效果。

XMLHttpRequest对象

它在创建到销毁整个生命周期中,不同阶段是调用不同的函数,在函数中需要通过XMLHttpRequest对象的特定属性来判断函数执行的情况。

  1. abort函数:如果请求已经发送,那么暂停当前的请求;
  2. getAllResponseHeaders函数,获取http请求的响应头部,作为键值对返回,如果没有那么返回一个null;
  3. getRequestHeader(key)函数,获取指定key的响应头,如果没有或者不存在key对应的报头,那么返回null。
  4. open("method",,"url",[asyncFlag],"username","password"),建立对服务器的调用。其中asyncFlag表示异步还是同步,默认是true,表示异步
  5. send(content)函数,向服务器发送请求
  6. setRequestHeader("key","value")函数,设置请求头,设置header之前,先调用open函数,设置的header和send函数一起发送;

XMLHttpRequest对象声明周期

标准的XMLHttpRequest创建方法:

        let xmlhttp = null;
function createXMLHttp() {
// code for IE7+, Firefox, Chrome, Opera, Safari
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}
// code for IE6, IE5
if (window.ActiveXObject) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e) {
try {
xmlhttp = new ActiveXObject("msxml2.XMLHTTP");
}
catch (ex) { }
}
}
}

这个是为了兼容其他低版本的浏览器,可以根据需要直接使用new XMLHttpRequest来创建:

var xhr = new XMLHttpRequest();
xhr.open("post", "/admin/login", true);
// 请求参数
var content = {
userName: 'duxin',
password: '123456',
}

// 发送请求
xhr.send(content);

// 处理响应
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.readyState === 200) {
document.write(xhr.responseText)
}
}

Ajax优缺点

有点:

  1. 不需要刷新就可以个新数据;
  2. 异步通信,Ajax使用异步的方式和服务器通信,能够减少不必要的数据通信,降低网络数据流量。
  3. 前后端分离,前端专注于页面逻辑的处理,后端则是专注于接收和响应数据
  4. 前后端负载均衡,可以将一些在后端处理的数据逻辑,放到前端来处理。

缺点:

  1. 安全性问题,比如跨域脚本攻击、SQL注入攻击;
  2. 对搜索引擎支持比较弱,因为浏览器在进行SEO的时候,会屏蔽所有的JavaScript代码,而Ajax正好是JavaScript一部分。

表单提交

现在有一个简单的表单:

<!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>
<form name="userForm" id="userForm">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control" name="username" id="username">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" name="password" id="password">
</div>
<div class="text-center">
<input type="button" class="btn btn-default btn-primary" value="提交" id="submit">
</div>
</form>
<div id="result"></div>
<script>
const textDiv = document.getElementById("result")
function ajaxBtn() {
var xhr = new XMLHttpRequest();
xhr.open("post", "http://192.168.0.114:3000/user/login", true);
xhr.setRequestHeader("Content-Type", "application/json")
// 请求参数
const username = document.getElementById("username").value;
const password = document.getElementById("password").value
var content = {
name: username,
password: password,
}

// 发送请求
xhr.send(JSON.stringify(content));

// 处理响应
xhr.onreadystatechange = function () {
console.log("网络请求", xhr)
if (xhr.readyState === 4 && xhr.status === 200) {
textDiv.innerText = xhr.responseText
}
}
}
var btn = document.getElementById("submit");
btn.addEventListener("click", function () {
ajaxBtn();
})
</script>
</body>

</html>

效果如图:

在这里插入图片描述

这里的服务器端是使用nodejs。

进度事件

在上面的代码中,时使用onreadystatechange事件,在回调函数中获取readyState和status的值,并且做相关的判断请求是否成功。

进度事件就是让Ajax在请求的不同阶段触发不同类型的事件,所以我们可以不用readystate的属性,也能够处理请求成功和失败的操作。 下面是一些常见的进度事件:

  1. loadstart,在开始接收响应时触发
  2. progress,在接收响应期间不断触发,直到请求结束
  3. error,请求失败
  4. abort,在主动请求时,调用abort函数表示请求终止
  5. load,数据接收完成后触发,
  6. loadend,在通信完成或者error、abort、load事件后触发
  7. timeout,请求超时触发

progress事件在浏览器接收数据的过程中周期性调用,在回调处理程序中接收一个event对象,里面含有对应的XMLHttpRequest对象的实例,另外含有三个属性:分别是lengthComputable、loaded和total。

lengthComputable,表示进度信息是否可用; loaded,表示已经接受到的字节数; total,表示响应的实际字节数。

Ajax跨域解决方案

首先浏览器同源策略约定了客户端脚本在没有明确授权的情况下,不能访问不同源的目标资源。

同源指的是相同的协议、域名、端口号,如果两个资源路径在协议、端口号或者是域名上的任何一点不同,那么这两个资源就是属于同源资源。

浏览器跨域限制,这是因为有一些没有遵守浏览器的同源策略引起的,浏览器跨域访问的限制,在一定程度上可以保护用户的隐私数据安全。

如果没有DOM同源策略限制,不同域名的iframe可以相互访问,这样黑客做假的网站,里面使用iframe嵌套一个银行的网站,这个假网站的内容就是和银行网站一模一样。当用户输入用户名和密码后,黑客就可以获取到iframe所嵌套的银行网站的DOM节点,从而拿到用户的账号和密码了。

另一个是XMLHttpRequest同源策略,如果没有这个策略的话,黑客就可以进行跨站请求伪造CSRF攻击。

当我们需要跨域请求的时候,主要是在服务端通过设置响应头,接收跨域请求处理,在express开发的服务端跨域处理:

app.all('*', function (req, res, next) {
// 设置可以接收请求的域名
res.header('Access-Control-Allow-Origin', '*');
// 是否可以携带cookie
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});

可以指定具体域名。

另一个跨域解决方法就是JSONP,优点就是简单、兼容低版本浏览器,对服务端影响小。 但是它只支持get请求。另外很难判断JSONP请求是否成功