月度归档:2020年10月

浏览器跨域访问到底是怎么回事

从何说起

Access to XMLHttpRequest at 'http://127.0.0.1:3000/' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

  如果你在控制台中看到类似以上的输出信息,这就是跨域访问的拦截提示;那到底什么叫跨域呢?简单一点说,你网页是xxx.com/xxx.html,这个网页中的元素不允许访问除这个域名以外的其他域名下的资源(也许不严谨,化繁为简能快速理解其本质)。再具体一点就是这个页面下的Ajax请求,不能去调类似http://abc.com/xxxx这样的接口,必须是xxx.com下的接口。

出浅入深

  • 什么是域?简单一点,就是域名,http://www.abc.com下的网页只能调http://www.abc.com/开头的接口,否则就是跨域了,跨域就会报错,上面那个错误提示;
  • 为什么不允许跨域?为了安全!废话,到底哪里有风险?简单描述一下, 我们的浏览器可以同时打开多个网站,也就是说浏览器是一个公共场所。不同的站点请求下来的数据可能会被互相“看到”,就像公共澡堂,大家坦诚相见,难免会有非份之想。所以要“隔开”,每个站点下的Ajax只能请求自己站点下的数据,不能请求其他站点的数据,即不允许跨域访问。
  • 到了这里或许你已经有了一点点概念了,原来跨域访问的限制就A站点下的脚本只能从A拿数据,B站点下的脚本只能从B拿数据。如果我A站点确实想拿B站点的数据怎么办,有没有办法呢?肯定是有的,你在B站点上做相关设置,允许别人拿咯。
  • 误区来了,由于在服务器上做相关匹配就可以允许跨域,所以很多同学认为只所以不能跨域访问,是服务器做了相关限制?!这是不对的。请注意:跨域访问限制是浏览器的行为。 阻止你跨域进行Ajax请求的是浏览器,不是服务端,并且事实上服务端已经返回了数据到本地,被浏览器拦截下来了,没有呈现到页面上来还抛出了异常。
  • 还有一点疑惑,如果跨域限制是浏览器行为,为什么是在服务器上做相关配置?我们可以理解为“服务器授权”,默认情况下浏览器是不允许跨域的,这样可以为各站点数据增加一点安全保护,但如果你的站点在响应请求的时候,带回信息告诉浏览器:“没事,你尽管让他们都来请求好了,我有什么给什么。”,这样一来跨域的限制就解除了。
  • 再多说两句,为什么App调接口没有跨域的问题?因为跨域的限制是浏览器行为,服务器本身并没有限制访问的源是谁。

简单总结

  跨域访问限制是浏览器为了数据安全而设计的一种保护机制,跨域指的是不两只域名(简单一点就这么认为吧,不要纠结)下的站点数据不能互相访问,换句话说页面中的Ajax请求只能当前站点下的接口,否则就会被抛出异常。

  值得注意的是,这种禁止跨域访问的行为是浏览器的“规矩”,服务端本身没并有这种限制。但可以在服务端做相关配置来允许跨域访问,这是服务端在“授权”,让浏览器解除跨域访问本站的限制。

扩展与思考

  前面一直提到的是Ajax请求,是不是跨域针对的就是Ajax?我的理解是从该页面发出的所有网络请求都在其限制范围内。然而浏览器又并没有对所有的网络请求都做了跨域的限制。

  什么意思呢,比如说img标签中的src属性,当前页面引入一张其他站点的图片资源,这其实是跨域的,可这个是却是合法的,类似的现象还有css资源外链与javascript资源。换句话说,我可以通过script标签的src属性从别的域中拿到数据,浏览器没拦截,然后再想办法将数据取出来不就可以了吗?想得挺美,事实上还真可以。jsonp就是依据这个原理实现的,相关细节不在这里讨论,点到为止。

ES6中的Symbol、Iterator、Generator的简单理解

  本来是想说说生成器Generator的,但我发现和迭代有关,所以要先讲讲迭代器Iterator,可迭代器又会扯到Symbol,所以拉出来一起说说;

Symbol

Symbol是ES6引进的一种新数据类型,它代表一个独一无二的值,至此JavaScript一共有7种数据类型,分别是:UndefindedStringSymbolObjectNullNumberboolean

Symbol是什么

Symbol到底是什么?先来个栗子看看效果:

let s1 = Symbol();
let s2 = Symbol();
console.log(s1, typeof s1); // Symbol() "symbol"
console.log(s1 === s2); // false

let s3 = Symbol('symbol');
let s4 = Symbol('symbol');
console.log(s3, s4);    // Symbol(symbol) Symbol(symbol)
console.log(s3 === s4); // false

let s5 = Symbol.for('symbol');
let s6 = Symbol.for('symbol');
console.log(s5, s6);    // Symbol(symbol) Symbol(symbol)
console.log(s5 === s6); // true
console.log(s4 === s5); // false
  • Symbol有两种创建方式,一种是Symbol(),一种是Symbol.for();
  • 注意: Symbol不是对象,不要使用new关键字;
  • Symbol()函数可接收一个字符串为参数,仅做描述使用,不代表Symbol的唯一性;
  • Symbol.for()接收一个字符串做为参数,代表Symbol的唯一性,相同的字符串能取到唯一的Symbol;

Symbol怎么用

既然Symbol的特性代表独一无二,那典型的应用场景就是需要唯一性的场所,比如说枚举值、对象的属性名或方法名等。
* 以性别为例,我们定义一个gender对象,分别有malefemale两个属性,我们在应用的时候可能只需要区分这两者不一样即可,并不关心他们实际值是多少,可以将值设置为Symbol类型。(如果需要与服务端交互另说,这里主要是为了说明Symbol的用法,勿喷)

