Javascript高级

140 阅读22分钟

面向对象😺

  • 面向对象本质是 对面向过程的封装

    ​ 面向过程 : 注重的是过程

    ​ 面向对象 : 注重的是结果

面向对象三大特征

  • 继承:一个对象(子对象)拥有另一个对象(父对象)的所有成员(js主要用继承)
  • 封装:把代码放入对象的方法中
  • 多态:一个对象 在不同情况下的不同状态(js语言基本不涉及多态)

内置对象😺

数组对象api

let arr=[10,20,30,40,50] // new Array(10,20,30,40,50)
  1. arr.concat(数组)

连接数组,返回值是连接后的数组

应用:上拉加载下一页。 需要将下一页的数组连接到后面

arr = arr.concat([5, 6, 7])
console.log(arr) //[10, 20, 30, 40, 50, 5, 6, 7]
  1. arr.join(‘分隔符’)

    把数组中的每一个元素连接成字符串(括号里面什么都不放,默认分隔符是逗号 放个空字符串'',就是紧密连接)

    应用:把数组元素拼接成字符串在页面显示。例如:歌手['周杰伦','蔡依林'] -->周杰伦/蔡依林

    let str = arr.join('|')
    console.log(str) //10|20|30|40|50|5|6|7
    
  2. arr.reverse()

    翻转数组

    arr.reverse()
    console.log(arr) //[7, 6, 5, 50, 40, 30, 20, 10]
    
  3. arr.sort()

    数组排序

    let numArr=[20,55,30,10,90]
    numArr.sort(function(a,b){
        // return a - b //从小到大 //[10, 20, 30, 55, 90]
        return b - a //从大到小 //[90, 55, 30, 20, 10]
    })
    console.log(numArr)
    

字符串对象api

let str = '祝大家女神节快乐!'
  1. 字符串类似于数组,也有长度和下标

    console.log(str.length) //8
    console.log(str[5]) //节
    
  2. str.indexOf(‘字符串’)

获取‘字符串’首字母在str中的下标(如果存在,返回首字母下标;如果不存在,则返回固定值-1)

应用:可以判断一个字符串 在不在str中

console.log(str.indexOf('女神')) //3
console.log(str.indexOf('女快')) //-1
  1. str.split(‘分隔符’)

以分隔符分割str,分割的每一部分放入数组中

应用:解析url中的参数

let url = 'http://www.baidu.com?name=张三&age=20'
console.log(url.split('?')) //['http://www.baidu.com', 'name=张三&age=20']
console.log(url.split('=')) //['http://www.baidu.com?name', '张三&age', '20']
//取到'张三'
console.log(url.split('?')[1].split('&')[0].split('=')[1]) //张三
  1. str.substr(下标,长度)

从‘下标’开始截取‘长度’的字符串

console.log(str.substr(3, 3)) //女神节  从3下标开始截取3个字
  1. 大小写转换(中文没有大小写)
console.log('aedJNMIsswSEW'.toLocaleUpperCase()) //AEDJNMISSWSEW
console.log('aedJNMIsswSEW'.toLocaleLowerCase()) //aedjnmisswsew

原型对象😺

工厂函数

函数作用是用于 创建对象 的函数

构造函数

构造函数作用与工厂函数一致。但是代码更加简洁

  • 构造函数new工作原理
    1. 创建空对象
    2. this指向这个对象
    3. 对象赋值
    4. 返回这个对象

构造函数new在使用时需注意的地方:

  1. 构造函数首字母一般大写,为了提醒调用者不要忘记关键字new

  2. 如果构造函数内部 手动return:

    (1)return 值类型:无效,还是返回new创建的对象

    (2)return引用类型(数组、函数、对象):有效,会覆盖new创建的对象

function Person(name, age, sex) {
      //(1)创建空对象   {}
      //(2)this指向这个对象  this = {}
      //(3)对象赋值
       this.name = name
       this.age = age
       this.sex = sex
      //(4)返回这个对象 return this
 }

let p1 = new Person('张三', 20, '男')
let p2 = new Person('李四', 22, '女')
console.log(p1, p2)

原型对象

任何函数在声明的时候,系统会自动帮你创建一个对象,称之为原型对象

作用:解决 内存浪费 和 变量污染

原型对象三个相关属性: 描述构造函数、原型对象、实例对象三者关系

  1. prototype : 属于构造函数, 指向原型对象

作用:解决构造函数内存浪费 + 变量污染

  1. constructor : 属于原型对象,指向构造函数

