day-069-sixty-nine-20230513-同步异步任务-作用域与this指向与变量自增-发布订阅设计模式-webpack
同步异步任务
-
所有的代码都是在ECStack执行环境栈-即主线程开始的。
- 这个也有人认为它是宏任务,进而得出:
- 标签由宏任务开始->把异步任务放到WebAPI任务监听队列。
- 标签主线程空闲。
- WebAPI任务监听队列->把触发后的异步任务放到EventQueue事件队列。
- EventQueue事件队列微任务->EventQueue事件队列宏任务。
- 执行过程中,标签主线程由空闲变为活动状态。
- 执行结束后,标签由活动状态又变成空闲状态。
- 重复执行第3步与第4步的过程,走到标签被关闭。
- 这个也有人认为它是宏任务,进而得出:
-
async-await与setTimeout()与Promise()及then()。
-
就算定时器写了0毫秒,但实际上也不是0毫秒,会等待浏览器最快的反应时间执行。
- 浏览器最快的反应时间一般是5-7ms,但个人实测为1-2ms左右。
-
async函数中的代码还是会以同步代码的方式来执行。
-
await后面的代码还是会以同步代码的方式来执行。
-
new Promise()内部的东西还是会以同步方式执行。
-
new Promise().then()传入的回调函数会以异步函数执行。
async function async1() {console.log("async1 start");await async2();console.log("async1 end"); } async function async2() {console.log("async2"); } console.log("script start"); setTimeout(function () {console.log("setTimeout"); }, 0); async1(); new Promise(function (resolve) {console.log("promise1");resolve(); }).then(function () {console.log("promise2"); }); console.log("script end");// console.log(//---------------------); // 同步任务 // console.log("script start"); // console.log("async1 start"); // console.log("async2"); // console.log("promise1"); // console.log("script end"); // 异步微任务 // console.log("async1 end"); // console.log("promise2"); // 等待一会,得浏览器最快反应时间 // 异步宏任务 // console.log("setTimeout");
-
-
DOM绑定事件与Promise()与then()方法。
-
EventQueue队列中,异步微任务优先级要高于异步宏任务,即便异步异任务先排队。
let body = document.body; body.addEventListener("click", function () {Promise.resolve().then(() => {console.log(1);});console.log(2); }); body.addEventListener("click", function () {Promise.resolve().then(() => {console.log(3);});console.log(4); });// console.log(//---------------------); // 同步事件结束,会等待一会,等待body被点击。 // 两个点击的宏任务放到异步宏任务队列中。 // 第1次个宏任务,创建了微任务1。宏任务同步代码执行完后,就先执行了第1个微任务队列中的任务。 //console.log(2); //console.log(1); // 第1次个宏任务,创建了微任务1。宏任务同步代码执行完后,就先执行了第1个微任务队列中的任务。 //console.log(4); //console.log(3);
-
作用域与this指向与变量自增
-
级作用域变量与this指向
var x = 3,obj = {x: 5}; obj.fn = (function () {this.x *= ++x;return function (y) {this.x *= (++x)+y;console.log(x);} })(); var fn = obj.fn; obj.fn(6); fn(4); console.log(obj.x, x);
-
变量访问与变量递增与递减
var i = 2 i=(++i)+(++i)+(++i)+i //i=(++i)+(++i)+(++i)+i//此时i依旧为2 // i = 3+(++i)+(++i)+i//此时i由2自增变成3,取自增后的值3 // i = 3+(4)+(++i)+i//此时i由3变成4,取自增后的值4 // i = 3+(4)+(5)+i//此时i由4变成5,取自增后的值5 // i = 3+(4)+(5)+5//此时i依旧为5 // i=17
var i = 2; i = i + (++i) + (++i) + (++i); // i = i + (++i) + (++i) + (++i); //此时i依旧为2 // i = 2 + (++i) + (++i) + (++i); //此时i依旧为2,因为此时还没有++i。 // i = 2 + 3 + (++i) + (++i); //此时i由2自增变成3,取自增后的值3,此时第一个++i已经执行。 // i = 2 + 3 + 4 + (++i); //此时i由3自增变成4,取自增后的值4 // i = 2 + 3 + 4 + 5; //此时i由4自增变成5,取自增后的值5 // i = 14; //此时i依旧为5 // i = 14;
-
加法赋值运算符表达式
var i = 2; i += (++i); // 等价于 var i = 2; i = i +(++i);
var i = 2; i += (++i) + (++i) + (++i); // i += (++i) + (++i) + (++i); //此时i依旧为2 // i = i + (++i) + (++i) + (++i); //此时i依旧为2 // i = 2 + (++i) + (++i) + (++i); //此时i依旧为2 // i = 2 + 3 + (++i) + (++i); //此时i由2自增变成3,取自增后的值3 // i = 2 + 3 + 4 + (++i); //此时i由3自增变成4,取自增后的值4 // i = 2 + 3 + 4 + 5; //此时i由4自增变成5,取自增后的值5 // i = 14; //此时i依旧为5 // i = 14;
- 运算表达式中先取变量,在具体运算到的时候再取变量值。
- 自增与自减会改变量的值。
- 对于已经取值后的变量,自增与自减不再影响到。但还没开始运算到的地方,自增与自减会影响到变量的取值。
- 自增与自减会改变量的值。
- 运算表达式中先取变量,在具体运算到的时候再取变量值。
发布订阅设计模式
-
支持标准事件的浏览器内置事件池
- 标准事件:浏览器内部实现的,可以自动监听。
- 事件池中默认存在监听机制。
- 浏览器会分配事件监听机制,对事件进行监听。
- 当监听到事件被触发后,会找到事件池中所有符合条件的方法,然后按照顺序依次执行!并且把获取的事件对象,传递给每个方法!
- 内置事件池核心
- 发布所需的事件池也是浏览器一开始就创建好了。
- addEventListener(‘事件名’,事件函数,参数选项) 用于把事件放进事件池。
- removeEventListener(‘事件名’,事件函数) 用于把事件从事件池移除。
- 通知,是由浏览器自动进行的。
-
发布订阅设置模式:
- 灵感来源:参照DOM2中的事件池机制,实现事件与方法的管理
- 加入事件池、移除事件池、通知事件池中的方法执行。
- 只不过浏览器的内置事件池,只能对浏览器标准事件进行管理。
- 也只有内置事件池具备自动通知执行的机制!
- 只不过浏览器的内置事件池,只能对浏览器标准事件进行管理。
- 加入事件池、移除事件池、通知事件池中的方法执行。
- 设计思路:
-
对于一些自定义事件,可以自己参照浏览器内置事件池的机制,对其进行管理。
- 发布:自己创建一个自定义事件池。
- 也就是一个容器,可以是一个对象,也可以是一个数组。
- 用于发布自定义事件。
- 订阅:基于on()方法,向自定义事件池中加入信息。
- on(自定义事件名, 绑定的方法)
- 信息包含:
- 自定义事件的名字。
- 绑定的方法。
- 方法订阅自定义事件。
- 细节:
- 同一个自定义事件是否能够绑定相同的方法。
- 一般同一个自定义事件,不能绑定相同的方法。
- 想多次绑定,可以用一个给该方法包上一层。
- 一般同一个自定义事件,不能绑定相同的方法。
- 方法要不要依次执行。
- 一般要依次执行。
- 同一个自定义事件是否能够绑定相同的方法。
- 移除订阅:基于off方法,把信息从自定义事件池中移除。
- off(自定义事件名, 要移除的方法)
- 方法移除对自定义事件的订阅。
- 通知:基于emit方法,把自定义事件池中指定的自定义事件通知执行。
- emit(自定义事件名, 参数…)
- 也就是把其绑定的方法执行。
- 通知事件池中的方法执行。
- 把自定义事件对应的方法,依次执行。
- 把参数…传递给每一个方法。
- 这样的操作就是发布订阅设计模式,主要用来解决自定义事件的问题。
- 所以也有人说,发布订阅模式就是用来解决自定义事件的通知及执行的问题。
- 发布:自己创建一个自定义事件池。
-
简易实现-有问题:
// 核心事件池,里面记录事件与事件函数。 let listeners = {};// 向事件池中加入自定义事件及方法。 const on = function on(name, callback) {if (typeof name !== "string") {throw new TypeError("name is not a string");}if (typeof callback !== "function") {throw new TypeError("callback is not a function");}// 如果事件名不在对象中存在,就把它设置为空数组。if (!listeners.hasOwnProperty(name)) {listeners[name] = [];}let arr = listeners[name];if (arr.includes(callback)) {return;}arr.push(callback); };// 从事件池中移除自定义事件及方法。 const off = function off(name, callback) {if (typeof name !== "string") {throw new TypeError("name is not a string");}if (typeof callback !== "function") {throw new TypeError("callback is not a function");}let arr = listeners[name];if (!Array.isArray(arr)) {return;}// for (let i = 0; i < arr.length; i++) {// if (callback === arr[i]) {// arr.splice(i, 1);// break;// }// }//listeners[name]=arr.filter(item=>item!==callback)let index = arr.indexOf(callback);if (index >= 0) {arr.splice(index, 1);} };// 通知指定的自定义事件-即绑定的方法执行。 const emit = function emit(name, ...params) {if (typeof name !== "string") {throw new TypeError("name is not a string");}let arr = listeners[name];if (!Array.isArray(arr)) {return;}for(let i=0;i<arr.length;i++){let callback = arr[i]callback(...params)} };
- 函数执行中中移除同一事件函数对应其它函数会有事件塌陷问题。
- 每一次通知执行,都把数组克隆一份,循环的是克隆的,这样即便中间把原来的数组给删除了,对克隆的也不影响什么。
- 性能消耗较大,对于需要频繁通知自定义事件执行的操作,如果每一次都拷贝一份新的,会有性能消耗。
- 同时移除原本后方的事件函数时,不能及时通知,得打补丁。判断是否已经删除后方的事件了。对克隆的数组进行处理,符合实际。
- 在基于off移除的时候,不要基于splice处理,而是把要删除的项,值改为null,但是其索引还在。
- 也就是这项其实没删除,只是改值了,这样就不会引发数组的塌陷问题。
- 但是这样在后续再次通知执行的时候,会出现内容为null的项,此时我们再将其删除。- 因为此时我们可以在循环中,知道删除了谁,并且被删除的是谁。
- 每一次通知执行,都把数组克隆一份,循环的是克隆的,这样即便中间把原来的数组给删除了,对克隆的也不影响什么。
- 函数执行中中移除同一事件函数对应其它函数会有事件塌陷问题。
-
- 灵感来源:参照DOM2中的事件池机制,实现事件与方法的管理
-
所有的设计模式都是一种思想,都有一种应用场景。
- 单例设计模式
- 代码分组隔离,防止命名冲突。
- 工厂设计模式
- 批量处理流程
- 承诺者设计模式-Promise
- 构造函数设计模式
- 批量设计实例对象
- 设计插件
- 发布订阅模式
- 自定义事件的通知问题。
- 场景:
- 在vue实现组件通信的方式中,有一些就是复用发布订阅设计模式实现的。
- 父子组件通信。
- v-model自定义指令。
- 可以这样使用v-model
<Child v-model:show="active">
<Child :active.sync="active">
- 可以这样使用v-model
- EventBus事件巴士。
- 在react的redux源码中,也是用到了发布订阅。
- …
- 在vue实现组件通信的方式中,有一些就是复用发布订阅设计模式实现的。
- 观察者模式
- 单例设计模式
webpack
-
webpack解决的问题。
- 解决多个http网络请求。
- 把多个代码文件合并到一起。
- 写时把多个代码文件分开来写小文件,易于理解。
- 把多个代码文件合并到一起。
- 代码编译和代码合并和代码压缩
- 就是把写的代码文件合并打包
- 解决多个http网络请求。
-
webpack框架总思路
- 项目文件如SPA单页面应用
- webpack是一个平台
- 模式
- 入口
- 出口
- 插件
- 需要安装不同的插件,实现不同的打包效果
- html-weblack-plugin 打包html页面的
- clean-webpack-plugin 清除之前打包内容
- mini-css-ex
- 需要安装不同的插件,实现不同的打包效果
- 个人操作:按照平台提供的各个配置项,编写打包的规则-即配置项。
- webpack.config.js,默认文件的配置项文件
- 当以后执行webpack命令后,会自动查找这个文件,按照其编写的规则进行打包。
- 可以在执行打包命令的时候,单独指定的打包的配置项文件。
- webpack.config.js,默认文件的配置项文件
-
webpack执行步骤
- 找到入口文件,按照CommonJS与
-
webpack.config.js
const path = require("path"); //node自带的模块。// 作用:打包HTML页面,把打包后的bundle包,自动导入到html中...。 let HtmlWebpackPlugin = require("html-webpack-plugin");// 作用:清除之前打包的文件。 const { CleanWebpackPlugin } = require("clean-webpack-plugin");module.exports = {//指定打包的模式:production生产环境、development开发环境。不同模式,默认的配置是不一样的。// 默认情况下,开发环境打包后的内容不压缩,方便调试,而生产环境是压缩混淆的。// 在代码中,可以基于process.env.NODE_ENV获取表示当前是生产环境还是开发环境的环境变量值。mode: "production",entry: "./src/index.js",//=>入口,多文件可以用对象。//=>出口。output: {filename: "build.[hash:8].js", //=>输出文件的文件名//[name]默认值为main。[hash:8]表示一个8位的哈希值。path: path.resolve(__dirname, "dist"),//=>输出目录的"绝对路径"},// 使用插件。plugins: [// 作用:打包HTML页面,把打包后的bundle包,自动导入到html中...。new HtmlWebpackPlugin({title: "fang-title-webpack学习", //在模块中可以使用htmlWebpackPlugin.options.title来修改了。template: "./public/index.html",//要使用的模板html文件。minify: true,//是否要压缩html文件。favicon: "./public/favicon.ico",//要使用的网页图标。}),// 作用:清除之前打包的文件。new CleanWebpackPlugin(),], };
进阶参考
- Event
- EventTarget.addEventListener()
- webpack官网
- webpack官方中文文档
- webpack官方推荐插件plugins
- 简书 跨域地址 - 可跨域请求的接口
- 知乎 跨域地址 - 可跨域请求的接口