JavaScript学习笔记(二)

1.语句

1.1声明

//const、let与var
const PI = Math.PI
let num = 9
var text = "Hello World!"

//常量不可更改
try {
    PI = "A"
} catch (err) {
    console.log(err)
}

//function
function tFun(val, num) {
    console.log(val, num)
    arguments[0] = 100
    arguments[1] = 200
    console.log(val, num)
}
tFun(1, 2)

//class
class faceToObj {
    constructor(par) {
        this.radius = par
    }
    perimeter() {
        return (2 * Math.PI * this.radius).toFixed(2) - 0
    }

}
let newObj = new faceToObj(5)
console.log(newObj.perimeter())

1.2表达式语句

//声明赋值表达式语句
let num = 1
let newObj = {
    name: 'midshar',
    sex: 'male'
}
let newFun = function () {
    console.log(999)
}

//递增与递减表达式语句
num++
num--

//delete表达式语句
delete (newObj.sex)
console.log("sex" in newObj)//=>false

//函数调用表达式语句
newFun()
console.log(111)

1.3条件语句

let condition = 5

//if()else()语句
if (condition) {
    console.log(condition)
} else {
    console.log(-1)
}

//if()else if()语句
if (condition === 1) {
    console.log("1")
} else if (condition === 0) {
    console.log("0")
} else {
    console.log("other")
}

//switch分支选择语句
let dateDetail = "星期日"
switch (dateDetail) {
    case "星期一":
        console.log(dateDetail)
        break;
    case "星期二":
        console.log(dateDetail)
        break;
    case "星期三":
        console.log(dateDetail)
        break;
    case "星期四":
        console.log(dateDetail)
        break;
    case "星期五":
        console.log(dateDetail)
        break;
    case "星期六":
        console.log(dateDetail)
        break;
    case "星期日":
        console.log(dateDetail)
        break;
    default:
        console.log("input error!")
}

1.4跳转语句

//语句标签
// codeSign: for (let i = 0; i < 10; i++) {
//     if (i !== 5) {
//         console.log(i)
//     } else {
//         break codeSign
//     }
// }

//break 的使用,用于提前结束循环
let textArray = [111, 222, 333, 444, 555]
for (let i = 0; i < textArray.length; i++) {
    console.log(textArray[i])
    if (textArray[i] === 333) break
}

//求和二位数值数组之和
let planeArray = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]
let valueSum = 0
let flag = false
sumPlane: if (planeArray) {//可以直接在任何语句块前添加标识符
    for (let item = 0; item < planeArray.length; item++) {
        let rowLine = planeArray[item]
        if (!(rowLine instanceof Array)) {//判断planeArray中每个元素是不是数组类型
            break sumPlane//跳转结束循环
        }
        for (insideItem = 0; insideItem < rowLine.length; insideItem++) {
            if (typeof (rowLine[insideItem]) === "number") {//判断每个元素是不是number类型
                valueSum += rowLine[insideItem]
            } else {
                break sumPlane//跳转结束循环
            }
        }
    }
    flag = true
}
console.log(flag, valueSum)//=>true 45

//continue 的使用,从头开始执行循环的下一次迭代
let tContinueFun = function (val) {//过滤数组中的非 0 数值
    let newArray = []
    if (val instanceof Array) {//判断参数类型
        for (let i = 0; i < val.length; i++) {
            if (!val[i]) {
                continue
            } else {
                newArray.push(val[i])
            }
        }
        console.log("Done!")
    } else {
        console.warn("TypeError!")
    }
    return newArray
}
console.log(tContinueFun([1, 5, 1, 2, 0, 0, 5, 4, 3]))//=>[1, 5, 1, 2, 5, 4, 3]

let tFunReturn = function (val) {//当 val 是假性值时,返回 false
    if (!val) {
        return false
    } else {
        return true
    }
}
console.log(tFunReturn(NaN))//=>false
console.log(tFunReturn(undefined))//=>false
console.log(tFunReturn(""))//=>false
console.log(tFunReturn(0))//=>false

//使用 yield返回
let tFunYield = function* (from, to) {
    for (let i = from; i < to; i++) {
        yield i
    }
}
console.log(tFunYield(5, 10))

//使用 throw 返回值
let tFacFunThrow = function (val) {//计算阶乘
    if (val < 0) {//当参数小于 0 时,抛出错误
        throw new Error("The parameter must be a positive number.")
    }
    let bottle = 1
    for (let i = val; i > 1; bottle *= i, i--);
    return bottle
}
console.log(tFacFunThrow(9))

//使用 try/catch/finally
let tFunTry = function () {
    try {
        console.log({}.toFixed())
    } catch (err) {
        console.log(err)
    } finally {
        console.log("Finally")
    }
}
tFunTry()

let tFunFinally = function () {
    try {
        console.log(g)
    } catch {
        return undefined//catch 的简写形式
    }
}
tFunFinally()

1.5循环语句

//while循环
let i = 0
while (i < 10) {
    console.log(i++)
}

//do while循环
let j = 1
do {
    console.log(`DoWhile ${j++} time(s)`)
} while (j <= 10);

//for 循环,其中的分号不能省略
for (let m = 0, n = 10; m < n; m++, n--) {//逗号表达式
    console.log(m * n)
}

//for/of 循环可迭代Object,包括数组、字符串、映射、集合,对象不能使用for/of遍历
let newArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let newObj = {
    prop1: "val1",
    prop2: "val2",
    prop3: "val3"
}
let sum = 0
for (let item of newArray) {
    sum += item
}
console.log(sum)//=>45

//for/of与对象
//使用for/of迭代对象的key值
let str = ""
for (let key of Object.keys(newObj)) {
    str += key
}
console.log(str)

//使用for/of迭代对象的value值
let strv = ""
for (let val of Object.values(newObj)) {
    strv += val
}
console.log(strv)

//使用for/of迭代对象的key与value
for (let [key, val] of Object.entries(newObj)) {//解构
    console.log(key, val)
}

//for/of与字符串
let testStr = "mississippi"
let countLetter = {}//新建一个统计单词中字母出现次数的对象
for (let letter of testStr) {
    if (!countLetter[letter]) {//若对象中没有该key值,则初始化次数为 1
        countLetter[letter] = 1
    } else {//否则,次数+1
        countLetter[letter]++
    }
}
console.log(countLetter)

//Set(集合)中的for/of
let strTest = "li li li li ol li li ul"
let afterChange = new Set(strTest.split(" "))//新建 Set 类型数据集
let saveArray = []
for (let eItem of afterChange) {
    saveArray.push(eItem)
}
console.log(saveArray)

//Map(映射)中的for/of
let mapTest = new Map([[1, "one"], [2, "two"], [3, "three"]])
for (let [front, back] of mapTest) {
    console.log(front, back)
}

//for/in 循环
let testObj = {
    id: 1,
    title: "标题",
    content: "内容"
}
let collectProp = []
for (let perItem in testObj) {
    collectProp.push(perItem)
}
console.log(collectProp)

//利用for/in粘贴对象属性
let index = 0
let propArray = []
for (propArray[index++] in testObj);
console.log(propArray)

1.6复合语句与空语句

//复合语句
let i = 0
while (i < 10) {
    console.log(`${i}次循环`)
    console.log(++i)
}

//空语句
let newArray = []
for (let j = 0; j < 10; newArray[j++] = j) {
    ;
}

1.7其他语句

//with 语句,快捷访问对象属性
let newObj = {
    name: "midshar",
    sex: "male",
    age: 18
}
with (newObj) {//使用 with 语句
    name = "changeName"
    sex = "changeSex"
    age = "changeAge"
}
console.log(newObj)

//debugger 语句,相当于断点
let tFunDebugger = function (val) {
    if (val === undefined) debugger//使用 debugger 断点,当被传入参数为 undefined 时,启用断点
    return val ** 2
}
console.log(tFunDebugger())

1.8严格模式

"use strict"//开启严格模式
let newObj = {
    name: "midshar",
    sex: "male",
    age: 18
}

//不允许使用 with
//所有变量必须声明
//函数调用中的 this 指向 undefined
let tFunThis = function (val) {
    console.log(this)
}
tFunThis()
//给不可写的属性赋值或尝试在不可扩展的对象中创建新属性会 throw TypeError
try {
    Object.prototype = "2"
} catch (err) {
    console.log(err)
}
//函数中的Arguments对象保存着一份传给函数的值的静态副本
!function (val) {
    console.log(val)
    arguments[0] = 100//更改 arguments 数组的值后,不会修改原参数的值
    console.log(val)
}(999)

//delete后跟一个未限定的标识符会 throw SyntaxError
// delete (newObj)

//在严格模式下,尝试删除不可配置属性会 throw TypeError
try {
    delete (Object.prototype)
} catch (err) {
    console.log(err)
}

//对象定义两个或两个以上同名属性是语法错误,但在 Chrom12 版本中不会报错
!function () {
    let tNewObj = {
        prop: "aaa",
        prop: "bbb"
    }
    console.log(tNewObj)
}()

//函数声明中有两个或两个以上同名参数是语法错误
// let tNewFun = function (val, val, val) {
//     console.log(val)
// }
// tNewFun(9)

//不允许使用八进制正数字面量
//eval与arguments被当做关键字,不允许修改他们的值
//检查调用栈的能力是受限的

2.对象

2.1创建对象