作用:可以让实例对象知道自己是被哪个构造函数创建的

  1. proto : 属于实例对象,指向原型对象

    作用:让实例对象直接访问原型成员

        //1.构造函数
        function Person(name, age) {
            this.name = name
            this.age = age
        }

        //2.原型对象
        Person.prototype.eat = function () {
            console.log('吃东西')
        }

        Person.prototype.learn = function () {
            console.log('学习')
        }

        console.log(Person.prototype.constructor) //Person
        console.log(Person.prototype.constructor === Person) //true


        //3.实例对象
        let p1 = new Person('张三', 20)
        console.log(p1)

        /*
        为什么实例对象可以直接访问原型中的成员,本质是通过__proto__来访问的
        __proto__ : 属于实例对象的,指向原型对象
        注意: 这个属性不是web标准,很多浏览器不会显示的。在实际开发中必须要省略
        */
        p1.eat() //p1.__proto__.eat()

        /* 验证 构造函数、原型对象、实例对象三者关系 */
        console.log(p1.__proto__.constructor) //Person
        console.log(Person.prototype === p1.__proto__) //true
  • 静态成员与实例成员:

    静态成员: 属于函数的成员

    实例成员:属于实例对象的成员

              function Person(name, age) {
                this.name = name
                this.age = age
            }
    
            let p1 = new Person('张三', 20)
            console.log(p1.name) //实例成员
            console.log(p1.age) //实例成员
    
            console.log(Person.prototype) //静态成员
            console.log(Math.PI) //静态成员
    
  • Object的value方法

    Object.values( 对象名 )

        /* 需求:获取对象所有的属性值 : Object.values( 对象名 )*/
        let person = {
            name: '张三',
            age: 20,
            sex: '男'
        }

        //1 以前的写法:  for-in 循环
        for (let key in person) {
            console.log(person[key])
        }

        //2.静态方法  Object.values(对象名)
        //返回值是一个数组,会存储对象每一个属性值
        console.log(Object.keys(person)) //['name', 'age', 'sex']
        console.log(Object.values(person)) //['张三', 20, '男']

原型继承(important!)😺

  • 原型继承:把父对象(实例)作为子对象 构造函数的原型

    PicModal继承modal的open方法:

    ​ 原型继承: 把父对象(实例) 作为子对象构造函数的原型

    ​ (1)若Modal.prototype 有自己的eat

    ​ (2)如果把父对象原型 作为 子对象原型. 只要给子对象原型添加eat就会覆盖Modal原来的eat

    ​ (3)如果把父对象实例 作为 子对象原型. 给子对象原型添加eat,本质是给父对象实例添加,不会污染Modal原来的eat

        /* 
        1.继承 :  一个对象 拥有 另一个对象 所有的 成员
        2.原型继承 :把父对象 作为子对象构造函数的原型
        */

        //父对象
        let father = {
            house: {
                address: '深圳湾一号',
                price: 20000000
            },
            car: {
                brand: '劳斯莱斯幻影',
                price: 15000000
            }
        }

        //子对象
        //构造函数
        function Son(name, age) {
            this.name = name
            this.age = age
        }
        //原型继承: 把父对象(实例) 作为子对象 构造函数的原型
        Son.prototype = father

        //可选 : 原型继承之后,由于父对象覆盖原来的 子对象构造函数原型, 就会导致constructor消失.
        //解决办法: 手动添加。(对开发几乎没有影响,也可以不加)
        Son.prototype.constructor = Son

        //实例对象
        let s1 = new Son('ikun', 30)
        let s2 = new Son('班长', 20)
        console.log(s1, s2)

原型链😺

原型链

  • 原型链::每一个实例对象都有自己的原型,而原型也是对象,也有自己的原型,以此类推形成链式结构. 称之为原型链

  • 对象访问原型链规则:就近原则

    对象优先访问自己的属性,自己没有才会访问原型,原型也没有就访问原型的原型,以此类推直到原型链终点(null),如果还没有,属性则获取undefined,方法则报错:xxx is not a function

  • 原型链作用(!):继承

//1.构造函数
        function Person(name, age) {
            this.name = name
            this.age = age
            //    this.type = '学生'//如果自己有type,优先访问自己的(就近原则)
        }

        //2.原型对象 : 存储具有共同特征的数据
        Person.prototype.type = '哺乳动物'
        Person.prototype.country = '中国'
        Person.prototype.eat = function () {
            console.log(this.name + '吃东西')
        }

        //3.实例对象
        let p1 = new Person('帅哥', 20)
        let p2 = new Person('美女', 20)
        console.log(p1)


        /* 小测试 */
        console.log(p1.name) //帅哥  p1自己有name属性
        console.log(p1.age) //20   p1自己有age
        console.log(p1.type) //哺乳动物  p1自己没有type,但是p1的原型有
        console.log(p1.girlFrined) //undefined  p1自己没有girlFrined, p1的原型也没有         girlFrined

        p1.eat() // 吃东西
        //    p1.learn()//报错   undefined is not a function
        /* 思考:  p1自己没有toString, p1的原型也没有toString, 但是为什么不报错呢?
        原因:  p1的原型的原型有toString
        */
        p1.toString()

        /* 如何查看实例对象原型 : 两行 */

        //查看p1的原型
        console.log(p1.__proto__.constructor) //Person
        console.log(Person.prototype === p1.__proto__) //true
        //查看p1的原型的原型
        console.log(p1.__proto__.__proto__.constructor) //Object
        console.log(Object.prototype === p1.__proto__.__proto__) //true
        //查看p1的原型的原型的原型
        console.log(p1.__proto__.__proto__.__proto__) //null

![](C:\Users\cc\Desktop\web H5C3笔记\JS笔记\JS高级课程笔记 张晓坤\day02.assets\1559057933326.png)

