• JavaScript权威指南(原书第7版) 犀牛书


    第3章 语法结构

    3.10.1 使用let和const声明

    ES6后,变量通过let关键字声明

    let i
    let sum
    
    • 1
    • 2
    • 可以使用一条let语句声明多个变量
    let i, sum
    
    • 1
    • 声明变量的同时,(如果可能)也为其赋予一个初始值
    let message = 'hello'
    let i = 0, j = 1
    
    • 1
    • 2

    let 语句中 不为变量指定初始值,变量也会被声明, 但在被赋值之前它的值是undefined

    要声明常量而非变量,则要使用const而非let,const与let类似,区别在于const必须在声明时初始化常量

    const AU = 1.496E8 // 天文单位
    
    • 1

    常量是不能改变的,重新赋值会报错 TypeError

    何时使用const

    • 只有值在基本不会改变的情况下使用const,比如物理常数,程序版本号
    • 有时程序中很多所谓的变量实际上在程序运行时不会改变,应用const,如果发现确实需要值改变再改成let
    • JS中的for、for/in和for/of循环语句, 其中每种循环都包含一个循环变量,在循环的每次迭代都会取得一个新值
    for(let datum of data) console.log(datum)
    
    • 1

    上述也可以使用const来声明这些循环变量,只要保证在循环体内不给它重新赋值即可,此时const声明只是一次循环迭代期间的常量值

    for(const datum of data) console.log(datum)
    
    • 1

    变量与常量作用域

    • 通过let和const声明的变量和常量具有块级作用域,这意味着它们只在let和const语句所在的代码块有定义
    • JS 类和函数的函数体是代码块,if/else语句的语句体、while和for循环的循环体都是代码块,
    • 作为for、for/in、for/of 循环的一部分声明的变量和常量,以循环体作为它们的作用域

    重复声明

    • 在同一个作用域中使用多个let或const声明同一个名字是语法错误,在嵌套作用域中声明同名变量却是合法的(不推荐)
    const x = 1
    if(x === 1) {
        let x = 2
        console.log(x)
    }
    console.log(x) // 1 回到全局变量
    let x = 3 // 报错,重复声明
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    声明与类型

    • JS的变量声明与值的类型无关,JS变量可以保存任何类型的值
    let i = 10
    i = 'len' // 给一个变量赋一个数值,然后再给它赋一个字符串是合法的
    
    • 1
    • 2

    3.10.2使用var的变量声明

    • ES6之前的JavaScript,声明变量的唯一方式是使用var关键字,无法声明常量,var的语法与let语法相同
    • var和let有相同的语法,但是它们有重要的区别:
      ① var声明的变量不具有块级作用域
      ② 在函数体外部使用var,则会声明一个全局变量。通过var声明的全局变量被实现为全局对象的属性,全局对象可以通过globalThis(全局属性globalThis包含全局的this值,类似于全局对象)引用, 而通过let和const声明的全局变量和常量不是全局对象的属性。
      ③ 与let声明的变量不同,使用var多次声明同名变量是合法的。var变量具有函数作用域而不是块级作用域,
    // var变量不会将这些变量的作用域限定在函数体内,每次循环都会重新声明和重新初始化同一个变量
    for(var i = 0; ....) {}
    
    • 1
    • 2

    ④ var声明的变量,最不寻常的一个特性是,作用域提升(在使用var声明变量时,该声明会被提高到包含函数的顶部)
    因此对使用var声明的变量,可以在包含函数内部的任何地方使用而不会报错。如果初始化的代码尚未运行,则变量的值可能是undefined

    3.10.3 解构赋值

    /* 
        解构赋值:
        - ES6允许按照一定模式从数组和对象中提取值,对变量进行赋值,
            - 1、数组的解构
            - 2、对象的解构
    */
    // 1、数组的解构 []
    const F4 = ['小沈阳', '刘能', '赵四', '宋小宝']
    let [xiao, liu, zhao, song] = F4
    
    console.log(xiao) // 小沈阳
    
    // 2、对象的解构 {}
    const zao = {
        nam:'赵本山',
        age:'不详', 
        xiaopin:function() {
            console.log('我可以演小品')
        }
    }
    
    let {nam, age, xiaopin} = zao // 此处用name关键字问题等 对对象里面的值进行一一对应
    console.log(nam) // 赵本山 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    解构赋值让使用返回数组的函数变得异常便捷 - 使得返回数组的函数使用

    // 将[x, y] 坐标转换为[r, theta] 极坐标
    function toPolar(x, y) {
        return [Math.sqrt(x*x, y*y), Math.atan2(y, x)]
    }
    let [r, theta] = toPolar(1.0, 1.0) // r == Math.sqrt(2) theta == Math.PI/4 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以在JavaScript的各种for循环中声明变量和常量,同样也可以在这个上下文中使用变量解构赋值

    let o = {x:1, y:2}
    for(const [name, value] of Object.entries(o)) {
        console.log(name, value) // 'x 1' 和 'y 2'
    }
    
    • 1
    • 2
    • 3
    • 4

    解构赋值左侧变量的个数不一定与右侧数组中的元素个数相同,左侧多余的变量会被设置为undefined,而右侧多余的值会被忽略 左侧的变量列表可以包含额外的逗号,以跳过右侧的某些值

    let [x, y] = [1]  // x == 1, y == undefined
    [x, y] = [1, 2, 3]  // x == 1, y == 2
    [, x, , y]  // x == 2, y == 4
    
    • 1
    • 2
    • 3

    在解构赋值的时候,如果想把所有未使用或剩余的值收集到一个变量中, 可以在左侧最后一个变量名前面加上3个点 (…)

    let [x, ...y] = [1, 2, 3, 4]  // y == [2, 3, 4]
    
    • 1

    解构赋值可用于嵌套函数,此时赋值的左侧看起来也应该像一个嵌套的数组字面量

    let [a, [b, c]] = [1, [2, 2.5], 3]  // a == 1, b == 2, c == 2.5
    
    • 1

    第4章 表达式与操作符

    链判断运算符(?. 运算符)

    • 实际开发中,如果要读取对象内部的某个属性,往往需要先判断该对象是否存在
    • 比如,要读取 user.firstName,安全的写法应该是下面这样的:
    const firstName = (user && user.firstName) || 'default'
    
    • 1
    • 使用链判断运算符来简化上述写法
    const firstName = user?.firstName || 'default'
    // 解释
    上述代码使用 ?. 运算符,直接在链式调用的时候判断,左侧对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined
    
    • 1
    • 2
    • 3

    使用 ?. 运算符的注意点

    1. 短路机制
    a?.[++x] 
    // 等同于
    a == null ? undefined : a[++x]
    // 上述代码中,如果a是undefined或者null, 那么x不会进行递增运算,也就是说链判断运算符一旦为真,右侧表达式就不再求值
    
    • 1
    • 2
    • 3
    • 4

    Null判断运算符

    • 读取对象属性的时候,如果某个属性的值是 null 或 undefined,有时候需要为它们指定默认值。常见做法是通过 || 运算符指定默认值。
    const text = data.text || 'Hello, world!'
    
    • 1
    • 上面的代码都通过 || 运算符指定默认值,但是这样写可能和预期的结果不一致。
    • 开发者的原意是,只要属性的值为 null 或 undefined,默认值就会生效,但是属性的值如果为空字符串或 false 或 0,默认值也会生效。
    • 为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似 ||,但是只有运算符左侧的值为 null 或 undefined 时,才会返回右侧的值。
    const text = data.text ?? 'Hello, world!'
    
    • 1

    关键点

    • Null判断运算符有一个目的就是可以跟链判断运算符 ?. 配合使用,为 null 或 undefined 的值设置默认值。
    const animationDuration = settings?.animationDuration ?? 300
    
    • 1

    上面代码中,settings 如果是 null 或 undefined,就会返回默认值300。

    4.13.5 await操作符

    • ES2017新加的,用于让JavaScript异步编程更加的自然
    • 简单来说,await期待一个Promise对象(表示异步计算)作为其唯一的操作数,可以让代码看起来好像是在等待异步计算完成(但实际上它不会阻塞其他异步操作进行)
    • await操作符的值是Promise对象的兑现值,关键在于,await只能出现在已经通过async关键字声明为异步的函数中,即 await和async总是搭配进行使用的

    第6章 对象

    6.1对象简介

    • 对象是一个属性的无序集合,每个属性都有名字和值
    • 对象不仅仅是简单的字符串到值的映射,除了维持自己的属性之外,JS对象也可以从其他对象继承属性,这个其他对象称其为“原型”。对象的方法通常是继承来的属性。

    有时候, 区分直接定义在对象上的属性和那些从原型对象上继承的属性很重要。JS使用术语 “自有属性” 指代非继承属性。

    除了名字和值之外,每个属性还有3个属性特征:
    ① writable(可写)特性指定是否可以设置属性的值
    ② enumerable(可枚举)特性指定是否可以在 for/in 循环中返回属性的名字
    ③ configurable (可配置)特性指定是否可以删除属性,以及是否可修改其特性

    6.2 创建对象

    创建对象的方式(3种)

    1. 对象字面量
    2. new关键字
    3. Object.create()

    6.2.1 对象字面量
    对象字面量的最简单形式是包含在一对花括号中的一组逗号分隔开来的 “名 : 值”对形式

    let empty = {} // 没有属性的对象
    // 包含两个数值的属性
    let point = {
    	x : 1, 
    	y : 0, 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6.10 对象字面量扩展语法 (原型)

    6.10.1 简写属性

    // 之前写法
    let x = 1, y = 2
    let o = {
    	x : x, 
    	y : y, 
    }
    // ES6之后写法
    let x = 1, y = 2
    let o = { x, y }
    o.x + o.y = 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6.2.2 使用new创建对象

    • new操作符用于创建和初始化一个对象,new关键字后面必须跟一个函数调用
    • 以这种方式使用的函数称为构造函数,目的是初始化新创建对象
    • JS为内置的类型提供了构造函数
    let o = new Object() // 创建一个空对象 {}
    let a = new Array() // 创建一个空数组 []
    let d = new Date() // 创建一个表示当前时间的日期对象
    let r = new Map() // 创建一个映射对象
    
    • 1
    • 2
    • 3
    • 4

    除了内置构造函数,我们经常需要定义自己的构造函数来初始化新创建的对象

    6.2.3 原型

    • 每个JS对象都有另一个与之关联的对象,这另一个对象被称为原型,第一个对象从这个原型继承属性
    • 通过对象字面量创建的所有对象都有相同的原型对象,在JS中可以通过Object.prototype引用这个原型对象!
    • 使用new Object() 创建的对象继承自 Object.prototype 与通过 {} 创建的对象一样,类似的Array() 创建的对象以Array.prototype为原型,通过new Date() 创建的对象以Date.prototype为原型
    • **切记:**几乎所有对象都有原型(Object.prototype是为数不多的没有原型的对象,因为它不继承任何属性,其他原型对象都是常规对象,都有自己的原型),但只有少数对象有prototype属性,正是这些有prototype属性的对象为所有其他对象定义了原型
      多数内置构造函数(和多数用户定义的构造函数)的原型都继承自Object.prototype - 例如:Date.prototype 从Object.prototype继承属性,因此通过new Date() 创建的日期对象从 Date.prototype和Object.prototype继承属性。这种原型对象链接起来的序列被称为原型链!

    Object.assign()

    • ES6新增语法,用于合并对象
    let a = {
        kang:''
    }
    let b = {
        kang:'kang'
    }
    console.log(Object.assign(a,b)) // { kang: 'kang' }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Object.create()

    • Object.create()用来创建一个新对象,使用其第一个参数作为新对象的原型
    let o1 = Object.create({x:1, y:2}) // o1继承属性x和y
    o1.x + o1.y // 3
    
    • 1
    • 2
    • 传入null值可以创建一个没有原型的新对象,但是这样创建的新对象不会继承任何东西,连toString() 这种基本方法都没有(意味着不能对该对象应用 +操作符)
    let o2 = Object.create(null) // o2不继承任何属性或方法
    
    • 1
    • 如果想创建一个普通的空对象(类似{}或new Object() 返回的对象,)传入Object.prototype
    let o3 = Object.create(Object.prototype) // o3 与 {} 或 new Object() 类似
    
    • 1
    • 能够以任意原型创建新对象是一种非常强大的技术(Object.create()还可以接收可选的第二个参数,用于描述新对象的属性,这个参数属于高级特性)
    • Object.create()的一个用途:防止对象被某个第三方库函数意外修改,这种情况下,不要直接把对象传给库函数,而要传入一个继承自它的对象。如果函数读取这个对象的属性,可以读到继承的值。但是要设置这个对象的属性,则修改不会影响原始对象。
    let o = {x :"don't change this value"}
    library.function(Object.create(o)) // 防止意外修改
    
    • 1
    • 2

    查询和设置属性

    • 要获得一个属性的值,可以使用(.)或方括号([])操作符
    let author = book.author // 取得book的'author'属性
    let name = author.surname // 取得author的'surname' 属性
    let title = book['main title'] // 取得book的'main title'属性
    
    • 1
    • 2
    • 3
    • 要创建或设置属性,与查询函数一样,可以使用点或方括号,只是要把它们放到赋值表达式的左边
    book.edition = 7 // 为book创建一个'edition'属性
    book['main title'] = 'ECMAScript' // 修改"main title"属性
    
    • 1
    • 2

    使用方括号时,其中的表达式必须求值为一个字符串,更准确的说法是,该表达式必须求值为一个字符串或一个可以转换为字符串或符号的值 - 在下一章,我们看到方括号中使用数字也是很常见的

    第7章 数组

    7.1 创建数组

    基于浏览器F12测试

    let digits = [...'abc']
    digits
    (3) ['a', 'b', 'c']
    
    • 1
    • 2
    • 3

    集合对象是可迭代的,因此要去除数组中的重复元素,一种便捷方式就是先把数组转换为集合,再使用扩展操作符将这个集合转换成数组

    let letters = [...'hello world']
    [...new Set(letters)]
    (8) ['h', 'e', 'l', 'o', ' ', 'w', 'r', 'd']
    
    • 1
    • 2
    • 3

    Array() 构造函数

    1. 不传参数调用
    let a = new Array()
    
    • 1
    1. 传入一个数组参数,指定长度
    let a = new Array(10)
    
    • 1
    1. 传入两个或多个数组元素,或传入一个非数值元素
    let a = new Array(5, 4, 3, 2, 1, 'testing')
    
    • 1

    Array.of()

    • 使用数组参数调用Array() 构造函数时,这个参数指定的是数组长度, Array() 构造函数无法创建只包含一个数值元素的数组
    • ES6中,Array.of() 函数可以解决这个问题,其为工厂方法,可以使用其参数值作为数组元素来创建并返回新数组
    Array.of(1)
    [1]
    Array.of(10)
    [10]
    Array.of(1,2,3,'hello')
    (4) [1, 2, 3, 'hello']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Array.from()

    • ES6中新增的另一个工厂方法, 其期待一个可迭代对象或类数组对象作为其第一个参数,并返回包含该对象元素的新数组,是一个创建数组副本的简单形式
    let origin = [1, 2, 3]
    let copy = Array.from(origin)
    copy
    (3) [1, 2, 3]
    
    • 1
    • 2
    • 3
    • 4
    • 其定义了一种给类数组对象创建真正的数组副本的机制
    • 类数组对象不是数组对象,但也有一个数值length属性,每个属性的键也都是整数,
    • 在客户端JavaScript中,有些浏览器方法返回的值就是类数组对象,那么可以先把它们转换成真正的数组便于后续的操作
    let trueArray = Array.from(arrayLike)
    
    • 1

    读写数组元素

    • 数组索引其实就是一种特殊的对象属性,所以JavaScript数组中没有所谓 “越界” 错误,
    • 查询任何对象中不存在的属性都不会导致错误,只会返回undefined,数组作为一种特殊对象也是如此
    let a = [true, false]
    a[2]
    undefined
    a[-1]
    undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5

    稀疏数组

    • 稀疏数组就是其元素没有从0开始的索引的数组,
    • 数组是稀疏的,则length属性的值会大于元素的个数
    • 真的碰到了稀疏矩阵,可以把稀疏矩阵当成包含undefined元素的非稀疏矩阵

    数组长度

    b = [1, 2, 3, 4, 5]
    (5) [1, 2, 3, 4, 5]
    b.length = 3
    3
    b
    (3) [1, 2, 3]
    b.length = 0 // 删除所有元素 b是[]
    0
    b.length = 5 // 长度为5,但没有元素,类似 new Array(5)
    5
    b
    (5) [空属性 × 5]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    添加和删除数组元素

    • 对新索引赋值
    let a = []
    a[0] = 'zero'
    
    • 1
    • 2
    • 使用push()方法在数组末尾添加一个或多个元素 (**与push() 执行相反操作的是pop()方法,它删除数组最后一个元素并返回该元素,同时导致数组长度-1 **)
    let a = []
    a.push('zero')
    a.push('one', 'two')
    
    • 1
    • 2
    • 3
    • 要在数组开头插入值,可以使用unshift() 方法(在开头删除值 使用shift() 删除并返回数组的第一个元素)

    可以使用delete操作符删除数组元素

    • 使用delete操作符不会修改length属性,即从数组中删除元素后,数组会变稀疏

    迭代数组

    到ES6为止,遍历一个数组的最简单方式就是使用for/of 循环

    let letters = [..."Hello world"]
    let string = ""
    for(let letter of letters){
        // string += letter
        console.log(letter)
    }
    // console.log(string)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    对于稀疏数组,这个循环没有特殊行为,凡是不存在的元素都返回undefined

    如果要对数组使用for/of循环,并且想知道每个数组元素的索引,可以使用数组的entries() 方法和解构赋值

    let letters = [..."Hello world"]
    
    for(let [index, letter] of letters.entries()) {
        console.log('@', index) // 索引从0开始
        console.log('#', letter)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    forEach()

    • 并非新的for循环,而是数组提供的一种用于自身迭代的函数式方法
    • 需要给forEach() 传一个函数,然后forEach() 会用数组的每个元素调用一次这个函数
    let letters = [..."Hello world"]
    
    let uppercase = ''
    letters.forEach(letter => {
       uppercase += letter.toUpperCase() 
    });
    console.log(uppercase) // HELLO WORLD
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    当然使用老式的for循环也可以遍历数组

    for(let i = 0; i < letters.length; i++){
            console.log(letters[i])
        }
    
    • 1
    • 2
    • 3

    当数组是稀疏的,我们想要跳过未定义或不存在的元素时

    for(let i = 0; i < letters.length; i++){
            if(letters[i] === undefined) continue
        }
    
    • 1
    • 2
    • 3

    二维数组

    let table = new Array(10)
    for(let i = 0; i < table.length; i++) {
        table[i] = new Array(10)
    }
    console.log(table)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    二维数组用的较少,访问二维数组的时候使用matrix[row][col]

    数组方法

    数组迭代器方法

    forEach()

    • forEach() 方法迭代数组的每个元素,并对每一个元素都调用一次我们指定的函数
    • 传统forEach() 方法的第一个参数是函数,forEach() 在调用这个函数时会给他它传3个参数:数组元素的值、数组元素的索引、数组本身
    • 如果只关心数组元素的值,可以把函数写成只接收一个参数,忽略其他参数
    <script>
        let data = [1, 2, 3, 4, 5], sum = 0
        // 计算数组元素之和 - 只关心数组元素的值,可以把函数写成只接收一个参数
        data.forEach(value => {
            sum += value
        })
        console.log(sum)  // 15 
    
        /* 
            forEach() 第一个参数是函数
            该函数有三个参数
            - 1、数组元素的值
            - 2、数组元素的索引
            - 3、数组本身
        */
       // 递增每个元素的值
       data.forEach(function(v, i, a){
            a[i] = v + 1
       })
       console.log(data) // [2, 3, 4, 5, 6]
       
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    map()

    • map() 方法把调用它的数组的每个元素分别传给我们指定的函数,返回这个函数的返回值构成的数组
    <script>
        let a = [1, 2, 3]
        b = a.map( x => x * x)
        console.log(a) // [1, 2, 3]
        console.log(b) // [1, 4, 9]
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • map() 返回一个新数组,并不修改调用它的数组, 想要修改的话可以使用原数组进行修改操作
    • 如果数组是稀疏的,缺失元素不会调用我们的函数,但返回的数组也会与原始数组一样稀疏,长度相同,缺失的元素也相同

    filter()

    • filter() 方法返回一个数组,该数组包含调用它的子数组,传给这个方法的函数应该是一个断言函数(返回true和false函数)
    <script>
        let a = [5, 4, 3, 2, 1]
        b = a.filter( x => x < 3) 
        console.log(a) // [5, 4, 3, 2, 1]
        console.log(b) // [2, 1]
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 该函数返回内部逻辑为true的子数组,且该方法不会对原数组进行修改操作,返回一个新的数组
    • 注意:filter()函数会跳过稀疏数组中缺失的数组,返回的数组始终都是稠密的, 因此可以使用filter() 方法来处理掉稀疏矩阵中的空隙
    let dense = sparse.filter(() => true)
    
    let a = [5, 4, , , 1]
    let b = a.filter(() => true) // [5, 4, 1]
    console.log(b)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 如果即想清理空隙又想删除值为undefined和null的元素,可以这样,过滤掉里面的内容即可
    a = a.filter( x => x !== undefined && x!== null)
    
    • 1

    find() 与 findIndex()

    • 与filter() 类似,但是这两个方法会在断言函数中找到第一个元素时停止迭代
    • find() 返回匹配的元素 - 没找到返回undefined
    • findIndex() 返回匹配元素的索引 - 没找到返回-1
    let a = [1, 2, 3, 4, 5]
    console.log(a.findIndex(x => x === 3)) // 2
    console.log(a.find(x => x % 5 === 0)) // 5
    
    • 1
    • 2
    • 3

    every() 与 some()

    • 都是数组断言方法,对数组元素调用我们传入的断言函数,最后返回true或false
    • every() 同数学上的 任意
    • some() 同数学上的存在

    every

    let a = [1, 2, 3, 4, 5]
    console.log(a.every(x => x < 10)) // true
    console.log(a.every(x => x % 2 === 0)) // false 并非所有值都是偶数
    
    • 1
    • 2
    • 3

    some

    let a = [1, 2, 3, 4, 5]
    a.some(x => x % 2 === 0) // true
    
    • 1
    • 2

    注意:

    • every() 和 some() 都会在它们知道要返回什么值时停止迭代数组,如some() 在断言函数第一次返回true时返回true,只有全部返回false才会遍历数组

    reduce() 与 reduceRight()

    • reduce() 与 reduceRight() 使用我们指定的函数归并数组元素,最终产生一个值

    reduce() 接收两个参数

    • 第一个参数:执行归并操作的函数—> 任务:把两个值 归并或组合为一个值并返回这个值,
    • 第二个参数:可选的,是传給归并函数的初始值
    let a = [1, 2, 3, 4, 5]
    console.log(a.reduce((x, y) => x + y, 0)) // 15
    console.log(a.reduce((x, y) => x * y, 1)) // 120
    console.log(a.reduce((x, y) => (x > y) ? x : y)) // 5
    
    • 1
    • 2
    • 3
    • 4
    • 如果不传入初始值,在空数组调用reduce() 会导致typeError
    doneTotal() {
      return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
    },
    
    • 1
    • 2
    • 3

    在此处函数里面的第二个参数又是一个对象

    reduceRight()

    • 与reduce() 类似,只不过是从高索引向低索引来处理数组

    使用flat() 和 flatMap() 打平数组

    flat()

    • 不传参调用时,默认是打平一层嵌套
    [1, [2, 3]].flat() // [1, 2, 3]
    
    • 1
    • 想要打平更多层级,需要给flat() 传一个数组参数
    let a = [1, [2, [3, [4]]]]
    a.flat(2) // [1, 2, 3, [4]]
    
    • 1
    • 2

    flatMap()

    • 与map() 方法类似,只不过返回的数组会自动被打平,就像传给了是flat()
    let phrases = ["hello world", "my love"]
    let words = phrases.flatMap((item) => item.split(""))
    console.log(words)
    
    • 1
    • 2
    • 3

    结果

    [
      'h', 'e', 'l', 'l', 'o',
      ' ', 'w', 'o', 'r', 'l',
      'd', 'm', 'y', ' ', 'l',
      'o', 'v', 'e'
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用concat() 添加数组

    • concat() 方法创建并返回一个新数组,并不修改调用它的数组
    • 新数组包含concat() 方法的数组元素,以及传给concat() 的参数
    • 并不会打平嵌套的数组
    let a = [1, 2, 3]
    console.log(a.concat(4, 5)) // [ 1, 2, 3, 4, 5 ]
    console.log(a.concat([4, 5], [6, 7]))
    console.log(a.concat(4, [5, [6, 7]])) // [ 1, 2, 3, 4, 5, [ 6, 7 ] ]
    console.log(a) // [1, 2, 3]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    说白了,就是只能打平一级的数组

    通过push() 、pop()、shift() 和 unshift() 实现栈和队列的操作

    push() & pop()

    • push() 或者 pop() 可以把数组作为栈来操作
    • push() 方法用于在数组末尾添加一个或多个新元素,并返回数组的新长度, pop() 用于删除数组最后的元素
    • 与concat() 不同 push() 不会打平数组参数
    let stack = []
    stack.push(1, 2)
    console.log(stack) // [1, 2]
    stack.pop()
    console.log(stack) // [1]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • push() 方法不会打平传入的数组,如果想把一个数组中的所有元素 都推送到另一个数组中,可以使用扩展操作符显式打平
    a.push(...value)
    
    • 1

    unshift() & shift()

    • 从数组开头而非末尾插入和删除元素
    • unshift() 用于在数组开头添加一个或多个元素,已有元素索引会向更高索引移动,并返回数组新长度
    • shift() 删除并返回数组的第一个元素
    • 可以使用push() 在数组末尾添加元素,使用shift() 在数组开头删除元素来实现队列
    let q = []
    let len = q.push(1, 2)
    console.log(len) // 2
    q.shift()
    console.log(q) // [2]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • unshift() 有一个很重要的特性:在给unshift() 传多个参数的时候,这些参数会一次性插入数组,这意味着一次插入与多次插入之后的数组顺序是不一样的
    let a = []
    a.unshift(1)
    a.unshift(2) // [2, 1]
    a = []
    a.unshift(1, 2) // [1, 2]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用slice() 、splice() 、fill() 、copywith()

    以上是数组定义的处理连续区域(数组切片)的方法

    1、slice()

    • slice() 方法返回一个数组的切片或者子数组
    • 两个参数:指定切片的开始位置和结束位置**(取不到)** ① 只指定一个参数:该起点直到数组末尾的所有元素 ② 其中任何一个参数为负数,则这个值相对于数组长度来指定数组元素(如:参数-1指定数组的最后一个元素
    • slice() 不会修改调用它的数组
    let a = [1, 2, 3, 4, 5]
    a.slice(0, 3) // [1, 2, 3]
    a.slice(3) // [4, 5]
    a.slice(1, -1) // [2, 3, 4]
    console.log(a.slice(-3, -2)) // [3]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、splice()

    删除操作:

    • splice() 是一个对数组进行插入和删除的通用方法
    • 与slice() 和 concat() 不同, splice() 会修改调用它的数组
    • 作用:① 从数组中删除元素 ② 向数组插入新元素 ③ 可以同时执行这两种操作
    • 位于插入点和删除点之后的元素的索引会按照需要增大或减小,从而与数组剩余部分保持连续,
    • 参数:参数一:指定插入或删除的起点位置 参数二:指定要从数组中删除的元素个数(如果省略,从起点元素开始的所有数组元素将被删除)
    • splice() 返回被删除元素的数组,如果没有删除元素返回空数组
    
    let a = [1, 2, 3, 4, 5, 6, 7, 8]
    console.log(a.splice(4)) // [5, 6, 7, 8]
    console.log(a) // [1, 2, 3, 4]
    
    a.splice(1, 2) // [2, 3] , a现在是[1, 4]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    插入操作

    • 前两个参数指定要删除哪些元素,这两个参数后面可以跟任意多个参数,表示要在第一个参数指定的位置插入到数组中的元素,例如:
    let a = [1, 2, 3, 4, 5]
    a.splice(2, 0, 'a', 'b') // [], a现在是[1, 2, 'a', 'b', 3, 4, 5]
    a.splice(2, 2, [1, 2], 3) // ['a', 'b'], a现在是[1, 2, [1, 2], 3, 3, 4, 5]
    
    • 1
    • 2
    • 3

    3、fill()

    • fill() 方法将数组的元素或切片设置为指定的值
    • 修改调用它的数组, 也返回修改后的数组
    • 参数:参数一:要把数组元素设置成的值, 可选参数二:指定起始索引(如果省略,则从索引0开始填充) 可选参数三:指定终止索引,到这个索引为止(但不包含)的数组元素会被填充(如果省略直接填充到末尾)
    let a = new Array(5) // 创建一个长度为5的没有元素的数组
    a.fill(0) // [0, 0, 0, 0, 0] 用0来填充数组
    a.fill(9, 1) // [0, 9, 9, 9, 9]
    a.fill(8, 2, -1) // [0, 9, 8, 8, 9]
    
    • 1
    • 2
    • 3
    • 4

    **4、copyWithin()**用的较少

    • copyWithin() 把数组切片复制到数组中的新位置
    • 其会就地修改数组并返回修改后的数组,但不会改变数组长度
    • 参数:参数一:指定要把第一个元素复制到的目的索引 参数二:指定要复制的第一个元素索引(省略的话默认为0) 参数三:指定要复制的元素切片的终止位置(包含)
    let a = [1, 2, 3, 4, 5]
    a.copyWithin(1) // [1, 1, 2, 3, 4] 把数组元素复制到索引及之后
    a.copyWithin(2, 3, 5) // [1, 1, 3, 4, 4] 把最后两个元素复制到索引2
    a.copyWithin(0, -2) // [4, 4, 3, 4, 4] 负偏移也可以
    
    • 1
    • 2
    • 3
    • 4

    数组索引和排序方法

    1、indexOf() 和 lastIndexOf()

    • 从数组中搜索指定的值并返回找到第一个元素的索引,如果没有找到返回-1
    • indexOf() 从前往后, lastIndexOf()从后往前
    let a = [0, 1, 2, 1, 0]
    a.indexOf(1)  // 1
    a.lastIndexOf(1) // 3
    a.indexOf(3) // -1
    
    • 1
    • 2
    • 3
    • 4

    注意: indexOf()和lastIndexOf() 都接收第二个可选的参数,指定从哪个位置开始搜索,如果省略这个参数,indexOf() 从头开始搜索,lastIndexOf() 从尾开始搜索

    let a = [1, 2, 3, 4, 1, 2]
    console.log(a.indexOf(1, 2)) // 4
    
    • 1
    • 2
    /* 
        从数组a中找到所有值x,返回匹配索引的数组
    */
    function findAll(a, x) {
        let result = []
        len = a.length
        pos = 0 
        while(pos < len) {
            pos = a.indexOf(x, pos)
            if(pos === -1) break // 如果没有找到,结束即可
            result.push(pos) // 把索引保存在数组中
            pos += 1
        }
        return result
    }
    
    console.log(findAll([1, 2, 3, 1, 1, 2], 1)) // [0, 3, 4]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    字符串也有indexOf() 和 lastIndexOf() 方法,和这两个数组方法类似,区别在于第二个参数如果是负数会被当成是 0

    **2、includes() **

    • ES2016的includes() 方法接收一个参数,如果数组包含该值则返回true,否则返回false,并不会告诉你值的索引
    • 实际上就是测试数组的成员是否属于某个集合
    • indexOf() 无法检测到数组中的NaN值,但是includes() 可以
    let a = [1, true, 3, NaN]
    a.includes(true) // true
    a.includes(2) // false 
    a.includes(NaN) // true
    a.indexOf(NaN) // -1 indexOf无法找到NaN的值
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、sort()

    • sort() 对数组元素就地排序并返回排序后的数组,不传递参数时,sort() 按字母顺序对数组元素排序
    let a = ['banana', 'apple']
    a.sort() // a == ['apple', 'banana']
    
    • 1
    • 2
    • 要对数组元素执行非字母顺序的排序,必须给sort() 传递一个比较函数作为参数
    let a = [33, 4, 1111, 222]
    
    a.sort() 
    
    a.sort(function(a, b) { // 传入一个比较函数
        return a - b // 取决于顺序,返回 <0、0、>0
    }) // a == [4, 33, 222, 1111]
    
    a.sort((a, b) => b - a) // a == [1111, 222, 33, 4]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 如果相对字符串数组做不区分大小写的字母排序,传入的比较函数应该先使用(toLowerCase() 方法)

    4、reverse()

    • reverse() 反转数组元素的顺序,并返回反序后的数组
    • 不会用重新排序后的元素创建新数组,而是直接对已经存在的数组重新排序
    let a = [1, 2, 3]
    a.reverse() // a == [3, 2, 1]
    
    • 1
    • 2

    数组到字符串的转换

    Array类定义了3个把数组转换为字符串的方法,通常可以用在记录日志或错误信息的时候

    **join() **

    • join() 方法把数组的所有元素转换为字符串,然后把他们拼接起来并返回结果字符串
    • 可以指定一个可选的字符串参数,用于分隔结果字符串中的元素,如果不指定分隔符,则默认使用逗号
    let a = [1, 2, 3]
    a.join() // '1,2,3'
    
    a.join(" ") // '1 2 3'
    
    a.join("") // '123'
    
    let b = new Array(10)
    b.join("-") // '---------'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    String.split()
    join() 方法执行的是String.split() 的反向操作,后者通过把字符串分割为多个片段来创建数组 split() 方法中需要放入的是字符串形式的

    let res = 'hello world'
    res.split("") // (11) ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
    
    "2:3:4".split(':') // (3) ['2', '3', '4']
    
    • 1
    • 2
    • 3
    • 4

    toString() - 对于数组而言,该方法的逻辑和没有参数的join方法是一致的

    [1, 2, 3].toString()
    '1,2,3'
    ['a', 'b', 'c'].toString()
    'a,b,c'
    [1, [2, 'c']].toString() 
    '1,2,c'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    静态数组函数

    Array类也定义了3个静态函数,可以通过Array构造函数而非数组调用 Array.of() 和 Array.from() 是创建新数组的工厂方法,之前介绍过,另一个静态数组函数是Array.isArray() 用于确定一个未知值是不是数组

    Array.isArray([]) // true
    Array.isArray({}) // false
    
    • 1
    • 2

    类数组对象

    JavaScript数组具有一些其他对象不具备的特殊特性

    1. 数组的length属性会在新元素加入时自动更新
    2. 设置length属性为更小的值会截断数组
    3. 数组从Array.prototype继承有用的方法
    4. Array.isArray() 对数组返回true

    以上特性让JavaScript数组与常规对象有了明显区别,但这些特性并非定义数组的本质特性 事实上只要对象有一个数值属性length,而且有相应的非负整数属性,就完全可以视同为数组

    下面的代码会为一个常规对象添加属性,让它成为一个类数组对象,然后再遍历得到伪数组的“元素”

    let a = {} // 创建一个常规的空对象
    
    // 添加属性让它变成“类数组”对象
    let i = 0
    while(i < 10) {
        a[i] = i * i
        i++
    }
    /* 
        只要对象有一个数值属性length,而且有相应的非负整数属性
        那就可以完全可以视同为数组
    */
    a.length = i 
    
    // 像遍历真正的数组一样遍历这个对象
    let total = 0
    for(let j = 0; j < a.length; j++) {
        total += a[j]
    }
    console.log(total) // 285
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注意:

    • 在客户端JavaScript中,很多操作HTML文档的方法(document.querySelectorAll())都返回类数组对象,下面的函数可以用来测试对象是不是类数组对象
    • 测试:是否为类数组对象:
    /* 
        确定对象o是不是类数组对象
    */
    function isArrayLike(o) {
        if (o
            && typeof o === 'object' &&
            Number.isFinite(o.length) &&
            o.length >= 0 &&
            Number.isInteger(o.length) &&
            o.length < 4294967295) {
            return true
        } else {
            return false
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 字符串的行为与数组类似,但是上述function对字符串肯定返回false
    • 字符串最好还是最为字符串而非数组来处理

    多数数组方法有意设计成了泛型方法,因此除了真正的数组,同样可以用于类数组对象,但由于类数组对象不会继承Array.prototype,所以无法直接在他们身上调用数组方法,因此可以使用Function.call() 方法

    let a = {'0':'a', '1':'b', '2':'c', length:3} // 类数组对象
    Array.prototype.join.call(a, '+')  // 'a+b+c'
    Array.prototype.slice.call(a, 0) // ['a', 'b', 'c'] 
    Array.from(a) // ['a', 'b', 'c'] 更容易的数组复制
    
    • 1
    • 2
    • 3
    • 4

    倒数第二行代码在类数组对象调用了Array的slice方法,把该对象的元素复制到一个真正的数组对象中,在很多遗留demo中这是常见的习惯做法, 但是现在使用Array.from() 会更容易

    作为数组的字符串

    • JavaScript字符串的行为类似于UTF-16 Unicode字符的只读数组,除了使用charAt() 方法访问个别字符,还可以使用方括号语法
    let s = 'test'
    s.charAt(0) // 't'
    s[1] // 'e'
    
    • 1
    • 2
    • 3

    当然对字符串而言,typeof操作符仍然返回’string’, 把字符串传给Array.isArray()方法仍然返回false

    重要

    • 字符串与数组的行为类似意味着我们可以对字符串使用泛型的字符串方法
    Array.prototype.join.call('JavaScript', ' ')
    'J a v a S c r i p t'
    
    • 1
    • 2
    • 切记:字符串是不可修改的值,因为把他们当成数组来使用时,他们只是只读数组,向push()、sort() 、reverse() 这些就地修改数组的数组方法不生效!

    第8章 函数

    • 函数是一个JavaScript代码块,定义之后,可以被执行或调用任意多次
    • 函数定义可以包含一组标识符,称为**参数或形参,**这些形参类似于函数体内定义的局部变量,函数调用会为这些形参提供值或实参
    • 除了实参,每个调用还有另外一个值,即调用上下文,也就是this关键字的值
    • 如果函数是在一个对象上被调用或通过一个对象被调用,这个对象就是函数的调用上下文或this值
    • JavaScript函数可以嵌套定义在其他函数里,内嵌的函数可以访问定义在函数作用域的任何变量 ==> JavaScript函数是闭包,基于闭包可以实现重要且强大的编程技巧

    定义函数

    • 使用function关键字
    • ES6定义了一种新的方式,可以不通过function关键字定义函数,使用箭头函数来定义函数

    函数声明

    function factorial(x) {
    	if(x <= 1) return 1
    	return x * factorial(x-1)
    
    • 1
    • 2
    • 3
    • 要理解函数声明,关键是理解函数的名字变成了一个变量,这个变量的值就是函数本身。

    函数表达式

    • 函数表达式看起来很像函数声明,但他们出现在复杂表达式或语句的上下文中,而且函数名是可选的
    /* let stack = []
    stack.push(1, 2)
    s
    console.log(stack) // [1, 2]
    stack.pop()
    console.log(stack) // [1] */
    
    
    let q = []
    let len = q.push(1, 2)
    console.log(len) // 2
    q.shift()
    console.log(q) // [2]
    
    let a = []
    a.unshift(1)
    a.unshift(2) // [2, 1]
    a = []
    a.unshift(1, 2) // [1, 2]
    
    // 定义一个对参数求平方的函数,并将其赋值给一个变量
    const square = function(x){ return x * x}
    
    // 函数表达式也可以包含名字,这对递归有用
    const f = function fact(x) { if (x <= 1) return 1; else return x * fact(x - 1)}
    
    // 函数表达式也可以用作其他函数的参数
    [3, 2, 1].sort(function(a, b){ 
        return a-b 
    })
    
    // 函数表达式也可以定义完以后立即调用
    let tensquared = (function(x) { return x * x}(10))
    
    // tensquared() // 报错
    tensquared // 100  tensquared代表的就是整个函数
    
    • 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

    使用函数声明定义函数f() 与 创建一个函数表达式再将其赋值给变量f有一个重要的区别

    • 声明形式:先创建好函数对象,然后再运行包含它们的代码,而且函数的定义会被提升到顶部,因此在定义函数的语句之前就可以调用它们。
    • 定义为表达式的函数:这些函数在定义它们的表达式实际被求值之前是不存在的,且定义为表达式的函数不能在它们的定义之前调用!

    箭头函数

    • 使用“箭头”分隔函数的参数和函数体,因为箭头函数是表达式而不是语句,所以不用使用function关键字,而且也不需要函数名
    • 箭头函数的一般形式
    const sum = (x, y) => { return x + y; }
    
    • 1
    • 函数体只有一个return 语句的话,那么可以省略return 关键字、语句末尾的分号以及花括号,将函数体写成一个表达式,值将被返回
    const sum = (x, y) => x + y
    
    • 1
    • 在进一步 如果箭头函数只有一个参数,也可以省略包围参数列表的圆括号
    const polynomial = x => x*x + 2*x + 3
    
    • 1
    • 对于没有参数的箭头函数则必须把空圆括号写出来
    const constantFunc = () => 42
    
    • 1

    重要

    • 如果箭头函数的函数体是一个return 语句,但要返回的表达式是对象字面量,那必须要把这个对象字面量放在一对圆括号中,以避免解释器分不清花括号到底是函数体的花括号,还是对象字面量的花括号。
    const f = x => {return { value : x;}} // 正 f() 返回一个对象
    const g = x = > ({value : x}) // 正 g() 返回一个对象
    
    • 1
    • 2

    箭头函数的简洁语法让他们非常适合作为值传给其他函数,在使用map() 、filter()和reduce() 等数组方法时非常常见

    // 得到一个过滤掉null 元素的数组
    let filtered = [1, null, 2, 3].filter(x => x !== null) // filtered == [1, 2, 3]
    // 求数值的平方
    let squares = [1, 2, 3, 4].map(x => x*x) // squares == [1, 4, 9, 16]
    
    • 1
    • 2
    • 3
    • 4

    与其他方式定义的函数的区别

    • 箭头函数从定义自己的环境中继承this关键字的值,而不是像以其他方式定义的函数那样定义自己的调用上下文
    • 箭头函数没有prototype属性,这意味着箭头函数不能作为新类的构造函数

    嵌套函数

    • js中函数可以嵌套在其他函数中
    function hypotenuse(a, b) {
        function square(x) { return x * x;}
        return Math.sqrt(square(a) + square(b));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 关于嵌套函数,最重要的是理解它们的变量作用域规则:嵌套函数可以访问包含自己的函数(或更外层函数)的参数和变量
    • 在上述函数中,内部函数square() 可以读写外部函数 hypotenuse() 定义的参数a和b

    调用函数

    5种方式来进行调用

    • 作为函数
    • 作为方法
    • 作为构造函数
    • 通过call() 或 apply() 方法间接调用
    • 通过js语言特性隐式调用

    1、函数调用

    let total = distance(0, 0, 2, 1) + distance(2, 1, 3, 5)
    
    • 1

    2、方法调用

    • 方法其实就是js中的函数,只不过他保存为对象的属性,如:有一个函数f和一个对象o,那么可以给o定义一个名为m的方法
    o.m = f
    
    • 1
    • 对象o有了方法m() 后,就可以这样进行调用
    o.m()
    
    • 1
    • 如果m期待两个参数,可以这样调用
    o.m(x, y)
    
    • 1

    方法调用与函数调用有一个重要区别

    • 调用上下文, 属性访问表达式有两部分构成,对象(这里的o)和属性名(m),在像这样的方法调用表达式中,对象o会成为调用上下文,而函数体可以通过关键字this引用这个对象
    /* let stack = []
    stack.push(1, 2)
    console.log(stack) // [1, 2]
    stack.pop()
    console.log(stack) // [1] */
    
    
    // let q = []
    // let len = q.push(1, 2)
    // console.log(len) // 2
    // q.shift()
    // console.log(q) // [2]
    
    // let a = []
    // a.unshift(1)
    // a.unshift(2) // [2, 1]
    // a = []
    // a.unshift(1, 2) // [1, 2]
    
    // // 定义一个对参数求平方的函数,并将其赋值给一个变量
    // const square = function(x){ return x * x}
    
    // // 函数表达式也可以包含名字,这对递归有用
    // const f = function fact(x) { if (x <= 1) return 1; else return x * fact(x - 1)}
    
    // // 函数表达式也可以用作其他函数的参数
    // [3, 2, 1].sort(function(a, b){ 
    //     return a-b 
    // })
    
    // // 函数表达式也可以定义完以后立即调用
    // let tensquared = (function(x) { return x * x}(10))
    
    // // tensquared() // 报错
    // tensquared // 100  tensquared代表的就是整个函数
    
    
    /* let a = function square(x) {
        return x * x
    } */
    
    /* function hypotenuse(a, b) {
        function square(x) { return x * x;}
        return Math.sqrt(square(a) + square(b));
    } */
    
    let calculator = {
        operand1 : 1, 
        operand2 : 1, 
        add() {
            // 此处的this就是 包含了calculator等内容 
            /* 
                { operand1: 1, operand2: 1, add: [Function: add] }
            */
            console.log(this) // 其中此处的this就是函数调用的上下文
            this.result = this.operand1 + this.operand2
            console.log(this.result)
        }, 
    }
    console.log(calculator.add())
    console.log(calculator.result)
    
    // 显示结果 非常关键
    { operand1: 1, operand2: 1, add: [Function: add] }
    2
    undefined
    2
    
    • 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
  • 相关阅读:
    【JVM】Java虚拟机
    【Qt】QLocalSocket与QLocalServer问题:接收不到数据、只能收到第一条、数据不完整解决方案【2023.05.24】
    物联网开发笔记(1)- 使用Wokwi仿真树莓派Pico点亮LED灯
    JavaSwing项目合集专栏订阅须知
    chrome浏览器 调试鼠标悬停后出现的元素样式
    项目管理工具dhtmlxGantt入门教程(一):如何安装dhtmlxGantt
    Android Media Framework(一)OpenMAX 框架简介
    多边形三角剖分的最小值 python
    Java多线程基础
    redis的安装、基础命令及常用数据结构
  • 原文地址:https://blog.csdn.net/A13526_/article/details/125567167