JavaScript进阶

JavaScript进阶

文章目录

一、作用域&解析&箭头函数

1.作用域

1.1 局部作用域

局部作用域分为函数作用域和块作用域

  • 函数作用域:

在函数内部声明的变量只能在函数内部被访问,外部无法直接访问

<script>
        function getSum () {
            // 函数内部是函数作用域  属于局部变量
            let num = 10
        }
        console.log(num); // 此处报错  函数外部不能使用局部作用域变量
    </script>

总结:

  1. 函数内部声明的变量,在函数外部无法被访问
  2. 函数的参数也是函数内部的局部变量
  3. 不同函数内部声明的变量无法互相访问
  4. 函数执行完毕后,函数内部的变量实际被清空了
  • 块作用域:

在JavaScriptr中使用{ }包裹的代码称为代码块,代码块内部声明的变量外部将[有可能]无法被访问

for(let i = 1; i <= 6; i++){
    // i 只能在改代码块中被访问
    console.log(i) // 正常
}
// 超出了i的作用域
console.log(i) // 报错
  1. let声明的变量会产生块作用域,var不会产生块作用域
  2. const声明的常量也会产生块作用域
  3. 不同代码块之间的变量无法相互访问
  4. 推荐使用let和const
1.2 全局作用域

全局作用域中声明的变量,任何其他作用域都可以被访问

    <script>
        // 全局作用域
        // 全局作用域下声明了num变量
        let num = 10
        function fn () {
            // 函数内部可以使用全局作用域的变量
            console.log(num);
        }
        fn ()
    </script>

注意:

  1. 为window对象动态添加的属性默认也是全局的,不推荐!!
  2. 函数中使用任何关键字声明的变量为全局变量,不推荐!!!
  3. 尽可能少的声明全局变量,防止变量污染
1.3 作用域链

作用域链本质上是底层的变量查找机制

  1. 在函数被执行时,会优先查找当前函数作用域中查找变量
  2. 如果当前作用域找不到则会依次逐级查找父级作用域直到全局作用域

总结:

  1. 嵌套关系的作用域串联起来形成了作用域链
  2. 相同作用域链中按着从小到大的规则查找变量
  3. 子作用域能够访问父作用域,父级作用域无法访问子级作用域
1.4 JS垃圾回收机制
(1)什么是垃圾回收机制?

垃圾回收机制(Garbage Collection)简称Gc

JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。

正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题

但如果不了解JS的内存管理机制,我们同样非常容易成内存泄露(内存无法被回收)的情况不在用到的内存,没有及时释放,就叫做内存泄漏

(2) 内存的声明周期

JS环境中分配的内存,一般有如下生命周期:

  1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
  2. 内存使用:即读写内存,也就是使用变量、函数等
  3. 内存回收:使用完毕,有垃圾回收自动回收不在使用的内存
  4. 说明:
  5. 全局变量一般不会回收(关闭页面回收);
  6. 一般情况下局部变量的值,不用了,会被自动回收掉
(3)垃圾回收算法说明

所谓垃圾回收,核心思想就是如何判断内存是否已经不会被使用了,如果是,就视为垃圾,释放掉下面介绍两种常见的浏览器垃圾回收算法:引用计数法和标记清除法

  • 引用计数

IE采用了引用计数算法,定义"内存不在使用"的标准很简单,就是看一个对象是否有指向他的引用。

算法:

  1. 跟踪记录每个值被引用的次数
  2. 如果这个值被引用了一次,那么就记录次数1
  3. 多次引用会累加
  4. 如果减少一个引用就减1
  5. 如果引用次数是0,则释放内存
<script>
        const person = {
            age:18,
            name:'pink老师'
        }
        const p = person
        person = 1
        p = null
    </script>

由上面可以看出,引用计数法是个简单有效的算法

但他却存在一个致命的问题:嵌套引用

如果两个对象相互引用,尽管他们已不在使用,垃圾回收器 不会进行回收,导致内存泄漏

function fn() {
    let o1 = {}
    let o2 = {}
    o1.a = 02
    o2.a = 01
    return '引用计数无法回收'
}
fn()

因为他们的引用次数永远不会是0,这样的相互引用如果说很大量的存在就会导致大量的内存泄漏

  • 标记清除法

现代的浏览器语句不再使用引用计数法了

现代的浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的

核心:

  1. 标记清除算法将"不再使用的对象"定义为"无法达到的对象"
  2. 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的
  3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收
1.5 闭包

一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域

闭包的作用:

  1. 封闭数据,实现数据私有,外部也可以访问函数内部的变量
  2. 闭包很有用,因为他允许将函数与其所操作的某些数据(环境)关联起来

闭包的问题:

可能引起内存泄漏

简单理解:

闭包 = 内层函数 + 外层函数的变量

function outer () {
    let a = 1
    function f() {
        console.log(a)
    }
    f()
}
outer()
  function outer () {
            let i = 100
            function fn () {
                console.log(i);
            }
            return fn
        }
        let fun = outer()
        fun()
// 因为i是全局变量,容易被修改
        // 闭包形式 统计函数调用次数
        function count () {
            let i = 0
            function fn () {
                i++
                console.log(`函数被调用了${i}`);
            }
            return fn
        }
        let fun = count()
        fun()
1.6 变量提升

变量提升是JavaScript中比较"奇怪"的现象,他允许在变老了声明之前即被访问(仅存在于var声明变量)

  • 把所有var声明的变量提升到当前作用域的最前面
  • 只提升声明,不提升赋值

注意:

  1. 变量在未声明即被访问时会报语法错误
  2. 变量在var声明之前即被访问,变量的值为underfined
  3. let / const 声明的变量不存在变量提升
  4. 变量提升出现在相同作用域当中
  5. 实际开发中推荐先声明再访问变量

2.函数进阶

2.1 函数提升

函数提升与变量提升比较类似,是指函数在声明之前即可被调用

// 调用函数
fun()
// 声明函数
function fun() {
    console.log('声明之前即被调用...')
}

函数表达式不能被i提升

// 不存在提升现象
bar() // 错误
var bar = function() {
    console.log('函数表达式不存在提升现象...')
}

总结:

  1. 函数提升能够使函数的声明调用更灵活
  2. 函数表达式不存在提升现象
  3. 函数提升出现在相同作用域当中
2.2 函数参数
  • 动态参数

arguments是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参

function getSum() {
            // 动态参数arguments
            // arguments是伪数组
           // console.log(arguments);
           let sum = 0
           for(let i = 0; i < arguments.length; i++) {
                sum += arguments[i]
           }
           return sum
        }
        let sum = getSum(2,3)
        console.log(sum);
        let sum1 = getSum(1,2,3,4,5)
        console.log(sum1);