内置对象原型链

        // 数组对象  
        //实例化对象
        let arr = [10,20,30]//new Array(10,20,30)
        console.log( arr )
        //1.1 查看arr的原型
        console.log( arr.__proto__.constructor )//Array
        console.log( arr.__proto__ === Array.prototype )//true
        //1.2 查看arr的原型的原型
        console.log( arr.__proto__.__proto__.constructor )//Object
        console.log( arr.__proto__.__proto__ === Object.prototype )//true
        
        // 字符串对象
        let str = new String('abc')
        console.log( str )
        //2.1 查看str的原型
        console.log( str.__proto__.constructor )//String
        console.log( str.__proto__ === String.prototype )//true
        //2.2 查看arr的原型的原型
        console.log( str.__proto__.__proto__.constructor )//Object
        console.log( str.__proto__.__proto__ === Object.prototype )//true
        
        // 日期对象
        let date = new Date()
        /* js有几个特殊的对象 无法使用 log来打印的,需要用dir来打印: function date dom对象 */
        console.dir( date )
        //3.1 查看date的原型
        console.log( date.__proto__.constructor )//Date
        console.log( date.__proto__ === Date.prototype )//true
        //3.2 查看date的原型的原型
        console.log( date.__proto__.__proto__.constructor )//Object
        console.log( date.__proto__.__proto__ === Object.prototype )//true

DOM对象原型链

<body>
    <div>我是div</div>
    <p>我是pp</p>
    <a href="#">我是aa</a>

    <script>
        let div = document.querySelector('div')
        let p = document.querySelector('p')
        let a = document.querySelector('a')
    </script>
</body>

![](C:\Users\cc\Desktop\web H5C3笔记\JS笔记\JS高级课程笔记 张晓坤\day02.assets\1559057590648.png)

instanceof运算符

  • instanceof(关键字):运算符。检测某个构造函数的prototype在不在实例对象的原型链中

    说人话:亲子鉴定,鉴定两个对象间有没有血缘关系

    语法:实例对象 instanceof 构造函数

    检测 右边的构造函数的prototype 在不在 左边实例对象的原型链中

    ​ true:在 false:不在

  • 重点:instanceof运算符原理. 检测右边构造函数prototype是否在左边实例对象得原型链中

        let arr = [10, 20, 30]
        let str = new String('abc')
        // arr-> Array.prototype -> Object.prototype -> null
        console.log(arr instanceof Array) //true
        console.log(arr instanceof Object) //true
        console.log(arr instanceof String) //false

        // str-> String.prototype -> Object.prototype -> null
        console.log(str instanceof Array) //false
        console.log(str instanceof Object) //true
        console.log(str instanceof String) //true

        //封装一个函数,要求这个函数必须要传数组类型、 传其他类型不可以
        function fn(arr) {
            if (arr instanceof Array) {
                console.log(arr.reverse())
            } else {
                console.log('数据类型错误')
            }
        }

        fn([10, 20, 30])
        fn('abc')

ES6😺

剩余参数

  1. 剩余参数(rest参数):获取函数剩余的所有参数
    • 语法:function 函数名(形参1,形参2,...形参3){ }
    • 特点:
      1. 只能作为最后一个参数
      2. 是真数组
  2. arguments关键字:获取函数所有实参
    • 是一个伪数组,有数组三要素(元素、下标、长度),但是不能使用数组的方法
    • 应用:一般用户参数数量不限的函数
      1. 例如:arr.push() 、Math.max() 这些函数实参数量不限,底侧原理就是使用arguments来接受所有的实参

一般情况下,剩余参数可以取代arguments

函数默认参数

  • 语法:function 函数名(形参=默认值){ }
       function fn(a = 10, b = 20) {
            //以前: 逻辑或短路
            // 找真 : 左边是真就返回左边式子的值,否则返回右边式子的值
            // a = a || 10
            // b = b || 20
            console.log(a + b)
        }

        fn(5)

let与const

  • ES5声明变量:var

    1. 有变量预解析:声明提前
    2. 没有块级作用域:分支和循环大括号内都是全局
  • ES6声明变量:let、const

    1. 没有变量预解析:变量先声明后赋值
    2. 有块级作用域 : 分支和循环大括号内是局部(没有变量污染)
  • let和const的区别:

    let:变量。可以修改

    const:常量。不可以修改,内存更安全,效率更高

对象解构赋值

  • 解构赋值:变量赋值的简写(解构精髓:当变量名和对象属性值一致时,只需要写一个)

    1. 对象的属性值赋值给变量

      let { name,age,sex} = obj

    2. 变量的值赋值给对象

      let obj = {name,age,sex}

        let obj = {
            name: '张三',
            age: 20,
            sex: '男'
        }
        //1. 取出对象的属性  赋值 给 变量
        //ES5 
        // let name = obj.name
        // let age = obj.age
        // let sex = obj.sex
        // let score = obj.score
        
        //ES6
        let {
            name,
            age,
            sex,
            score
        } = obj
        console.log(name, age, sex, score) //'张三',20,'男',undefined

        //2. 取出变量的属性  赋值 给 对象
        let username = 'admin'
        let password = '123456'
        let a = 'aaa123'
        //ES5
        // let user = {
        //     username:username,
        //     password:password
        // }

        //ES6
        let user = {
            username,
            password: a,
            eat() { //等价于 eat:function(){}
                console.log(111)
            }
        }
        console.log(user)
        user.eat()