//使用对象字面量创建(允许属性名为空字符串 "")
let newObjWithLiteralQuantity = {
    "": "空字符串",
    id: 110,
    subObj: {
        name: "midshar",
        sex: "male",
        hobbies: ["basketball", "running", "singing"]
    },
    for: "originMethod"
}
console.log(newObjWithLiteralQuantity)

//使用 new Object() 创建对象
let newObjWithNewConstructionFun = new Object()
newObjWithNewConstructionFun.propName = "构造函数"
newObjWithNewConstructionFun.propIdUnm = "构造函数"
newObjWithNewConstructionFun.propSex = "构造函数"
newObjWithNewConstructionFun.propT = "构造函数"
console.log(newObjWithNewConstructionFun)

//使用 Object.create() 创建对象
//Object.prototype()没有原型
console.log(Object.prototype)
//Date.prototype()从Object.prototype()继承原型
console.log(Date.prototype)
//使用 Object.create() 创建一个普通对象原型
let newNormalObj = Object.create({ prop1: "属性值1", prop2: "属性值2" })
console.log(newNormalObj)
//使用 Object.create() 创建一个null对象
let newNullObj = Object.create(null)
console.log(newNullObj)
//使用 Object.create() 创建一个普通空对象
let newBlankObj = Object.create(Object.prototype)
console.log(newBlankObj)

2.2对象的方法

let newObj = {
    name: 'midshar',
    sex: 'male',
    age: 20
}

//toString() 方法,只要需要将对象转换为字符串形式时,会自动调用该方法
console.log(newObj.toString())//=>[object Object]

//很多类会定义自己得 toString() 方法
//Function会返回函数源码
console.log(function () { return false }.toString())
//Array会返回单独的每个元素
console.log([1, 2, 3, 4, 5].toString())

//单独为自己的元素定义 toString() 方法
let tNewObj = {
    propX: "测试数据",
    propY: "测试数据",
    toString: function () {
        let resObj = ""
        for (let i in this) {
            resObj += `${i}:${this[i]}\n`
        }
        return resObj
    }
}
console.log(tNewObj.toString())
console.log(String(tNewObj))

//使用 toLocaleString() 方法
//Date对象
console.log(new Date().toLocaleString())
//Number对象
console.log((999).toLocaleString())
//Array对象,会调用每个元素对应的 toLocaleString() 方法
let newArray = [
    {
        name: 'midshar',
        sex: 'male',
        age: 18
    },
    function () {
        return false
    },
    new Date(),
    999
]
console.log(newArray.toLocaleString())

//valueOf() 方法,在某些对象需要转换为原始值时会自动调用,一般是转换为数值时
let tPoint = {
    x: 3,
    y: 4,
    valueOf: function () {
        return ((this.x) ** 2 + (this.y) ** 2) ** (1 / 2)
    }
}
console.log(tPoint.valueOf())
console.log(new Date().valueOf())//转换为时间戳

//toJSON() 方法
//Date 类定义了自己的 toJSON 方法
console.log(new Date().toJSON())

2.3对象字面量的扩展方法

//属性简写,当对象中属性与值同名时,可以简写
let oName = 'midshar'
let oSex = 'male'
let oAge = 18
let newPerson = {
    oName,
    oSex,
    oAge
}
console.log(newPerson)

//使用变量属性名,在对象中将变量使用方括号[]
let varPropName1 = 'prop1'
let varPropName2 = function () {
    return varPropName1.slice(0, -1) + '2'
}
let newObjWithVarProp = {
    [varPropName1]: 1,
    [varPropName2()]: 2
}
console.log(newObjWithVarProp)

2.4函数作为对象属性值的简写方法

//在对象中简写函数方法
let newObj = {
    id: 1,
    max(a, b) {
        return a > b ? a : b
    },
    min(a, b) {
        return a > b ? b : a
    }
}

console.log(newObj.max(999, 255))
console.log(newObj.min(999, 255))

2.5扩展操作符

//扩展操作符 ... 不会扩展继承属性
let inheritObj = Object.create({ root: "根" })
inheritObj.x = "newX"
inheritObj.y = "newY"
let extendNewObj = { ...inheritObj }
console.log(inheritObj)
console.log(extendNewObj)
console.log(extendNewObj.root)

2.6扩展对象

//定义测试对象
let targetObj = {
    tName: "mimimi"
}
let sourceObj = {
    sSex: "male",
    sAge: 20,
    sLocation: "unknown"
}

//复制对象
for (let key of Object.keys(sourceObj)) {
    targetObj[key] = sourceObj[key]
}
console.log(targetObj, sourceObj)

//扩展方法 Object.assign,用来合并对象
let o1 = {
    x: "x"
}
let o2 = {
    y: "y"
}
let o3 = {
    z: "z"
}
let o4 = {
    m: "m"
}
let o5 = {
    n: "n"
}
Object.assign(o1, o2, o3, o4, o5)
console.log(o1)

let newObj = Object.assign({}, o1, o2, o3, o4, o5)
console.log(newObj)

//使用展开运算符
newObj = {
    ...newObj,
    ...targetObj,
    ...sourceObj
}
console.log(newObj)

//定义新方法,复制不覆盖版本
let newFunWithNoRepeatCopy = function (target, ...source) {
    console.log(source)
    for (let item of source) {
        console.log(item)
        for (let sKey of Object.keys(item)) {
            if (!target[sKey]) {
                target[sKey] = item[sKey]
            }
        }
    }

    return target
}
let res = newFunWithNoRepeatCopy({
    q: "tProp",
    r: "tProp"
}, {
    q: "oProp",
    w: "oProp",
    e: "oProp"
}, {
    q: "lProp",
    e: "lProp",
    m: "lProp",
    n: "lProp"
})
console.log(res)

2.7枚举属性

//定义测试对象
let newObj = {
    name: "midshar",
    sex: "male",
    age: 18,
    location: "SiChuan",
    hobbies: ["singing", "swimming", "running"],
    familyMembers: [
        {
            relationship: "Dad",
            sex: "male",
            age: 42
        },
        {
            relationship: "Mom",
            sex: "female",
            age: 40
        }
    ],
    "100": 100,
    "-100": -100,
    toString: "owner"
}

//使用 for/in 获取可枚举属性
let keysArray = []
for (let key in newObj) {
    keysArray.push(key)
}
console.log(keysArray)

//使用 Object.keys 获取可枚举属性
console.log(Object.keys(newObj))

//使用 Object.getOwnPropertyNames 获取可枚举与不可枚举属性
console.log(Object.getOwnPropertyNames(newObj))

//使用 Object.getOwnPropertySymbols 获取 Symbol 类型属性
console.log(Object.getOwnPropertySymbols(newObj))

//使用 Reflect.ownKeys获取所有属性
console.log(Reflect.ownKeys(newObj))

2.8删除属性

//使用 delete 删除自有属性,即使属性不存在,也会返回 true,它只会删除自有属性,不会删除继承属性
let protoObj = {
    self: "原型"
}
let newObj = Object.create(protoObj)
newObj.prop = "自有属性"
console.log(newObj)

//删除属性
console.log(delete (newObj.prop))//删除自有属性=>true
console.log(delete (newObj.self))//删除原型属性,不会影响原型=>true
console.log(delete (newObj.toString))//删除原型属性,不会影响原型=>true
console.log(delete (1))//=>删除其他值
console.log(protoObj)

//delete 不会删除 configurable 为 false 的属性以及某些内置属性
console.log(delete (Object.prototype))//=>false
let newFun = new Function()
console.log(delete (newFun))//=>false
console.log(delete (protoObj))//=>false
num = 1
console.log(delete (num))//非严格模式下,未声明类型时可返回 true;严格模式下,会 throw SyntaxError

2.9属性的获取方法与设置方法

//在对象中,利用 get 与 set 设置访问器属性,两者必须有相同的属性名
let newObj = {
    id: 1,
    name: 'midshar',
    sex: 'male',
    get change() {
        return `${this.name} is a ${this.sex} person.`
    },
    set change(sex) {
        this.sex = sex
    }
}
console.log(newObj.change)
newObj.change = "After"
console.log(newObj.change)

//访问器属性也可以继承
let newInheritObj = Object.create(newObj)
newInheritObj.ownProp = '新建自有属性'
newInheritObj.change = "更改继承属性"
console.log(newInheritObj.change)
console.log(newInheritObj)

//保证序号严格递增
let newToNext = {
    _nIndex: 0,
    get toNext() {
        return this._nIndex++
    },
    set toNext(index) {
        if (index < this._nIndex) {
            throw new Error(`The value must be bigger than ${this._nIndex}.`)
        } else {
            this._nIndex = index
        }
    }
}
console.log(newToNext._nIndex)
newToNext.toNext = 50
newToNext.toNext
newToNext.toNext
console.log(newToNext.toNext)

//返回随机值
let resRandom = {
    get getRandom() {
        return Math.floor(Math.random() * 1000)//返回0~1000随机值
    }
}
console.log(resRandom.getRandom)

2.10属性访问错误

//在对象中,访问不存在的属性不是错误,而是 undefined
let newObj = {
    name: "midshar",
    sex: "male",
    age: 20
}
console.log(newObj.location)//=>undefined

//访问不存在的对象的属性会 throw TypeError,在JS中,null与undefined没有属性
try {
    console.log(newObj.hobbies.length)//=>error
} catch (error) {
    console.log(error)
}

//当在 null 或者 undefined 上设置属性时会 throw TypeError
let newNullType = null
try {
    newNullType.prop = "新建属性"//=>error
} catch (error) {
    console.log(error)
}

2.11序列化对象