总结:

  1. arguments是一个伪数组,只存在于函数中
  2. arguments的作用是动态获取函数的实参
  3. 可以通过for循环依次得到传递过来的实参
  • 剩余参数

剩余参数允许我们将一个不定数量的参数表示为一个数组

  1. …是语法符号,置于最未函数形参之前,用于获取多余的实参
  2. 借助…获取的剩余实参,是个真数组
 function getSum(a,b,...arr) {
           // console.log(arr);
           let sum = a + b
           for(let i = 0; i < arr.length; i++){
                sum += arr[i]
           }
           console.log(sum);
        }
        getSum(2,3)
        getSum(1,2,3)
        getSum(1,2,3,4,5)
  • 展开运算符

展开运算符(…),将一个数组进行展开

 let arr = [1,2,3,4,5]
        // 1. 求数组最大值
        console.log(Math.max(...arr));
        // 2.求数组最小值
        console.log(Math.min(...arr));
        console.log(...arr);
        let arr1 = [6,7,8,9]
        // 3.合并数组
        let arr2 = [...arr,...arr1]
        console.log(arr2);
        for(let i = 0; i < arr2.length; i++){
            console.log(arr2[i]);
        }
  • 展开运算符 or 剩余参数区别
  1. 剩余参数:函数参数使用,得到真数组
  2. 展开运算符:数组中使用,数组展开

3. 箭头函数

目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁

  • 使用场景:箭头函数更适合于那些本来需要匿名函数的地方
3.1 基本语法
 // 1.普通函数
        function fun(a,b) {
            console.log(a+b);
        }
        fun(1,3)
        // 2.箭头函数
        let fn = (a,b) => {
            console.log(a+b);
        }
        fn(1,3)
       // 3.只有一个形参的时候可以省略小括号
        let f = x => {
            console.log(x);
        }
        f(4)
       // 4.只有一行代码的时候可以省略大括号
        let fm = x => console.log(x)
        fm(4)
       // 5. 省略return
       let getSum = (a,b) => a+b
       console.log(getSum(1,3));
       // 6.箭头函数返回一个对象
       let fn1 = uname => ({uname:uname})
       console.log(fn1('许文强'));
3.2箭头函数的参数
 // 1.利用箭头函数求和
        let getSum = (...arr) => {
            let sum = 0
            for(let i = 0; i < arr.length; i++){
                sum += arr[i]
            }
            return sum
        }
        let result = getSum(1,2,3,4,5)
        console.log(result);
3.3 箭头函数的this

在箭头函数出现之前,每一个新函数根据它是被如调用的来定义这个函数的this值,非常令人讨厌

箭头函数不会创建自己的this,他只会从自己的作用链的上一层沿用this

  console.log(this);
        function fn (){
            console.log(this);
        }
        fn()
        let fun = () => {
            console.log(this);
        }
        fun()
        let obj = {
            uname:'许文强',
            sayHi: () => {
                console.log(this);
            }
        }
        obj.sayHi()
        let object = {
            uname:'刘德华',
            sayHi:function () {
                let count = () => {
                    console.log(this);
                }
                count()
            }
        }
        object.sayHi()

4.解构赋值

4.1 数组解构

数组解构是将数组的单元值快速批量赋值给一系列变量的简介语法

  • 基本语法:
  1. 赋值运算符 = 左侧的[ ]用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
  2. 变量的顺序对应数组单元值的位置依次进行赋值操作
 // 1.声明一个数组
      //  let arr = [100,60,80]
        let [max,min,avg] = [100,60,80]
        console.log(`最大值为${max}`);
        console.log(`最小值为${min}`);
        console.log(`平均值为${avg}`);
        // 2. 交换变量
        let a = 1
        let b = 2;
        [b,a] = [a,b]
        console.log(a);
        console.log(b);
        // 3. 冒泡排序
        let arr = [2,6,4,3,5,1]
        // 1. 外层循环控制趟数
        for(let i = 0; i < arr.length - 1; i++) {
            // 2. 内层循环控制一趟交换几次
            for(let j = 0; j < arr.length - i - 1; j++) {
                // 3. 如果前一个数比后一个数大就交换
                if(arr[j] > arr[j + 1]) {
                    [arr[j + 1],arr[j]] = [arr[j],arr[j + 1]]
                }
            }
        }
        console.log(arr);
  • JS前面必须加分号情况

(1)立即执行函数

(function () { })();
(function () { })();

(2)使用数组的时候

let a = 1
let b = 2;
[b,a] = [a,b]
  • 变量多 单元值少的情况:
// 变量多 单元值少
let [a,b,c,d] = ['小米','华为','苹果']
console.log(a) // 小米
console.log(b)  // 华为
console.log(c) // 苹果
console.log(d) // undefined
 let [a,b,...c] = [1,2,3,4]
        console.log(a);
        console.log(b);
        console.log(c);
4.2 多维数组解构
 let arr = [[1,2],[3,4]]
        console.log(arr[0][0]);
        console.log(arr[0][1]);
        console.log(arr[1][0]);
        console.log(arr[1][1]);
        console.log('---------------------------------');
        for (let i = 0; i < arr.length; i++){
            for(let j = 0; j < arr.length; j++){
                console.log(arr[i][j]);
            }
        }
        console.log('---------------------------------');
        let [[a,b],[c,d]] = arr
        console.log(a);
        console.log(b);
        console.log(c);
        console.log(d);
4.3 对象解构

基本语法:

  1. 赋值运算符 = 左侧的{ }用于批量声明变量,右侧对象的属性值将赋值给左侧变量
  2. 对象属性的值将被赋值给与属性名相同的变量
  3. 注意解构的变量名不要和外卖的变量名冲突否则报错
  4. 对象中找不到与变量名一致的属性时变量值为undefined
let obj = {
            uname:'许文强',
            age: 18
        }
        console.log(obj.uname);
        console.log(obj.age)
        console.log('--------------------------');
        let {uname,age} = obj
        console.log(uname);
        console.log(age);
  • 给新的变量赋值:

可以从一个对象中提取变量并同时修改新的变量名

 let {uname:name,age} = obj
        console.log(name);
        console.log(age);
  • 多级对象解构
 let pig = {
            name:'佩奇',
            age:6,
            family: {
                father:'猪爸爸',
                mother:'猪妈妈',
                sister:'乔治'
            }
        }
        let {name,age,family:{father,mother,sister}} = pig
        console.log(name);
        console.log(age);
        console.log(father);
        console.log(mother);
        console.log(sister);