数组解构赋值

  • 数组解构:
    1. 取出数组元素 赋值给变量
    2. 取出变量的值 赋值给数组元素
        let arr = [10, 20, 30]
        //ES5
        // let n1 = arr[0]
        // let n2 = arr[1]
        // let n3 = arr[2]
        // let n4 = arr[3]
        
        //ES6
        let [n1, n2, n3, n4] = arr
        console.log(n1, n2, n3, n4) //10 20 30 undefined
        //取出变量的值 赋值给数组
        let num1 = 1
        let num2 = 2
        let arr1 = [num1, num2]
        console.log(arr1) //1,2

函数解构赋值

        function fn({ name, age }) { //let {name,age}={ name: '李四', age: 25 }
            //把参数解构
            // let {name,age}=obj
            // let name = obj.name
            // let age = obj.age
            console.log(name, age)
        }
        fn({ name: '李四', age: 25 })

箭头函数

  • 箭头函数:相当于function函数的简写
    1. 把function改成箭头 =>
    2. 形参小括号()写到箭头左边
  • 箭头函数语法注意点:
    1. 如果箭头函数只有一个形参,则可以省略小括号
    2. 如果箭头函数的 函数体 只有一行代码,则可以省略大括号。 (此时必须省略return)
    let fn = (a, b) => {
      console.log(a + b)
    }
    fn(10, 20)

    //(1)如果箭头函数只有一个形参,则可以省略小括号
    let fn1 = a => { return a * a }
    console.log(fn1(10))//100

    //(2)如果箭头函数的 函数体 只有一行代码,则可以省略大括号。 (此时必须省略return)
    let fn2 = a => a * 2
    console.log(fn2(9))//18

箭头函数的this指向

箭头函数本质是访问 上级作用域中的this

  1. 箭头函数不能上下文调用 : 无法修改this指向
  2. 箭头函数不能作为构造函数
  3. 有两种函数最好不要是箭头函数:构造函数、事件处理函数
        let fn = () => {
            //1级
            console.log(this)
        }

        fn()

        let obj = {
            name: '张三',
            eat: fn
        }

        obj.eat()

        //由于箭头函数没有this,所以箭头函数不能作为构造函数 (new会修改this指向,而箭头没有this)
        // new fn()
        //箭头函数也无法修改this (call apply bind)对箭头函数是无效的
        fn.call({ name: '张三' })
        //由于箭头函数没有this,所以箭头函数一般不作为事件处理函数
  • 箭头函数this指向景点面试题:

          let obj = {
            name: "ikun",
            eat() {
              //1级   this : obj
              function fn1() {
                //2级  
                console.log(this)//window  
              }
              fn1()
              let fn2 = () => {
                //2级  : 箭头函数访问上级 1级obj
                console.log(this)//obj   
              }
              fn2()
            },
            learn: () => {
              //1级 : 上级 this->window
              function fn1() {
                console.log(this)//window 
              }
              fn1()
              let fn2 = () => {
                //2级  访问1级 this -> window  
                console.log(this)//window  
              }
              fn2()
            }
          }
    
          obj.eat()
          obj.learn()
    

展开运算符

  • 展开运算符:...

    相当于对象遍历的简写

  • 应用:

    1. 连接两个数组/对象
    2. 求数组最大值
        let arr1 = [10, 20, 30]
        let arr2 = [40, 50, 60]
        //ES5 :  concat()
        // let arr = arr1.concat(arr2)
        // arr.push(70)

        //ES6
        //连接数组
        let arr = [...arr1, ...arr2, 70]
        console.log(arr) //[10, 20, 30, 40, 50, 60, 70]

        //连接对象
        let student = {
            score: 90,
            car: {
                name: '张三'
            }
        }
        let teacher = {
            name: 'kunkun',
            ...student
        }
        console.log(teacher) //{name: 'kunkun', score: 90, car: {…}}

        //求数组最大值
        let max = Math.max(...arr1, ...arr2, 100)
        console.log(max) //100

数据类型Set

以下所有的方法,都可以使用传统的for循环来代替,只是语法不同而已

数组几种遍历介绍(共同点:回调函数一样)应用场景回调执行次数函数返回值回调是否需要return
map遍历映射数组== 原数组长度新数组(==)一定要return(当前元素)
filter遍历过滤数组== 原数组长度新数组(!=)return true(元素添加到新数组)
forEach遍历遍历数组== 原数组长度
some遍历找出符合条件的数!= 原数组长度布尔类型return true;循环结束
every遍历判断所有元素是否符合条件!= 原数组长度布尔类型return true; 循环继续
findIndex遍历获取符合条件的第一个元素位置(下标)!= 原数组长度数字return true; 循环结束
includes方法(底层是遍历)判断数组/字符串是否包含某一个值无回调布尔类型无回调
  • 数据类型Set:集合(类似于数组)

    Set相当于是数组类型,Set和数组最大区别是:Set无法存储重复数据

  • 应用:数组去重

    let newArr = [ ...new Set(需要去重的数组) ]

        let arr = [10, 20, 30, 50, 60, 20, 50, 60]
        console.log(arr)

        //1.创建Set,去除数组重复元素
        let set = new Set(arr)
        console.log(set) //{10, 20, 30, 50, 60}
        // 2.把set变成真数组
        let newArr = [...set]

        //经典面试题: 一行代码实现数组去重
        // let newArr = [...new Set(arr)]
        console.log(newArr) //[10, 20, 30, 50, 60]

