[前端学习] JavaScript 笔记 (六)

一、编程思想

  1. 面向过程编程

    ​ 面向过程就是分析出解决问题需要的步骤,然后用函数把这些步骤一步一步实现,实现的时候再一个一个地依次调用就可以了。

  2. 面向对象(oop)

    ​ 面向对象是把事务分解成为一个个对象,然后由对象之间分工合作。面向对象以功能来划分问题。

  3. 面向对象的特点

    ​ 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。

    ​ 特性:封装性、继承性、多态性。

  4. 面向过程和面向对象的优缺点

    • 面向过程:
      • 优点:性能比面向对象搞,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程
      • 缺点:没有面向对象易维护、易复用、易扩展
    • 面向对象:
      • 易维护、易复用、易扩展,由于面向对象有封装、继承、堕胎性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
      • 性能比面向过程低
  5. 封装性

    ​ JS中使用构造函数实现封装。构造函数实例创建的对象彼此独立、互不影响。

    ​ 但是构造函数存在浪费内存的问题(为了解决这个问题,引入了原型)。

二、原型

​ 原型可以解决多个对象实例之间资源共用的问题,原型可以理解成是一块共享内存块,在这个内存块中存储着一份数据,所有的实例需要的时候,只需要访问该内存块即可,从而达到共用共享的问题。

  1. 原型对象(prototype

    • 构造函数通过原型分配的函数是所有对象所共享的

    • JavaScript 规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象

    • 这个对象可以挂载函数,对象实例化必会多次创建原型上的函数,节约内存;

    • 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法;

    • 构造函数和原型对象中的this都指向实例化对象。

      const arr = [1, 2, 3];
      Array.prototype.max = function() {
      	// 使用展开运算符
          // this 指向实例化对象
          return Math.max(...this)
      }
      console.log(arr.max());	// 输出:3
      
  2. constructor属性

    ​ 每个原型对象里面都有个constructor属性(构造函数),该属性指向该原型对象的构造函数。也就是构造函数名.prototype.constructor === 构造函数名true

  3. 对象原型(__proto__

    ​ 对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以实例对象可以使用构造函数原型对象(prototype)的属性和方法就是因为有对象原型(__proto__)的存在。

    • __proto__是 JS 非标准属性;
    • [[prototype]]__proto__意义相同;
    • __proto__对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数。
  4. 原型继承

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

    /**
    // 定义一个对象
    const fruit = {
        apple: 1,
        banana: 2
    }
    // 定义两个不同的构造函数,他们有公共部分,都继承自同一个对象
    function Woman(){};
    function Man(){};
    // 通过原型继承fruit对象
    Woman.prototype = fruit;
    Man.prototype = fruit;
    // 指回原来的构造函数
    Woman.prototype.constructor = Woman;
    Man.prototype.constructor = Man;
    const p1 = new Woman();
    const p2 = new Man();
    console.log(p1);
    console.log(p2);
    // 上面写法有一个问题:由于指向的是同一个对象,当其中一个修改了另一个也会跟着修改
    Woman.prototype.name = 'Woman';
    console.log(p1, p2); // 两个都变了
    **/
    
    // 使用构造函数,定义一个构造函数
    function Fruit () {
        this.apple = 1,
        this.banana = 2
    }
    // 定义两个不同的构造函数,他们有公共部分,都继承自同一个对象
    function Woman(){};
    function Man(){};
    // 通过原型继承Fruit构造函数
    Woman.prototype = new Fruit;
    Man.prototype = new Fruit;
    // 指回原来的构造函数
    Woman.prototype.constructor = Woman;
    Man.prototype.constructor = Man;
    const p1 = new Woman();
    const p2 = new Man();
    console.log(p1);
    console.log(p2);
    Woman.prototype.name = 'Woman';
    console.log(p1, p2); // 只有p1变了
    
  5. 原型链

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

    查找规则:

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

三、深浅拷贝

​ 深浅拷贝只针对引用数据类型。

  1. 浅拷贝

    ​ 浅拷贝拷贝的是地址,常见方法有:拷贝对象(Object.assign、{...obj})、拷贝数组(Array.prototype.concat()、[...arr])。

    ​ 如果是简单数据类型拷贝的是值,引用数据类型拷贝的是地址(如果是单层对象没有问题,如果是多层对象还会出现问题)。

  2. 深拷贝

    ​ 深拷贝拷贝的是对象,不是地址。常见方法:递归实现、lodash的_.cloneDeep()JSON.stringfy()

    1)递归

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

    • 递归函数的作用和循环效果类似;

    • 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须加退出条件 return

      const obj = {
          name: '张三',
          age: 18,
          hobby: ['唱歌', '跳舞'],
          info: {
              math: 80,
              chinese: 90
          }
      }
      // 利用回调函数实现深拷贝
      function deepCopy(newObj, oldObj) {
          for (let k in oldObj) {
              // 处理引用数据类型,先判断数组再判断对象
              // 因为 [2, 3] instanceof Object 的结果为 true
              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]
              }
          }
      }
      const o = {}
      deepCopy(o, obj);
      obj.hobby.push('aas')
      console.log(obj);
      console.log(o);
      

    2)JS 中的 lodash 库

    ​ 在lodash里面cloneDeep实现了深拷贝。

    ​ 使用方法:旧对象 = _.cloneDeep(新对象)

    3)利用 JSON.stringfy实现深拷贝

    JSON.stringfy(obj)会将可以将对象转换为 JSON字符串,字符串是简单字符串,可以直接赋值,然后再使用JSON.parse(JSON字符串)将字符串转为对象,也就是新对象 = JSON.parse(JSON.stringfy(旧对象))

