ALEX CODE PARK

vuePress-theme-reco ALEX CODE PARK    2020
ALEX CODE PARK

Choose mode

  • dark
  • auto
  • light
阅读笔记
  • 《深入浅出Vue.js》
  • 《JavaScript高级程序设计》
  • 《你不知道的JavaScript》
  • 《CSS世界》
前端面试
  • CSS
  • HTML
  • JavaScript
  • 常见算法
  • 网络相关
  • 剑指Offer
Github
Tag
Category
  • 前端面试
  • 算法题目
  • 前端书籍
  • 个人笔记
author-avatar

ALEX CODE PARK

19

文章

10

标签

阅读笔记
  • 《深入浅出Vue.js》
  • 《JavaScript高级程序设计》
  • 《你不知道的JavaScript》
  • 《CSS世界》
前端面试
  • CSS
  • HTML
  • JavaScript
  • 常见算法
  • 网络相关
  • 剑指Offer
Github
Tag
Category
  • 前端面试
  • 算法题目
  • 前端书籍
  • 个人笔记
  • JS杂谈

    • ⭕️ 关于 arguments 的使用
      • ... 与 arguments
      • [].slice.call(arguments)
    • ⭕️ 函数柯里化和反柯里化
      • 柯里化
      • 总结
    • ⭕️ 关于前端路由
      • 前端路由的由来
      • 什么是前端路由
      • hash 模式
      • history 模式
      • hash 🆚 history
    • ⭕️ EventLoop 深度解析
      • 面试题
    • ⭕️requestAnimationFrame
      • 详解隐式转换
        • 转字符串
        • 转数字
        • 转布尔值
        • 对象转原始 toPrimitive
        • 宽松相等的隐式转换规则
        • null&undefined
        • 自测题
      • 箭头函数
        • 没有 this
        • 没有 arguments
        • 不能通过 new 关键字调用
        • 没有原型
        • 没有 super
        • 自执行函数

    JS杂谈

    vuePress-theme-reco ALEX CODE PARK    2020

    JS杂谈


    AlexChiang 2020-02-23

    # ⭕️ 关于 arguments 的使用

    # ... 与 arguments

    // 函数内使用arguments对象
    function log() {
      console.log([...arguments]);
      console.log(...arguments);
    }
    log(1, 2, 3); // [1,2,3] 1 2 3
    
    // 参数不定长时写的arguments与arguments对象无关 仅为一个形参名字
    // 可以是 arguments 也可以是 args
    function log2(a, ...args) {
      console.log(a);
      console.log(args);
      console.log(...args);
    }
    log2(1, 2, 3); // 1 [2,3] 2 3
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    参数写成...args 时 在函数内部使用 args[index]即可访问对应参数
    ...args 会自动将我们传入的多个参数变为一个数组

    # [].slice.call(arguments)

    [].slice.call(arguments)等效于Array.prototype.slice.call(arguments)

    • 目的:让类数组对象 arguments 使用数组方法 slice(把类数组对象转为数组对象)
    • 返回值:参数组成的数组
    • 原理:
      • slice()返回未改变的数组
      • .call(arguments)让 this 指向 arguments 对象

    由此可得 [].shift.call(arguments) 则可返回传入参数的第一项

    该方法会推出 args 中的第一项

    # ⭕️ 函数柯里化和反柯里化

    参考

    # 柯里化

    柯里化,可以理解为提前接收部分参数,延迟执行,不立即输出结果,而是返回一个接受剩余参数的函数。因为这样的特性,也被称为部分计算函数

    # 例子

    实现add(1)(2, 3)(4)() = 10的效果

    • 传入参数时,代码不执行输出结果,而是先记忆起来
    • 当传入空的参数时,代表可以进行真正的运算
    function currying(fn) {
      var allArgs = [];
    
      return function next() {
        var args = [].slice.call(arguments);
    
        if (args.length > 0) {
          allArgs = allArgs.concat(args);
          return next;
        } else {
          return fn.apply(null, allArgs);
        }
      };
    }
    var add = currying(function() {
      var sum = 0;
      for (var i = 0; i < arguments.length; i++) {
        sum += arguments[i];
      }
      return sum;
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 解析

    • 使用闭包记忆传入参数
    • 判断触发函数执行条件
    function currying(fn) {
      var allArgs = []; // 用来接收参数 闭包存储
    
      return function next() {
        var args = [].slice.call(arguments);
    
        // 判断是否执行计算
        if (args.length > 0) {
          allArgs = allArgs.concat(args); // 收集传入的参数,进行缓存
          return next;
        } else {
          return fn.apply(null, allArgs); // 符合执行条件,执行计算
        }
      };
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 总结

    • 逐步接收参数,并缓存供后期计算使用
    • 不立即计算,延后执行
    • 符合计算条件时将缓存参数统一传给执行方法

    # ⭕️ 关于前端路由

    # 前端路由的由来

    SPA 中用户的交互通过 js 改版 html 内容来实现,页面本身的 url 并没有变化,这导致两个问题:

    • SPA 无法记住用户的操作记录
    • 只有一个 url 对 SEO 不友好

    🔥 前端路由就是为了解决上述问题出现的

    # 什么是前端路由

    保证只有一个 HTML 页面,且与用户交互时不刷新跳转的同时,为每一个视图匹配一个特殊的 url。

    需要做到以下两点:

    • 改变 url 且不让浏览器发送请求
    • 可以监听到 url 变化

    接下来介绍

    # hash 模式

    # 什么是 hash?

    www.baidu.com/#hash中#hash 即为我们期望的 hash 值。

    hash 值的变化不会导致浏览器像服务器发送请求,而且 hash 改变会触发 hashchange 事件。(实现核心)

    # 使用到的 api

    window.location.hash = "#..."; // 设置hash
    let hash = window.loaction.hash; // 获取hash
    window.addEventListener("hashchange", e => {}); // 监听hash
    
    1
    2
    3

    # 实现

    class HashRouter {
      constructor() {
        //用于存储不同hash值对应的回调函数
        this.routers = {};
        window.addEventListener("hashchange", this.load.bind(this), false);
      }
      //用于注册每个视图
      register(hash, callback = function() {}) {
        this.routers[hash] = callback;
      }
      //用于注册首页
      registerIndex(callback = function() {}) {
        this.routers["index"] = callback;
      }
      //用于处理视图未找到的情况
      registerNotFound(callback = function() {}) {
        this.routers["404"] = callback;
      }
      //用于处理异常情况
      registerError(callback = function() {}) {
        this.routers["error"] = callback;
      }
      //用于调用不同视图的回调函数
      load() {
        let hash = location.hash.slice(1),
          handler;
        //没有hash 默认为首页
        if (!hash) {
          handler = this.routers.index;
        }
        //未找到对应hash值
        else if (!this.routers.hasOwnProperty(hash)) {
          handler = this.routers["404"] || function() {};
        } else {
          handler = this.routers[hash];
        }
        //执行注册的回调函数
        try {
          handler.apply(this);
        } catch (e) {
          console.error(e);
          (this.routers["error"] || function() {}).call(this, e);
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45

    # history 模式

    # 介绍

    在 HTML5 之前,浏览器已经有了 history 对象,但在早期的 history 中只能用于多页面的跳转

    在 H5 规范中新增了以下几个 api:

    history.pushState(); // 添加新的状态到历史状态栈
    history.replaceState(); // 用新的状态代替当前状态
    history.state; // 返回当前状态对象
    
    1
    2
    3

    history.pushState() 和 history.replaceState() 的区别在于:

    • history.pushState() 在保留现有历史记录的同时,将 url 加入到历史记录中。
    • history.replaceState() 会将历史记录中的当前页面历史替换为 url。

    🔥因为 history 改变不会触发任何事件,所以我们无法直接监听 history。因此我们需要将所有可能触发 history 改变的情况一一进行拦截并监听

    对于 SPA 中 history 模式,url 改变只能由以下四种方式引起:

    • 点击浏览器的前进或后退按钮
    • 点击 a 标签
    • 在 JS 代码中触发 history.pushState 函数
    • 在 JS 代码中触发 history.replaceState 函数

    # 实现

    class HistoryRouter {
      constructor() {
        //用于存储不同path值对应的回调函数
        this.routers = {};
        this.listenPopState();
        this.listenLink();
      }
      //监听popstate
      listenPopState() {
        window.addEventListener(
          "popstate",
          e => {
            let state = e.state || {},
              path = state.path || "";
            this.dealPathHandler(path);
          },
          false
        );
      }
      //全局监听A链接
      listenLink() {
        window.addEventListener(
          "click",
          e => {
            let dom = e.target;
            if (dom.tagName.toUpperCase() === "A" && dom.getAttribute("href")) {
              e.preventDefault();
              this.assign(dom.getAttribute("href"));
            }
          },
          false
        );
      }
      //用于首次进入页面时调用
      load() {
        let path = location.pathname;
        this.dealPathHandler(path);
      }
      //用于注册每个视图
      register(path, callback = function() {}) {
        this.routers[path] = callback;
      }
      //用于注册首页
      registerIndex(callback = function() {}) {
        this.routers["/"] = callback;
      }
      //用于处理视图未找到的情况
      registerNotFound(callback = function() {}) {
        this.routers["404"] = callback;
      }
      //用于处理异常情况
      registerError(callback = function() {}) {
        this.routers["error"] = callback;
      }
      //跳转到path
      assign(path) {
        history.pushState({ path }, null, path);
        this.dealPathHandler(path);
      }
      //替换为path
      replace(path) {
        history.replaceState({ path }, null, path);
        this.dealPathHandler(path);
      }
      //通用处理 path 调用回调函数
      dealPathHandler(path) {
        let handler;
        //没有对应path
        if (!this.routers.hasOwnProperty(path)) {
          handler = this.routers["404"] || function() {};
        }
        //有对应path
        else {
          handler = this.routers[path];
        }
        try {
          handler.call(this);
        } catch (e) {
          console.error(e);
          (this.routers["error"] || function() {}).call(this, e);
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83

    # hash 🆚 history

    # ⭕️ EventLoop 深度解析

    # 面试题

    new Promise((resolve, reject) => {
      console.log("promise1", 1);
      resolve();
    })
      .then(() => {
        console.log("then11", 2);
        new Promise((resolve, reject) => {
          console.log("promise2", 3);
          resolve();
        })
          .then(() => {
            console.log("then21", 4);
            new Promise((resolve, reject) => {
              console.log("promise3", 5);
              resolve();
            })
              .then(() => {
                console.log("then31", 7);
              })
              .then(() => {
                console.log("then32", 9);
              });
          })
          .then(() => {
            console.log("then22", 8);
          });
      })
      .then(() => {
        console.log("then12", 6);
      });
    
    // 1 2 3 4 5 6 7 8 9
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    解析:

    1. 输出 1 -> then11 入队
      队伍:【then11】
    2. then11 出队 -> 输出 2 -> 输出 3 -> then21 入队 -> then12 入队
      队伍:【then21 then12】
    3. then21 出队 -> 输出 4 -> 输出 5 -> then31 入队 -> then22 入队
      队伍:【then12 then31 then22】
    4. then12 出队 -> 输出 6
      队伍:【then31 then22】
    5. then31 出队 -> 输出 7 -> then32 入队
      队伍:【then22 then32】
    6. then22 出队 -> 输出 8 -> then32 出队 -> 输出 9

    process.nextTick优先级高于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");
    });
    async1();
    new Promise(function(resolve) {
      console.log("promise1");
      resolve();
    }).then(function() {
      console.log("promise2");
    });
    setImmediate(() => {
      console.log("setImmediate");
    });
    process.nextTick(() => {
      console.log("process");
    });
    console.log("script end");
    // [script start,async1 start,async2,promise1,script end,process,async1 end,promise2,setTimeout,setImmediate]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    # ⭕️requestAnimationFrame

    随着技术与设备的发展,用户的终端对动画的表现能力越来越强,更多的场景开始大量使用动画。在 Web 应用中,实现动画效果的方法比较多,JavaScript 中可以通过定时器 setTimeout 来实现,css3 可以使用 transition 和 animation 来实现,html5 中的 canvas 也可以实现。除此之外,html5 还提供一个专门用于请求动画的 API,即 requestAnimationFrame

    # 详解隐式转换

    # 转字符串

    注意:Object.prototype.toString([]) 与 Array.prototype.toString([])返回值不同

    • 数组:转为字符串是将所有元素按照","连接起来,相当于调用数组的 Array.prototype.join()方法,如[1, 2, 3]转为"1,2,3",
    • 空数组[]转为空字符串,数组中的 null 或 undefined,会被当做空字符串处理
    • 普通对象:转为字符串相当于直接使用Object.prototype.toString(),返回"[object Object]"
    String(null); // 'null'
    String(undefined); // 'undefined'
    String(true); // 'true'
    String(10); // '10'
    String(1e21); // '1e+21'
    String([1, 2, 3]); // '1,2,3'
    String([]); // ''
    String([null]); // ''
    String([1, undefined, 3]); // '1,,3'
    String({}); // '[object Objecr]'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 转数字

    Number(null); // 0
    Number(undefined); // NaN
    Number("10"); // 10
    Number("10a"); // NaN
    Number(""); // 0
    Number(true); // 1
    Number(false); // 0
    Number([]); // 0
    Number(["1"]); // 1
    Number({}); // NaN
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 转布尔值

    js 中的假值只有false、null、undefined、空字符、0、NaN,其它值转为布尔型都为 true。

    # 对象转原始 toPrimitive

    • 先调用 valueOf
    • 再调用 toString

    数组调用 valueOf 返回本身

    # 宽松相等的隐式转换规则

    • 布尔 🆚 其他:布尔 -> 数字
    • 数字 🆚 字符串:字符串 -> 数字
    • 对象 🆚 原始:对象执行 toPrimitive

    # null&undefined

    • null == undefined // true
    • null/undefined -> false
    • ES 规范:和其他所有值都不宽松相等

    # 自测题

      [] == ![] // true
      // ![] = false -> 数组vs布尔
      // false = 0
      // [] = 0
    
      [] == 0 // true
      // 对象vs原始
      // [].valueOf -> [] -> [].toString -> '0' -> 字符串vs数字 -> 0 == 0
    
      [2] == 2 // true
      // 对象vs原始
      // [].valueOf -> [] -> [].toString -> '2' -> 字符串vs数字 -> 2 == 2
    
      ['0'] == false // true
      // 对象vs原始
      // [].valueOf -> [] -> [].toString -> '0' -> 字符串vs布尔
      // false -> 0 -> 字符串vs数字
    
      '0' == false // true
      // 字符串vs布尔
      // false -> 0
    
      [] == false // true
      // 对象vs原始
      // [] -> '0' -> 转为上题
    
      [null] == 0 // true
      // 对象vs原始
      // [null] -> '' -> 字符串vs数字
      // '' -> 0 -> 0 == 0
    
      null == 0 // false
      // 规范
    
      [null] == false // true
      // [null] -> '' -> 0
      // false -> 0
    
      null == false // false
      // 规范
    
      [undefined] == false // true
      // [undefined] -> '' -> 0
      // false -> 0
    
      undefined == false // false
      // 规范
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47

    # 箭头函数

    # 没有 this

    箭头函数没有 this,所以需要通过查找作用域链来确定 this 的值。

    eg:

    <button id="button">点击变色</button>
    
    1
    function Button(id) {
      this.element = document.querySelector("#" + id);
      this.bindEvent();
    }
    
    Button.prototype.bindEvent = function() {
      this.element.addEventListener("click", this.setBgColor, false);
    };
    
    Button.prototype.setBgColor = function() {
      // 如不绑定this 该函数内部this为元素引用
      this.element.style.backgroundColor = "#1abc9c";
    };
    
    var button = new Button("button");
    
    // Uncaught TypeError: Cannot read property 'style' of undefined
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    原因: **当使用 addEventListener() 为一个元素注册事件的时候,事件函数里的 this 值是该元素的引用。**即<button id="button">123</button>

    解决方法: .bindEvent 中的 this 是我们正确想要的,即 Button 的实例Button {element: button#button}。所以我们需要将 addEventListener 中的事件函数 this 强制绑定为.bindEvent 中的 this

    • 方法 1 使用 bind:this.setBgColor.bind(this)
    • 方法 2 使用箭头函数:() => this.setBgColor()

    # 没有 arguments

    箭头函数可以访问外围函数的 arguments 对象

    function constant() {
      return () => arguments[0];
    }
    
    var result = constant(1);
    console.log(result()); // 1
    
    1
    2
    3
    4
    5
    6

    # 不能通过 new 关键字调用

    JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]。 当通过 new 调用函数时,执行 [[Construct]] 方法,创建一个实例对象,然后再执行函数体,将 this 绑定到实例上。 当直接调用的时候,执行 [[Call]] 方法,直接执行函数体。 箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错。

    # 没有原型

    由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在 prototype 这个属性。

    # 没有 super

    连原型都没有,自然也不能通过 super 来访问原型的属性,所以箭头函数也是没有 super 的,不过跟 this、arguments、new.target 一样,这些值由外围最近一层非箭头函数决定。

    # 自执行函数

    (function() {
      console.log(1);
    })();
    // 或
    (function() {
      console.log(1);
    })();
    // 用箭头函数简写
    (() => {
      console.log(1);
    })();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11