面向对象
现实世界面向对象:
对象:万事万物都是对象,每个对象都有自己的属性(共有属性,私有属性);
类:抽象对象的特性和功能,形成描述一类事物的抽象概念;在 js 中分为内置类和自定义类;
实例:类中的一个具体的个体。只要是类中的个体就会拥有这个类型全部的属性和特性。
每个人都是人类中的一个实例;
地球上的每个人都是对象,而此时对象属性就是大家都是生活在地球上
按照人类生活的陆地不同,将人类分为5大洲的人,如亚洲人、美洲人、非洲人、欧洲人…. 分类时是抽象了人类生存的陆地不同;
我们每个人就是亚洲人的一个实例,所以我们都有亚洲人的属性,我们都生活在亚洲这块陆地上
JS中的面向对象
面向对象的研究范畴:封装、类的继承和多态(重写、重载)
- 类:js中的类都是一个函数数据类型,都天生自带一个 prototype 属性,它的值是一个对象;
- prototype(原型):每个 prototype 对象都天生自带一个属性 constructor,这个属性的值指向当前类的构造函数本身;
- 对象(实例对象、prototype对象)都有一个 proto** **的属性,这个属性指向当前实例所属类的 prototype。
内置类:Array、String、Number、Function、Date 等
- 内置类的原型:每个类都有自己的原型,存储这个类型公有的属性和方法的
console.log(Array.prototype); // 数组的原型:存储的数组类型公有的属性和方法如果 push、pop、slice
console.log(Date.prototype); // Date 的原型:存储 Date 类公有的属性和方法,如 getFullYear、getMonth、getHours
console.log(String.prototype); // String 的原型:存储 String 类的公有属性和方法,如 charAt、charCodeAt、toUpperCase 等
- 内置类的实例:
var ary = new Array(1, 3, 5, 7);
var date = new Date();
ary.push(9);
date.getFullYear();
思考?
- 数组实例 ary 怎么找到 push 方法的?
- Date 实例 date 怎么找到 getFullYear 方法的?
自定义类型:通过自定义构造函数
- 定义一个构造函数就是创建一个类
function Teacher(name, age, subject, from = '珠峰') {
// 在构造函数中给实例添加私有属性
// 构造函数中的 this 指向当前实例
this.name = name;
this.age = age;
this.subject = subject;
this.from = from;
}
- 给这个类的原型上添加这个类的公有属性
Teacher.prototype.teach = function () {
console.log(`${this.name} 老师教 ${this.subject} 课程`);
};
- 创建这个类型的实例:
var t1 = new Teacher('马宾', 18, 'js');
t1.name// 访问 t1 的私有属性 name
t1.age; // 访问 t1 的私有属性 age
t1.teach(); // 访问 t1 的公有方法 teach
如何检测私有属性?
- hasOwnProperty() 检测一个属性是否是一个对象的私有属性
console.log(t1.hasOwnProperty('name')); // true
console.log(t1.hasOwnProperty('age')); // true
console.log(t1.hasOwnProperty('teach')); // false
原型链:属性的查找机制;
- 每个对象(实例对象、普通对象、函数对象),自身都有一个 proto** **属性,这个属性指向所属类的原型
- 当我们 对象.属性名 时,浏览器会现在自己的私有属性找,如果私有属性有这个属性,就使用这个私有属性;如果没有,就根据对象的 proto** 找到所属类的原型(prototype),如果原型上也没有,就通过原型(prototype)对象的 __proto__ **继续向上查找;一直找到基类 Object 的原型对象,如果还没有就返回 undefined;
重写
子类改写父类上的属性或者方法
重载:根据不同的函数签名(函数的参数)自动调用不同的方法;JS 中没有真正意义的重载;
真正意义上的重载:
function sum(int a, int b) {
return a + + b;
}
function sum(char a, char b) {
return Number(a) + Number(b)
}
sum(1, 2)
sum('2', '3')
- 但是因为 js 中的同名变量会互相覆盖,同名的函数,这个函数名只能代表最后一个函数,所以不会有真实意义上的重载;
- 但是可以模拟:因为重载要的效果就是根据参数的不同情况作出不同的处理
function sum(a, b) {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
} else {
return Number(a) + Number(b);
}
}
深入原型、原型链
原型链:对象.属性名 (obj.xxx) 先看私有属性是否有这样是一个属性,如果私有属性中没有,根据实例对象的 proto** 找到当前对象所属类的原型查找,如果原型上也没有,就通过原型对象 __proto__** 继续向上查找; 一直找到 Object.prototype 如果也没有就返回 undefined
function Fn() {
this.name = 'Hi';
}
1. 原型上添加公有属性的方法:
- 1.1 直接给原型添加方法:
Fn.prototype.say = 'hello';
Fn.prototype.title = 'world';
Fn.prototype.greeting = function () {
console.log('hi~');
};
- 1.2 通过实例对象的 proto**
因为实例的 __proto** 指向当前实例所属类的原型对象,所以可以通过修改实例的 proto__ 方式来修改原型对象;
let f1 = new Fn();
f1.__proto__.say = 'hello';
f1.__proto__.hello = 'world';
f1.__proto__.greeting = function () {
console.log('hi~')
};
- 1.3 修改原型对象的指向:
Fn.prototype = {
say: 'hell',
hello: 'world',
greeting: function () {
console.log('hi~')
}
};
console.log(Fn.constructor); // Object
Fn.prototype.constructor = Fn;
// 当需要批量给原型增加属性或者方法时,我们需要把一个新的对象赋值给类的原型时,此时要给这个对象增加一个 constructor 属性
用 constructor 检测数据类型:
new Fn().constructor === Fn; // true
[].constructor === Array; // true
- 但是有一个问题,constructor 很容易被修改,导致检测结果不准确
Array.prototype.constructor = 123;
console.log([1, 2, 3].constructor === Array); // false
面向过程和面向对象比较
- 面向过程
var name = '张三';
var age = 26;
function eat() {
console.log('张三eat')
}
function drink() {
console.log('张三drink')
}
function sleep() {
console.log('张三sleep')
}
eat();
drink();
sleep();
- 面向对象
function People(name, age) {
this.name = name;
this.age = age;
}
People.prototype.eat = function () {
console.log(this.name + 'eat')
};
People.prototype.drink = function () {
console.log(this.name + 'drink')
};
People.prototype.sleep = function () {
console.log(this.name + 'sleep')
};
// 描述一个主题:
let lisi = new People('李四', 20);
lisi.eat();
lisi.drink();
lisi.sleep();
let wangwu = new People('王五', 35);
wangwu.eat();
wangwu.drink();
这样我们发现当我们以后再描述一个人的时候,我们把这个问题划分到解决描述人的问题,需要 People 这个类来完成。只需要 new 一下 People 这个类就可以得到一个对象,而且这个对象自带了 eat、drink、sleep 方法。
面向对象选项卡
- HTML代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>选项卡</title>
<style>
* {
margin: 0;
padding: 0;
}
ul, li {
list-style: none;
}
.wrapper {
margin: 30px auto;
width: 606px;
height: 400px;
border: 1px solid #000;
}
.header {
border: 1px solid #000;
}
.header li {
float: left;
width: 200px;
height: 40px;
line-height: 40px;
text-align: center;
cursor: pointer;
-webkit-user-select: none; /*禁止用户选中*/
}
.header li:nth-child(2) {
border-left: 1px solid #000;
border-right: 1px solid #000;
}
li.active {
background: yellow;
}
.clearfix:after {
display: block;
content: '';
visibility: hidden;
clear: both;
}
.wrapper div {
text-align: center;
height: 356px;
line-height: 356px;
display: none;
}
div.active {
display: block;
}
</style>
</head>
<body>
<div class="wrapper" id="tab1">
<ul class="header clearfix" id="header">
<li class="active">
视频
</li>
<li>
秒拍
</li>
<li>
综艺
</li>
</ul>
<div class="active">
程序员惨遭亲妈拍卖
</div>
<div>
预防脱发秘籍(黑芝麻糊)
</div>
<div>
蔡徐坤-我们很爷们儿
</div>
</div>
<div class="wrapper" id="tab2">
<ul class="header clearfix">
<li class="active">
视频2
</li>
<li>
秒拍2
</li>
<li>
综艺3
</li>
</ul>
<div class="active">
程序员惨遭亲妈拍卖2
</div>
<div>
预防脱发秘籍(黑芝麻糊)2
</div>
<div>
蔡徐坤-我们很爷们儿2
</div>
</div>
<script src="js/5-选项卡插件.js"></script>
</body>
</html>
- JS代码
// 1. 创建选项卡类
function Tab(options) {
// 1. 参数合法校验
if (!options || !options.el) {
console.error('缺少el元素');
return;
}
// 2. 将传进来的参数对象保存到实例上
this._options = options;
// 3. 执行获取元素
this.queryEle();
// 4. 执行绑定事件
this.bindEvent();
}
// 为 Tab 增加公用的获取元素的方法
Tab.prototype.queryEle = function () {
// 1. 从 this 中的 options 中的 el 获取最外层元素
const container = document.querySelector(this._options.el);
// 2. 获取选项卡头,并挂载到实例上
this.headerList = container.querySelectorAll('.header > li');
// 3. 获取所有的卡片并挂载到实例上
this.cardList = container.querySelectorAll('div');
};
// 为 Tab 增加公用的绑定事件的元素
Tab.prototype.bindEvent = function () {
const HEADER_LIST = this.headerList; // 用一个常量缓存 headerList
// 变量 headerList 给每个 li 绑定点击事件
for (let i = 0; i < HEADER_LIST.length; i++) {
HEADER_LIST[i].onclick = () => {
// 这里使用箭头函数,是因为我们希望这里的 this 是 Tab 的实例,如果不使用箭头函数,点击事件函数中的 this 就是选项卡头了
this.clearClass();
this.addClass(i);
}
}
};
// 为 Tab 类增加移除类名的方法
Tab.prototype.clearClass = function () {
const HEADER_LIST = this.headerList;
const CARD_LIST = this.cardList;
for (let i = 0; i < HEADER_LIST.length; i++) {
HEADER_LIST[i].className = '';
CARD_LIST[i].className = '';
}
};
// 为 Tab 类添加类名的方法
Tab.prototype.addClass = function (index) {
this.headerList[index].className = 'active';
this.cardList[index].className = 'active';
};
new Tab({
el: '#tab1'
});
new Tab({
el: '#tab2'
});
选项卡插件封装
- 插件分装需要简化构造函数
- 确保是由new操作符调用
// 1. 创建选项卡类
function Tab(options) {
// 1. 确保如何是通过 new 操作调用
if (!(this instanceof Tab)) {
console.error('Tab is a constructor which should be call with new');
return;
}
// 2. 参数合法校验
if (!options || !options.el) {
console.error('缺少el元素');
return;
}
// 3. 将传进来的参数对象保存到实例上
this._options = options;
// 4. 执行初始化
this.init();
}
// 为 tab 增加 init 方法:
Tab.prototype.init = function () {
this.queryEle();
this.bindEvent();
};
// 为 Tab 增加公用的获取元素的方法
Tab.prototype.queryEle = function () {
// 1. 从 this 中的 options 中的 el 获取最外层元素
const container = document.querySelector(this._options.el);
// 2. 获取选项卡头,并挂载到实例上
this.headerList = container.querySelectorAll('.header > li');
// 3. 获取所有的卡片并挂载到实例上
this.cardList = container.querySelectorAll('div');
};
// 为Tab增加公用的绑定事件的元素
Tab.prototype.bindEvent = function () {
const HEADER_LIST = this.headerList; // 用一个常量缓存 headerList
// 变量 headerList 给每个 li 绑定点击事件
for (let i = 0; i < HEADER_LIST.length; i++) {
HEADER_LIST[i].onclick = () => {
// 这里使用箭头函数,是因为我们希望这里的 this 是 Tab 的实例,如果不使用箭头函数,点击事件函数中的 this 就是选项卡头了
this.clearClass();
this.addClass(i);
}
}
};
// 为 Tab 类增加移除类名的方法
Tab.prototype.clearClass = function () {
const HEADER_LIST = this.headerList;
const CARD_LIST = this.cardList;
for (let i = 0; i < HEADER_LIST.length; i++) {
HEADER_LIST[i].className = '';
CARD_LIST[i].className = '';
}
};
// 为 Tab 类添加类名的方法
Tab.prototype.addClass = function (index) {
this.headerList[index].className = 'active';
this.cardList[index].className = 'active';
};
new Tab({
el: '#tab1'
});
new Tab({
el: '#tab2'
});