四、异常处理

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

  1. throw抛出异常

    • throw抛出异常信息,程序也会终止执行;
    • throw后面跟的是错误提示信息,如throw '发生错误,参数不能为空'throw new Error('发生错误,参数不能为空')
    • 配合Error对象使用,能设置更详细的错误信息。
  2. try……catch……捕获错误信息

    ​ 通过try……catch……可以捕获浏览器提供的错误信息,有try、catch、finally三个关键字。

    function fn(arr) {
        try {
            // 可能会发生错误的代码
            arr.push(arr.length);
            console.log(arr);
        } catch (error) {
            // 拦截错误,但是不中断程序的执行
            console.error(error);
            // 中断程序需要加 return
            return
        }
        // 这个关键字按需求使用
        finally {
            // 不管程序对错,一定会执行的代码
            console.log(111);
        }
        // 如果try中的代码出错且 catch中加了return,这里的代码不会执行
        console.log(222);
    }
    fn('0', '6');   // 报错
    fn(['0', '6'])    // 继续输出:['0', '6', 2]
    
  3. debugger

    ​ debugger 语句用于停止执行 JavaScript,并调用 (如果可用) 调试函数。

    使用 debugger 语句类似于在代码中设置断点。

五、处理 this

  1. 普通函数

    ​ 普通函数的调用方式决定了this的值,一般是谁调用指向谁。

    ​ 普通函数没有明确调用者时 this指向window,严格模式下没有调用者时this指向undefined

  2. 箭头函数

    ​ 实际上箭头函数中并不存在this,箭头函数会默认绑定外层this的值。箭头函数中的this引用就是最近作用域中的this

    ​ 事件回调函数使用箭头函数时,this为全局的window,因此,DOM 事件回调函数如果需要DOM对象的this,不推荐使用箭头函数。

    ​ 构造函数,原型函数,字面量对象中函数也不推荐采用箭头函数。

  3. 改变this指向

    1. call()方法

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

      ​ 语法:fun.call(指定的this值, 其他参数1, 其他参数2, ...),返回值就是函数的返回值。

    2. apply()方法

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

      ​ 语法:fun.apply(指定的this值, [其他参数1, 其他参数2, ...]),返回值就是函数的返回值。

    3. bind()方法

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

      ​ 语法:fun.bind(指定的this值, 其他参数1, 其他参数2, ...),返回是一个函数,是一个拷贝自原函数但是已经改变了this指向的新函数。

    const obj = {
        name: 'Lily',
    }
    function fn (a, b) { 
        console.log(this);
        console.log(a + b);
    }
    fn(1, 2); // this 指向window,输出 window对象和 3
    // 使用 call()
    fn.call(obj, 1, 2); // this 指向obj,输出 obj对象和 3
    // 使用 apply(),其他参数必须包在数组里
    fn.apply(obj, [1, 2]); // this 指向obj,输出 obj对象和 3
    // 使用 bind 方法
    fn.bind(obj, 1, 2); // 没有调用函数,所以没有输出
    // 定义一个参数接收改变了this指向的返回值,并调用新函数
    const fun = fn.bind(obj, 1, 2);
    fun(); // this 指向obj,输出 obj对象和 3
    // 打印原函数,this指向没有变
    fn(1, 2); // this 指向window,输出 window对象和 3
    
    1. 异同点
      • 相同点:都可以改变this指向。
      • 不同点:call()和apply()都会调用函数,但这两者传递参数形式不一样;bind()不会调用函数。