4.4遍历数组forEach方法
  • forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
// 1.普通函数
被遍历的数组.forEach(function(当前数组元素,当前元素索引号){
    //函数体
})
// 2.箭头函数
被遍历的数组.forEach((当前数组元素,当前元素索引号) => {
    // 函数体
})

注意:

  1. forEach主要是遍历数组
  2. 参数当前数组元素是必须要写的,索引号可选
 let arr = ['red','pink','green','blue']
        arr.forEach((item,index) => {
            console.log(item);
            console.log(index);
        })
        console.log('--------------------------');
        arr.forEach((item) => {
            console.log(item);
        })
4.5 商品渲染案例
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>商品渲染</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    .list {
      width: 990px;
      margin: 0 auto;
      display: flex;
      flex-wrap: wrap;
      padding-top: 100px;
    }

    .item {
      width: 240px;
      margin-left: 10px;
      padding: 20px 30px;
      transition: all .5s;
      margin-bottom: 20px;
    }

    .item:nth-child(4n) {
      margin-left: 0;
    }

    .item:hover {
      box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
      transform: translate3d(0, -4px, 0);
      cursor: pointer;
    }

    .item img {
      width: 100%;
    }

    .item .name {
      font-size: 18px;
      margin-bottom: 10px;
      color: #666;
    }

    .item .price {
      font-size: 22px;
      color: firebrick;
    }

    .item .price::before {
      content: "¥";
      font-size: 14px;
    }
  </style>
</head>

<body>
  <div class="list">
    <!-- <div class="item">
      <img src="" alt="">
      <p class="name"></p>
      <p class="price"></p>
    </div> -->
  </div>
  <script>
    const goodsList = [
      {
        id: '4001172',
        name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
        price: '289.00',
        picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
      },
      {
        id: '4001594',
        name: '日式黑陶功夫茶组双侧把茶具礼盒装',
        price: '288.00',
        picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg',
      },
      {
        id: '4001009',
        name: '竹制干泡茶盘正方形沥水茶台品茶盘',
        price: '109.00',
        picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',
      },
      {
        id: '4001874',
        name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
        price: '488.00',
        picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',
      },
      {
        id: '4001649',
        name: '大师监制龙泉青瓷茶叶罐',
        price: '139.00',
        picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',
      },
      {
        id: '3997185',
        name: '与众不同的口感汝瓷白酒杯套组1壶4杯',
        price: '108.00',
        picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg',
      },
      {
        id: '3997403',
        name: '手工吹制更厚实白酒杯壶套装6壶6杯',
        price: '99.00',
        picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg',
      },
      {
        id: '3998274',
        name: '德国百年工艺高端水晶玻璃红酒杯2支装',
        price: '139.00',
        picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg',
      },
    ]


      // 1. 声明一个字符串变量
      let str = ''
      // 2. 遍历数据
      goodsList.forEach(item => {
        let {name,price,picture} = item // 解构对象
        str += `
      <div class="item">
      <img src="${picture}" alt="">
      <p class="name">${name}</p>
      <p class="price">${price}</p>
    </div> 
        `
      })
      document.querySelector('.list').innerHTML = str
  </script>
</body>

</html>
4.6 筛选数组filter 方法

filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素

  • 主要使用场景:筛选数组符合条件的元素,并返回筛选之后元素的新数组
// 1.箭头函数写法        
let arr = [10,20,30]
        let newArr = arr.filter(item => item >= 20)
        console.log(newArr);
// 2.普通函数写法
let newArr = arr.filter(function (item) {
    return item >= 20
})

总结:

  1. filter () 筛选数组
  2. 返回值:返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组
  3. 参数:currentValue 必须写,index可选
  4. 因为返回新数组,所有不会影响原数组
4.7 综合案例
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>商品渲染</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    .list {
      width: 990px;
      margin: 0 auto;
      display: flex;
      flex-wrap: wrap;
    }

    .item {
      width: 240px;
      margin-left: 10px;
      padding: 20px 30px;
      transition: all .5s;
      margin-bottom: 20px;
    }

    .item:nth-child(4n) {
      margin-left: 0;
    }

    .item:hover {
      box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
      transform: translate3d(0, -4px, 0);
      cursor: pointer;
    }

    .item img {
      width: 100%;
    }

    .item .name {
      font-size: 18px;
      margin-bottom: 10px;
      color: #666;
    }

    .item .price {
      font-size: 22px;
      color: firebrick;
    }

    .item .price::before {
      content: "¥";
      font-size: 14px;
    }

    .filter {
      display: flex;
      width: 990px;
      margin: 0 auto;
      padding: 50px 30px;
    }

    .filter a {
      padding: 10px 20px;
      background: #f5f5f5;
      color: #666;
      text-decoration: none;
      margin-right: 20px;
    }

    .filter a:active,
    .filter a:focus {
      background: #05943c;
      color: #fff;
    }
  </style>
</head>

<body>
  <div class="filter">
    <a data-index="1" href="javascript:;">0-100</a>
    <a data-index="2" href="javascript:;">100-300</a>
    <a data-index="3" href="javascript:;">300元以上</a>
    <a href="javascript:;">全部区间</a>
  </div>
  <div class="list">
    <!-- <div class="item">
      <img src="" alt="">
      <p class="name"></p>
      <p class="price"></p>
    </div> -->
  </div>
  <script>
    // 2. 初始化数据
    const goodsList = [
      {
        id: '4001172',
        name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
        price: '289.00',
        picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
      },
      {
        id: '4001594',
        name: '日式黑陶功夫茶组双侧把茶具礼盒装',
        price: '288.00',
        picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg',
      },
      {
        id: '4001009',
        name: '竹制干泡茶盘正方形沥水茶台品茶盘',
        price: '109.00',
        picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',
      },
      {
        id: '4001874',
        name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
        price: '488.00',
        picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',
      },
      {
        id: '4001649',
        name: '大师监制龙泉青瓷茶叶罐',
        price: '139.00',
        picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',
      },
      {
        id: '3997185',
        name: '与众不同的口感汝瓷白酒杯套组1壶4杯',
        price: '108.00',
        picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg',
      },
      {
        id: '3997403',
        name: '手工吹制更厚实白酒杯壶套装6壶6杯',
        price: '99.00',
        picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg',
      },
      {
        id: '3998274',
        name: '德国百年工艺高端水晶玻璃红酒杯2支装',
        price: '139.00',
        picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg',
      },
    ]

    // 1. 渲染函数
     let  render = arr => {
      // 声明一个空字符串
      let str = ''
      // 遍历数组
      arr.forEach(item => {
      // 解构对象
      let {name,price,picture} = item
      str += `
      <div class="item">
      <img src="${picture}" alt="">
      <p class="name">${name}</p>
      <p class="price">${price}</p>
    </div> 
      `
      })
      // 追加
      document.querySelector('.list').innerHTML = str
      // 2. 过滤筛选
      document.querySelector('.filter').addEventListener('click', e => {
        // 解构对象
        let {tagName,dataset} = e.target
        // 判断
        if(tagName === 'A') {
          let arr = goodsList
          if(dataset.index === '1') {
            arr = goodsList.filter(item => item.price > 0 && item.price <= 100)
          } else if(dataset.index === '2') {
            arr = goodsList.filter(item => item.price >= 100 && item.price <= 300)
          } else if(dataset.index === '3') {
            arr = goodsList.filter(item => item.price >= 300)
          }
          // 渲染函数
          render(arr)
        }
      })
    }
    render(goodsList)
  </script>