//JSON(JavaScript Object Notation)
let newObj = {
    name: "midshar",
    age: 18,
    sex: "male",
    hobbies: ["running", "swimming"],
    familyAddress: {
        location: "lingjiangriver",
        province: "SiChuan"
    }
}
console.log(newObj)

//将对象序列化
let container = JSON.stringify(newObj)
console.log(container)

//恢复序列化对象
console.log(JSON.parse(container))

2.12以符号作为属性名

//用符号作为属性名
let symProp = Symbol("Prop")
let newObj = {
    [symProp]: {}
}
newObj[symProp].new = "newValue"
console.log(newObj)

2.13测试对象中的属性

//定义测试对象
let newObj = {
    name: "midshar",
    sex: "male",
    age: 18
}

//使用 in 操作符测试对象属性,可以判断自有属性与继承属性
console.log("name" in newObj)//自有属性=>true
console.log("location" in newObj)//自有属性=>false
console.log("toString" in newObj)//继承属性=>true
console.log("valueOf" in newObj)//继承属性=>true

//使用 hasOwnProperty 测试对象属性,自有属性返回 true,继承属性返回 false
console.log(newObj.hasOwnProperty("name"))//自有属性=>true
console.log(newObj.hasOwnProperty("toString"))//继承属性=>false

//使用 propertyIsEnumerable 测试对象属性,当属性是自有属性并且 enumerable 为 true 时返回 true,否则返回 false
console.log(newObj.propertyIsEnumerable("sex"))//自有属性=>true
console.log(newObj.propertyIsEnumerable("valueOf"))//非自有属性=>false
console.log(Object.prototype.propertyIsEnumerable("toString"))//非可枚举属性=>false

//使用 !== 判断属性是否存在
console.log(newObj.age !== undefined)//属性存在=>true
console.log(newObj.location !== undefined)//属性不存在=>false
console.log(newObj.valueOf !== undefined)//=>true

//使用 in 可以判断对象中的属性值设置为 undefined 还是未有该属性
let tNewObj = {
    prop: undefined
}
console.log("prop" in tNewObj)//=>true
console.log("toString" in tNewObj)//=>true
console.log("PROPERTY" in tNewObj)//=>false

2.14查询和设置对象属性

//关联数组
//以字符串而非数值作为索引的数组
let newObjWithNormal = {
    name: "midshar",
    sex: "male",
    age: 20,
    location: "SiChuan"
}
//使用 . 访问
console.log(newObjWithNormal.name)//=>midshar
//使用 [] 访问
console.log(newObjWithNormal["age"])//=>20

//继承
let nObjP = {}
nObjP.propp = "nObjP==>自有属性"
let nObjQ = Object.create(nObjP)
nObjQ.propq = "nObjQ==>自有属性"
let nObjR = Object.create(nObjQ)
nObjR.propr = "nObjR==>自有属性"

let readProp = nObjR.propp
console.log(readProp)//继承自原型=>nObjP==>自有属性
//为 nObjR 修改自有属性,有则更新,无则添加
nObjR.propr = "修改已有的自有属性"
nObjR.proprnew = "添加自有属性"
console.log(nObjR)

//当为 nObjR 添加原型上的已有属性时,会将原型上的属性隐藏
nObjR.propp = "创建原型上的已有属性"
console.log(nObjR.propp)//访问自有属性以代替访问原型上的同名属性=>创建原型上的已有属性
console.log(nObjP)//原型本身不受影响

3.数组

3.1创建数组

{//利用数组字面量创建数组,末尾可以接逗号 ,
    let newArrayWithLiterals = [1, "item", 3, null,]//=>length=4
    let newArrayWithsparse = [,]//=>length=1
    console.log(newArrayWithLiterals.length, newArrayWithsparse.length)
}

{//利用扩展操作符 ...
    let simpleArray = [1, 2, 3]
    let copyArray = [...simpleArray, 4, 5, 6]
    console.log(copyArray)

    //另外,扩展操作符可用于任何可迭代对象,例如数组、对象、字符串
    let newString = "ThisIsANewString"
    let containerArray = [...newString]
    console.log(containerArray)

    //利用扩展运算符 ... 与 集合 实现剥离重复元素
    let containsRepeatEle = [..."Mississippi"]
    console.log([...new Set(containsRepeatEle)])
}

{//利用 Array 构造函数创建数组,无法创建数组元素仅有一个的情况
    //传入两个及以上参数,或者传入非数值参数
    let newArrayWithConstructionFun = new Array(1, '2', 3, null, 5)
    console.log(newArrayWithConstructionFun)
    //传入一个数值参数,表示创建一个 length 为 num 的数组
    let newArrayL10 = new Array(10)
    console.log(newArrayL10)
    //不传入任何参数,相当于创建了一个空数组[]
    let newBlankArray = new Array()
    console.log(newBlankArray)
}

{//使用 Array.of 创建数组,传入的参数会被当做新数组的元素
    let newArrayWithOf1 = Array.of('555')
    console.log(newArrayWithOf1)
    let newArrayWithOf3 = Array.of(null, undefined, 5)
    console.log(newArrayWithOf3)
}

//使用 Array.from 创建数组,可以将类数组对象转换为真数组,接收两个参数,第二个参数传入一个函数,原数组的元素经过函数加工处理后,返回该函数的返回值,这些返回值成为新的数组元素

3.2读写数组元素

//为数组写入元素
//数组是特殊的对象,可以将任何值设置为数组的属性,但仅当属性为非负整数且<2^32-1 时,会自动维护 length 值

{//使用非数值字符串作为索引
    let newArray = []
    newArray["index"] = "hello"//被当成常规对象
    newArray[1.235] = "strange"
    newArray["10"] = 10//会创建一个长度为11的数组(包含索引为0)
    newArray[11.00] = 11//会创建一个索引为11的元素
    console.log(newArray)
}

{//使用负数作为索引
    let newArrayWithNN = []
    newArrayWithNN[-1] = 999//被当成常规对象
    console.log(newArrayWithNN)
}

{//当查询不存在的索引时,会返回 undefined
    let newArrayL1 = [999]
    console.log(newArrayL1[-999])//=>undefined
    console.log(newArrayL1[999])//=>undefined
}

3.3多维数组

//JS 并不真正支持多维数组,但是可以用来模拟

{//创建一个多维数组(二维)
    let newArray = []
    for (let i = 0; i < 9; i++) {
        newArray[i] = new Array()
        for (let j = 0; j <= i; j++) {
            newArray[i][j] = `${j + 1}*${i + 1}=${(i + 1) * (j + 1)}`
        }
    }
    console.log(newArray)
}

{//访问多维数组
    console.log(newArray[0][0])
    console.log(newArray[8][8])
}

3.4数组的长度

//数组的长度会自动维护不变式

let newArray = [1, 2, 3, 4, 5]

{//为数组变更 length,当被赋值元素的索引i大于原有length时,新的length将会成为i+1
    newArray[10] = 10
    console.log(newArray.length)//=>11
}

{//将数组的 length 设置为一个小于当前 length 的数,会将多余的元素删除
    newArray.length = 1
    console.log(newArray)
}

3.5添加和删除数组元素

let newArray = []

{//添加元素
    //使用索引值添加元素
    newArray[0] = "使用索引值添加元素1"
    newArray[1] = "使用索引值添加元素2"
    console.log(newArray)
    //使用 push 方法在数组末尾添加元素
    newArray.push("利用push方法添加的值1", "利用push方法添加的值2")
    console.log(newArray)
    //使用 unshift 在数组头部添加元素
    newArray.unshift("利用unshift添加的元素1", "利用unshift添加的元素2")
    console.log(newArray)
}

{//删除元素
    //使用 pop 方法删除数组末尾的元素
    newArray.pop()
    console.log(newArray)
    //使用 shift 方法删除数组头部的元素
    newArray.shift()
    console.log(newArray)
    //使用 delete 方法删除数组中的元素,但是不会改变数组的长度
    delete (newArray[0])
    console.log(newArray)
    //更改数组的 length 也可以从末尾删除该数组的元素
    newArray.length = 1
    console.log(newArray)
}

{//通用方法 splice ,用于添加与删除
    newArray.splice(0, 1, "替换的元素1", "替换的元素2")//参数依次为:指定索引开始,要删除的元素个数,需要替换的值
    console.log(newArray)
    newArray.splice(0, 1, ...[1, 2, 3, 4, 5, 6])
    console.log(newArray)
    newArray.splice(1, 5)
    console.log(newArray)
    newArray.splice(1, 0, "添加的元素")//将值添加到指定索引处,或者说将值插入到指定索引处的元素之前
    console.log(newArray)
}

3.6迭代数组

let newArray = [..."myNewStrings"]
console.log([...newArray.entries()])

{//利用 entries 对数组进行迭代
    for (let [indexNum, letterEle] of newArray.entries()) {//解构下标与值
        console.log(indexNum, letterEle)
    }
}

{//利用 forEach 对数组进行迭代,其参数是一个回调函数
    let newTArray = [1, 2, 3, 4, 5]
    let container = []
    newTArray.forEach(item => container.push(`变更后的${item}`))
    console.log(container)
}

3.7稀疏数组

//稀疏数组是索引没有从0开始的数组,未定义的元素可以看作元素是 undefined
//创建稀疏数组

{//使用 Array 创建
    let newSparseWithArray = new Array(5)//length=5
    console.log(0 in newSparseWithArray)//=>false 索引0处没有元素
}