六、性能优化——防抖(debounce

​ 防抖是指单位时间内,频繁触发事件,只执行最后一次

​ 在一个代码执行未完成时,如果再次被触发,则会取消上次未完成的,重新开始准备执行,循环往复,只执行最后一次。

​ 防抖的实现方式:lodash提供的防抖、手写防抖函数。

  1. lodash提供的防抖

    ​ 语法:_.debounce(func, [wait=0], [options=]),创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。

    ​ 使用参考:lodash.debounce | Lodash中文文档 | Lodash中文网 (lodashjs.com)

    ​ 使用场景:

    • 搜索框搜索输入。只需用户最后一次输入完,再发送请求。
    • 手机号、邮箱验证输入检测。
    • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
  2. 手写防抖函数

    ​ 防抖的核心就是利用定时器(setTimeout)来实现。

    1. 声明一个定时器变量;

    2. 当再次触发时先判断是否已经有定时器了,如果有定时器先清除以前的定时器;

    3. 如果没有定时器则开启定时器。

      const box = document.querySelector('.box');
      let i = 1;
      function mouseMove() {
          box.innerHTML = i++;
      }
      // box.addEventListener('mousemove', mouseMove)
      function debounce(fn, t) {
          let timer;
          // 返回一个匿名函数
          return function() {
              if (timer) {
                  clearTimeout(timer);
              }
              timer = setTimeout(function() {
                  fn();   // 加小括号调用 fn 函数
              }, t)
          }
      }
      box.addEventListener('mousemove', debounce(mouseMove, 500));
      

七、性能优化——节流(throttle

​ 节流是指单位时间内,频繁触发事件,只执行一次

​ 在一个代码执行未完成时期间,如果再次被触发,不会执行。

​ 防抖的实现方式:lodash提供的节流、手写节流函数。

  1. lodash提供的节流

    ​ 语法:_.throttle(func, [wait=0], [options=]),创建一个节流函数,在 wait 秒内最多执行 func 一次的函数。

    ​ 使用参考:lodash.throttle | Lodash中文文档 | Lodash中文网 (lodashjs.com)

    ​ 使用场景:

    • 滚动加载,加载更多或滚到底部监听。
    • 搜索框,搜索联想功能。
    • 高频点击提交,表单重复提交。
  2. 手写节流函数

    ​ 节流的核心就是利用定时器(setTimeout)来实现。

    1. 声明一个定时器变量;

    2. 当再次触发时先判断是否已经有定时器了,如果有定时器则不开启新定时器;

    3. 如果没有定时器则开启定时器。

      const box = document.querySelector('.box');
      let i = 1;
      function mouseMove() {
          box.innerHTML = i++;
      }
      // box.addEventListener('mousemove', mouseMove)
      function debounce(fn, t) {
          let timer = null;
          // 返回一个匿名函数
          return function() {
              if (!timer) {
                  timer = setTimeout(function() {
                      fn();
                      // 清除定时器
                      timer = null;
                  }, t)
              }
          }
      }
      box.addEventListener('mousemove', debounce(mouseMove, 500));
      

防抖是单位时间内频繁触发只执行最后一次,而节流是只执行一次。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值