数组map方法

  • map作用与场景:映射数组(将数组每一个元素处理后,得到一个新数组)

    举例子: 商品打八折(数组中每一个元素都要乘以0.8)

  • 经典场景:数据驱动渲染dom树(将数组直接渲染到页面)

  • 语法特点:

    1. 循环执行次数 == 数组长度

    2. 回调函数内部return作用:

      (1)return 新元素值

      (2)没有return,新元素都是undefined

    3. map本身返回值作用:映射之后的新数组

        let arr = [20, 66, 80, 90, 100]

        //完整写法
        // let res = arr.map((item, index) => {
        //     return item * 0.8
        // })

        //简写
        let res = arr.map(item => item * 0.8)
        console.log(res) //[16, 52.800000000000004, 64, 72, 80]
  • map方法渲染到页面:

    封装渲染函数,使用map来渲染数据;由于页面会渲染很多次,而且每一次渲染的数组也不一样。

    数组不一样解决方案:给渲染函数添加一个‘函数’

    const renderData = arr => {
       document.querySelector('.list').innerHTML=arr.map(item=>``).join('')
    }
    

数组filter方法

  • filter作用与场景:筛选数组

  • 经典场景:筛选,根据条件筛选数组,将符合条件的元素放入新数组中

  • 语法特点:

    1. 循环执行次数 == 数组长度

    2. 回调函数内部return作用:

      (1)return true:满足筛选条件,当前元素放入新数组中

      (2)return false:不满足筛选条件,当前元素不放入新数组中

    3. filter本身返回值作用:筛选之后的新数组

        let arr = [20, 61, 80, 95, 100]

        //需求: 筛选出数组中的偶数

        //完整写法
        // let res = arr.filter((item, index) => {
        //     console.log(item, index)
        //     if (item % 2 == 0) {
        //         return true
        //     }
        // })

        //简介写法
        let res = arr.filter(item => item % 2 == 0)
        console.log(res) //[20, 80, 100]

数组forEach

  • 数组forEach方法作用:遍历数组
  • 应用场景和 for(let i=0;i<arr.length;i++){} 功能一致
  • 特点:
    1. 回调函数执行次数 == 数组长度
    2. 回调函数内部的return:没有返回值 undefined
    3. forEach方法的返回值:没有返回值
        let arr = [45, 60, 88, 90, 20]

        arr.forEach((item, index) => {
            console.log(item, index)
        })

数组some方法

  • some作用与场景:判断数组中是否有满足条件的元素(逻辑或||)

  • 经典场景:非空判断. 如:多个表单只要有一个是空的,就不能提交

  • 语法特点:

    1. 循环执行次数 != 数组长度

    2. 回调函数内部return作用:

      (1)return true:循环结束. 找到满足条件的元素,此时some的返回值也是true

      (2)return false:循环继续. 没有找到满足条件的元素,如果循环执行完毕还是 false,最终some的返回值也是false

    3. some本身返回值作用

      return true : 有满足条件的元素

      return false : 没有满足条件的元素

        //需求: 判断数组中有没有负数
        let arr = [10, 20, 50, 60, 70, 80]
        //标准写法
        // let res = arr.some((item, index) => {
        //     if (item < 0) {
        //         return true
        //     }
        // })

        //简写
        let res = arr.some(item => item < 0)
        console.log(res) //false (所以没有满足条件的元素)

数组every方法

  • every作用与场景:判断数组中是否所有元素都满足条件(逻辑与&&)

  • 经典场景:开关思想. 购物车全选

  • 语法特点:

    1. 循环执行次数 != 数组长度

    2. 回调函数内部return作用:

      (1)return true:循环继续. 当前元素满足条件,继续判断. 如果循环执行完毕还是true,则every的返回值就是true

      (2)return false:循环结束. 当前元素不满足条件,every的返回值也是false

    3. every本身返回值作用

      return true : 全部元素都满足条件

      return false : 有元素不满足条件

        //需求: 判断数组中是否所有数都是正数
        let arr = [10, 20, 50, 60, 70, 80]
        //标准写法
        // let res = arr.every((item, index) => {
        //     if (item > 0) {
        //         return true
        //     }
        // })

        //简写
        let res = arr.every(item => item > 0)
        console.log(res) //true (所有数都是正数)

