箭头函数;
函数表达式;
递归;
尾调用优化;
参数扩展与收集;
this;
特权方法;
闭包(这块本书较晦涩,you don’t konw js更好);
模块模式;
new.target;

书摘&心得

1 概述

定义函数的方式有两种,一种是函数声明,另一种是函数表达式。

  • 函数声明

    • 函数声明的语法:

      1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612229753245-2c3d5f51-2d8b-4185-b9f6-b0bdcfef7b5f.png#align=left&display=inline&height=71&margin=%5Bobject%20Object%5D&name=image.png&originHeight=95&originWidth=556&size=16770&status=done&style=none&width=416)
    • 函数声明提升:在执行代码之前会先读取函数声明。

  • 函数表达式

    • 常用形式:

      1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612229768172-f841d778-6b17-4fd5-a2e7-d4fe83594be0.png#align=left&display=inline&height=60&margin=%5Bobject%20Object%5D&name=image.png&originHeight=96&originWidth=662&size=14363&status=done&style=none&width=417)
    • 这种情况下创建的函数叫做匿名函数(也叫拉姆达函数),匿名函数的name属性是空字符串。

    • 不存在函数声明提升。
    • 能够创建函数再赋值给变量,也就能够把函数作为其他函数的返回值。

      2 递归

      1、递归函数是在一个函数通过名字调用自身的情况下构成的
  • 但由于函数名可变这种方式不是很稳定

2、也可以通过arguments.callee实现对函数的递归调用。

  • 严格模式下不可访问arguments.callee

3、可以使用命名函数表达式来达成相同的结果
image.png

3 闭包

3.1 定义

闭包是指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式:在一个函数内部创建另一个函数。

3. 2 原理

1、作用域链

当某个函数被调用时,会创建一个执行环境及相应的作用域链。
有compare函数:

  1. function compare(value1, value2){
  2. if (value1 < value2){
  3. return -1;
  4. } else if (value1 > value2){
  5. return 1;
  6. } else {
  7. return 0;
  8. }
  9. }
  10. var result = compare(5, 10);

