面向对象基础知识点汇总(4)

125 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

十二、面向对象案例

面向对象的本质:定义不同的类,让类的实例进行工作

  • 优点:程序编写更清晰、代码结构更严密、使代码更健壮更利于维护
  • 面向对象经常用到的场合:需要封装和复用的场合(组件思维)

12-1、案例

比如页面上要做一个红绿灯,点击红灯就变黄,点击黄灯就变绿,点击绿灯变回红灯

  • 如果页面上有一个红绿灯,我们可通过添加点击事件,设置一个变量来存储要切换的状态,根据不同的状态来切换不同的图片即可实现
  • 试想一下,如果有100个红绿灯呢?难道要设置100个变量吗?如果变量有冲突怎么办?那么此时我们就要考虑面向对象来解决问题了,使用面向对象变成,就能以“组件化”的思维解决大量红绿灯互相冲突的问题,面向对象编程,最重要的就是编写类

那么我们可以定义一个TrafficLight类(红绿灯类),该类可拥有如下属性和方法:

  • 属性:那么该红绿灯可以拥有当前颜色color、和自己的DOM元素
  • 方法:初始化init()、切换颜色changeColor()、绑定事件bindEvent()

代码示例

  // 定义红绿灯类
  function TrafficLight() {
    // 颜色属性,一开始都是红色
    // 红色1、黄色2、绿色3
    this.color = 1;
    // 调用自己的初始化方法
    this.init();
    // 绑定监听
    this.bindEvent();
  }
  // 得到盒子
  var box = document.getElementById('box');
  // 初始化方法
  TrafficLight.prototype.init = function () {
    // 创建自己的DOM
    this.dom = document.createElement('img');
    // 设置src属性
    this.dom.src = 'images/' + this.color + '.jpg';
    box.appendChild(this.dom);
  };
  // 绑定监听
  TrafficLight.prototype.bindEvent = function () {
    // 备份上下文,这里的this指的是JS的实例
    var self = this;
    // 当自己的dom被点击的时候
    this.dom.onclick = function () {
      // 当被点击的时候,调用自己的changeColor方法
      self.changeColor();
    };
  }
  // 改变颜色
  TrafficLight.prototype.changeColor = function () {
    // 改变自己的color属性,从而有一种“自治”的感觉,自己管理自己,不干扰别的红绿灯
    this.color++;
    if (this.color == 4) {
      this.color = 1;
    }
    // 光color属性变化没有用,还要更改自己的dom的src属性
    this.dom.src = 'images/' + this.color + '.jpg';
  };


  // 实例化100个
  var count = 100;

  while (count--) {
    new TrafficLight();
  }
  • 我们向构造函数的原型上添加函数,该函数是能够被实例访问到的(这样会节约内存空间),构造函数中的this指向创建出来的实例对象,所以可以通过this打点调用原型上添加的方法
  • 在init方法中,我们可以为实例添加dom属性,并将创建好的dom添加到父盒子中展示
  • 在bindEvnet方法中,我们可为实例的dom属性添加点击事件(要注意保留一下上下文,因为在绑定事件中会改变上下文,这里的上下文会变成触发事件的元素本身),点击事件中去触发自己改变颜色方法
  • 在changeColor方法中,我们去改变其颜色值,并将其dom属性的src更换以达到切换颜色的效果(不同颜色对应不同图片)

十三、包装类

13-1、什么是包装类

Number()、String()、Boolean()分别是数字、字符串、布尔值的“包装类”。

很多语言都有“包装类”的设计,包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法。

var a = new Number(123);
var b = new String('123');
var c = new Boolean(true);

console.log(a);
console.log(typeof a);      // object
console.log(b);
console.log(typeof b);      // object
console.log(c);
console.log(typeof c);      // object