数组findIndex方法

  • 数组findIndex方法作用:查找元素下标

  • 数组的findIndex与indexOf异同点:

    相同点:功能一致,都是查找元素下标。 有则返回下标,无则返回固定值-1

    不同点:应用场景不同

    1. indexOf : 查找数组中的元素都是值类型
    2. findIndex : 查找数组中的元素都是引用类型
  • findIndex方法特点:

    1. 回调函数执行次数 != 数组长度

    2. 回调函数内部的return

      return true:循环结束。 找到了,此时返回值就是下标

      return false:循环继续。 没找到,循环继续。 如果所有元素全部遍历还是没找到,最终结果就是-1

  • findIndex本身返回值作用

    return -1 : 没有

    return 下标 : 有

  • findIndex方法返回的是索引值(下标)

  • find方法返回的是具体属性值

  • find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回undefined

        let arr = [
            { name: '张三', age: 20 },
            { name: '李四', age: 18 },
            { name: '王五', age: 16 },
        ]

        //需求:找名字为王五的人在哪里
        //    let res = arr.findIndex(item=>{
        //        if( item.name == 'sfs' ){
        //            return true
        //        }else{
        //            return false
        //        }
        //    })

        let res = arr.findIndex(item => item.name == '王五')
        console.log(res)//2

数组reduce方法

  • 数组reduce方法:数组累加器方法

    对数组每一个元素执行一次回调函数,累加最后一次回调的结果

  • reduce场景:数组元素求和、求数组元素最大值

        let arr = [20,55,60]

        // let sum = 0
        // for(let i = 0;i<arr.length;i++){
        //     sum = sum + arr[i]
        // }
        // console.log( sum )
        

        /* 
        第一个参数:回调   (上一次值,当前值,当前下标)=>{}
            * 默认下标不是从0开始,而是从1开始。 开发中一般需要设置默认值
            * return 值 就是下一次 sum的值
        第二个参数: 初始值
            * 一般需要设置初始值为0, 如果不设置遇到空数组则会报错
        reduce方法返回值是 : 最后一次sum的结果
        */
        //    let res = arr.reduce( ( sum,item,index )=>{
        //    console.log( sum,item,index)
        //    return sum + item
            
        //    } , 0 )

        let res = arr.reduce( ( sum,item )=>sum + item , 0 )

        console.log( res )

数组includes方法

**包含: includes( )**方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true

函数this的三种指向😺

  • 环境对象 this:谁‘调用’我,我就指向谁

    1. 普通函数:函数名() this -> window

    2. 构造函数:new 函数名() this -> new创建的实例对象

    3. 对象方法:对象名.方法名() this -> 对象

      小技巧:没点没new是window,有new是实例,有点就是点左边的对象

 //作用域链
    let obj = {
      name: "张三",
      eat: function () {
        //1级链
        console.log(this) //1.obj
        function fn() {
          //2级链
          console.log(this) //2.window
        }
        fn()
      }
    }
    let eat = obj.eat
    obj.eat()

函数上下文调用😺

  • 默认情况下,函数内部的this是固定的,无法动态修改,如果需要修改,则需要使用函数上下文调用(函数上下文:函数作用域)

call()调用函数

函数名.call(修改后的this,参数1,参数2…………)

        let fn = function (a, b) {
            console.log(this)
            console.log(a + b)
        }
        // 函数名.call(修改后的this,参数1,参数2…………)
        fn.call({
            name: '齐齐'
        }, 20, 30)

call()场景1:Array.from(伪数组)伪数组转真数组

  • 伪数组:有数组三要数(下标、长度、元素),但没有数组的方法(本质是对象)

    Array.from(伪数组)

        let weiArr = {
            0:88,
            1:20,
            2:50,
            3:60,
            length:4
        }

        console.log( weiArr )

        //如果希望伪数组也可以调用数组的方法(排序、拼接),就需要把伪数组转成真数组

        /* 
        1. slice可以查询数组,默认情况下不传参这个方法会得到数组本身
        2. 但是伪数组由于原型不是Array,所以无法调用slice
        3. slice方法存储在哪里? : Array.prototype
        */
        weiArr = Array.prototype.slice.call(weiArr)
        console.log(weiArr)
        
        // weiArr.slice()

        //实际开发中,ES6新增语法用于伪数组转真数组 :  Array.from(伪数组)
        let arr = Array.from(weiArr)
        console.log( arr )

call()场景2:万能数据类型检测

  • typeof 数据:有两种数据类型无法检测(null和数组:结果都是‘object’)

  • 解决方案:万能数据类型检测

    Object.prototype.toString.call(数据)

        //值类型
        let str = 'abc'
        let num = 123
        let bol = true
        let und = undefined
        let nul = null
        //引用类型
        let arr = [10, 20, 30]
        let fn = function () {}
        let obj = {
            name: '张三'
        }

        console.log(typeof str) //'string'
        console.log(typeof num) //'number'
        console.log(typeof bol) //'boolean'
        console.log(typeof und) //'undefined'
        console.log(typeof nul) //'object'
        console.log(typeof arr) //'object'``
        console.log(typeof fn) //'function'
        console.log(typeof obj) //'object'

        /* 万能数据类型检测: Object.prototype.toString.call(数据)
         原因: 在Object.prototype中有一个方法叫toString,返回一个固定格式字符串 '[object 数据类型]'
        */
        console.log(Object.prototype.toString.call(str)) //'[object String]'
        console.log(Object.prototype.toString.call(num)) //'[object Number]'
        console.log(Object.prototype.toString.call(bol)) //'[object Boolean]'
        console.log(Object.prototype.toString.call(und)) //'[object Undefined]'
        console.log(Object.prototype.toString.call(nul)) //'[object Null]'
        console.log(Object.prototype.toString.call(arr)) //'[object Array]'
        console.log(Object.prototype.toString.call(fn)) //'[object Function]'
        console.log(Object.prototype.toString.call(obj)) //'[object Object]'

