同步:顺序执行,优点是静态预判结果可控, 缺点就是如果某一步任务执行慢,那就会影响到后续的所有代码
异步:乱序执行,优点就是不会阻塞代码,但是缺点也很明显就是顺序不可控
以银行排队办业务为例
* 1. 同步: 默认排队叫号, 依次办理
* 2. 异步: 耗时任务(如修改密码忘带身份证)则离开队列, 后面任务继续
* 3. 任务队列: 取了身份证回来了, 就待在"任务队列"中等待再次叫号
哪些是异步任务(耗时)?
* 1. 定时任务: setTimeout, setInterval
* 2. 事件监听: addEventListener
* 3. 网络请求: ajax, promise,fetch
* 4. 文件读写等涉及IO的操作
(二) 进程与线程
* 1. 进程: 程序的运行状态, 执行实例
* 2. 一个cpu同一时刻只能执行一个进程,通过上下文切换实现多任务,除非多核
* 3. 线程: 进程中的某个任务,即一个进程,可以由多个线程完成
* 4. js的特征,决定了它只能是单线程,例如dom操作中, 增删元素就不可能同时进行
* 5. 单线程可确保js按用户要求的顺序执行,并确定业务逻辑正确,结果可控
* 6. 但是单线程,也决定了所有任务必须在一个执行栈中完成,导致耗时任务必然会阻塞整个线程
* 7. 解决文案: 任务队列与事件循环(事件轮询)
比较通俗的解释:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
(三) 单线程,任务队列,事件循环之间的关系与协同
* 1. js所有任务都在主线程中"同步"执行
* 2. 异步任务以回调的形式声明,并离开主线程,交给多线程的浏览器去执行
* 3. 异步任务执行完毕,进入到"任务队列"中排队等待进入主线程执行
* 4. 主线程同步任务全部完成后, 通过"事件循环"查询"任务队列"中是否有等待的任务
* 5. 如果有,则该就绪任务进入主线程同步执行
* 6. 该任务完成后, 事件循环会再次取出下一个就绪的异步任务进入主线程
* 7. 以上过程不断重复, 直到主线程中的同步任务, 任务队列中的异步任务全部执行完毕
*/
下面的代码按正常顺序会输出100 .200 .300 但是因为输出200的地方是一个定时器,也就是异步任务,因为在JS中会先执行同步任务,在执行异步任务 所以会先输出100.300 最后再输出200.
console.log(100);
setTimeout('console.log(200)', 1000);
console.log(300);
传统方式,信息会在新页面中或者弹窗中查看,用户体验不好 —>
Ajax: 局部更新,用户不需要离开当前页面就可以看到新内容, 单页面应用SPA(基于异步任务)
传统XHR的编程步骤
**
* 1. 创建对象: new XMLHttpRequest();
* 2. 响应类型: xhr.responseType = "json";
* 3. 配置参数: xhr.open("GET", url, true);
* 4. 请求回调: xhr.onload = () => console.log(xhr.response);
* 5. 失败回调: xhr.onerror = () => console.log("Error");
* 6. 发起请求: xhr.send(null);
*
* xhr 至少监听2个事件: load,error, 调用2个函数: open,send
* post请求,需要设置一下请求头与请求的数据,其它与get请求完全相同
*/
下面是实例演示
在页面创建两个按钮并创建点击事件
<button onclick="getUser1(this)">查询用户信息: XHR</button>
<button onclick="getUser2(this)">查询用户信息: Fetch</button>
然后根据上面的步骤一步一步创建xhr
function getUser1(btn) {
//1. 创建对象
const xhr = new XMLHttpRequest();
//2. 响应类型
xhr.responseType = 'json';
//3. 配置参数
// 不传get参数,默认返回全部用户, 用表格
let url = 'http://website.io/users.php';
// 如果有get参数,就是用户id,输入对应的id 获取对应的值
url = 'http://website.io/users.php?id=1';
//url:当前的服务器地址
xhr.open('GET', url);
//4. 请求回调
xhr.onload = () => {
console.log(xhr.response);
// 将数组渲染到页面中
render(xhr.response, btn);
};
//5. 失败回调
xhr.onerror = () => console.log('Error');
//6. 发起请求
xhr.send(null);
}
fetch请求语法
fetch: 基于Promise, 返回 Promise对象
// fetch
/**
* fetch(url) //当前请求的地址
* .then(res) //then(然后的意思)链式执行,一步一步的写
* .then(...)
* .catch(err)
*/
function getUser2(btn) {
let url = 'http://website.io/users.php';
// fetch的所有操作都是异步的,但是通过then使用顺序可控
fetch(url)
.then(function (response) {
return response.json();
})
.then(function (json) {
// 控制台
console.log(json);
// 将数组渲染到页面中
render(json, btn);
});
}
现在推荐使用fetch请求,更加简便明了
fetch 请求中的api(浏览器原生实现的异步技术,可以直接用)
fetch: 基于Promise, 返回 Promise对象
随机生成json数据的网站:http://jsonplaceholder.typicode.com/users
通过上方的网站可以生成一组假的JSON数据
然后通过上方网站提供的代码访问拿到其中的第一条数据(以列表的形式渲染到页面)
(路径最后的todos/1 表示第一条数据)
这里如果直接访问会出现跨域的错误,需要在php中进行处理
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
如果我们想拿到所有的一组数据,就将路径直接改为/users(如果请求一组数据,会以表格的形式渲染到页面)
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(json => console.log(json));
上方的代码就是请求数据接口的代码,后期可以直接将需要访问的接口网址直接替换进去
同源策略要求:协议相同,域名相同,端口相同,否则就是跨域请求,这是禁止的(这个禁止是脚本禁止,标签的话一般不受影响)
js脚本禁止跨域是因为js脚本可以动态的进行dom操作,对网站进行增删改查等,这样又很大的安全隐患
//写在PHP里面,表示允许所有跨域请求
header("access-control-allow-origin: *");
创建一个按钮。点击之后会返回对应的数据,其实就是创建一个点击事件,点击之后会通过上面的方法,访问php数据,然后传回并渲染到页面
function getUser(btn) {
let url = 'http://website.io/users.php?id=1';
fetch(url)
.then(response => response.json())
.then(json => {
console.log(json);
// 将数组渲染到页面中
render(json, btn);
});
ECMA2017中, 有async,await,用来简化异步回调方法
首先在上方对象的前面加上一个async关键字(这个关键字的意思是,这是一个异步操作)
然后在下面创建一个常量接收到数据,在fetch之前加上 await关键字(这个关键字只能在异步操作中使用)
async function getUser(btn) {
let url = 'http://website.io/users.php';
// 这是一个异步耗时操作,需要等待结果才可进入下一步
const response = await fetch(url);
// 获取到结果之后,再转为json
const result = await response.json();
console.log(result);
// 将数组渲染到页面中
render(result, btn);
}
模块成员的导出与导入
js中, 所有代码共用一个全局作用域: window,有时候会产生一些冲突,在ES6中,可以通过模块的来解决这个问题
1.一个模块就是一个JS文档
2.模块有自己独立的作用域
3.模块内的成员,默认全部私有,只有导出才能被外部使用
导出与导入
1.逐个导出:
//第一个方法:在变量前面加上export
export let username = '猪老师';
//第二种方法:也是添加export 只是这是导出一个函数
export function hello(username) {
return `Hello, ${username}`;
}
导入模块
通过import关键字导入
默认script在浏览器环境下不支持模块,需要指定type
<script type="module">
// js中, 所有代码共用一个全局作用域: window
// 导入模块
//import 导入模板中的变量或函数,需要用模板字面量来接收,这个模板字面量的名称要与模板中定义的变量或者函数名一致
import { username, hello } from './modules/m1.js';
console.log(username);
console.log(hello(username));
</script>
2.逐个导出
// (1) 声明
let username = '猪老师';
function hello(username) {
return `Hello, ${username}`;
}
// (2) 导出: 对象字面量 {...}
export { username, hello };
在导出或者导入时,可能会出现命名重复,这时候可以使用别名导出
let username = '欧阳老师';
function hello(username) {
return `Hello, ${username}`;
}
// (2) 别名导出: 隐藏模块成员的真实名称, as
export { username as myname, hello as getName };
在导出的时候通过as关键字,为需要到导出的数据定义一个别名,同样的,在使用别名后,导入使用时也需要使用别名导入
<script type="module">
import { myname,getName } from './modules/m2.js';
console.log(myname);
console.log(getName(myname));
</script>
如果导入时 导出时定义的别名还是已经被定义了,还是可以在导入时使用as关键字再次重命名,然后调用时需要用最新的名字
<script type="module">
import { myname as name,getName as get } from './modules/m2.js';
console.log(name);
console.log(get(name));
</script>
默认导出
一个模块,只能有一个默认导出
默认导出,导出的是一个值,没有命名,由导出的时候再命名
定义一个类,然后直接导出
export default class {
constructor(username) {
this.username = username;
}
getUsername() {
return `Hello, ${this.username}`;
}
}
默认导入
<script type="module">
// 导入模块的默认成员不要用对象字面量,用标识符
// 只要不用对象字面量接收模块成员,就是默认导出
import User from './modules/m3.js';
//因为默认导出的是一个类,所以需要new一个 再赋值输出
const user = new User('王老师');
console.log(user.username);
console.log(user.getUsername(user.username));
</script>
混合导出(默认成员与非默认成员的导出)
// 默认成员与非默认成员的导出
let username = '牛老师';
function hello(username) {
return `Hello, ${username}`;
}
let useremail = 'admin@php.cn';
// 私有成员, 不对外
let sex = 'male';
// 将邮箱 useremail 做为默认导出 , 起一个特殊的别名 default
export { username, hello, useremail as default };
混合导入
<script type="module">
// 接收混合导入成员,默认成员的命名,必须放在非默认的前面
//默认导入是可以随便命名,因为 默认导出时没有命名
import email, { username, hello } from './modules/m4.js';
console.log(email);
console.log(hello(username));
</script>
命名空间
导入的成员,过多时,使用{…}会很长的
可以将所有的导入成员,全部挂载到一个对象上
此时, 导入模块的成员,自动成为该对象上的属性
这个对象就是: 命名空间
注释:在导入时候 用 * (命名空间名) 将所有的对象全部挂载到同一个新的命名空间里面
import * as user from './modules/m4.js';
console.log(user);
如何访问这个新命名空间里面的成员
在访问命名空间的模块成员是,直接添加命名空间前缀
console.log(user.username);
console.log(user.hello(user.username));