console.log(5 + a);         // 128
console.log(b.slice(0, 2)); // '23'
console.log(c && true);     // true

  • 包装类typeof的结果是object,是一个对象,它们的PrimitiveValue属性存储着本身的值
  • 通过包装类产生的实例可以正常参与计算
  var d = 123;
  console.log(d.__proto__ == Number.prototype);       // true

  var e = '123';
  console.log(e.__proto__ == String.prototype);       // true
  console.log(String.prototype.hasOwnProperty('toLowerCase'));    // true
  console.log(String.prototype.hasOwnProperty('slice'));          // true
  console.log(String.prototype.hasOwnProperty('substr'));         // true
  console.log(String.prototype.hasOwnProperty('substring'));      // true
  • 即便变量123不是通过 new出来的数据,也可以看作是Number new出来的,其__proto__等于Number的prototype,同理string类型的变量也是这样
  • 这就是为什么string类型的变量能调用slice、substr、substring...方法(这些方法都是在String的prototype上定义的)

十四、Math对象

14-1、Math对象相关方法

14-2、使用round方法四舍五入到指定位数

比如我们要四舍五入3.6498到小数点后两位:3.65,如何做呢?

具体思路:

  • 3.6498先乘以100变为364.98
  • 然后364.98四舍五入为365
  • 365再除以100变为3.65
console.log(Math.round(3.49));      // 3
console.log(Math.round(3.51));      // 4

var a = 3.6498;

console.log(Math.round(a * 100) / 100); // 3.65

14-3、使用max方法求数组中的最大值

由于Math.max()要求参数化必须是“罗列出来”,而不能是数组,如:Math.max(3,7,1,8)

  • 由于apply方法可以指定函数的上下文,并且以数组的形式传入“零散值”当作函数的参数,所以我们可以这样实现:
console.log(Math.max(44, 55, 33, 22));      // 55
console.log(Math.min(44, 55, 33, 22));      // 22

var arr = [3, 4, 4, 3, 2, 2, 1, 3, 5, 7, 4, 3];
console.log(Math.max.apply(null, arr));     // 7

// 在今后学习ES6之后,求数组最大值还可以
console.log(Math.max(...arr));              // 7

14-4、使用Math.random()得到[a,b]之间的整数

为了得到[a,b]区间内的整数,可以使用:

parseInt(Math.random() * (b - a + 1)) + a

// [3, 8]
console.log(parseInt(Math.random() * 6) + 3);

十五、Date对象

15-1、创建日期对象

  • 使用new Date()可得到当前时间的日期对象
  • 使用new Date(2022, 4, 1)可得到指定日期的日期对象(第二个参数表示月份,从0开始,4表示5月)
  • 也可以使用new Date('2022-5-01')这样的写法,字符串中的月份就不是从0开始了,即2022年5月1日
// 什么参数都不加,自动得到今天此时此刻的日期对象
var d1 = new Date();
console.log(d1);
console.log(typeof d1); // object

// 得到五月一日
var d2 = new Date(2022,4, 1);      // 不算时区
var d3 = new Date('2022-05-01');    // 算时区,8点

console.log(d2);
console.log(d3);

  • new Date(字符串)这种方式创建出来的日期对象是会算上时区的,由于处于东八区,所以默认时间是上午8点

15-2、日期对象常见方法

var d = new Date();

console.log('日期', d.getDate());
console.log('星期', d.getDay());
console.log('年份', d.getFullYear());
console.log('月份', d.getMonth() + 1);
console.log('小时', d.getHours());
console.log('分钟', d.getMinutes());
console.log('秒数', d.getSeconds());

15-3、时间戳

时间戳表示1970年1月1日零点整距离某时刻的毫秒数

  • 通过getTime() 方法或者Date.parse() 函数可以将日期对象变为时间戳
  • 也可通过new Date(时间戳) 的写法,将时间戳变为日期对象
// 日期对象
var d = new Date();

// 显示时间戳的两种方法。时间戳表示1970年1月1日距离此时的毫秒数
var timestamp1 = d.getTime();       // 精确到毫秒
var timestamp2 = Date.parse(d);     // 精确到秒,也是毫秒数,只不过最后三位一定是000