apply()调用函数

函数名.apply(修改后的this,数组/伪数组)

        function fn(a, b) {
            console.log(a + b)
            console.log(this)
        }

        // (1)函数名.call(修改后的this,参数1,参数2…………)
        fn.call({
            name: '张三'
        }, 10, 20)

        // (2)函数名.apply(修改后的this, 数组/伪数组 )
        // apply底层会自动帮你遍历数组,然后按照顺序逐一传参
        fn.apply({
            name: '李四'
        }, [30, 40])

apply()场景1:伪数组转真数组

        let weiArr = {
            0: 20,
            1: 66,
            2: 88,
            3: 90,
            length: 4
        }
        //需求: 伪数组转真数组
        let arr = []

        //思路一: 把伪数组每一个元素push到数组中
        // arr.push( weiArr[0],weiArr[1],weiArr[2],weiArr[3])
        // for (let i = 0; i < weiArr.length; i++) {
        //     arr.push(weiArr[i])
        // }

        //思路二: 使用apply   arr.push.apply(arr,伪数组)
        //这里不需要修改this,只是借助apply传参的特点. this指向原来是谁,还是设置谁
        arr.push.apply(arr, weiArr)
        console.log(arr)

        //思路三: 伪数组转真数组 : ES6提供一个更简洁的静态方法
        //  Array.from( 伪数组 )
        console.log(Array.from(weiArr))

apply()场景2:求数组最大值

let max = Math.max(...arr)

        //求数组最大值
        let arr = [20, 50, 66, 100, 30]

        //思路一: .js基础 : 擂台思想
        let max = arr[0]
        for (let i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i]
            }
        }
        console.log(max)

        //思路二: Math.max()
        // let max1 = Math.max(arr[0],arr[1],arr[2],arr[3],arr[4])
        //这里使用apply只是借助传参特点,this指向不用修改。还是原来的this
        //Math.max.apply(Math,数组/伪数组)
        let max1 = Math.max.apply(Math, arr)
        console.log(max1)

        //思路三:
        /* ES6有更简单的方式可以求数组最大值
        ... 展开运算符,自动遍历数组传参(类似于apply传参特点)
        */
        let max2 = Math.max(...arr)
        console.log(max2)

bind()调用函数

函数名.bind(修改后的this)

        function fn(a, b) {
            console.log(a + b)
            console.log(this)
        }

        // 函数名.call(修改后的this,形参1,形参2…………)
        fn.call({
            name: '张三'
        }, 10, 20)

        // 函数名.apply(修改后的this, 数组或伪数组 )
        // apply会自动帮你遍历数组,然后按照顺序逐一传参
        fn.apply({
            name: '李四'
        }, [30, 40])

        //函数名.bind(修改后的this)
        // bind不会立即执行函数,而是返回一个修改this之后的新函数
        let newFn = fn.bind({
            name: '王五'
        })
        newFn(100, 200) //300
        newFn(10, 20) //30

bind()场景:修改定时器的this

定时器的this:默认指向window

    setTimeout(function () {
      console.log(this) //{name: '李四'}
    }.bind({
      name: '李四'
    }), 3000)


    //   let obj = {
    //     name: "张三",
    //     hobby: ["学习", "干饭", "睡觉"],
    //     sayHi: function() {
    //       setTimeout(function() {
    //         console.log(this)
    //       }, 1000)
    //     }
    //   }

    //   obj.sayHi()

经典面试题:call、apply、bind的异同点

  • 相同点:作用一致,都是修改函数this的指向
  • 不同点:
    1. 传参方式不同 : call是单个传参, apply是数组/伪数组传参
    2. 执行机制不同 : call和apply会立即执行函数,而bind不会立即执行而是返回一个修改this之后的新函数

闭包😺

  • 闭包closure:

    1. 闭包是一个 访问其他函数内部变量 的 函数
    2. 闭包 = 函数 + 上下文引用
  • 闭包作用:解决变量污染

    //局部作用域 :  在函数内部声明

    function fn() {
      //局部变量num
      let num = 10
      // test函数 + 引用num 这个组合才叫闭包
      function test() {
        console.log(num)
      }
      test()
    }

    fn()
  • example:

    <body>
        <input type="text" placeholder="请输入搜索内容">
        <button class="btn">点我搜索</button>
    
        <script>
            document.querySelector('.btn').onclick = function () {
                //1.获取用户搜索的内容
                let txt = document.querySelector('input').value
                //2.网络请求 : 不是立即就能出结果的,网络请求需要时间的。
                //使用定时器来模拟请求
                //定时器使用了input
                setTimeout(function () {
                    alert(`${txt}的搜索结果如下:123条`)
                }, 1000)
            }
        </script>
    </body>
    