{//使用赋值的方式
    let newSparseWithPutValue = []
    newSparseWithPutValue[10] = 10
    console.log(newSparseWithPutValue)
}

{//使用 delete 创建
    let newSparseWithDelete = [1, 2, 3, 4, 5]
    delete (newSparseWithDelete[2])
    console.log(newSparseWithDelete)
}

{//使用字面量创建
    let newSparseArray = [1, , , 4]
    console.log(newSparseArray)
}

3.8数组的方法

3.8.1数组的迭代器方法

{// forEach() 方法,迭代数组中的每个元素,接收两个参数,第一个回调函数,第二个参数会将第一个参数作为它的方法
    //其中,回调函数接收三个参数,第一个参数是数组中的元素,第二个参数是元素索引,第三个参数是数组本身
    //forEach() 方法不返回值,都会返回 undefined
    let newArray_forEach = [1, 2, 3, 4, 5]
    let sum_forEach = 0
    newArray_forEach.forEach((ele) => sum_forEach += ele)//求和数组
    console.log(sum_forEach)
    newArray_forEach.forEach((ele, index, array) => array[index]--)//数组元素递减
    console.log(newArray_forEach)
}

{// map() 方法,返回一个新数组,新数组由传递给调用 map 方法的数组的回调方法返回值组成
    let newArray_map = [1, 2, 3, 4, 5]
    let resArray_map = newArray_map.map((item) => `--${item * item}--`)
    console.log(resArray_map)
}

{// filter() 方法,返回一个新数组
    let newArray_filter = ["abc", "bcd", "dca", "cab", "bda", "dad", "aab", "cdc"]
    let resArray_filter = newArray_filter.filter((item) => /[c]/.test(item))//过滤出包含字母 c 的元素
    console.log(resArray_filter)

    //使用 filter 过滤稀疏数组
    let newArraySparse = [1, , 3, 4, , 6, undefined, null, undefined, null]
    let afterFilter = newArraySparse.filter(() => true)
    console.log(afterFilter)

    //使用 filter 过滤稀疏数组并且清理null与undefined
    let afterFilterWithoutNullish = newArraySparse.filter((item) => item !== null && item !== undefined)
    console.log(afterFilterWithoutNullish)
}

{// find() 与 findIndex() 方法
    //find() 方法,当条件成立时,返回成立时的第一个元素,若无匹配元素,则返回 undefined
    let newArray_find = ["ele1", "ele2", "ele3", 1, 2, 3, 4, 5]
    console.log(newArray_find.find((item) => item.length === 4))//=>ele1
    console.log(newArray_find.find((item) => item.length === 5))//=>undefined

    //findIndex() 方法,当条件成立是,返回成立时的第一个索引,若无匹配元素,则返回-1
    console.log(newArray_find.findIndex((item) => item.includes(2)))//查找元素中包含2的元素下标=>1
    console.log(newArray_find.findIndex((item) => item.length === 5))//查找元素长度为5的元素下标=>-1
}

{// every() 与 some()
    //every() 方法,只有当数组中的每个元素都满足条件时才会返回 true,否则返回 false
    let newArray_every = [4, 5, 6, 7, 8]
    console.log(newArray_every.every((item) => item < 8))//=>false
    console.log(newArray_every.every((item) => item < 10))//=>true

    //some() 方法,只要数组中存在元素满足条件就返回 true,当所有元素都不满足条件时返回 false
    let newArray_some = ["string", 1]
    console.log(newArray_some.some((item) => typeof (item) === "string"))//=>true
    console.log(newArray_some.some((item) => typeof (item) === "boolean"))//=>false

    //特别地,对于空数组,调用 every 返回 true,调用 some 返回 false
    console.log([].every((item) => item.length === 5))//=>true
    console.log([].some((item) => item.length === 0))//=>false
}

{// reduce() 与 reduceRight() 归并方法
    //reduce() 方法,接收两个参数,第一个回调函数,第二个是初始值,当初始值未赋值时,取用数组第一个元素
    //回调函数接收四个参数,第一个表示到目前为止归并操作的累计值,第二个至第四个分别是数组元素、数组元素下标、数组本身
    let newArray_reduce = [1, 2, 3, 4, 5, 4, 3, 2, 1]
    console.log(newArray_reduce.reduce((until, item) => until > item ? until : item))
    console.log(newArray_reduce.reduce((until, item) => until + item))
    //特别地,对于只含有一个元素的数组,会返回该元素本身
    console.log([1].reduce((x, y) => x - y))
    //特别地,对于空数组,传入一个初始值的情况,会直接返回该初始值
    console.log([].reduce((x, y) => x + y, 999))
    //特别地,对于空数组并且不传初始值,会 throw TypeError
    try {
        console.log([].reduce((x, y) => x + y))
    } catch (err) {
        console.log(err)
    }

    //reduceRight() 方法与 reduce() 方法类似,但是是从高索引向低索引操作,当操作具有右结合性时可以使用
    let newArray_reduceRight = [2, 3, 4]//计算 2^(3^4)
    console.log(newArray_reduceRight.reduceRight((until, item) => item ** until))//第一次循环开始时,until=4,item=3,每次计算的值会存储在 until 中,作为下一次的初始值
}

3.8.2数组的栈与队列操作

{//使用 push() 与 pop() 实现数组的栈操作,push() 方法会在数组末尾插入新元素并返回新数组的长度,pop() 方法会弹出数组末尾的元素并且返回,这两个方法会修改原数组
    let newArray_stack = []
    //使用 push()
    console.log(newArray_stack.push("Hello"))//=>1
    console.log(newArray_stack.push("World"))//=>2
    console.log(newArray_stack.push("Hello"))//=>3
    console.log(newArray_stack.push("JavaScript"))//=>4
    console.log(newArray_stack)//=>['Hello', 'World', 'Hello', 'JavaScript']
    //使用 pop()
    console.log(newArray_stack.pop())//=>JavaScript
    console.log(newArray_stack)
}

{//使用 unshift() 与 shift(),unshift() 方法会在数组首部添加新的元素并且返回新数组的长度,shift() 方法会在删除数组首部的元素并返回该元素,这两个方法会改变原数组
    let newArray_line = [555]
    //使用 unshift()
    console.log(newArray_line.unshift(11, 22, 33))//=>4
    //使用 shift()
    console.log(newArray_line.shift())//=>11

    //使用 shift() 与 push() 实现数组的队列操作
    newArray_line.push('--tail--')
    newArray_line.push('--tail--')
    newArray_line.shift()
    console.log(newArray_line)
}

{//利用递归算法实现深复制
    let deepCopy = function (to, from) {
        for (let i in from) {
            if (typeof (from[i]) === '[object Object]') {//判断为对象
                deepCopy(to[i], from[i])//递归
            }
            to[i] = from[i]
        }
        return to
    }

    //深复制对象
    let cObj = {
        id: 1,
        name: "midshar",
        age: 18,
        hobbies: ["swimming", "running", {
            playing: ["pingpong", "football", "tennis"]
        }],
        family: {
            member1: "dad",
            member2: "mom"
        }
    }
    let newBlankObj = {}
    deepCopy(newBlankObj, cObj)
    //修改深复制后的对象不会影响源对象
    for (let i in newBlankObj) {
        newBlankObj[i] = "修改后的数据"
    }
    console.log('源数据=>', cObj, newBlankObj)

    //深复制数组
    let cArray = [1, 2, 3, [4, [5, 6]], {
        name: "midshar",
        sex: "male",
        age: 20,
        hobbies: ["studying", "singing"],
        family: {
            member1: "dad",
            member2: "mom"
        }
    }]
    let newBlankArray = []
    deepCopy(newBlankArray, cArray)
    //修改深复制后的对象不会影响源对象
    for (let i in newBlankArray) {
        newBlankArray[i] = "修改后的数据"
    }
    console.log('源数据=>', cArray, newBlankArray)
}

3.8.3数组的提取、替换、填充与切片方法

{//使用 slice() 方法,返回调用该方法的子数组,不会修改源数组
    let newArray_slice = [1, 2, 3, 4, 5]
    //接收两个参数,分别是子数组的起止位置(索引),包含起始位置元素,但是不包含末尾元素
    let subArray_slice = newArray_slice.slice(1, 4)//=>[2, 3, 4]
    console.log(subArray_slice)
    //接收一个正值,将会作为起始位置返回其后的所有元素
    subArray_slice = newArray_slice.slice(2)//=>[3, 4, 5]
    console.log(subArray_slice)
    //接收一个负值,相对于数组长度获取元素
    subArray_slice = newArray_slice.slice(-1)//=>[5]
    console.log(subArray_slice)
}

{//使用 splice() 方法,返回被删除的数组,该方法可以删除、插入数组元素,并且会修改原数组
    let newArray_splice = [1, 2, 3, 4, 5]
    //传入一个参数,省略第二个参数,则从第一个参数索引开始的所有元素都将被删除
    console.log(newArray_splice.splice(3))//=>[4,5]
    console.log(newArray_splice)
    //传入两个参数,第一个参数指定起始的索引位置,第二个参数指定需要删除的元素个数
    newArray_splice = [1, 2, 3, 4, 5]//重置数组
    console.log(newArray_splice.splice(2, 2))//=>[3,4]
    console.log(newArray_splice)
    //传入参数个数大于2,第三个参数及以后的参数都将作为新元素被添加到第一个参数指定的索引之前
    newArray_splice = [1, 2, 3, 4, 5]//重置数组
    console.log(newArray_splice.splice(2, 0, "newEle", "newEle"))//=>[]
    console.log(newArray_splice)
}