</body>

</html>

二、构造函数&数据常用函数

1. 深入对象

1.1 创建对象三种方式
  • 利用对象字面量创建对象
// 1. 利用字面量创建对象
        let obj = {
            name: '许文强',
            age: 18
        }
        console.log(obj);
  • 利用new Object创建对象
// 2. 利用new Object创建对象
        let o = new Object()
        o.name = '许文强'
        o.age = 18
        console.log(o);
        let ob = new Object({name:'刘德华',age: 29})
        console.log(ob);
1.2 构造函数
  • 构造函数:是一种特殊的函数,主要用来初始化对象
  • 使用场景:常规的{…}语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一遍,此时可以通过构造函数来快速创建多个类似的对象。
 function Pig (name,age) {
        this.name = name
        this.age  = age
      }
      let xwq = new Pig('许文强', 18)
      let ldh = new Pig('刘德华',20)
      console.log(xwq);
      console.log(ldh);
    </script>

说明:

  1. 使用new 关键字调用函数的行为被称为实例化
  2. 实例化构造函数时没有参数时可以省略
  3. 构造函数内部无需写return,返回值即为新创建的对象
  4. 构造函数内部的return 返回的值无效,所有不要写return
  5. new Object() new Date ( ) 也是实例化构造函数
  • 实例化执行过程
  1. 创建新对象
  2. 构造函数this指向新对象
  3. 执行构造函数代码,修改this,添加新属性
  4. 返回新对象
  • 构造函数创建对象案例
function Goods (name,price,count) {
            this.name = name
            this.price = price
            this.count = count
        }
        let xm = new Goods('小米',1999,20)
        let hw = new Goods('华为',3999,59)
        let vivo = new Goods('vivo',1888,100)
        console.log(xm);
        console.log(hw);
        console.log(vivo);
1.3 实例成员&静态成员
  • 实例成员:

通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员

说明:

  1. 实例对象的属性和方法即为实例成员
  2. 为构造函数传入参数,动态创建构造相同但值不同的对象
  3. 构造函数创建的实例对象彼此独立互不影响
  • 静态成员:

构造函数的属性和方法被称为静态成员

说明:

  1. 构造函数的属性和方法被称为静态成员
  2. 一般公共特征的属性或方法静态成员设置为静态成员
  3. 静态成员方法中this指向构造函数本身

2.内置构造函数

在JavaScript中最主要的数据类型有6种:

  • 基本数据类型:

字符串、数值、布尔、undefined、null

  • 引用数据类型:

对象

但是,我们会发现有特殊情况:

// 普通字符串
let str = 'andy'
console.log(str.length) // 4

其实字符串、数值、布尔、等基本类型也都有专门的构造函数,这些我们称为包装类型

JS中几乎所有的数据都可以基于构造函数创建

2.1 Object

三个常用静态方法(静态方法就是只有构造函数Object可以调用)

(1) Object.keys
  • 作用:Object.keys 静态方法获取对象中所有属性(键)
  • 语法:
let obj = {
    name: '刘德华',
    age: 18
}
// 获取对象的所有键,并返回是一个数组
let arr = Object.keys(obj)
console.log(arr)
  • 注意:返回的是一个数组
(2) Object.values
  • 作用:Object.values 静态方法获取对象中所有属性值
  • 语法:
let obj = {
    name: '刘德华',
    age: 18
}
// 获取对象的所有制,并且返回是一个数组
let arr = Object.values(obj)
console.log(arr)
  • 注意:返回的是一个数组
(3) Object.assign静态方法常用于对象拷贝
  • 作用:Object.assign静态方法常用于对象拷贝
  • 语法:
// 拷贝对象 把o拷贝给obj
let o = {
    name: '刘德华',
    age: 18
}
let obj = {}
Object.assign(obj,o)
console.log(obj)
2.2 Array

Array是内置的构造函数,用于创建数组

let arr = new Array(3,5)
console.log(arr) // [3,5]

创建数组建议使用字面量创建,不用Array构造函数创建

(1)数组的核心方法:
方法作用说明
forEach遍历数组不返回,用于不改变值,经常用于查找打印输出值
filter过滤数组筛选数组元素,并生成新数组
map迭代数组返回新数组,新数组里面的元素是处理之后的值,经常用于处理数据
reduce累计器返回函数累计处理的结果,经常用于求和等
(2) reduce方法
  • 作用:reduce返回函数累计处理的结果,经常用于求和等
  • 基本语法:
arr.reduce(function(累计值,当前元素 [,索引号][,源数组]) { },起始值)
  • 参数:

起始值可以省略,如果写就作用第一次累计的起始值

  • 累计值参数:
  1. 如果有起始值,则以起始值为准开始累计,累计值 = 起始值
  2. 如果没有起始值,则累计值以数组的第一个数组元素作为起始值开始累计
  3. 后面每次变量就会用后面的数组元素累计到累计值里面(类似求和里面的sum)
  • 其他方法:
方法作用
join数组元素拼接为字符串,返回字符串(重点)
find查找元素,返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回undefined(重点)
every检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回true,否则返回false(重点)
some检测数组中的元素是否满足指定条件,如果数组中有元素满足条件返回true,否则返回false
concat合并两个数字,返回生成新数组
sort对原数组单元值排序
splice删除或替换原数组单元
reverse反转数组
findIndex查找元素的索引值
  • find
