一起养成写作习惯!这是我参与「掘金日新计划 · 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