{//使用 fill() 方法,返回修改后的数组,该方法用来填充数组元素为指定值,会修改原始数组
    let newArray_fill = [1, 2, 3, 4, 5]
    //传入一个参数,该参数表示用该值来填充覆盖原数组元素,不传入第二个参数与第三个参数时,默认从索引0处覆盖数组中的所有元素
    console.log(newArray_fill.fill('*'))//=>['*', '*', '*', '*', '*']
    //传入两个参数,第二个参数表示从该索引处开始覆盖,当省略第三个参数时,会默认填充至数组末尾
    newArray_fill = [1, 2, 3, 4, 5]//重置数组
    console.log(newArray_fill.fill('*', 2))//=>[1, 2, '*', '*', '*']
    //传入三个参数,第三个参数表示覆盖的截止索引(不包含)
    newArray_fill = [1, 2, 3, 4, 5]//重置数组
    console.log(newArray_fill.fill('*', 1, -1))//=>[1, '*', '*', '*', 5]
}

{//使用 copyWithin() 方法,返回修改后的数组,该方法用来把数组中的元素切片复制到数组中的其他索引处,不会改变数组长度,并且会修改原数组
    let newArray_copy = [1, 2, 3, 4, 5]
    //传入一个参数,表示将原数组整体复制到目标索引处,多余元素将会被删除
    console.log(newArray_copy.copyWithin(2))//=>[1,2,1,2,3]
    //传入两个参数,第二个参数表示将从原数组的该索引处开始复制,当不传入第三个参数时,默认会从该索引一直覆盖到末尾元素
    newArray_copy = [1, 2, 3, 4, 5]//=>重置数组
    console.log(newArray_copy.copyWithin(1, 3))//=>[1,4,5,4,5]
    //传入三个参数,第三个参数表示截止索引位置(不包含),从第二个参数位起至第三个参数位止的数组元素将会被复制到第一个参数索引处
    newArray_copy = [1, 2, 3, 4, 5]//=>重置数组
    console.log(newArray_copy.copyWithin(2, 0, -1))//=>[1,2,1,2,3]
}

3.8.4数组的索引与排序方法

{// indexOf() 方法与 lastIndexOf() 方法,支持传入两个参数,用于查找数组中目标值的下标,返回成功匹配的第一个元素的下标
    // indexOf() ,接收两个参数,第一个参数表示在数组中搜索的目标值,第二个参数表示要从该索引开始查找,无法匹配返回 -1
    let newArray_index = [1, 2, 3, 3, 2, 1]
    console.log(newArray_index.indexOf('3'))//=>-1
    console.log(newArray_index.indexOf(3))//=>2
    console.log(newArray_index.indexOf(3, -4))//=>2
    // lastIndexOf() ,用法一致,从数组末尾开始匹配,无法匹配返回 -1
    console.log(newArray_index.lastIndexOf(1))//=>5
    console.log(newArray_index.lastIndexOf(3, -1))//=>3

    console.log('----------------')
    //编写函数循环查找出数组中所有目标元素的下标
    let findAllIndex = function (array, value) {
        //判断输入参数类型
        let count = 0
        if (array instanceof Array) {
            //初始判断
            if (array.indexOf(value) === -1) {//无匹配值
                console.warn("无匹配值!")
                return count
            } else {//有匹配值
                //初始化下标
                let findIndex = array.indexOf(value)
                console.log(findIndex)
                count++
                //为下一次查找设置索引
                let nextIndex = findIndex + 1
                //开始查找其他值
                for (let i = 0; i < array.length; i++) {
                    if (array.indexOf(value, nextIndex) === -1) {//没有匹配值时
                        return count
                    }
                    findIndex = array.indexOf(value, nextIndex)
                    console.log(findIndex)
                    count++
                    nextIndex = findIndex + 1
                }
                return count
            }
        } else {
            throw new Error("参数类型不匹配!")
        }
    }

    let tArray = [1, 2, 3, 4, 2, 3, 4, 4, 1, 5, 6]
    let findValue = 2
    let findCount = findAllIndex(tArray, findValue)
    console.log(`We can find ${findCount} num${findValue}.`)
    console.log('----------------')
}

{//使用 includes() 方法,接收一个参数,当数组中存在该元素返回 true,否则返回 false
    let newArray_include = [1, null, undefined, false, true, '666', NaN]
    console.log(newArray_include.includes('1'))//=>false
    console.log(newArray_include.includes(1))//=>true
    console.log(newArray_include.includes(NaN))//=>true
    console.log(newArray_include.includes(null))//=>true
    console.log(newArray_include.includes(undefined))//=>true
    console.log(newArray_include.includes(false))//=>true

    //与 indexOf() 的区别,indexOf() 无法识别 NaN
    console.log(newArray_include.indexOf('1'))//=>-1
    console.log(newArray_include.indexOf(1))//=>0
    console.log(newArray_include.indexOf(null))//=>1
    console.log(newArray_include.indexOf(undefined))//=>2
    console.log(newArray_include.indexOf(false))//=>3
    console.log(newArray_include.indexOf(NaN))//=>-1
}

{//使用 sort() 方法,进行就地排序,返回排序后的数组,会修改原数组
    let newArray_number = [112, 15, 21, 71, 143, 253, 91, 10, null, undefined]
    let newArray_sort = ['cry', 'banana', 'Apple', 'desk', 'Enable']
    //当不传入参数时,默认以字母顺序进行排序(区分大小写)
    console.log(newArray_sort.sort())
    console.log(newArray_number.sort())
    //当希望数值按照顺序排列时,参数应当 <0,前-后
    console.log(newArray_number.sort((x, y) => x - y))
    //当希望数值按照倒序排列时,参数应当 >0,后-前
    console.log(newArray_number.sort((x, y) => y - x))
    //当期望字符排序不区分大小写时,需要统一将字符转换成大写或者小写
    console.log(newArray_sort.sort((s, t) => {
        let x = s.toLowerCase()
        let y = t.toLowerCase()
        if (x < y) return -1
        if (x > y) return 1
    }))
}

{//使用 reverse() 方法,返回反转后的数组,会修改原数组
    let newArray_reverse = [1, 2, 3, 4, 5]
    console.log(newArray_reverse.reverse())
}

3.8.5数组到字符串的转换

{//使用 join() 方法,不会影响原数组
    let newArray_join = [1, 2, 3, 4, 5]
    let newArray_joinComplex = [1, [2, [3, [4, 5]]]]

    //在传递参数的情况下,以该参数作为分隔数组元素的内容
    console.log(newArray_join.join("*"))
    console.log(newArray_join.join(""))
    console.log(newArray_join.join(" "))
    //创建一个长度为 5 的稀疏数组
    let newSparseArray = new Array(5)
    console.log(newSparseArray)
    console.log(newSparseArray.join('--分隔符--'))//以该参数作为稀疏数组的分隔符
}

{//同时使用 toString() 方法
    let newArray_join = [1, 2, 3, 4, 5]
    let newArray_joinComplex = [1, [2, [3, [4, 5]]]]
    //默认情况下,join() 方法的返回值与 toString() 一致,以逗号 ,分隔数组中的各个元素,打平数组的所有层级,直接返回数组中的元素
    console.log(newArray_join.join())
    console.log(newArray_join.toString())
    //在嵌套数组的情况下
    console.log(newArray_joinComplex.toString())
    console.log(newArray_joinComplex.join())
}

{// toLocaleString() 方法是本地版本的 toString() 方法
    let newArray_local = ['China', 'Time', new Date()]
    console.log(newArray_local)
    console.log(newArray_local.toLocaleString())
}

3.8.6使用flat与flatMap方法

{//使用 flat(),该方法会将传入的数组自动打平(去掉数组的[]),当不传参时,默认打平一层,传参 n 时打平 n 层,当 n 大于原有数组的层级时,会默认打平至最高层级
    let newArray_class1 = [1, [2, [3, [4, [5]]]]]
    console.log(newArray_class1.flat())//=>[1,2,[3,[4,[5]]]]
    console.log(newArray_class1.flat(1))//=>[1,2,[3,[4,[5]]]]
    console.log(newArray_class1.flat(2))//=>[1,2,3,[4,[5]]]
    console.log(newArray_class1.flat(3))//=>[1,2,3,4,[5]]
    console.log(newArray_class1.flat(4))//=>[1,2,3,4,5]
    console.log(newArray_class1.flat(100))//=>[1,2,3,4,5]
}

{//使用 flastMap() 方法,与 map() 方法类似
    let newArray_map = [1, 2, 3, 4, [5]]
    //返回值会自动打平一层
    let resArrayMap = newArray_map.flatMap((item) => item > 0 ? item : '')//=>[1, 2, 3, 4, 5]
    console.log(resArrayMap)
}

3.8.7使用concat添加数组

{//concat() 方法会创建一个自身副本作为新数组,连接它的参数至调用它的数组,并且会将参数中的一级数组打平,但不会打平嵌套数组
    let originArray = [1, 1]
    let newResArray_1 = originArray.concat(2, 2, 2)//=>[1, 1, 2, 2, 2]
    console.log(newResArray_1)
    let newResArray_2 = originArray.concat(3, [3, 3])//=>[1, 1, 3, 3, 3]
    console.log(newResArray_2)
    let newResArray_3 = originArray.concat([4, 4, [5, 5]])//=>[1,1,4,4,[5,5]]
    console.log(newResArray_3)
}

3.8.8静态数组函数

