期中测试题

289 阅读14分钟

一、什么是闭包?闭包的用途是什么?闭包的缺点是什么?

1、什么是闭包?

闭包 是指有权访问 另一个函数作用域中变量的 函数

1.1、形成闭包的原因

内部的函数存在外部作用域的引用就会导致闭包。

2、在闭包的用途是什么?

  • 保护函数的私有变量不受外部的干扰。形成不销毁的栈内存。

  • 保存,把一些函数内的值保存下来。闭包可以实现方法和属性的私有化

2.2 闭包经典使用场景

1、return 回一个函数

var n = 10

function fn(){

var n =20

function f() {

n++;

console.log(n)

}

return f

}

var x = fn()

x() // 21

这里的 return f, f()就是一个闭包,存在上级作用域的引用。

\

2、函数作为参数

var a = 'kx'

function foo(){

var a = 'foo'

function fo(){

console.log(a)

}

return fo

}

function f(p){

var a = 'f'

p()

}

f(foo())

/* 输出

  • foo

/

使用 return fo 返回回来,fo() 就是闭包,f(foo()) 执行的参数就是函数 fo,因为 fo() 中的 a 的上级作用域就是函数foo(),所以输出就是foo

\

3、循环赋值

for(var i = 0; i<10; i++){

(function(j){

setTimeout(function(){

console.log(j)

}, 1000)

})(i)

}

\

4、使用回调函数就是在使用闭包

window.name = '方法'

setTimeout(function timeHandler(){

console.log(window.name);

}, 100)

3、闭包的缺点是什么?

1、容易导致内存泄漏。

2、闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。

二、call、apply、bind 的用法分别是什么?

在 MDN 中定义 apply :

\

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

\

语法:

\

fun.apply(thisArg, [argsArray])

\

thisArg:在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

apply 和 call 的区别

其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。

\

call 的语法为:

\

fun.call(thisArg[, arg1[, arg2[, ...]]])复制代码

所以 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

\

例 1:

\

var a ={

name : "Cherry",

fn : function (a,b) {

console.log( a + b)

}

}

\

var b = a.fn;

b.apply(a,[1,2]) // 3复制代码

例 2:

\

var a ={

name : "Cherry",

fn : function (a,b) {

console.log( a + b)

}

}

\

var b = a.fn;

b.call(a,1,2) //

\

bind 和 apply、call 区别

\

var a ={

name : "Cherry",

fn : function (a,b) {

console.log( a + b)

}

}

\

var b = a.fn;

b.bind(a,1,2)

会发现并没有输出,这是为什么呢, MDN 上的文档说明:

\

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

\

所以,bind 是创建一个新的函数,必须要手动去调用:

\

var a ={

name : "Cherry",

fn : function (a,b) {

console.log( a + b)

}

}

\

var b = a.fn;

b.bind(a,1,2)() //

三、请说出至少 10 个 HTTP 状态码,并描述各状态码的意义。

常见的14个HTTP状态码:

200 请求成功,一般用于GET与POST请求

204 表示客户端发送给客户端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分(没有资源可以返回)

206 表示客户端进行了范围请求,并且服务器成功执行了这部分的GET请求

301 永久性重定向,表示请求的资源被分配了新的URL,之后应使用更改的URL

302 临时性重定向,表示请求的资源被分配了新的URL,希望本次访问使用新的URL

301与302的区别:前者是永久移动,后者是临时移动(之后可能还会更改URL)

303 请求的资源被分配了新的URL,应使用GET方法定向获取请求的资源

302与303的区别:后者明确表示客户端应当采用GET方式获取资源

304 客户端发送附带条件(是指采用GET方法的请求报文中包含if-Match、If-Modified-Since、If-None-Match、If-Range、If-Unmodified-Since中任一首部)的请求时,服务器端允许访问资源,但是请求为满足条件的情况下返回改状态码

307 临时重定向,与303有着相同的含义,307会遵照浏览器标准不会从POST变成GET;(不同浏览器可能会出现不同的情况)

400 语义有误,当前请求无法被服务器理解或者请求参数有误

401 未经许可,需要通过HTTP认证

403 服务器拒绝该次访问(访问权限出现问题)

404 服务器无法根据客户端的请求找到资源(网页)

500 服务器错误,导致了无法完成对请求的处理

503 服务器暂时处于超负载或正在进行停机维护,无法处理请求

四、如何实现数组去重?--著名面试题:

假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
你要写一个函数 unique,使得
unique(array) 的值为 [1,5,2,3,4]
也就是把重复的值都去掉,只保留不重复的值。

要求写出两个答案:

  1. 一个答案不使用 Set 实现(6)
  2. 另一个答案使用 Set (4)
  3. (附加分)使用了 Map / WeakMap 以支持对象去重的,5 。
  4. 说出每个方案缺点的, 2 。

1、不使用 Set 实现(使用indexof)

创建对象,遍历数组,将数组中的值设为对象的属性,并给该属性赋初始值1,每出现一次,对应的属性值增加1,这样,属性值对应的就是该元素出现的次数了

let arr = [1,5,2,3,4,2,3,1,3,4];

function unique(ary) {

return ary.filter(function (item,index,a) {

return ary.indexOf(item)===index;

})

}

console.log(unique(arr));

\

2、使用set实现(使用了map)

ES6中新增了数据类型set,set的一个最大的特点就是数据不重复。Set函数可以接受一个数组(或类数组对象)作为参数来初始化,利用该特性也能做到给数组去重

(set不是一种数据类型,是一种数据结构;成员唯一)

let arr = [1,5,2,3,4,2,3,1,3,4];

function unique(ary) {

let newAry =[];

let map = new Map();

for(let i=0;i<ary.length;i++){

if(!map.has(ary[i])){

map.set(ary[i],true);

newAry.push(ary[i]);

}

}

}

unique(arr);

\

3、优缺点

方法一 优点: '1'和1可以区分;缺点: 对象不可以去重 因为对象不可以用来比较;NaN不可以去重

方法二 使用map的优点:NaN可以去重;缺点:对象不可以去重

五、DOM 事件相关

1. 什么是事件委托?

事件委托,通俗的说就是将元素的事件委托给它的父级或者更外级的元素处理,它的实现机制就是事件冒泡。

function delegate(element, eventType, selector, fn) {

element.addEventListener(eventType, e => {

let target = e.target

while (!target.matches(selector)) {

if (element === target) {

target = null

break

}

target = target.parentNode

}

target && fn.call(target, e, target)

})

return element

}

2. 怎么阻止默认动作?

return false不能适用于直接用onclick绑定的事件,所以当我们使用这种绑定事件方式时,我们还是需要采用e.preventDefault()这个函数

function defaultEvent(e){

if(e && e.preventDefault) {

//非IE浏览器

e.preventDefault();

} else {

//IE浏览器(IE11以下)

window.event.returnValue = false;

}

return false;

}

3. 怎么阻止事件冒泡?

1.阻止事件冒泡,使成为捕获型事件触发机制.

\

function stopBubble(e) {

\

//如果提供了事件对象,则这是一个非IE浏览器

\

if( e && e.stopPropagation )

\

//因此它支持W3C的stopPropagation()方法

\

e.stopPropagation();

\

else

\

//否则,我们需要使用IE的方式来取消事件冒泡

\

window.event.cancelBubble =true;

\

}

\

function bubbles(e){

var ev = e || window.event;

if(ev && ev.stopPropagation) {

//非IE浏览器

ev.stopPropagation();

} else {

//IE浏览器(IE11以下)

ev.cancelBubble = true;

}

}

六、如何理解 JS 的继承?

1. 答出基于原型的继承

2. 答出基于 class 的继承

一、原型链继承

\

将父类的实例作为子类的原型

\

function Parent() {

this.isShow = true

this.info = {

name: "yhd",

age: 18,

};

}

\

Parent.prototype.getInfo = function() {

console.log(this.info);

console.log(this.isShow); // true

}

\

function Child() {};

Child.prototype = new Parent();

\

let Child1 = new Child();

Child1.info.gender = "男";

Child1.getInfo(); // {name: "yhd", age: 18, gender: "男"}

\

let child2 = new Child();

child2.getInfo(); // {name: "yhd", age: 18, gender: "男"}

child2.isShow = false

\

console.log(child2.isShow); // false

\

  • 优点:

父类方法可以复用

  • 缺点:

父类的所有引用属性(info)会被所有子类共享,更改一个子类的引用属性,其他子类也会受影响

子类型实例不能给父类型构造函数传参


\

二、盗用构造函数继承(构造函数继承)

\

在子类构造函数中调用父类构造函数,可以在子类构造函数中使用call()和apply()方法

\

function Parent() {

this.info = {

name: "yhd",

age: 19,

}

}

\

function Child() {

Parent.call(this)

}

\

let child1 = new Child();

child1.info.gender = "男";

console.log(child1.info); // {name: "yhd", age: 19, gender: "男"};

\

let child2 = new Child();

console.log(child2.info); // {name: "yhd", age: 19}

\

​ 通过使用call()或apply()方法,Parent构造函数在为Child的实例创建的新对象的上下文执行了,就相当于新的Child实例对象上运行了Parent()函数中的所有初始化代码,结果就是每个实例都有自己的info属性。

​ 1、传递参数

​ 相比于原型链继承,盗用构造函数的一个优点在于可以在子类构造函数中像父类构造函数传递参数。

function Parent(name) {

this.info = { name: name };

}

function Child(name) {

//继承自Parent,并传参

Parent.call(this, name);

//实例属性

this.age = 18

}

\

let child1 = new Child("yhd");

console.log(child1.info.name); // "yhd"

console.log(child1.age); // 18

\

let child2 = new Child("wxb");

console.log(child2.info.name); // "wxb"

console.log(child2.age); // 18

\

​ 在上面例子中,Parent构造函数接收一个name参数,并将他赋值给一个属性,在Child构造函数中调用Parent构造函数时传入这个参数, 实际上会在Child实例上定义name属性。为确保Parent构造函数不会覆盖Child定义的属性,可以在调用父类构造函数之后再给子类实例添加额外的属性

优点:

可以在子类构造函数中向父类传参数

父类的引用属性不会被共享


\

缺点:

子类不能访问父类原型上定义的方法(即不能访问Parent.prototype上定义的方法),因此所有方法属性都写在构造函数中,每次创建实例都会初始化



\

三、组合继承

\

组合继承综合了原型链继承和盗用构造函数继承(构造函数继承),将两者的优点结合了起来,

基本的思路就是使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性,这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性

\

function Parent(name) {

this.name = name

this.colors = ["red", "blue", "yellow"]

}

Parent.prototype.sayName = function () {

console.log(this.name);

}

\

function Child(name, age) {

// 继承父类属性

Parent.call(this, name)

this.age = age;

}

// 继承父类方法

Child.prototype = new Parent();

\

Child.prototype.sayAge = function () {

console.log(this.age);

}

\

let child1 = new Child("yhd", 19);

child1.colors.push("pink");

console.log(child1.colors); // ["red", "blue", "yellow", "pink"]

child1.sayAge(); // 19

child1.sayName(); // "yhd"

\

let child2 = new Child("wxb", 30);

console.log(child2.colors); // ["red", "blue", "yellow"]

child2.sayAge(); // 30

child2.sayName(); // "wxb"


\

​ 上面例子中,Parent构造函数定义了name,colors两个属性,接着又在他的原型上添加了个sayName()方法。Child构造函数内部调用了Parent构造函数,同时传入了name参数,同时Child.prototype也被赋值为Parent实例,然后又在他的原型上添加了个sayAge()方法。这样就可以创建 child1,child2两个实例,让这两个实例都有自己的属性,包括colors,同时还共享了父类的sayName方法

​优点:

1、父类的方法可以复用

2、可以在Child构造函数中向Parent构造函数中传参

3、父类构造函数中的引用属性不会被共享



\

四、原型式继承

\

对参数对象的一种浅复制

\

function objectCopy(obj) {

function Fun() { };

Fun.prototype = obj;

return new Fun()

}

\

let person = {

name: "yhd",

age: 18,

friends: ["jack", "tom", "rose"],

sayName:function() {

console.log(this.name);

}

}

\

let person1 = objectCopy(person);

person1.name = "wxb";

person1.friends.push("lily");

person1.sayName(); // wxb

\

let person2 = objectCopy(person);

person2.name = "gsr";

person2.friends.push("kobe");

person2.sayName(); // "gsr"

\

console.log(person.friends); // ["jack", "tom", "rose", "lily", "kobe"]

\

  • 优点:

父类方法可复用

\

  • 缺点:

1、父类的引用会被所有子类所共享

2、子类实例不能向父类传参

\

ES5的Object.create()方法在只有第一个参数时,与这里的objectCopy()方法效果相同


\

五、寄生式继承

\

使用原型式继承对一个目标对象进行浅复制,增强这个浅复制的能力

\

function objectCopy(obj) {

function Fun() { };

Fun.prototype = obj;

return new Fun();

}

\

function createAnother(original) {

let clone = objectCopy(original);

clone.getName = function () {

console.log(this.name);

};

return clone;

}

\

let person = {

name: "yhd",

friends: ["rose", "tom", "jack"]

}

\

let person1 = createAnother(person);

person1.friends.push("lily");

console.log(person1.friends);

person1.getName(); // yhd

\

let person2 = createAnother(person);

console.log(person2.friends); // ["rose", "tom", "jack", "lily"]


\

六、寄生式组合继承

function objectCopy(obj) {

function Fun() { };

Fun.prototype = obj;

return new Fun();

}

\

function inheritPrototype(child, parent) {

let prototype = objectCopy(parent.prototype); // 创建对象

prototype.constructor = child; // 增强对象

Child.prototype = prototype; // 赋值对象

}

\

function Parent(name) {

this.name = name;

this.friends = ["rose", "lily", "tom"]

}

\

Parent.prototype.sayName = function () {

console.log(this.name);

}

\

function Child(name, age) {

Parent.call(this, name);

this.age = age;

}

\

inheritPrototype(Child, Parent);

Child.prototype.sayAge = function () {

console.log(this.age);

}

\

let child1 = new Child("yhd", 23);

child1.sayAge(); // 23

child1.sayName(); // yhd

child1.friends.push("jack");

console.log(child1.friends); // ["rose", "lily", "tom", "jack"]

\

let child2 = new Child("yl", 22)

child2.sayAge(); // 22

child2.sayName(); // yl

console.log(child2.friends); // ["rose", "lily", "tom"]

\

优点:

1、只调用一次父类构造函数

2、Child可以向Parent传参

3、父类方法可以复用

4、父类的引用属性不会被共享

  • 寄生式组合继承可以算是引用类型继承的最佳模式

七 数组排序

给出正整数数组 array = [2,1,5,3,8,4,9,5]
请写出一个函数 sort,使得 sort(array) 得到从小到大排好序的数组 [1,2,3,4,5,5,8,9]
新的数组可以是在 array 自身上改的,也可以是完全新开辟的内存。

不得使用 JS 内置的 sort API let arr = [2,1,5,3,8,4,9,5]

\

console.log(arr)

arr.sort((a, b) => {

console.log("b:" + b)

console.log("a:" + a)

return a - b

})

console.log(arr)

八 你对 Promise 的了解?

答题要点:

  1. Promise 的用途
  2. 如何创建一个 new Promise(
  3. 如何使用 Promise.prototype.then(可查 MDN)
  4. 如何使用 Promise.all(可查 MDN)
  5. 如何使用 Promise.race(可查 MDN)

一、什么是Promise?我们用Promise来解决什么问题?

Promise 是异步编程的一种解决方案:

从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。

promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行。我相信大家经常写这样的代码:// 当参数a大于10且参数fn2是一个方法时 执行fn2

function fn1(a, fn2) {

if (a > 10 && typeof fn2 == 'function') {

fn2()

}

}

fn1(11, function() {

console.log('this is a callback')

}) 一般来说我们会碰到的回调嵌套都不会很多,一般就一到两级,但是某些情况下,回调嵌套很多时,代码就会非常繁琐,会给我们的编程带来很多的麻烦,这种情况俗称——回调地狱。这时候我们的promise就应运而生、粉墨登场了promise是用来解决两个问题的:回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象promise可以支持多个并发的请求,获取并发请求中的数据这个promise可以解决异步的问题,本身不能说promise是异步的。

\

2、如何创建一个 new Promise?

Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法。

使用new Promise

let p = new Promise((resolve, reject) => {

//做一些异步操作

setTimeout(() => {

console.log('执行完成');

resolve('我是成功!!');

}, 2000);

}); Promise的构造函数接收一个参数:函数,并且这个函数需要传入两个参数:resolve :异步操作执行成功后的回调函数reject:异步操作执行失败后的回调函数。

\

3、如何使用 Promise.prototype.then?

Promise.prototype.then()返回值

var p1 = function(){

return new Promise((resolve,reject)=>{

console.log('p1')

resolve()

})

}

var p2 = function(){

return new Promise((resolve,reject) => {

console.log('p2')

resolve('I am p2 and I m resolved')

})

}

var p3 = function(){

return new Promise((resolve,reject) =>{

console.log('p3')

reject('I am p3 and Im rejected')

})

}

var p4 = function(){

return new Promise((resolve,reject) => {

setTimeout(()=>{

console.log('p4')

// resolve('p4 after 3s resolve')

reject('p4 after 3s reject')

},3000)

})

}

对于一个Promise来说,当一个Promise完成(fulfilled)或者失败(rejected),返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回:

\

如果then中的回调函数返回一个值,那么then返回的Promise就会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值

\

  • 例子

p1().then(() => {

return 'I am return a val'

}).then((val) => {

console.log(val) // I am return a val

})

如果then中的回调函数没有返回值,那么then返回的Promise将会成为接受状态,并且该接受状态的回调函数的参数值为undefined

\

p1().then(()=>{

console.log('nothing return')

}).then((val)=>{

console.log(val); // undefined

})

如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值

\

p1().then(()=>{

throw new Error('I am Error')

}).then(()=>{

console.log('I am not called') // Not called

}).catch((err) => {

console.log('err',err) // err I am Error

})

如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值

\

p1().then(()=>{

return p2() // 不加return 这个回调将没有返回值,下面val为undefined

}).then((val) => {

console.log(val) // I am p2 and I m resolved

})

如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值

\

p1().then(()=>{

return p3() // 不加return,这个回调没有返回值,但会调用下个then,val为undefined

}).then((val)=>{

console.log(val) // Not called

}).catch((err)=>{

console.log('err',err) // err I am p3 and Im rejected

})

如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同,同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的

\

p1().then(()=>{

return p4()

}).then((val)=>{

console.log(val) // p4 resolve -> p4 after 3s resolve

}).catch((err) => {

console.log('err',err) // err p4 after 3s reject

})

4、如何使用 Promise.all?

Promise.all()的类型签名:

\

Promise.all(promises: Iterable): Promise

\

返回情况:

完成(Fulfillment):

如果传入的可迭代对象为空,Promise.all 会同步地返回一个已完成(resolved)状态的promise。

如果所有传入的 promise 都变为完成状态,或者传入的可迭代对象内没有 promise,Promise.all 返回的 promise 异步地变为完成。

在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)。

失败/拒绝(Rejection):

如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。

  • 例子:

const promises = [

Promise.resolve('a'),

Promise.resolve('b'),

Promise.resolve('c'),

];

Promise.all(promises)

.then((arr) => assert.deepEqual(

arr, ['a', 'b', 'c']

));

如果其中的一个 promise 被拒绝,那么又是什么情况:

const promises = [

Promise.resolve('a'),

Promise.resolve('b'),

Promise.reject('ERROR'),

];

Promise.all(promises)

.catch((err) => assert.equal(

err, 'ERROR'

));

\

5、如何使用Promise.race

Promise.race()方法的定义:

Promise.race(promises: Iterable): Promise

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

  • 例子

const promises = [

new Promise((resolve, reject) =>

setTimeout(() => resolve('result'), 100)), // (A)

new Promise((resolve, reject) =>

setTimeout(() => reject('ERROR'), 200)), // (B)

];

Promise.race(promises)

.then((result) => assert.equal( // (C)

result, 'result'));

在第 A 行,Promise 是完成状态 ,所以 第 C 行会执行(尽管第 B 行被拒绝)。

如果 Promise 被拒绝首先执行,在来看看情况是嘛样的:

const promises = [

new Promise((resolve, reject) =>

setTimeout(() => resolve('result'), 200)),

new Promise((resolve, reject) =>

setTimeout(() => reject('ERROR'), 100)),

];

Promise.race(promises)

.then(

(result) => assert.fail(),

(err) => assert.equal(

err, 'ERROR'));

注意,由于 Promse 先被拒绝,所以 Promise.race() 返回的是一个被拒绝的 Promise

这意味着Promise.race([])的结果永远不会完成。

九 说说跨域

1、什么是同源

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

image.png

  • 同源策略限制内容有:

Cookie、LocalStorage、IndexedDB 等存储性内容

  • DOM 节点

AJAX 请求发送后,结果被浏览器拦截了

2、什么是跨域

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域,不同域之间相互请求资源,就算作“跨域”。因为JavaScript出于安全考虑,有同源策略。

有一点必须要注意:跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

3、JSONP 跨域

JSONP是JSON with padding的简写,是应用JSON的一种新方法,JSONP看起来和JSON差不多,只不过是被包含在函数调用的JSON,像这样:callback({name: 'nany'})。

  • JSONP跨域原理

通过

  • 前端:

4、CORS 跨域

CORS是跨源AJAX请求的根本解决方法。JSONP只能发GET请求,但是CORS允许任何类型的请求。

整个CORS通信过程都是浏览器自动完成的,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

一、两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。只要同时满足以下两大条件,就属于简单请求。

  • (1) 请求方法是以下三种方法之一:

HEAD

GET

POST

  • (2)HTTP的头信息 Request Headers 不超出以下几种字段:

Accept

Accept-Language

Content-Language

Last-Event-ID

Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

二、简单请求

  • 基本流程

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

  • 例子 浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

GET /cors HTTP/1.1

Origin: api.bob.com

Host: api.alice.com

Accept-Language: en-US

Connection: keep-alive

User-Agent: Mozilla/5.0...

\