递归😺

  • 递归函数:一个函数 在内部 调用自己

    递归作用和循环类似,也需要有结束条件(优先用循环,递归少用)

            function fn() {
                console.log('今天学得很开心')
                fn()
            }
    
            // fn()
    
            //双函数递归 : 两个函数互相调用
            function fn1() {
                console.log('哈哈')
                fn2()
            }
    
            function fn2() {
                console.log('呵呵')
                fn1()
            }
    
            // fn1()
    
  • 递归应用:

    1. 浅拷与深拷贝

      浅拷贝:拷贝地址,修改拷贝后的数据对原数据有影响

      深拷贝:拷贝数据,修改拷贝后的数据对原数据没有影响

      • 方式一:JSON方式实现(推荐)

        let newObj = JSON.parse( JSON.stringify( obj ))

      • 方式二:递归(了解)

    2. 递归遍历dom树

浅拷贝与深拷贝(JSON实现)

        let obj = {
            name: '张三',
            age: 20,
            sex: '男',
            hobby: ['吃饭', '睡觉', '学习']
        }

        //1.浅拷贝 : 拷贝地址
        // let newObj = obj
        // newObj.name = '李四'
        // newObj.hobby[0] = '游戏'
        // console.log(obj, newObj) //修改newObj,obj也会跟着改

        //2.深拷贝 : 使用JSON

        //(1)把obj转成json格式字符串 : 底层会自动深拷贝
        // let jsonStr = JSON.stringify(obj)
        //(2)把json转成对象
        // let newObj = JSON.parse(jsonStr)

        let newObj = JSON.parse(JSON.stringify(obj))
        newObj.name = '李四'
        newObj.hobby[0] = '游戏'
        console.log(obj, newObj)

浅拷贝与深拷贝(递归实现)

        let obj = {
            name: '张三',
            age: 20,
            sex: '男',
            hobby: ['吃饭', '睡觉', '学习'],
            student: {
                name: "班长",
                score: 90
            }
        }

        //使用递归函数
        function copy(obj, newObj) {
            for (let key in obj) {
                if (obj[key] instanceof Array) {
                    //声明一个空数组,然后继续拷贝数组里面的数据
                    newObj[key] = []
                    //递归调用继续拷贝 数组
                    copy(obj[key], newObj[key])
                } else if (obj[key] instanceof Object) {
                    //声明一个空对象
                    newObj[key] = {}
                    //递归调用继续拷贝 对象
                    copy(obj[key], newObj[key])
                } else {
                    newObj[key] = obj[key]
                }
            }
        }
        //创建一个空对象,然后开始深拷贝
        let newObj = {}
        copy(obj, newObj)

        newObj.name = '李四'
        newObj.hobby[0] = '摸鱼'
        newObj.student.name = '坤坤'
        console.log(obj, newObj)

递归遍历dom树

<style>
    * {
      padding: 0;
      margin: 0;
    }

    .menu p {
      width: 100px;
      border: 3px solid;
      margin: 5px;
    }

    .menu>div p {
      margin-left: 10px;
      border-color: red;
    }

    .menu>div>div p {
      margin-left: 20px;
      border-color: green;
    }

    .menu>div>div>div p {
      margin-left: 30px;
      border-color: yellow;
    }
  </style>
</head>

<body>
  <div class="menu">
    <!-- <div>
        <p>第一级菜单</p>
        <div>
          <p>第二级菜单</p>
          <div>
            <p>第三级菜单</p>
          </div>
        </div>
      </div> -->
  </div>
  <script>
    //服务器返回一个不确定的数据结构,涉及到多重数组嵌套
    let arr = [{
        type: "电子产品",
        data: [{
            type: "手机",
            data: ["iPhone手机", "小米手机", "华为手机"]
          },
          {
            type: "平板",
            data: ["iPad", "平板小米", "平板华为"]
          },
          {
            type: "智能手表",
            data: []
          }
        ]
      },
      {
        type: "生活家居",
        data: [{
            type: "沙发",
            data: ["真皮沙发", "布沙发"]
          },
          {
            type: "椅子",
            data: ["餐椅", "电脑椅", "办公椅", "休闲椅"]
          },
          {
            type: "桌子",
            data: ["办公桌"]
          }
        ]
      },
      {
        type: "零食",
        data: [{
            type: "水果",
            data: []
          },
          {
            type: "咖啡",
            data: ["雀巢咖啡"]
          }
        ]
      }
    ]

    addElement(arr, document.querySelector('.menu'))

           //封装一个遍历dom树函数
         function addElement(arr, father) {
           //遍历数组
           for (let i = 0; i < arr.length; i++) {
             //(1)创建空标签
             let div = document.createElement("div")
             //(2)设置内容
             div.innerHTML = `<p>${arr[i].type || arr[i]}</p>`
             //(3)添加到父盒子
             father.appendChild(div)
             //如果元素还有data属性,则需要使用递归继续添加下级菜单
             if (arr[i].data) {
               addElement(arr[i].data, div)
             }
           }
         }
         addElement(arr, document.querySelector(".menu"))
  </script>
</body>