// 性别:常规定义
const gender = {
    male: 'male',   // 男
    female: 'female'    // 女
}
// 性别:Symbol定义
const gender = {
    male: Symbol(),
    female: Symbol()
}
  • 做为对象的属性名或方法名在ES6中也已经很常见了,可查看数组的原型对象不可以找到名称为Symbol类型的方法。
  • 举例说明,假设有一个person对象,我想扩展一个方法,但又不确定新加的方法名是否会引起冲突,所以使用Symbol类型的值做为方法名
// person对象
let person = {
    name: 'yusian',
    age: 21,
    say() {
        console.log('saying...');
    }
}
// 自定义方法名
let methods = {
    say: Symbol('say'),
    run: Symbol('run')
}
person[methods.say] = function () {
    console.log('symbol saying...');
}
person[methods.run] = function () {
    console.log('symbol running...');
}
person.say();   // saying...
person[methods.say]();  // symbol saying...
person[methods.run]();  // symbol running...
  • 为什么要说Symbol类型的方法名呢,我们以后会经常使用Symbol类型的数据做为方法名来扩展对象功能吗?我觉得不应该这样,这并不是一个很好的解决方案。比较有价值的是ES6提供的内置的Symbol值
  • 重点来了 前面所有Symbol相关的点都为这一刻,以Symbol.hasInstance为例,定义一个Person类,在类中定义一个静态方法名为Symbol.hasInstance,那么当对该类进行instanceof调用时就会执行该静态方法,返回值即为调用返回结果。
class Person {
    // 方法名一般为字符串,Symbol类型做为方法名需要加方括号
    static [Symbol.hasInstance] = function (foo) {
        console.log('hasInstance, ', foo);
        return false;
    }
}
let p = new Person();
console.log(p instanceof Person);   // false
  • 大致用法就是这个样子,更多内置Symbol值参考:MDN
  • 有了这个基础后面就可以讲Iterator了

参考链接:阮一峰ES6入门
继续阅读

发送Ajax请求的4种方式

1、XMLHttpRequest

XMLHttpRequest是WebAPI中的标准对象,可发起Ajax请求。三步曲:
* 创建XMLHttpRequest对象;
* open方法设置请求方式及url地址;
* send方法正式发起请求;

<script>
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = event => {
        console.log(event);
    }
    xhr.open('get', 'https://jsonplaceholder.typicode.com/todos');
    xhr.send();
</script>

除此之外,还有一些常用的属性及方法
* responseType属性设置返回数据类型;
* onreadystatechange属性监听状态变化,获取返回数据;
* timeout属性设置超时时间;
* ontimeout属性设置超时回调方法;
* onerror属性设置错误回调方法;
* 官方文档:MDN参考链接

2、Fetch

  fetch是一个全局作用下的函数,该函数返回一个Promise,是官方提供的一个新的发起Ajax请求的WebAPI。

  fetch使用也非常简单,默认get请求可以只传入一个url地址。

fetch('https://jsonplaceholder.typicode.com/todos/1').then(response => {
    console.log(response);
    return response.json();
}).then(json => {
    console.log(json);
})

引用官方的一段话:

Fetch 提供了对 RequestResponse (以及其他与网络请求有关的)对象的通用定义。使之今后可以被使用到更多地应用场景中:无论是 service worker、Cache API、又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式。

  我的理解是,fetch在将来会成为XMLHttpRequest的一个替代品,因为fetch对异步请求和响应进行了重新设计,会更加通用与灵活,使用起来也很简单。但就目前而言,兼容性还不是很好,一些功能还在完善中……

3、jQuery

  jQuery绝对是一个伟大的发明,它使得dom操作变得简单而直观,并且还解决了一大部分的兼容性问题,在Ajax请求方面,jQuery同样对XMLHttpRequest进行了封装,使得Ajax请求变得简单而直观。
* 常规调用

// jQuery Ajax请求
.ajax('https://jsonplaceholder.typicode.com/todos', {
    data: { // 数据
        a: 1,
        b: 2
    },
    headers: {  // 头部信息
        ContentType: 'application/json',
        a: 1
    },
    success: function (response) {
        console.log('ajax: ', response);
    }
}).done(function (response) {
    // 结果回调
    console.log('done', response);
})
// 最简调用.ajax('https://jsonplaceholder.typicode.com/todos').then(response => {
    console.log(response);
})
  • jQuery针对get与post请求还有两个简化的调用
// jQuery Ajax Get请求
.get('http://localhost:3000/jquery', {
    a: 1,
    b: 2
}, function (response) {
    console.log('get:', response);
})


// jQuery Ajax Post请求.post('http://localhost:3000/jquery', {
    a: 1,
    b: 2
}, function (response) {
    console.log(response);
})

4、Axios

  • axios的使用与jQuery类似
// 设置默认参数
axios.defaults.baseURL = 'http://127.0.0.1:3000';

Axios发送post请求
axios.post('http://127.0.0.1:3000/axios', {
    a: 1,
    b: 2
}, {
    headers: {
        'ContentType': 'application/json'
    }
}).then(function (response) {
    console.log(response);
})

// axios发送请求
axios({
    method: 'get',
    url: 'axios',
    headers: {
        // 设置Content-Type无效
        'Content-Type': 'application/json'
    },
    params: {
        a: 1,
        b: 2
    },
    data: {
        key: 'value'
    }
}).then(response => {
    console.log(response);
})