加载中...
  • 现代JS学习笔记 loading

    一、JS简介

    JS既可以运行在浏览器中,也可以运行在服务器端,甚至可以在任意搭载了Javascript引擎的设备中执行。

    现代的 JavaScript 是一种“安全的”编程语言。它不提供对内存或 CPU 的底层访问,因为它最初是为浏览器创建的,不需要这些功能。

    1.引擎是如何工作的?

    引擎很复杂,但是基本原理很简单。

    1. 引擎(如果是浏览器,则引擎被嵌入在其中)读取(“解析”)脚本。
    2. 然后,引擎将脚本转化(“编译”)为机器语言。
    3. 然后,机器代码快速地执行。

    引擎会对流程中的每个阶段都进行优化。它甚至可以在编译的脚本运行时监视它,分析流经该脚本的数据,并根据获得的信息进一步优化机器代码。

    2.同源策略

    不同的标签页/窗口之间通常互不了解。有时候,也会有一些联系,例如一个标签页通过 JavaScript 打开的另外一个标签页。但即使在这种情况下,如果两个标签页打开的不是同一个网站(域名、协议或者端口任一不相同的网站),它们都不能相互通信。

    这就是所谓的“同源策略”。为了解决“同源策略”问题,两个标签页必须 包含一些处理这个问题的特定的 JavaScript 代码,并均允许数据交换。本教程会讲到这部分相关的知识。

    这个限制也是为了用户的信息安全。例如,用户打开的 http://anysite.com 网页必须不能访问 http://gmail.com(另外一个标签页打开的网页)也不能从那里窃取信息。

    二、JS基础知识

    1.现代模式 “use strict”

    长久以来,JavaScript 不断向前发展且并未带来任何兼容性问题。新的特性被加入,旧的功能也没有改变。

    这么做有利于兼容旧代码,但缺点是 JavaScript 创造者的任何错误或不完善的决定也将永远被保留在 JavaScript 语言中。

    这种情况一直持续到 2009 年 ECMAScript 5 (ES5) 的出现。ES5 规范增加了新的语言特性并且修改了一些已经存在的特性。为了保证旧的功能能够使用,大部分的修改是默认不生效的。你需要一个特殊的指令 —— "use strict" 来明确地激活这些特性。

    没有类似于 "no use strict" 这样的指令可以使程序返回默认模式。 没有办法取消 use strict

    现代 JavaScript 支持 “classes” 和 “modules” —— 高级语言结构(本教程后续章节会讲到),它们会自动启用 use strict。因此,如果我们使用它们,则无需添加 "use strict" 指令。

    因此,目前我们欢迎将 "use strict"; 写在脚本的顶部。稍后,当你的代码全都写在了 class 和 module 中时,你则可以将 "use strict"; 这行代码省略掉。

    2.变量

    变量命名

    1. 变量名称必须仅包含字母,数字,符号 $_
    2. 首字符必须非数字。

    如果命名包括多个单词,通常采用驼峰式命名法(camelCase)。也就是,单词一个接一个,除了第一个单词,其他的每个单词都以大写字母开头:myVeryLongName

    有趣的是,美元符号 '$' 和下划线 '_' 也可以用于变量命名。它们是正常的符号,就跟字母一样,没有任何特殊的含义。

    3.数据类型

    在 JavaScript 中有 8 种基本的数据类型(译注:7 种原始类型和 1 种引用类型)。

    数学运算是安全的

    在 JavaScript 中做数学运算是安全的。我们可以做任何事:除以 0,将非数字字符串视为数字,等等。

    脚本永远不会因为一个致命的错误(“死亡”)而停止。最坏的情况下,我们会得到 NaN 的结果。

    在 JavaScript 中,“number” 类型无法表示大于 (253-1)(即 9007199254740991),或小于 -(253-1) 的整数。这是其内部表示形式导致的技术限制。

    String类型

    在 JavaScript 中,有三种包含字符串的方式。

    1. 双引号:"Hello".
    2. 单引号:'Hello'.
    3. 反引号:Hello.

    null类型

    相比较于其他编程语言,JavaScript 中的 null 不是一个“对不存在的 object 的引用”或者 “null 指针”。

    JavaScript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值。

    上面的代码表示 age 是未知的。

    JavaScript的最初版本是这样区分的:null是一个表示”无”的对象,转为数值时为0;undefined是一个表 示”无”的原始值,转为数值时为NaN。

    现在的用法:

    null表示”没有对象”,即该处不应该有值。

    (1) 作为函数的参数,表示该函数的参数不是对象。

    (2) 作为对象原型链的终点

    undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。

    typeof运算符

    typeof 运算符返回参数的类型。当我们想要分别处理不同类型值的时候,或者想快速进行数据类型检验时,非常有用。

    它支持两种语法形式:

    1. 作为运算符:typeof x
    2. 函数形式:typeof(x)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    typeof undefined // "undefined"

    typeof 0 // "number"

    typeof 10n // "bigint"

    typeof true // "boolean"

    typeof "foo" // "string"

    typeof Symbol("id") // "symbol"

    typeof Math // "object" (1)

    typeof null // "object" (2)

    typeof alert // "function" (3)

    null 绝对不是一个 objectnull 有自己的类型,它是一个特殊值。

    并非JavaScript中的所有内容都是对象,应该说所有内容都可以充当对象。

    原始类型不是 但是有包装函数,可以有对象的属性和方法

    4.类型转换

    字符串转换

    当我们需要一个字符串形式的值时,就会进行字符串转换。比如,alert(value)value 转换为字符串类型,然后显示这个值。我们也可以显式地调用 String(value) 来将 value 转换为字符串类型:

    数字类型转换

    在算术函数和表达式中,会自动进行 number 类型转换。比如,当把除法 / 用于非 number 类型:

    我们也可以使用 Number(value) 显式地将这个 value 转换为 number 类型。

    当我们从 string 类型源(如文本表单)中读取一个值,但期望输入一个数字时,通常需要进行显式转换。如果该字符串不是一个有效的数字,转换的结果会是 NaN

    number 类型转换规则:

    变成……
    undefined NaN
    null 0
    true/false 1/0
    string 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN

    布尔型转换

    转换规则如下:

    • 直观上为“空”的值(如 0、空字符串、nullundefinedNaN)将变为 false
    • 其他值变成 true
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    "" + 1 + 0 // '10'
    "" - 1 + 0 // -1
    true + false // 1
    6 / "3" // 2
    "2" * "3" // 6
    4 + 5 + "px" // '9px'
    "$" + 4 + 5 // '$45'
    "4" - 2 // 2
    "4px" - 2 // NaN
    7 / 0 // infinity
    " -9 " + 5 // '-9 5'
    " -9 " - 5 // -14
    null + 1 // 1
    undefined + 1 // NaN
    " \t \n" - 2 // -2

    5.值的比较

    普通的相等性检查 == 存在一个问题,它不能区分出 0false,也同样无法区分空字符串和 false

    严格相等运算符 === 在进行比较时不会做任何的类型转换。

    1
    2
    3
    alert( null > 0 );  // (1) false
    alert( null == 0 ); // (2) false
    alert( null >= 0 ); // (3) true

    为什么会出现这种反常结果,这是因为相等性检查 == 和普通比较符 > < >= <= 的代码逻辑是相互独立的。进行值的比较时,null 会被转化为数字,因此它被转化为了 0。这就是为什么(3)中 null >= 0 返回值是 true,(1)中 null > 0 返回值是 false。

    另一方面,undefinednull 在相等性检查 == 中不会进行任何的类型转换,它们有自己独立的比较规则,所以除了它们之间互等外,不会等于任何其他的值。这就解释了为什么(2)中 null == 0 会返回 false。

    除非你非常清楚自己在做什么,否则永远不要使用 >= > < <= 去比较一个可能为 null/undefined 的变量。对于取值可能是 null/undefined 的变量,请按需要分别检查它的取值情况。

    1
    2
    3
    4
    5
    6
    7
    5 > 4  // true
    "apple" > "pineapple" // false
    "2" > "12" // true
    undefined == null // true
    undefined === null // false
    null == "\n0\n" // false
    null === +"\n0\n" // false

    6.空值合并运算符

    如果第一个参数不是 null/undefined,则 ?? 返回第一个参数。否则,返回第二个参数。空值合并运算符并不是什么全新的东西。它只是一种获得两者中的第一个“已定义的”值的不错的语法。

    与||比较

    它们之间重要的区别是:

    • || 返回第一个 值。
    • ?? 返回第一个 已定义的 值。

    换句话说,|| 无法区分 false0、空字符串 ""null/undefined。它们都一样 —— 假值(falsy values)。如果其中任何一个是 || 的第一个参数,那么我们将得到第二个参数作为结果。

    优先级

    ?? 运算符的优先级相当低:在 MDN table 中为 5。因此,??=? 之前计算,但在大多数其他运算符(例如,+*)之后计算。

    因此,如果我们需要在还有其他运算符的表达式中使用 ?? 进行取值,需要考虑加括号

    7.函数

    默认值

    如果未提供参数,那么其默认值则是 undefined

    如果我们想在本示例中设定“默认”的 text,那么我们可以在 = 之后指定它:

    1
    2
    3
    4
    5
    function showMessage(from, text = "no text given") {
    alert( from + ": " + text );
    }

    showMessage("Ann"); // Ann: no text given

    现在如果 text 参数未被传递,它将会得到值 "no text given"

    在 JavaScript 中,函数是一个,所以我们可以把它当成值对待。上面代码显示了一段字符串值,即函数的源码。

    的确,在某种意义上说一个函数是一个特殊值,我们可以像 sayHi() 这样调用它。

    但它依然是一个值,所以我们可以像使用其他类型的值一样使用它。

    为什么会有分号?

    1
    2
    3
    4
    5
    6
    7
    8
    function sayHi() {
    // ...
    }

    let sayHi = function() {
    // ...
    };

    答案很简单:

    • 在代码块的结尾不需要加分号 ;,像 if { ... }for { }function f { } 等语法结构后面都不用加。
    • 函数表达式是在语句内部的:let sayHi = ...;,作为一个值。它不是代码块而是一个赋值语句。不管值是什么,都建议在语句末尾添加分号 ;。所以这里的分号与函数表达式本身没有任何关系,它只是用于终止语句。

    一个函数是表示一个“行为”的值

    字符串或数字等常规值代表 数据

    函数可以被视为一个 行为(action)

    我们可以在变量之间传递它们,并在需要时运行。

    在编程语言中,一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量。

    三、对象

    1.计算属性

    当创建一个对象时,我们可以在对象字面量中使用方括号。这叫做 计算属性

    1
    2
    3
    4
    5
    6
    7
    let fruit = prompt("Which fruit to buy?", "apple");

    let bag = {
    [fruit]: 5, // 属性名是从 fruit 变量中得到的
    };

    alert( bag.apple ); // 5 如果 fruit="apple"

    这里有个小陷阱:一个名为 __proto__ 的属性。我们不能将它设置为一个非对象的值:

    1
    2
    3
    let obj = {};
    obj.__proto__ = 5; // 分配一个数字
    alert(obj.__proto__); // [object Object] — 值为对象,与预期结果不同

    我们从代码中可以看出来,把它赋值为 5 的操作被忽略了。

    2.属性存在性测试,‘in’操作

    相对于其他语言,javascript的对象中有一个需要特别注意的特性:能够被访问任何属性。即使属性不存在也不会报错!读取不存在的属性只会得到 undefined。所以我们可以很容易地判断一个属性是否存在

    1
    2
    3
    let user = {};

    alert( user.noSuchProperty === undefined ); // true 意思是没有这个属性

    这里还有一个特别的,检查属性是否存在的操作符 "in"

    1
    "key" in object
    1
    2
    3
    4
    let user = { name: "John", age: 30 };

    alert( "age" in user ); // true,user.age 存在
    alert( "blabla" in user ); // false,user.blabla 不存在。

    请注意,in 的左边必须是 属性名。通常是一个带引号的字符串。

    in 与 undefined比较

    1
    2
    3
    4
    5
    6
    7
    let obj = {
    test: undefined
    };

    alert( obj.test ); // 显示 undefined,所以属性不存在?

    alert( "test" in obj ); // true,属性存在!

    对象属性排序

    对象有顺序吗?换句话说,如果我们遍历一个对象,我们获取属性的顺序是和属性添加时的顺序相同吗?这靠谱吗?

    简短的回答是:“有特别的顺序”:整数属性会被进行排序,其他属性则按照创建的顺序显示。

    我们可以使用非整数属性名来 欺骗 程序。只需要给每个键名加一个加号 "+" 前缀就行了。

    3.对象的引用和复制

    与原始类型相比,对象的根本区别之一是对象是“通过引用”被存储和复制的,与原始类型值相反:字符串,数字,布尔值等 —— 始终是以“整体值”的形式被复制的。

    4.垃圾回收

    JavaScript 中主要的内存管理概念是 可达性

    简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。

    1. 这里列出固有的可达值的基本集合,这些值明显不能被释放。

      比方说:

      • 当前函数的局部变量和参数。
      • 嵌套调用时,当前调用链上所有函数的变量与参数。
      • 全局变量。
      • (还有一些内部的)

      这些值被称作 根(roots)

    2. 如果一个值可以通过引用或引用链从根访问任何其他值,则认为该值是可达的。

    主要需要掌握的内容:

    • 垃圾回收是自动完成的,我们不能强制执行或是阻止执行。
    • 当对象是可达状态时,它一定是存在于内存中的。
    • 被引用与可访问(从一个根)不同:一组相互连接的对象可能整体都不可达。

    5.this

    在 JavaScript 中,this 是“自由”的,它的值是在调用时计算出来的,它的值并不取决于方法声明的位置,而是取决于在“点符号前”的是什么对象。

    以“方法”的语法调用函数时:object.method(),调用过程中的 this 值是 object

    6.构造器和操作符‘new’

    构造函数

    当一个函数被使用 new 操作符执行时,它按照以下步骤:

    1. 一个新的空对象被创建并分配给 this。这个新对象会被执行[[prototype]]连接
    2. 函数体执行。通常它会修改 this,为其添加新的属性。
    3. 返回 this 的值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function User(name) {
    // this = {};(隐式创建)

    // 添加属性到 this
    this.name = name;
    this.isAdmin = false;

    // return this;(隐式返回)
    }

    这是构造器的主要目的 —— 实现可重用的对象创建代码。

    构造器的return

    通常,构造器没有 return 语句。它们的任务是将所有必要的东西写入 this,并自动转换为结果。

    但是,如果这有一个 return 语句,那么规则就简单了:

    • 如果 return 返回的是一个对象,则返回这个对象,而不是 this
    • 如果 return 返回的是一个原始类型,则忽略。

    换句话说,带有对象的 return 返回该对象,在所有其他情况下返回 this

    例如,这里 return 通过返回一个对象覆盖 this

    1
    2
    3
    4
    5
    6
    7
    8
    function BigUser() {

    this.name = "John";

    return { name: "Godzilla" }; // <-- 返回这个对象
    }

    alert( new BigUser().name ); // Godzilla,得到了那个对象

    7.可选链?.

    ‘不存在属性’的问题

    我们大多数用户的地址都存储在 user.address 中,街道地址存储在 user.address.street 中,但有些用户没有提供这些信息。

    在这种情况下,当我们尝试获取 user.address.street,而该用户恰好没提供地址信息,我们则会收到一个错误:

    1
    2
    3
    let user = {}; // 一个没有 "address" 属性的 user 对象

    alert(user.address.street); // Error!

    可选链

    为了简明起见,在本文接下来的内容中,我们会说如果一个属性既不是 null 也不是 undefined,那么它就“存在”。

    换句话说,例如 value?.prop

    • 如果 value 存在,则结果与 value.prop 相同,
    • 否则(当 valueundefined/null 时)则返回 undefined

    下面这是一种使用 ?. 安全地访问 user.address.street 的方式:

    1
    2
    3
    let user = {}; // user 没有 address 属性

    alert( user?.address?.street ); // undefined(不报错)

    我们可以使用 ?. 来安全地读取或删除,但不能写入

    8.Sysmbol类型

    根据规范,对象的属性键只能是字符串类型或者 Symbol 类型。不是 Number,也不是 Boolean,只有字符串或 Symbol 这两种类型。

    Symbol

    ‘symbol’值表示唯一的标识符

    可以使用symbol() 创建这种类型的值

    1
    2
    // id 是 symbol 的一个实例化对象
    let id = Symbol();

    创建时,我们可以给 Symbol 一个描述(也称为 Symbol 名),这在代码调试时非常有用:

    1
    2
    // id 是描述为 "id" 的 Symbol
    let id = Symbol("id");

    Symbol 保证是唯一的。即使我们创建了许多具有相同描述的 Symbol,它们的值也是不同。描述只是一个标签,不影响任何东西。

    例如,这里有两个描述相同的 Symbol —— 它们不相等:

    1
    2
    3
    4
    let id1 = Symbol("id");
    let id2 = Symbol("id");

    alert(id1 == id2); // false

    Symbol不会被自动转换为字符串

    JavaScript 中的大多数值都支持字符串的隐式转换。例如,我们可以 alert 任何值,都可以生效。Symbol 比较特殊,它不会被自动转换。

    例如,这个 alert 将会提示出错:

    1
    2
    let id = Symbol("id");
    alert(id); // 类型错误:无法将 Symbol 值转换为字符串。

    如果我们真的想显示一个 Symbol,我们需要在它上面调用 .toString(),如下所示:

    1
    2
    let id = Symbol("id");
    alert(id.toString()); // Symbol(id),现在它有效了

    或者获取 symbol.description 属性,只显示描述(description):

    1
    2
    let id = Symbol("id");
    alert(id.description); // id

    隐藏属性

    Symbol 允许我们创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。

    例如,如果我们使用的是属于第三方代码的 user 对象,我们想要给它们添加一些标识符。

    我们可以给它们使用 Symbol 键:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let user = { // 属于另一个代码
    name: "John"
    };

    let id = Symbol("id");

    user[id] = 1;

    alert( user[id] ); // 我们可以使用 Symbol 作为键来访问数据

    Object.assign 会同时复制字符串和 symbol 属性

    Object.keys(user) 也会忽略它们。这是一般“隐藏符号属性”原则的一部分。如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。

    全局Symbol

    正如我们所看到的,通常所有的 Symbol 都是不同的,即使它们有相同的名字。但有时我们想要名字相同的 Symbol 具有相同的实体。例如,应用程序的不同部分想要访问的 Symbol "id" 指的是完全相同的属性。

    为了实现这一点,这里有一个 全局 Symbol 注册表。我们可以在其中创建 Symbol 并在稍后访问它们,它可以确保每次访问相同名字的 Symbol 时,返回的都是相同的 Symbol。

    要从注册表中读取(不存在则创建)Symbol,请使用 Symbol.for(key)

    该调用会检查全局注册表,如果有一个描述为 key 的 Symbol,则返回该 Symbol,否则将创建一个新 Symbol(Symbol(key)),并通过给定的 key 将其存储在注册表中。

    1
    2
    3
    4
    5
    6
    7
    8
    // 从全局注册表中读取
    let id = Symbol.for("id"); // 如果该 Symbol 不存在,则创建它

    // 再次读取(可能是在代码中的另一个位置)
    let idAgain = Symbol.for("id");

    // 相同的 Symbol
    alert( id === idAgain ); // true

    Symbol.keyFor

    对于全局 Symbol,不仅有 Symbol.for(key) 按名字返回一个 Symbol,还有一个反向调用:Symbol.keyFor(sym),它的作用完全反过来:通过全局 Symbol 返回一个名字。

    1
    2
    3
    4
    5
    6
    7
    // 通过 name 获取 Symbol
    let sym = Symbol.for("name");
    let sym2 = Symbol.for("id");

    // 通过 Symbol 获取 name
    alert( Symbol.keyFor(sym) ); // name
    alert( Symbol.keyFor(sym2) ); // id

    Symbol.keyFor 内部使用全局 Symbol 注册表来查找 Symbol 的键。所以它不适用于非全局 Symbol。如果 Symbol 不是全局的,它将无法找到它并返回 undefined

    也就是说,任何 Symbol 都具有 description 属性。

    四、数据类型

    函数 toFixed(n) 将数字舍入到小数点后 n 位,并以字符串形式返回结果。

    有一个特殊的内建方法 Object.is,它类似于 === 一样对值进行比较,但它对于两种边缘情况更可靠:

    1. 它适用于 NaNObject.is(NaN,NaN)=== true,这是件好事。
    2. 0-0 是不同的:Object.is(0,-0)=== false,从技术上讲这是对的,因为在内部,数字的符号位可能会不同,即使其他所有位均为零。

    在所有其他情况下,Object.is(a,b)a === b 相同。

    这种比较方式经常被用在 JavaScript 规范中。当内部算法需要比较两个值是否完全相同时,它使用 Object.is(内部称为 SameValue)。

    结论:在处理小数时避免相等性检查。

    if 测试中 indexOf 有一点不方便。我们不能像这样把它放在 if 中:

    1
    2
    3
    4
    5
    let str = "Widget with id";

    if (str.indexOf("Widget")) {
    alert("We found it"); // 不工作!
    }

    上述示例中的 alert 不会显示,因为 str.indexOf("Widget") 返回 0(意思是它在起始位置就查找到了匹配项)。是的,但是 if 认为 0 表示 false

    因此我们应该检查 -1,像这样:

    1
    2
    3
    4
    5
    let str = "Widget with id";

    if (str.indexOf("Widget") != -1) {
    alert("We found it"); // 现在工作了!
    }
    方法 选择方式…… 负值参数
    slice(start, end) startend(不含 end 允许
    substring(start, end) startend 之间(包括 start,但不包括 end 负值代表 0
    substr(start, length) start 开始获取长为 length 的字符串 允许 start 为负数

    写一个函数 ucFirst(str),并返回首字母大写的字符串 str,例如:

    这里存在一个小问题。如果 str 是空的,那么 str[0] 就是 undefined,但由于 undefined 并没有 toUpperCase() 方法,因此我们会得到一个错误。

    1. 使用 str.charAt(0),因为它总是会返回一个字符串(可能为空)。
    2. 为空字符添加测试。

    1.数组

    splice

    数组是对象,所以我们可以尝试使用 delete

    1
    2
    3
    4
    5
    6
    7
    8
    let arr = ["I", "go", "home"];

    delete arr[1]; // remove "go"

    alert( arr[1] ); // undefined

    // now arr = ["I", , "home"];
    alert( arr.length ); // 3

    元素被删除了,但数组仍然有 3 个元素,我们可以看到 arr.length == 3

    这很正常,因为 delete obj.key 是通过 key 来移除对应的值。对于对象来说是可以的。但是对于数组来说,我们通常希望剩下的元素能够移动并占据被释放的位置。我们希望得到一个更短的数组。

    arr.splice 方法可以说是处理数组的瑞士军刀。它可以做所有事情:添加,删除和插入元素。

    1
    arr.splice(start[, deleteCount, elem1, ..., elemN])

    slice

    1
    arr.slice([start], [end])

    它会返回一个新数组,将所有从索引 startend(不包括 end)的数组项复制到一个新的数组。startend 都可以是负数,在这种情况下,从末尾计算索引。

    concat

    arr.concat 创建一个新数组,其中包含来自于其他数组和其他项的值。

    1
    arr.concat(arg1, arg2...)

    arr.forEach

    arr.forEach 方法允许为数组的每个元素都运行一个函数。

    1
    2
    3
    arr.forEach(function(item, index, array) {
    // ... do something with item
    });

    如果我们想检查是否包含某个元素,并且不想知道确切的索引,那么 arr.includes 是首选。

    此外,includes 的一个非常小的差别是它能正确处理NaN,而不像 indexOf/lastIndexOf

    1
    2
    3
    const arr = [NaN];
    alert( arr.indexOf(NaN) ); // -1(应该为 0,但是严格相等 === equality 对 NaN 无效)
    alert( arr.includes(NaN) );// true(这个结果是对的)

    我们已经知道,箭头函数没有自身的 this。现在我们知道了它们也没有特殊的 arguments 对象。

    上一篇:
    v-model 双向数据绑定实现原理
    下一篇:
    学习 Vue 原理:响应式
    本文目录
    本文目录