{// Array.of() 方法,用来创建新数组,弥补 new Array 无法创建一个元素的数组的情况
    let newArray_of = Array.of(1, '1', null, undefined, true, false, NaN)
    console.log(newArray_of)
    let newArray_oneEle = new Array(3)
    console.log(newArray_oneEle)
}

{// Array.from() 方法,用来转换类数组为真数组
}

{// Array.isArray() 方法,用来判断一个位置数据是不是数组
    let tData_1 = {}
    let tData_2 = []
    console.log(Array.isArray(tData_1))//=>false
    console.log(Array.isArray(tData_2))//=>true
}

3.8.9类数组对象

//作为数组
//可以自动根据元素个数修改 length 值
//通过 Array.isArray() 方法判断返回 true
//设置小于当前数组 length 的值会删除多余元素
//会从 Array.prototype 处继承数组方法

{//类数组对象,有 length 属性,且其值为非负整数
    let newLikeArray = {}
    let index = 0
    while (index < 10) {
        newLikeArray[index] = index + 100
        index++
    }
    newLikeArray.length = index
    console.log(newLikeArray)
    //像数组一样访问遍历
    for (let i = 0; i < newLikeArray.length; i++) {
        console.log(newLikeArray[i])
    }
    //通过下列方法可以判断一个数据对象是否是类数组对象
    let isArrayLike = function (obj) {
        if (obj && typeof (obj) === 'object' && Number.isFinite(obj.length) && obj.length >= 0 && Number.isInteger(obj.length) && obj.length < 4294967295) {//2^32-1
            return true
        } else {
            return false
        }
    }
    console.log(isArrayLike(newLikeArray))//=>true
}

{//在类数组对象中通过 Array.prototype.[methods].call() 使用数组中的方法
    let a = { 0: "测试1", 1: "测试2", 2: "测试3", length: 3 }
    console.log(Array.prototype.map.call(a, (item) => `修改后的${item}`))
    console.log(Array.prototype.join.call(a, "----"))
    console.log(Array.prototype.splice.call(a, (0, 2)))
}

3.8.10作为数组的字符串

{//字符串是特殊的数组,可以用[]的形式访问,但是,字符串是不可修改的
    let newString = "HelloWorld"
    for (let i = 0; i < newString.length; i++) {
        console.log(`${newString[i]}==>${newString[i].charCodeAt()}`)
    }
    //在字符串中使用数组的方法
    console.log(Array.prototype.join.call(newString, "-"))
    console.log(Array.prototype.map.call(newString, (item) => `修改后的${item}`))
    console.log(Array.prototype.filter.call(newString, (item) => (item.charCodeAt()) >= 97 && (item.charCodeAt()) <= 123))
}

3.8.11深入了解sort排序方法

{//在 sort() 方法中,回调函数的返回值>0,表示第一个参数应当出现在第二个参数之后;反之表示第一个参数应当出现在第二个之前
    //在 sort() 方法中,回调函数的参数分别表示数组中的索引值为 i+1 与 i 的值
    let newArray = [1, 2, 3, 2, 1]
    newArray.sort((a, b) => {
        if (a > b) return 1//若参数1>参数2,表示在数组中,前面的值>后面的值。若要进行顺序排序,则返回值应当>0;若要进行倒序排序,返回值应当<0
        if (a < b) return -1//若参数1<参数2,表示在数组中,前面的值<后面的值。若要进行顺序排序,则返回值应当<0;若要进行倒序排序,返回值应当>0
    })
    console.log(newArray)
}

4.函数

4.1定义函数

{//使用函数声明,当一个函数没有返回值时,默认返回 undefined
    //使用函数声明时,在一个JS代码块中(一个js文件中)声明的所有函数在该块的所有地方都有定义,且会被提升至该块最前面
    newFun_declaration()
    function newFun_declaration(parameter) {
        console.log("利用函数声明创建函数")
    }
    //在严格模式下,在语句块中定义的函数在该语句块中有效,在语句块外无效;非严格模式下,只能在声明定义函数的代码之后使用
    for (let i = 0; i < 10; i++) {
        function printi(get) {
            console.log(`在语句块中创建函数=>${get}=>${i}`)
        }
        printi('inside')
    }
}
printi('outside')

{//使用函数表达式,这种方法定义的函数无法被提升,只能先定义,再使用,并且只能在该代码块中使用
    //函数表达式将函数对象赋值给一个变量/常量
    const newFun_1 = function () {
        console.log("利用函数表达式创建函数")
    }
    newFun_1()

    //函数表达式中的函数可以添加名字,这个名字相当于是函数体内的一个局部变量,并且 newFun_2===facNum
    const newFun_2 = function facNum(x) {
        if (typeof (x) === 'number') {
            if (x < 0) {
                console.error("参数不能为负数")
                return false
            } else if (x === 1 || x === 0) {
                return 1
            } else {
                return x * facNum(x - 1)
            }
        } else {
            console.error("参数必须为数字")
            return false
        }
    }
    console.log(newFun_2(5))

    //函数表达式可以作为函数的参数
    const newFun_3 = function (fun, a, b) {
        fun(a, b)
    }
    newFun_3(function (x, y) {//定义函数参数
        console.log(x > y ? x : y)
    }, 999, 100)

    //函数表达式可以在定义完的同时立刻调用
    const newFun_4 = (function () {
        console.log("我是一个立即执行函数")
    })()

    //箭头函数,其中的 this 指向不是调用上下文,它没有 this
    let newFun_arrow = () => {
        console.log("我是一个箭头函数")
    }
    newFun_arrow()

    //嵌套函数,在嵌套函数中,内层函数可以访问外层函数的参数,而外层函数无法访问内层函数的参数
    //在嵌套函数中,this指向要么是全局对象(非严格模式),要么是undefined(严格模式)
    const newFun_nest = function (numA, numB) {
        const squareFun = function (num) {
            return num ** 2
        }
        return Math.sqrt(squareFun(numA) + squareFun(numB))
    }
    console.log(newFun_nest(3, 4))//=>5
}

4.2函数的形参与实参

{//可选形参与默认值
    //当传入的实参个数少于函数中定义的形参个数时,多余形参会被默认设置为 undefined
    const newFun_less = function (num1, num2, num3) {
        console.log(`num1=${num1}---num2=${num2}---num3=${num3}`)
        return (num1 > num2 ? num1 : num2) > num3 ? (num1 > num2 ? num1 : num2) : num3
    }
    newFun_less(1)

    //可以为可选参数设置默认值
    const newFun_acq = function (num1, num2, num3) {
        num2 = num2 || 10
        num3 = num3 || 100
        console.log(`num1=${num1}---num2=${num2}---num3=${num3}`)
        return (num1 > num2 ? num1 : num2) > num3 ? (num1 > num2 ? num1 : num2) : num3
    }
    newFun_acq(1, 2)

    //在 ES6 之后,可以直接在定义函数时在形参中给定默认值
    const newFun_direct = function (num1, num2 = 666, num3 = 999) {
        console.log(`num1=${num1}---num2=${num2}---num3=${num3}`)
        return (num1 > num2 ? num1 : num2) > num3 ? (num1 > num2 ? num1 : num2) : num3
    }
    newFun_direct(222)

    //在形参中支持用计算的数值
    const creatNewRectangle = (width, height = width * 2) => {
        console.log(width, height)
    }
    creatNewRectangle(5)
}

{//使用 ... 在定义函数形参时,这与展开运算符不同
    //利用剩余形参可以将在调用函数时多余的实参装入一个数组中,将剩余参数作为函数的最后一个参数---变长函数
    //当不存在剩余参数时,剩余参数是一个空数组
    const getMaxNum = function (firstNum, ...restNum) {
        console.log(restNum)
        let maxNum = firstNum
        for (let item of restNum) {
            maxNum = item > maxNum ? item : maxNum
        }
        return maxNum
    }
    console.log(getMaxNum(1, 5, 4, 9, 7, 5, 2))
    console.log(getMaxNum(1))
}

{//Arguments 对象
    const getMaxNum_argument = function () {
        console.log(arguments)
        if (arguments.length === 0) {
            console.error("参数不能为空")
            return false
        } else if (arguments.length === 1) {
            return arguments[0]
        } else {
            let max = arguments[0]
            for (let i = 1; i < arguments.length; i++) {
                max = max > arguments[i] ? max : arguments[i]
            }
            return max
        }
    }
    console.log(getMaxNum_argument(12))
    console.log(getMaxNum_argument(12, 32456, 48, 534, 653, 51))
}

{//在函数调用中使用 ... 扩展操作符,注意与剩余形参的区别
    function timed(f) {//返回包装后的函数
        return function (...argu) {//调用 timed() 时返回这一层函数
            console.log(arguments)
            console.log(`Import function ${f.name} success.`)
            let startTime = Date.now()
            try {
                return f(...argu)//返回该函数,直接执行,展开搜集的参数,相当于调用 benchmark(5)
            } finally {
                console.log(`Export function ${f.name} spent ${Date.now() - startTime}ms.`)
            }
        }
    }
    const benchmark = function (num) {
        let sum = 0
        for (let i = 1; i <= num; i++) {
            sum += i
        }
        return sum
    }
    const getMinFun = function (a, b) {
        return a > b ? b : a
    }
    console.log(timed(benchmark)(5))//在函数表达式后接参数调用,argu=[5]
    console.log(timed(getMinFun)(666, 999))//在函数表达式后接参数调用,argu=[666,999]
}