其作用域链如下图:
image.png

  • 后台的每个执行环境都有一个表示变量的对象——变量对象。在函数执行过程中,会在作用域链中查找变量。
  • 全局环境的变量对象始终存在,而compare函数这样的局部环境的变量对象只在函数执行的过程中存在。
  • 创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,被保存在内部的[[Scope]]属性中。
  • 调用compare()函数时,会为函数创建一个执行环境,然后复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。
  • 作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

    2、闭包的作用域链

  • 在另一个函数内部定义的函数会将外部函数(包含函数)的活动对象添加到它的作用域链中。

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612230532206-ea59768f-7af0-466f-8a55-644319114943.png#align=left&display=inline&height=428&margin=%5Bobject%20Object%5D&name=image.png&originHeight=601&originWidth=786&size=124999&status=done&style=none&width=560)
  • 以上述闭包为例,下图展示了闭包中外部函数与内部匿名函数的作用域链。

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612230567219-4e446883-c6cf-4af2-a647-78a8176060aa.png#align=left&display=inline&height=324&margin=%5Bobject%20Object%5D&name=image.png&originHeight=425&originWidth=785&size=71903&status=done&style=none&width=599)
  • 闭包只能取得外部函数中任何变量的最终值

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612230622449-62c84502-9a5b-4838-97ef-6ec66adb5c98.png#align=left&display=inline&height=242&margin=%5Bobject%20Object%5D&name=image.png&originHeight=299&originWidth=459&size=40404&status=done&style=none&width=371)
    • 上述函数每个都返回10
    • 考试最爱出这种题
    • 利用参数按值传递的特性可以强行让闭包的行为符合预期

      1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612230638288-426709cd-3a98-45c6-acbe-833dd9f3ff1f.png#align=left&display=inline&height=127&margin=%5Bobject%20Object%5D&name=image.png&originHeight=149&originWidth=371&size=21868&status=done&style=none&width=317)

      3、关于this对象

  • 匿名函数的执行环境具有全局性,其this对象通常指向window

  • 运行时基于函数的执行环境绑定。
  • 每个函数被调用时都会自动获取2个变量:this、arguments

    • 内部函数在搜索这两个变量时,只会搜索到其活动对象为止。
    • 因此永远不可能访问外部函数中的这两个变量
    • 保存外部作用域this为闭包即可访问
      1. var name = "The Window";
      2. var object = {
      3. name : "My Object",
      4. getNameFunc : function() {
      5. var that = this; // 保存外部作用域this为that
      6. return function(){
      7. return that.name; // 访问that
      8. };
      9. }
      10. };
      11. alert(object.getNameFunc()()); //"My Object"

      4、内存泄露

  • 通常有垃圾回收机制所以不必过于担心。

  • 本书中对内存泄露的讨论比较浅显,只涉及了低版本IE浏览器下的DOM内存泄露问题。

    4 模仿块级作用域

  • 匿名函数可以模仿块级作用域

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612231600120-bcda6211-4fab-4d0a-a424-82cbd6ec87b2.png#align=left&display=inline&height=73&margin=%5Bobject%20Object%5D&name=image.png&originHeight=90&originWidth=321&size=12423&status=done&style=none&width=259)
  • 然而ES6有了let和const

    5 私有变量

    5.1 我的笔记

    5.1.1 关于特权方法

    image.png

  • 在上述函数内部有3个私有变量,在函数内部可以访问这几个变量,在外部则不能访问它们。

  • 如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。利用这一点,就可以创建用于访问私有变量的共有方法。
  • 将上述公有方法称为特权方法。有两种创建特权方法的方式

    • 一种方式是使用静态私有变量来实现特权方法,详见书章节7.4.1。
    • 另一种是在构造函数中定义特权方法,在创建MyObject实例后,除了使用publicMethod()这一个途径外,没有任何办法可以直接访问privateVariable和privateFunction():

      1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612231643057-a86e004d-1927-4b76-b73d-fc030cb24d22.png#align=left&display=inline&height=286&margin=%5Bobject%20Object%5D&name=image.png&originHeight=421&originWidth=537&size=79288&status=done&style=none&width=365)

      5.1.2 模块模式

  • 模块模式是为单例创建私有变量的特权方法

    • 单例是指只有一个实例的对象
  • 如果必须创建一个对象并以某些数据对其初始化,同时还要公开一些能够访问这些私有数据的方法,就可以使用模块模式

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2841570/1612231722412-823fbf4c-9d72-49dd-8624-b8d079353558.png#align=left&display=inline&height=380&margin=%5Bobject%20Object%5D&name=image.png&originHeight=482&originWidth=448&size=66388&status=done&style=none&width=353)
  • 返回的对象字面量中包含可以公开的属性和方法,由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。

  • 从本质上来讲,这个对象字面量定义的是单例的公共接口。
  • 简而言之,如果必须创建一个对象并以某些数据对其初始化,同时还要公开一些能够访问这些私有数据的方法,那么就使用模块模式。

    5.1.3 其他笔记

    初始化未经声明的变量,总是会创建一个全局变量

    5.2 读后感

    5.2.1 静态私有变量

    关键词:全局变量、全局作用域、所有实例共享
    应用度:较低
    和其他高级语言中的静态变量有些像,但是js需要通过一种很晦涩的方式来实现。

    5.2.2 模块模式

    应用度:造轮子可参考
    简而言之,如果必须创建一个对象并以某些数据对其初始化,同时还要公开一些能够访问这些私有数据的方法,那么久使用模块模式。

    5.2.3 增强的模块模式

    应用度:造轮子可以参考
    适合单例必须是某种类型的实例,同时还必须添加某些属性和方法对其加以增强的情况。
    详见章节7.4.3

    6 第四版新增

    6.1 箭头函数

  • ES6新增

  • 箭头函数也可以不用大括号,不用大括号时直接返回表达式计算值
  • 箭头函数不能使用arguments、super和new.target,也不能用作构造函数。
  • 箭头函数中的this会保留定义该函数时的上下文

    6.2 扩展与收集

  • 扩展

    1. // arr is an array
    2. fn(...arr);
  • 收集

收集的参数必须在末尾

  1. function aa(...params) {
  2. // params is an array
  3. }

6.3 new.target

image.png

  • 如果函数是正常调用的,则new.target的值是undefined;
  • 如果是使用new关键字调用的,则new.target将引用被调用的构造函数。

    6.4 尾调用优化

  • 一项内存管理优化机制,让JavaScript引擎在满足条件时可以重用栈帧。

  • “尾调用”:外部函数的返回值是一个内部函数的返回值。
    • image.png
  • 尾调用优化条件
    • ❑ 代码在严格模式下执行;
    • ❑ 外部函数的返回值是对尾调用函数的调用;
    • ❑ 尾调用函数返回后不需要执行额外的逻辑;
    • ❑ 尾调用函数不是引用外部函数作用域中自由变量的闭包。
  • 这个优化在递归场景下的效果是最明显的,因为递归代码最容易在栈内存中迅速产生大量栈帧

    • 优化示例:image.png

      7 总结

      详见章节小节。
      在函数内部定义其他函数就创建了闭包。
  • 闭包的普通特性,保存词法环境

  • 闭包的应用
    • 模仿块级作用域
    • 在对象中创建私有变量