20230513----重返学习-同步异步任务-作用域与this指向与变量自增-发布订阅设计模式-webpack

day-069-sixty-nine-20230513-同步异步任务-作用域与this指向与变量自增-发布订阅设计模式-webpack

同步异步任务

  • 所有的代码都是在ECStack执行环境栈-即主线程开始的。

    • 这个也有人认为它是宏任务,进而得出:
      1. 标签由宏任务开始->把异步任务放到WebAPI任务监听队列。
      2. 标签主线程空闲。
      3. WebAPI任务监听队列->把触发后的异步任务放到EventQueue事件队列。
      4. EventQueue事件队列微任务->EventQueue事件队列宏任务。
        • 执行过程中,标签主线程由空闲变为活动状态。
        • 执行结束后,标签由活动状态又变成空闲状态。
      5. 重复执行第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(‘事件名’,事件函数) 用于把事件从事件池移除。
      • 通知,是由浏览器自动进行的。
  • 发布订阅设置模式:

    1. 灵感来源:参照DOM2中的事件池机制,实现事件与方法的管理
      • 加入事件池、移除事件池、通知事件池中的方法执行。
        • 只不过浏览器的内置事件池,只能对浏览器标准事件进行管理。
          • 也只有内置事件池具备自动通知执行的机制!
    2. 设计思路:
      • 对于一些自定义事件,可以自己参照浏览器内置事件池的机制,对其进行管理。

        1. 发布:自己创建一个自定义事件池。
          • 也就是一个容器,可以是一个对象,也可以是一个数组。
          • 用于发布自定义事件。
        2. 订阅:基于on()方法,向自定义事件池中加入信息。
          • on(自定义事件名, 绑定的方法)
          • 信息包含:
            • 自定义事件的名字。
            • 绑定的方法。
          • 方法订阅自定义事件。
          • 细节:
            • 同一个自定义事件是否能够绑定相同的方法。
              • 一般同一个自定义事件,不能绑定相同的方法。
                • 想多次绑定,可以用一个给该方法包上一层。
            • 方法要不要依次执行。
              • 一般要依次执行。
        3. 移除订阅:基于off方法,把信息从自定义事件池中移除。
          • off(自定义事件名, 要移除的方法)
          • 方法移除对自定义事件的订阅。
        4. 通知:基于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)
          }
        };
        
        • 函数执行中中移除同一事件函数对应其它函数会有事件塌陷问题。
          1. 每一次通知执行,都把数组克隆一份,循环的是克隆的,这样即便中间把原来的数组给删除了,对克隆的也不影响什么。
            • 性能消耗较大,对于需要频繁通知自定义事件执行的操作,如果每一次都拷贝一份新的,会有性能消耗。
            • 同时移除原本后方的事件函数时,不能及时通知,得打补丁。判断是否已经删除后方的事件了。对克隆的数组进行处理,符合实际。
          2. 在基于off移除的时候,不要基于splice处理,而是把要删除的项,值改为null,但是其索引还在。
            - 也就是这项其实没删除,只是改值了,这样就不会引发数组的塌陷问题。
            - 但是这样在后续再次通知执行的时候,会出现内容为null的项,此时我们再将其删除。
            • 因为此时我们可以在循环中,知道删除了谁,并且被删除的是谁。
  • 所有的设计模式都是一种思想,都有一种应用场景。

    • 单例设计模式
      • 代码分组隔离,防止命名冲突。
    • 工厂设计模式
      • 批量处理流程
    • 承诺者设计模式-Promise
    • 构造函数设计模式
      • 批量设计实例对象
      • 设计插件
    • 发布订阅模式
      • 自定义事件的通知问题。
      • 场景:
        • 在vue实现组件通信的方式中,有一些就是复用发布订阅设计模式实现的。
          • 父子组件通信。
          • v-model自定义指令。
            • 可以这样使用v-model
          • EventBus事件巴士。
        • 在react的redux源码中,也是用到了发布订阅。
    • 观察者模式

webpack

  • webpack解决的问题。

    • 解决多个http网络请求。
      • 把多个代码文件合并到一起。
        • 写时把多个代码文件分开来写小文件,易于理解。
    • 代码编译和代码合并和代码压缩
      • 就是把写的代码文件合并打包
  • webpack框架总思路

    • 项目文件如SPA单页面应用
    • webpack是一个平台
      • 模式
      • 入口
      • 出口
      • 插件
        • 需要安装不同的插件,实现不同的打包效果
          • html-weblack-plugin 打包html页面的
          • clean-webpack-plugin 清除之前打包内容
          • mini-css-ex
    • 个人操作:按照平台提供的各个配置项,编写打包的规则-即配置项。
      • webpack.config.js,默认文件的配置项文件
        • 当以后执行webpack命令后,会自动查找这个文件,按照其编写的规则进行打包。
      • 可以在执行打包命令的时候,单独指定的打包的配置项文件。
  • webpack执行步骤

    1. 找到入口文件,按照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(),
      ],
    };
    

进阶参考

  1. Event
  2. EventTarget.addEventListener()
  3. webpack官网
  4. webpack官方中文文档
  5. webpack官方推荐插件plugins
  6. 简书 跨域地址 - 可跨域请求的接口
  7. 知乎 跨域地址 - 可跨域请求的接口

你可能感兴趣的:(ES6,重返学习,原生js学习,webpack,学习,设计模式)