{//把函数实参解构为形参
    //复制数组中的指定长度切片至另一个数组中
    const copySliceArray = function ({ from, to = [], sliceLength = from.length, fromIndex = 0, toIndex = 0 }) {//形参分别表示 {被切片的数组,目的数组,切片长度,被切片数组的起始索引,目的数组的起始索引}
        to.splice(toIndex, 0, ...from.slice(fromIndex, fromIndex + sliceLength))//将运用 slice 方法取得的切片数组解构成为数组元素再插入 to 数组
        return to
    }
    let toArray = [1, 2, 3]
    let fromArray = [999, 999, 999, 999]
    console.log(copySliceArray({ from: fromArray, to: toArray }))

    //把数组参数中的多余实参搜集到剩余参数中
    const newFun_moreArrayEle = function ([x, y, ...surlpusEle], ...restParameter) {
        console.log(surlpusEle)
        console.log(restParameter)
        return { X: x, Y: y }
    }
    console.log(newFun_moreArrayEle([1, 2, 3, 4, 5, 6, 7], 777, 888, 999))

    //把对象参数中的多余属性搜集到剩余参数中,多余参数是一个剩余属性组成的对象
    const newFun_obj = function ({ x, y, z = 0, ...rest }, base) {
        return { x: x * base, y: y * base, z: z * base, restProp: rest }
    }
    console.log(newFun_obj({ x: 100, y: 200, z: 300, p: 555, q: 666, m: 777, n: 888 }, 1))

    //函数的参数类型,JS中函数的参数不会进行检查,可以手动设置判断
    const addAll = function (numArray) {
        if (!(numArray instanceof Array)) {
            throw new Error("参数必须是一个数组")
            return
        }
        let sum = 0
        for (let i of numArray) {
            if (typeof (i) !== 'number') {
                throw new Error("数组所有元素必须是 number 类型")
                return
            }
            sum += i
        }
        return sum
    }
    console.log(addAll([1, 2, 3, 5]))
}

4.3调用函数

"use strict"
{//作为函数调用
    //条件式调用函数,当调用函数是否是 null 或者 undefined 时,不会执行函数调用
    const newFun_null = null
    const newFun_function = function () {
        console.log("作为函数调用")
    }
    newFun_function?.()
    newFun_null?.()
    //条件式调用等价于
    console.log(newFun_null !== null && newFun_null !== undefined ? newFun_null() : undefined)

    //利用 this 值判断是否是位于严格模式中,在严格模式中,this 是 undefined,非严格模式中,是调用上下文。在箭头函数中, this 总是指向所在环境
    const isStrictModel = (function () {//当前环境是严格模式时,返回 true,否则返回 false
        return !this
    })()
    console.log(isStrictModel)//=>true
}

{//作为方法调用
    //作为对象的方法被调用时,当存在嵌套函数时,内层函数的 this 值不是调用对象,要么是全局对象 window(非严格模式),要么是 undefined(严格模式)
    let newObj = {
        numA: 3,
        numB: 4,
        addFun: function () {
            console.log(this)//=> newObj
            return insideSquareFun(this.numA) + insideSquareFun(this.numB)
            function insideSquareFun(val) {
                console.log(this)//=> undefined(严格模式)
                return val * val
            }
        }
    }
    console.log(newObj.addFun())
}

{//作为构造函数调用
    //当返回值是一个原始值时
    class PersonInfo {
        constructor(name, sex, age) {
            this.name = name
            this.sex = sex
            this.age = age
            return //当 return 后不接返回值或者返回一个原始值,将会仍然以新创建的对象作为 this 值
        }
        printThis() {
            console.log(this)
        }
    }
    let newPerson = new PersonInfo('midshar', 'male', 18)
    newPerson.printThis()

    //当返回值是一个对象时
    class notPersonInfo {
        constructor(name, sex, age) {
            this.name = name
            this.sex = sex
            this.age = age
            return {//如果在构造函数中,return 一个对象,那么实例化的对象将会是这个值,即 this 是这个值
                prop: "测试属性",
                test: "测试属性"
            }
        }
        printThis() {
            console.log(this)
        }
    }
    let notNewPerson = new notPersonInfo('midshar', 'male', 18)
    console.log(notNewPerson)
}

{//使用 call() 或者 apply() 间接调用

}

{//隐式函数调用

}

4.4函数式编程

{//使用函数处理数组
    //传统方式下,计算一个数值数组的平均值 average 与标准差 stdDeviation
    let valueArray = [1, 1, 3, 5, 5]
    let total = 0
    let stdDeviation = 0
    let average = 0
    for (let i = 0; i < valueArray.length; i++) {
        total += valueArray[i]
    }
    average = Math.round(total / valueArray.length)
    //清零总值
    total = 0
    for (let i = 0; i < valueArray.length; i++) {
        total += Math.pow(valueArray[i] - average, 2)
    }
    stdDeviation = Math.round(Math.sqrt(total / (valueArray.length - 1)))
    console.log(`average=${average}\nstdDeviation=${stdDeviation}`)

    //利用 map 方法与 reduce 方法修改上述方法,可以使代码更加简洁
    let newAverage = Math.round(valueArray.reduce((until, item) => until + item, 0) / valueArray.length)
    let afterMapArray = valueArray.map(x => x - newAverage)
    let newStdDeviation = Math.round(Math.sqrt((afterMapArray.map(x => x * x).reduce((until, item) => until + item, 0)) / (valueArray.length - 1)))
    console.log(`newAverage=${newAverage}\nnewStdDeviation=${newStdDeviation}`)
}

{//高阶函数,高阶函数就是函数的形参是一个或者多个函数,并且返回值也是一个函数
    //一个返回新函数的逻辑非的函数
    function not(fun) {
        return (...allParameters) => {
            let res = fun.apply(this, allParameters)//apply接收数组形式的形参
            return !res
        }
    }
    const isEven = (i) => i % 2 === 0//判断数组元素是否为偶数
    const isOdd = not(isEven);//判断数组元素是否为奇数
    console.log([1, 1, 1, 1].every(isOdd))//=>true

    //让数组中的每个数值元素自增1
    function map(arr, ...arg) {//自定义 map 函数,传入一个数组,传入一个方法,将会把数组按照该方法映射为新数组
        let res = arr.map(...arg)
        return res
    }
    function mapper(fun) {
        return function (arr) {
            return map(arr, fun)//调用上述自定义 map 函数
        }
    }
    let addOne = x => x + 1
    let newArray = [2, 2, 2, 2, 2]
    console.log(mapper(addOne)(newArray))//=>[3,3,3,3,3]

    //接收两个函数参数的函数
    function firstSquareLaterAdd(fx, gx) {
        return function (...arg) {
            return fx.call(this, ...gx.call(this, ...arg))
        }
    }
    const tFx = (x, y) => x + y
    const tGx = arr => arr.map(i => i * i)
    console.log(firstSquareLaterAdd(tFx, tGx)([3, 4]))//调用函数
}

{//函数的部分应用
    //改变传入函数参数的顺序
    //将内层函数传递的参数放在左边
    function putInnerParameterLeft(f, ...outParameters) {
        return function (...innerParameters) {
            let args = [...innerParameters, ...outParameters]
            return f.apply(this, args)//将所有的参数最终都传递给 f 函数
        }
    }
    //将内层函数传递的参数放在右边
    function putInnerParameterRight(f, ...outParameters) {
        return function (...innerParameters) {
            let args = [...outParameters, ...innerParameters]
            return f.apply(this, args)//将所有的参数最终都传递给 f 函数
        }
    }
    //将外层 undefined 类型的参数用内层参数替换
    function replaceOutUndefinedParameters(f, ...outParameters) {
        return function (...innerParameters) {
            let outP = [...outParameters]
            let innerP = [...innerParameters]
            let innerIndex = 0
            for (let i = 0; i < outP.length; i++) {
                if (outP[i] === undefined) {
                    outP[i] = innerP[innerIndex++]
                }
            }
            let args = [...outP, ...innerP.slice(innerIndex)]
            return f.apply(this, args)
        }
    }
    //使用上述定义的函数
    const method = function (x, y, z) {
        return x * (y - z)
    }
    console.log(putInnerParameterLeft(method, 2)(3, 4))//=>3*(4-2)=>6
    console.log(putInnerParameterRight(method, 2)(3, 4))//=>2*(3-4)=>-2
    console.log(replaceOutUndefinedParameters(method, undefined, 3)(4, 5))//=>4*(3-5)=>-8
}

4.5匿名函数(立即执行函数)

{//对于立即执行函数(匿名函数)来说,function 关键字的左开括号是必须的

    //函数调用传参的小括号 () 可以放在外层括号之外
    (function immediateFun(val) {
        console.log("我是一个立即执行函数", val)
    })(999);

    //也可以放在外层括号之内
    (function immediateFun(val) {
        console.log("我是一个立即执行函数", val)
    }(888));
}

4.6函数作为值