const arr = [
            {
                name:'小米',
                price:1999
            },
            {
                name:'华为',
                price:2999
            },
            {
                name:'苹果',
                price:4999 
            }
        ]
        // 寻找小米这个对象,并且返回这个对象
        let re = arr.find(item => item.name === '小米')
        console.log(re);
  • every
let arr = [10,20,30]
        let result = arr.every(item => item >= 10)
        console.log(result);
        let re = [10,20,30].every(item => item >= 20)
        console.log(re);
  • form
const lis = document.querySelectorAll('ul li')
        let liss = Array.from(lis)
        liss.pop()
        console.log(liss);
2.3 String

在JavaScript中的字符串、数值、布尔值具有对象的使用特征,如具有属性和方法

// 字符串类型
let str = 'hello world'
// 统计字符的长度(字符数量)
console.log(str.length)
// 数值类型
let price = 12.345
// 保留两位小数
price.toFixed(2)

之所以具有对象特征的原因是字符串、数值、布尔类型数据是JavaScript底层使用Object构造函数"包装"来的,被称为包装类型

方法作用
length用来获取字符串的长度(重点)
split(‘分隔符’)用来将字符串拆分成数组(重点)
substring(需要截取的第一个字符的索引[,结束的索引号])用于字符串截取(重点)
startsWith(检测字符串[,检测位置索引号])检测是否以某字符开头(重点)
includes(搜索的字符串[,检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回true或false(重点)
toUpperCase用于将字母转换成大写
toLowerCase用于将字符转换成小写
indexOf检测是否包含某字符
endsWith检测是否以某字符结尾
replace用于替换字符串,支持正则匹配
match用于查找字符串,支持正则匹配
  • split
 // split把字符串转换为数组 与join相反
        let str = 'red,bule,pink'
        let arr = str.split(',')
        console.log(arr);
        let str1 = '2022-07-13'
        let arr1 = str1.split('-')
        console.log(arr1);
  • substring
// substring截取字符串
        let str = '今天星期四'
        let re = str.substring(0,3)
        console.log(re);
        let res = str.substring(4)
        console.log(res);
  • startswith
let str = '运筹帷幄之中,决胜千里之外'
        console.log(str.startsWith('运筹帷幄'));
        console.log(str.startsWith('帷幄'));
        console.log(str.startsWith('决胜',7));
  • includes
// includes 判断字符串是否存在
        let st = '运筹帷幄之中,决胜千里之外'
        console.log(st.includes('中')); // true
        console.log(st.includes('外')); // ture
        console.log(st.includes('是')); // false
2.4 Number

Number是内置的构造函数,用于创建数值

  • 常用方法:

toFixed( )设置保留小数位的长度

 let num = 12.345
    console.log(num.toFixed(1));
    let num1 = 10
    console.log(num1.toFixed(2));

综合案例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    .list {
      width: 990px;
      margin: 100px auto 0;
    }

    .item {
      padding: 15px;
      transition: all .5s;
      display: flex;
      border-top: 1px solid #e4e4e4;
    }

    .item:nth-child(4n) {
      margin-left: 0;
    }

    .item:hover {
      cursor: pointer;
      background-color: #f5f5f5;
    }

    .item img {
      width: 80px;
      height: 80px;
      margin-right: 10px;
    }

    .item .name {
      font-size: 18px;
      margin-right: 10px;
      color: #333;
      flex: 2;
    }

    .item .name .tag {
      display: block;
      padding: 2px;
      font-size: 12px;
      color: #999;
    }

    .item .price,
    .item .sub-total {
      font-size: 18px;
      color: firebrick;
      flex: 1;
    }

    .item .price::before,
    .item .sub-total::before,
    .amount::before {
      content: "¥";
      font-size: 12px;
    }

    .item .spec {
      flex: 2;
      color: #888;
      font-size: 14px;
    }

    .item .count {
      flex: 1;
      color: #aaa;
    }

    .total {
      width: 990px;
      margin: 0 auto;
      display: flex;
      justify-content: flex-end;
      border-top: 1px solid #e4e4e4;
      padding: 20px;
    }

    .total .amount {
      font-size: 18px;
      color: firebrick;
      font-weight: bold;
      margin-right: 50px;
    }
  </style>
</head>

<body>
  <div class="list">
    <!-- <div class="item">
      <img src="https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg" alt="">
      <p class="name">称心如意手摇咖啡磨豆机咖啡豆研磨机 <span class="tag">【赠品】10优惠券</span></p>
      <p class="spec">白色/10</p>
      <p class="price">289.90</p>
      <p class="count">x2</p>
      <p class="sub-total">579.80</p>
    </div> -->
  </div>
  <div class="total">
    <div>合计:<span class="amount">1000.00</span></div>
  </div>
  <script>
    const goodsList = [
      {
        id: '4001172',
        name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
        price: 289.9,
        picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
        count: 2,
        spec: { color: '白色' }
      },
      {
        id: '4001009',
        name: '竹制干泡茶盘正方形沥水茶台品茶盘',
        price: 109.8,
        picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',
        count: 3,
        spec: { size: '40cm*40cm', color: '黑色' }
      },
      {
        id: '4001874',
        name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
        price: 488,
        picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',
        count: 1,
        spec: { color: '青色', sum: '一大四小' }
      },
      {
        id: '4001649',
        name: '大师监制龙泉青瓷茶叶罐',
        price: 139,
        picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',
        count: 1,
        spec: { size: '小号', color: '紫色' },
        gift: '50g茶叶,清洗球'
      }
    ]
    // 1.渲染页面
      document.querySelector('.list').innerHTML = goodsList.map(item => {
        // 2. 对象解构
        const {name,price,picture,count,spec,gift} = item
        // 3. 规律文字处理
        let text = Object.values(spec).join('/')
        // 4. 处理赠品模块
        let str = gift ? gift.split(',').map(item => `<span class="tag">【赠品】${item}</span>`).join('') : ''
        // 5. 计算小计模块  单价 * 数量 
        let subTotal = ((price * 100 * count) / 100).toFixed(2)
        return `
      <div class="item">
      <img src="${picture}" alt="">
      <p class="name">${name} ${str}</p>
      <p class="spec">${text}</p>
      <p class="price">${price.toFixed(2)}</p>
      <p class="count">${count}</p>
      <p class="sub-total">${subTotal}</p>
    </div>
        `
      }).join('')
      // 6.合计模块
      let total = goodsList.reduce((prev,item) => prev + (item.price * 100 * item.count) / 100 ,0)
      document.querySelector('.amount').innerHTML = total.toFixed(2)
  </script>
</body>

</html>

三、深入面向对象

1. 编程思想

(1)面向过程
  • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了
  • 面向过程,就是按照我们分析好了的步骤,按照步骤解决问题
(2)面向对象
  • 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作
  • 面向对象是以对象功能来划分问题,而不是步骤
  • 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
  • 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目
  • 面向对象的特性:
  1. 封装性
  2. 继承性
  3. 多态性

2. 构造函数

  • 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装
  • 同样的将变量和函数组合到了一起并能通过this实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间彼此不影响的
function Star(uname,age) {
            this.uname = uname
            this.age = age
            this.sing = () => console.log('我会唱歌');
        }
        // 实例对象,获得了构造函数中封装的所有逻辑
        let ldh = new Star('刘德华',18)
        let zxy = new Star('张学友',20)
        console.log(ldh.uname,ldh.age,ldh.sing);
        console.log(zxy.uname,zxy.age,zxy.sing);

总结:

  1. 构造函数体现了面向对象的封装特性
  2. 构造函数实例创建的对象彼此独立、互不影响
  • 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装
  • 前面我们学过的构造函数方法很好用,但是存在浪费内的问题

3. 原型

3.1 原型介绍
  • 构造函数通过原型分配的函数是所有对象所共享的
  • JavaScript规定,没一个构造函数都有一个prototype属性,指向另一个对象,所有我们也称为原型对象
  • 这个对象可以挂在函数,对象实例化不会多次创建原型上函数,节约内存
  • 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法
  • 构造函数和原型对象中的this都指向实例化的对象
let arr = [1,2,3,4,5]
        // 1. 求最大值
       Array.prototype.max = function max (){
        return Math.max(...this)
       }
       console.log(arr.max());
       // 2. 求最小值
       Array.prototype.min = function min () {
        return Math.min(...this)
       }
       console.log(arr.min());
       // 3. 求和
       Array.prototype.getSum  = function () {
        return this.reduce((prev,item) => prev + item,0)
       }
       console.log(arr.getSum());
3.2 constructor属性

在哪里? 每个原型对象里面都有个constructor属性(constructor构造函数)

  • 作用:该属性指向该原型对象的构造函数,简历理解,就是指向我的爸爸,我是有爸爸的孩子
// constructor 构造函数
        function Star () {

        }
        const ldh = new Star()
        console.log(Star.prototype.constructor === Star);
function Star() {
    }
    // console.log(Star.prototype)
    Star.prototype = {
      // 从新指回创造这个原型对象的 构造函数
      constructor: Star,
      sing: function () {
        console.log('唱歌')
      },
      dance: function () {
        console.log('跳舞')
      },
    }
    console.log(Star.prototype)

使用场景:

如果有多个对象的方法,我们可以给原型对象采取对象形式赋值

但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor就不再是指向当前构造函数了此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数

3.3 对象原型

对象都会有一个属性_proto_指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有_proto_原型的存在

注意:

  1. _proto_是JS非标准属性
  2. [[prototype]]和_proto_意义相同
  3. 用来表明当前实例对象指向那个原型对象prototype
  4. _proto_对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数
3.4 原型继承

继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承 的特性。

龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义。

(1)封装-抽取公共部分

把男人和女人公共的部分抽取出来放到人类里面

(2)继承

让男人和女人都能继承人类的一些属性和方法

  1. 把男人和女人公共的属性和方法抽取出来Person
  2. 然后赋值给Man的原型对象,可以共享这些属性和方法
  3. 注意让constructor指回Man这个构造函数
let Person = {
            eays:2,
            head:1
        }
        function Woman () {

        }
        // Woman 通过原型来继承Person
        Woman.prototype = Person
        // 指回原来的构造函数
        Woman.prototype.constructor = Woman
        let red = new Woman()
        console.log(red);
        function Man () {

        }
        // Man通过原型来继承Person
        Man.prototype = Person
         // 指回原来的构造函数
         Man.prototype.constructor = Man
        let man = new Man()
        console.log(man);
(3)问题:–原因

男人和女都相同使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个就会都影响

function Person () {
        this.eyes = 2
        this.head = 1
       }
        function Woman () {

        }
        // Woman 通过原型来继承Person
        Woman.prototype = new Person()
        // 指回原来的构造函数
        Woman.prototype.constructor = Woman
        // 女人独特的行为
        Woman.prototype.bady = () => {
            console.log('宝贝');
        }
        let red = new Woman()
        console.log(red);
        function Man () {

        }
        // Man通过原型来继承Person
        Man.prototype = new Person()
         // 指回原来的构造函数
         Man.prototype.constructor = Man
         // 抽烟
         Man.prototype.chouyan = () => {
            console.log('抽烟');
         }
        let man = new Man()
        console.log(man);
3.5 原型链

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
  2. 如果没有就查找它的原型(也就是_proto_指向的prototype原型对象)
  3. 如果还没有就查找原型对象的原型(Object的原型对象)
  4. 依此类推一直找到Object为止(null)
  5. _proto_对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
  6. 可以使用instanceof运算符用于检测构造函数prototype属性是否出现在某个实例对象的原型链上
  • instanceof运算符
function Person () {

        }
        let ldh = new Person()
        console.log(ldh instanceof Person);
        console.log(ldh instanceof Object);
        console.log(ldh instanceof Array);
        console.log([1,2,3] instanceof Array);
        console.log(Array instanceof Object);
4. 综合案例
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>面向对象封装消息提示</title>
  <style>
    .modal {
      width: 300px;
      min-height: 100px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
      border-radius: 4px;
      position: fixed;
      z-index: 999;
      left: 50%;
      top: 50%;
      transform: translate3d(-50%, -50%, 0);
      background-color: #fff;
    }

    .modal .header {
      line-height: 40px;
      padding: 0 10px;
      position: relative;
      font-size: 20px;
    }

    .modal .header i {
      font-style: normal;
      color: #999;
      position: absolute;
      right: 15px;
      top: -2px;
      cursor: pointer;
    }

    .modal .body {
      text-align: center;
      padding: 10px;
    }

    .modal .footer {
      display: flex;
      justify-content: flex-end;
      padding: 10px;
    }

    .modal .footer a {
      padding: 3px 8px;
      background: #ccc;
      text-decoration: none;
      color: #fff;
      border-radius: 2px;
      margin-right: 10px;
      font-size: 14px;
    }

    .modal .footer a.submit {
      background-color: #369;
    }
  </style>
</head>

<body>
  <button id="delete">删除</button>
  <button id="login">登录</button>

  <!-- <div class="modal">
    <div class="header">温馨提示 <i>x</i></div>
    <div class="body">您没有删除权限操作</div>
  </div> -->


  <script>
     // 1.  模态框的构造函数
     function Modal(title = '', message = '') {
      // 公共的属性部分
      this.title = title
      this.message = message
      // 因为盒子是公共的
      // 1. 创建 一定不要忘了加 this 
      this.modalBox = document.createElement('div')
      // 2. 添加类名
      this.modalBox.className = 'modal'
      // 3. 填充内容 更换数据
      this.modalBox.innerHTML = `
        <div class="header">${this.title} <i>x</i></div>
        <div class="body">${this.message}</div>
      `
      // console.log(this.modalBox)
    }
     // 4. 打开方法 挂载  到 模态框的构造函数原型身上
     Modal.prototype.open = function () {
        if(!document.querySelector('.modal')) {
          // 把刚才创建的盒子 modalBox 渲染到页面中
        document.body.appendChild(this.modalBox)
        // 获取×号
        document.querySelector('i').addEventListener('click', () => {
          this.close()
        })
        }
      }
      // 5. 关闭方法 挂载 到模态框的构造函数原型身上
      Modal.prototype.close = function () {
        document.body.removeChild(this.modalBox)
      }
      // 6. 按钮点击
      document.querySelector('#delete').addEventListener('click' , () => {
        let m = new Modal('温馨提示','您没有删除权限操作')
        // 调用 打开方法
        m.open()
      })
      document.querySelector('#login').addEventListener('click', () => {
        let m = new Modal('友情提示','您没有注册账号')
        // 调用方法
        m.open()
      })
  </script>
</body>

</html>

四、高级技巧

1. 深浅拷贝

首先浅拷贝和深拷贝只针对引用类型

1.1 浅拷贝

浅拷贝:拷贝的是地址

常见方法:

  1. 拷贝对象:Object.assgin( ) / 展开运算符 {…obj} 拷贝对象
  2. 拷贝数组:Object.prototype.concat( ) 或者{…arr}
let obj = {
            uname: '刘德华',
            age: 19
        }
        let o = {...obj}
        o.age = 20
        console.log(o);
        console.log(obj);
1.2 深拷贝

深拷贝:拷贝的是对象,不是地址

常见方法:

  1. 通过递归实现深拷贝
  2. lodash / cloneDeep
  3. 通过JSON.stringify () 实现
(1)通过递归实现深拷贝

递归函数:

如果一个函数在内部可以调用本身,那么这个函数就是递归函数

  1. 简单理解:函数内部自己调用自己,这个函数就是递归函数
  2. 递归函数的作用和循环效果类似
  3. 由于递归很容易发生"栈溢出"错误(stack overflow),所以必须要加退出条件return
fn = n => {
            if(n == 1 || n == 2) {
                return 1
            }
            return fn(n - 1) + fn(n - 2)
        }
        for(let i = 1; i < 10; i++){
            let num = fn(i)
            console.log(num);
        }
        let i = 1
        function fun () {
            if(i == 6) {
                return 
            }
            console.log(`这是第${i}`);
            i++
            fun()
        }
        fun()

模仿setInterval

getTimer = () => {
            document.querySelector('div').innerHTML = new Date().toLocaleString()
            setTimeout(getTimer,1000)
        }
        getTimer()

递归实现深拷贝:

 let obj = {
            uname: '刘德华',
            age: 18,
            hodby: ['篮球','乒乓球','足球'],
            ltf: {
                name:'牛子'
            }
        }
        let o = {}
        function deepCopy (newObj,oldObj) {
            for (let k in oldObj) {
              if(oldObj[k] instanceof Array) {
                newObj[k] = []
                deepCopy(newObj[k],oldObj[k])
              } else if (oldObj[k] instanceof Object){
                newObj[k] = {}
                deepCopy(newObj[k],oldObj[k])
              }  else {
                newObj[k] = oldObj[k]
              }
            }
        }
        deepCopy(o,obj)
        console.log(o);
(2) JS库lodash里面cloneDeep内部实现了深拷贝
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./lodash.min.js"></script>
</head>
<body>
    <script>
        let obj = {
            uname: '刘德华',
            age: 18,
            hodby: ['篮球','乒乓球','足球'],
            ltf: {
                name:'牛子'
            }
        }
        let o = _.cloneDeep(obj)
        o.ltf.name = '牛子哥'
        console.log(obj);
        console.log(o);
    </script>
</body>
</html>

2. 异常处理

2.1 throw 抛异常

异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行

 fn = (x,y) => {
            if(!x || !y) {
                throw new Error('参数不能为空!')
            }
            return x + y
        }
        fn()

总结:

  1. throw抛出异常信息,程序也会终止执行
  2. throw后面根的是错误提示信息
  3. Error对象配合throw使用,能够设置更详细的错误信息
2.2 try / catch捕获异常

通过try / catch捕获错误信息(浏览器提供的错误信息)try试试 catch拦住 finally 最后

fn = () => {
            try {
                // 可能发生错误的代码 
                document.querySelector('p').style.color = 'pink'
            } catch(err) {
                // 拦截错误,提示浏览器提供的错误信息,但是不中断程序
                console.log(err.message);
                throw new Error('选择器错误!')
            } finally {
                // 不管程序对不对,都会执行
                alert('弹出对话框')
            }
        }
        fn()

总结:

  1. try…catch用于捕获错误信息
  2. 将预估可能发生错误的代码写在try代码段中
  3. 如果try代码段中出现错误,会执行catch代码段,并截获到错误信息
  4. finally 不管是否有错误,都会执行
2.3 debugger
      let arr = [1,2,3,4,5]
        debugger
        for(let i = 0; i < arr.length; i++){
            console.log(arr[i]);
        }

3. 处理this

3.1 普通函数的this指向

普通函数的调用方式决定了this的值,即【谁调用this的值指向谁】

// 普通函数:谁调用我 this指向谁
        console.log(this); // window
        function fn () {
            console.log(this); // window
        }
        fn()
        document.querySelector('button').addEventListener('click',function () {
            console.log(this); // button
        })
        let obj = {
            sayHi: function () {
                console.log(this); // obj
            }
        }
        obj.sayHi()
3.2 箭头函数的this指向

箭头函数中的thsi与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this

  1. 箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中thsi的值和外层的thsi是一样的
  2. 箭头函数中的this引用的就是最近作用域中的thsi
  3. 向外层作用域中,一层一层查找thsi,直到有this定义
let obj = {
    sayHi: () => {
        console.log(this) // window
    }
}
obj.sayHi()

总结:

  1. 函数内不存在this,沿用上一级的,过程:向外层作用域中,一层一层查找this,直到有this定义
  2. 不适用: 构造函数,原型函数,字面量对象中的函数,dom事件函数
  3. 适用: 需要使用上层this的地方
3.3 改变this

JavaScript中还允许指定函数中this的指向,有3个方法可以动态指定普通函数中this的指向

  • call( )

使用call方法调用函数,同时指定被调用函数中this的值

语法:

fun.call(thisArg,arg1,arg2)
  1. thisArg:在fun函数运行时指定的this值
  2. arg1,arg2:传递的其他参数
  3. 返回值就是函数的返回值,因为他就是调用函数
 let obj = {
            uname : '刘德华'
        }
        function fn (x,y) {
            console.log(this);
            console.log(x+y);
        }
        fn.call(obj,3,5)
  • apply()

使用apply方法调用函数,同时指定被调用函数中this的值

语法:

fun.apply(thisArg,[argsArray])
  1. thisArg:在fun函数运行时指定的this值
  2. argsArray:传递的值,必须包含在数组里面
  3. 返回值就是函数的返回值,因为它就是调用函数
  4. 因此apply主要跟数组有关系,比如使用Math.max( )求数组的最大值
let arr = [100,82,28]
        let max = Math.max.apply(Math,arr)
        let min = Math.min.apply(Math,arr)
        console.log(Math.max(...arr),Math.min(...arr));
        console.log(max,min);
  • bind() - 重点

bind()方法不会调用函数。但是能够改变函数内部this指向

语法:

fun.bind(thisArg,arg1,arg2,...)
  1. thisArg:在fun函数运行时指定的this值
  2. arg1,arg2:传递的其他参数
  3. 返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
  4. 因此当我们只是想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向
 document.querySelector('button').addEventListener('click',function() {
            // 禁用按钮
            this.disabled = true
            window.setInterval(function(){
                // 在这个普通函数里面,我们要this由原来的window改为btn
                this.disabled = false
            }.bind(this),2000)
        })
  • 相同点:

都可以改变函数内部的this指向

  • 区别点:
  1. call和apply会调用函数,并且改变函数内部this指向
  2. call和apply传递的参数不一样,call传递参数aru1,aru2…形式apply必须数组形式[arg]
  3. bind不会调用函数,可以改变函数内部this指向
  • 主要应用场景:
  1. call调用函数并且可以传递参数
  2. apply经常跟数组有关系,比如借助与数学对象实现数组最大值最小值
  3. bind不调用函数,但是还想改变thsi指向,比如改变定时器内部的this指向

4. 性能优化

4.1 节流

所谓节流,就是指连续触发事件但是在n秒中只能执行一次

  • 开发使用场景:小米轮播图点击效果、鼠标移动、页面尺寸缩放resize、滚动条滚动就可以加节流
  • 假如一张轮播图完成切换效果需要300ms,不加节流效果,快速点击,则嗖嗖嗖的切换
  • 加上节流效果,不敢快速点击多少次,300ms时间内,只能切换一次
// 1. 获取元素
        let box = document.querySelector('.box')
        let i = 1
        function mouseMove () {
            box.innerHTML = i++
        }
        // 节流函数
        function throttle(fn,t) {
            // 起始时间
            let startTime = 0
            return () => {
                // 得到当前时间
                let now = Date.now()
                // 判断瑞国大于等于500 采取调用函数
                if(now - startTime >= t) {
                    // 调用函数
                    fn()
                    // 起始时间 = 现在时间  
                    startTime = now
                }
            }
        }
        box.addEventListener('mousemove',throttle(mouseMove,500))
4.2 防抖

所谓防抖,就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间

// 1. 获取元素
  let box = document.querySelector('.box')
        let i = 1
        function mouseMove () {
            box.innerHTML = i++
        }
        // 防抖函数
        function debounce(fn,t) {
            let timeId
            return () => {
                // 如果有定时器就清除
                if(timeId) {
                    clearTimeout(timeId)
                }
                // 开启定时器
                timeId = setTimeout(() => {
                    fn()
                },t)
            }
        }
        box.addEventListener('mousemove',debounce(mouseMove,200))
  1. 返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
  2. 因此当我们只是想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向
 document.querySelector('button').addEventListener('click',function() {
            // 禁用按钮
            this.disabled = true
            window.setInterval(function(){
                // 在这个普通函数里面,我们要this由原来的window改为btn
                this.disabled = false
            }.bind(this),2000)
        })
  • 相同点:

都可以改变函数内部的this指向

  • 区别点:
  1. call和apply会调用函数,并且改变函数内部this指向
  2. call和apply传递的参数不一样,call传递参数aru1,aru2…形式apply必须数组形式[arg]
  3. bind不会调用函数,可以改变函数内部this指向
  • 主要应用场景:
  1. call调用函数并且可以传递参数
  2. apply经常跟数组有关系,比如借助与数学对象实现数组最大值最小值
  3. bind不调用函数,但是还想改变thsi指向,比如改变定时器内部的this指向

4. 性能优化

4.1 节流

所谓节流,就是指连续触发事件但是在n秒中只能执行一次

  • 开发使用场景:小米轮播图点击效果、鼠标移动、页面尺寸缩放resize、滚动条滚动就可以加节流
  • 假如一张轮播图完成切换效果需要300ms,不加节流效果,快速点击,则嗖嗖嗖的切换
  • 加上节流效果,不敢快速点击多少次,300ms时间内,只能切换一次
// 1. 获取元素
        let box = document.querySelector('.box')
        let i = 1
        function mouseMove () {
            box.innerHTML = i++
        }
        // 节流函数
        function throttle(fn,t) {
            // 起始时间
            let startTime = 0
            return () => {
                // 得到当前时间
                let now = Date.now()
                // 判断瑞国大于等于500 采取调用函数
                if(now - startTime >= t) {
                    // 调用函数
                    fn()
                    // 起始时间 = 现在时间  
                    startTime = now
                }
            }
        }
        box.addEventListener('mousemove',throttle(mouseMove,500))
4.2 防抖

所谓防抖,就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间

// 1. 获取元素
  let box = document.querySelector('.box')
        let i = 1
        function mouseMove () {
            box.innerHTML = i++
        }
        // 防抖函数
        function debounce(fn,t) {
            let timeId
            return () => {
                // 如果有定时器就清除
                if(timeId) {
                    clearTimeout(timeId)
                }
                // 开启定时器
                timeId = setTimeout(() => {
                    fn()
                },t)
            }
        }
        box.addEventListener('mousemove',debounce(mouseMove,200))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值