console.log(timestamp1);
console.log(timestamp2);

// 比如将1649136438612毫秒变为日期对象
var dd = new Date(1649136438612);
console.log(dd);
console.log(dd.getFullYear());

!要注意的是,通过Date.parse得到的毫秒数是精确到秒的,也是表示毫秒,只不过后面3位是000


15-4、倒计时案例

在页面上实时显示距离2022年高考还有多少天、多少时、多少分、多少秒

<!DOCTYPE html>
<html lang="en">
  
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  
  <body>
    <h1>2022年高考倒计时</h1>
    <h2 id="info"></h2>
    <script>
      var info = document.getElementById('info');
      
      setInterval(function(){
        // 现在的日期
        var nd = new Date();
        // 目标的日期,5表示六月
        var td = new Date(2022, 5, 7);
        
        // 毫秒差
        var diff = td - nd;
        
        // 就是把diff换算为天、小时、分钟、秒
        // 换算为多少天,除以一天的总毫秒数,不就是换算为多少天么
        var day = parseInt(diff / (1000 * 60 * 60 * 24));
        // 零多少小时??差的总毫秒数与1天的毫秒数的相除的余数,就是零头的毫秒数
        var hours = parseInt(diff % (1000 * 60 * 60 * 24) / (1000 * 60 * 60));
        // 零多少分钟??
        var minutes = parseInt(diff % (1000 * 60 * 60) / (1000 * 60));
        // 零多少秒??
        var seconds = parseInt(diff % (1000 * 60 * 60) % (1000 * 60) / 1000);
        
        
        info.innerText = day + '天' + hours + '时' + minutes + '分' + seconds + '秒';
      }, 1000);
      
    </script>
  </body>
  
</html>

十六、内置构造函数

JS有很多内置构造函数,比如Array就是数组类型的构造函数,Function就是函数类型的构造函数,Object就是对象类型的构造函数。

  • 内置构造函数非常有用,所有该类型的方法都是定义在它的内置构造函数的prototype上的(比如Array类型的push pop ... 方法),我们可以给这个对象添加新的方法,从而拓展某类型的功能

16-1、在内置构造函数原型上扩展方法

比如,我们现在要在数组的原型上扩展一个求和的方法

// 扩展求和方法
Array.prototype.sum = function () {
  // this表示调用sum()方法的数组
  var arr = this;
  // 累加器
  var sum = 0;
  for (var i = 0; i < arr.length; i++) {
      sum += arr[i];
  }
  // 返回结果
  return sum;
};

var arr = [3, 6, 2, 1, 3];
var result = arr.sum();
console.log(result);

var arr2 = [3, 8];
var result2 = arr2.sum();
console.log(result2);

16-2、内置构造函数的关系

Object.prototype是原型链的终点。JS中函数、数组皆为对象,以数组为例,完整的原型链是这样的:

console.log([1, 2].__proto__ === Array.prototype); // true
console.log([1, 2].__proto__.__proto__ === Object.prototype); // true

console.log([] instanceof Object); // true
console.log([] instanceof Array); // true

console.log(Object.prototype.__proto__); // null

任何函数都可以看作是Function new出来的,那么Object也是函数,它是不是Function new出来的呢?答案是肯定的,所以Function与Object有这样一个关系:

  • Object本质是一个函数,一个构造函数,可以看作是被Function new出来的所以其__proto__指向Function.prototype
  • 而Function的prototype本质是一个对象,所以其 __proto__指向原型链的终点Object.prototype
  • Function本身也是一个函数,所以可以看成它自己new了它自己,所以其__proto__指向Function.prototype
console.log(Object.__proto__ === Function.prototype); // true
console.log(Object.__proto__.__proto__ === Object.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true

console.log(Function instanceof Object);        // true
console.log(Object instanceof Function);        // true
console.log(Function instanceof Function);      // true
console.log(Object instanceof Object);          // true