{//函数作为值被使用
    //在 JS 中,函数不仅是语法,也是值
    //使用函数声明创建函数,表示将函数对象赋值给了声明的标识符
    function newFun_declaretion() {
        console.log("我是声明式函数")
    }
    newFun_declaretion()
    const newFun_declaretion_copy = newFun_declaretion
    newFun_declaretion_copy()//将函数复制给另一个变量

    //函数作为函数的形参1
    const add = (x, y) => x + y
    const sub = (x, y) => x - y
    const mul = (x, y) => x * y
    const div = (x, y) => x / y
    const operationNum = function (fun, numA, numB) {
        return fun(numA, numB)
    }
    console.log(operationNum(add, operationNum(sub, 8, 3), operationNum(add, 2, 3)))//=>10

    //函数作为函数的形参2
    const operations = {
        add: (x, y) => x + y,
        sub: (x, y) => x - y,
        mul: (x, y) => x * y,
        div: (x, y) => x / y,
        mod: (x, y) => x % y,
        pow: (x, y) => x ** y
    }
    const operation_objMethods = function (operate, numA, numB) {
        if (typeof (operations[operate]) === 'function') {
            return operations[operate](numA, numB)
        } else {
            throw new Error("不是一种方法!")
        }
    }
    console.log(operation_objMethods('add', 999, 1))

    //定义自己的函数属性
    const computed_Factorial = function (val) {
        if (Number.isInteger(val) && val > 0) {//当传入的参数是个正整数时
            if (!(val in computed_Factorial)) {//像数组一样索引并在索引处缓存
                computed_Factorial[val] = val * computed_Factorial(val - 1)
            }
            return computed_Factorial[val]
        } else if (val === 0) {//传入参数为 0 时
            return computed_Factorial[0] = 1
        }
        else {
            return NaN
        }
    }
    console.log(computed_Factorial(5))
    console.log(computed_Factorial[4])//读取之前存入的属性
}

4.7闭包

{//在 JS 中,函数是使用定义它们的作用域来执行的,嵌套函数的作用域是外层函数之内,所以它能够访问同一定义域上的变量
    //在闭包中,外层函数一般返回一个函数对象
    let newFun = (function () {//定义一个立即执行函数来作为命名空间存放私有变量
        let count = 0//私有变量
        function uniqueInteger() {
            return count++
        }
        return uniqueInteger//返回一个函数
    }())
    console.log(newFun())//newFun() 小括号表示调用 uniqueInteger 函数
    console.log(newFun())

    //在一个私有领域中,可以由多个闭包共享,每调用一次 counter() 就会新创建一个私有变量,并且只供自己的调用者使用,多个调用者之间的私有变量是独立的,不会互相影响
    function counter() {
        let count = 0
        const addCount = () => count++
        const resetCount = () => count = 0
        return {
            addCount,
            resetCount
        }
    }
    let counter_1 = counter()//创建计数器1
    let counter_2 = counter()//创建计数器2
    console.log(counter_1.addCount())
    console.log(counter_1.addCount())
    console.log(counter_1.addCount())
    console.log(counter_1.addCount())
    console.log(counter_2.addCount())
    console.log(counter_2.addCount())
    counter_2.resetCount()//重置第二个计数器
    console.log(counter_1.addCount())
    console.log(counter_1.addCount())
    console.log(counter_1.addCount())
}

{
    //使用闭包定义对象上的 set() 与 get() 方法
    const newFun_set_get = function (i) {//定义一个只允许递增的函数
        return {
            get uniqueAdd() {//只允许内层函数访问 i
                return i++
            },
            set uniqueAdd(j) {
                if (j < i) {//只允许内层函数访问 i
                    throw new Error(`参数必须大于${i}`)
                } else {
                    i = j
                }
            }
        }
    }
    let newC = newFun_set_get(100)//初始化
    console.log(newC)
    console.log(newC.uniqueAdd)
    console.log(newC.uniqueAdd)
    console.log(newC.uniqueAdd)
    newC.uniqueAdd = 200//更改计数值
    console.log(newC.uniqueAdd)
    console.log(newC.uniqueAdd)
    console.log(newC.uniqueAdd)
}

{
    //使用闭包的私有属性访问器方法
    function addPrivateProp(Obj, propName, checkMethod) {
        let value//设置一个私有属性
        Obj[`get${propName}`] = function () {
            return value
        }
        Obj[`set${propName}`] = function (p) {
            if (!checkMethod(p)) {
                throw new TypeError("无效的属性名!")
            } else {
                value = p
            }
        }
    }
    //以下是使用方法
    let newBlankObj = {}
    addPrivateProp(newBlankObj, 'Name', x => typeof (x) === 'string')//为 newBlanObj 对象添加属性访问器方法
    newBlankObj.setName("newProp")//设置私有属性
    console.log(newBlankObj)
    console.log(newBlankObj.getName())//读取私有属性
}

{
    //创建一个常量数组
    const createNum = function (val) {
        return () => val
    }
    let newArray = []
    for (let i = 0; i < 10; i++) {
        newArray[i] = createNum(i)
    }
    console.log(newArray[5]())//=>5

    //错误的方式
    function consfuns() {
        let funcs = []
        for (var i = 0; i < 10; i++) {//在这里,使用 var 关键字与使用 let 关键字的结果不一致
            funcs[i] = () => i//嵌套函数
        }
        return funcs
    }
    let funcs = consfuns()
    console.log(funcs[5]())//=>10
    //在这里,当循环结束时,变量 i 的值为 10,此时已经创建了一个含有十个函数的数组,每个数组返回一个 i 值,
    //数组中的这些函数是嵌套函数,有权访问父作用域定义的 i 值,此时 i = 10,当调用这些函数时,都会返回 10
}

4.8函数的属性、方法与构造函数

{//在 JS 中,函数是一种特殊的对象
    //函数有一个只读的 length 属性,表示在声明的参数列表中的形参个数,剩余形参不包含在内
    const getMax = function (a, b) {
        return a > b ? a : b
    }
    console.log(getMax.length)//=>2

    //函数有一个只读的 name 属性,对于匿名函数,其值为第一次创建函数时付给函数的变量名或属性名
    console.log(getMax.name)//=>getMax

    //每个函数都有 prototype 属性
    console.log({ getMax })//=>{}
}

{
    // call() 方法与 apply() 方法

    //call()方法,用法是,将需要绑定到对象上的方法/函数调用 call() 方法,call() 方法的第一个参数为该对象,后续参数为方法的参数
    let newObj = {
        id: 1,
        name: 'midshar',
        age: 18
    }
    //将 getMin() 作为对象 newObj 的方法调用
    const getMin = function (m, n) {
        return m < n ? m : n
    }
    let smallest = getMin.call(newObj, 111, 222)
    console.log(smallest)//=>111

    //apply() 方法,用法是,将需要绑定到对象上的方法/函数调用 call() 方法,call() 方法的第一个参数为该对象,后续参数为方法的参数,参数的形式是数组
    const getMax = function (a, b) {
        return a > b ? a : b
    }
    let biggest = getMax.apply(newObj, [222, 999])
    console.log(biggest)//=>999

    //将对象中的方法替换为另外一种包装版本
    let newObj_test = {
        id: 1,
        name: 'midshar',
        packFun: (a) => a.slice(0, 1).toUpperCase() + a.slice(1)
    }
    function packageFun(obj, method) {
        let original = obj[method]//保存原始方法的副本
        console.log(original)
        obj[method] = function (...argu) {//开始包装函数
            console.log((new Date).toLocaleString(), `original function:==>`, method)
            let res = original.apply(this, argu)//按照原始方法执行
            console.log((new Date).toLocaleString(), `result function:==>`, method)
            return res//返回执行结果
        }
    }
    packageFun(newObj_test, 'packFun')
    console.log(newObj_test.packFun)
}

{//bind() 方法,主要目的是把函数绑定给对象,这个方法会返回一个新的函数,新函数的 name 属性由 bound 与调用 bind() 方法的函数 name 属性组成
    //为对象绑定新的函数
    function bindFun(value) {
        return this.num + value//this 在绑定后会指向绑定该方法的对象
    }
    let newObj = {
        num: 999
    }
    let newBindResFun = bindFun.bind(newObj, 1)//将返回的新函数赋值给变量,bind() 方法第一个参数是需要绑定的对象,后续参数作为新函数的参数
    console.log(newBindResFun.name)//=>bound bindFun
    console.log(newBindResFun())//=>1000

    //利用 bind 执行部分应用,表示在第一个参数之后传给 bind 的值会随着 this 值一起被绑定
    let sum = (a, b) => { return { a: a, b: b } }
    let res_sum = sum.bind({ x: 10, y: 5 }, 666)//将参数a(第一个)绑定成为666
    console.log(res_sum())
    let other = function (b, c) {//注意这里不能使用箭头函数
        return { this: this.num, b: b, c: c }
    }
    let res_ohter = other.bind({ num: 222 }, 111)//绑定this值与参数b
    console.log(res_ohter())
    let all = function (k, f, c) {
        return {
            k: k, f: f, c: c
        }
    }
    let res_all = all.bind(null, 444, 555)//绑定k与f
    console.log(res_all(999))//999将赋值给未绑定的c
}

{//Function 构造函数,可以接收任意多个参数,最后一个参数表示函数体的内容,其余参数表示函数接收的形参。若不接收形参,可以直接传入一个函数体文本的字符串。
    //利用 Function 构造函数创建的是匿名函数
    console.log((new Function('x', 'y', 'return x>y?x:y;'))(5, 9))
    //为匿名构造函数加入名字
    let noNameFun = new Function('x', 'y', 'return x>y?x:y')
    console.log(noNameFun(5, 3))
    //需要注意,Function 构造函数创建的函数不会遵循词法作用域的规则,它会始终被编译为顶级作用域的函数
    let scope = 'global'
    function newFun_top() {
        let scope = 'local'
        const insideFun = new Function('return scope')
        return insideFun
    }
    try {
        console.log(newFun_top()())
    } catch (err) {
        console.log(err)
    }
}
  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Midshar.top

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值