1.js面向对象基础

1.0.课程目标

1.掌握什么是面向对象,培养面向对象思想

2.掌握面向对象的写法

3.掌握js中最重的知识点-原型

4.掌握对象引用

5.掌握js中的面向对象继承方法

6.掌握组件封装

7.掌握自定义事件

1.1.什么是面向对象

1.1.1.理解对象和类

要理解面向对象,首先要理解什么是对象,通常一个对象可以理解为一个具体的实物,例如:一个杯子、一个人、一头猪,这些我们都可以称做是对象,这些对象都是有相应的特征的,例如:一个人有身高、年龄、性别等特征,可以吃饭、睡觉、打豆豆等行为,一条狗可以大小、颜色等特征,是否会咬人等行为,许多对象有相同的特征,我们可以把这些具有相同特征的对象进行分类,例如:人类、猫类、狗类,因此,对象和类的关系就是对象是类中的某一个具体的个体,类是对许多具有相同特征对象的抽象,归类,在编程中,我们说对象是类的实例,根据某个类创建一个具体的对象,我们又叫做类的实例化

1.1.2.面向对象和面向过程

面向对象和面向过程都是编程思想,通俗的说面向过程就是你一个人把一件事情从头到尾的做完,面向对象就是你找一个会做这件事情的人把这件事情做完,我们以面向对象的思想来写代码就叫做面向对象编程,举个栗子:组装一台电脑

面向过程的思想来实现:

1、买内存条
2、买硬盘
3、买显示器
4、买主板
5、买鼠标、键盘、显卡
6、自己动手组装

面向对象的思想来实现:

1、有一类人是专门做电脑组装生意的
2、找到这个会组装电脑的人(创建对象)
3、让这个人帮你组装一台电脑(对象.组装电脑() )

1.1.3.为什么要学习面向对象

面向对象有三大基本特点封装、继承、多态,封装可以使你忽略更多的细节、继承可以使你的代码更易于扩展、多态可以让你同一的接口有不同的实现,在面向过程编码的时候,代码都是柔和在一起的,当项目越大,修改起来就比较麻烦了,面向对象将各种功能封装并提供了一个对象的概念,让你忽略细节,这样有限的智力就可以用来处理更加复杂的问题,从而提高效率,总之,可以理解为当系统越复杂,面向对象编程就越能体现出效率的提升

1.2.面向对象的写法

1.2.1.对象的组成

对象由属性和方法组成,万物皆对象,每个对象都具有相应的特征,例如:一个人的身高、体重特征,这些特征在编程中我们叫做属性,一个人有各种技能,可以写代码、可以搬砖,这些技能我们在编程中又叫做方法,通常编程中对象的属性表现为变量,它们是一些静态的特征,对象的方法表现为函数,可以运行,它们是一些动态的特征。

var arr = [1, 2, 3]
// 在arr对象下添加一个变量 
arr.num = 4
alert(arr.num) //4  这里的变量num就是对象的属性

//在arr对象下添加一个函数
arr.fn = function(){
    alert(4)
}
arr.fn() // 弹出4  这里等函数fn就是对象的方法

1.2.2.对象的分类

js中对象可以分为系统自带的对象和我们自己定义的对象,系统自带的对象我们已经使用过了很多,例如:Array、String、Date等,除此以外,我们需要自定义一些对象来使用

1.2.3.创建对象

通过new Object()的方式可以创建一个对象

var person = new Object()

或者通过对象字面量的形式创建对象

var person = {}

对象创建好后,可以在上面添加属性和方法

var person = new Object()
person.name = "小强"
person.showName = function(){
    alert(this.name)
}
//调用对象上的方法
person.showName()

1.2.4.工厂方式创建对象

上面我们已经可以通过new Object()的方式创建出了一个叫小强的对象,那如果我需要创建另一个叫小花的对象,应该如何做呢?如果想创建100个对象又应该怎么实现呢?

按照上面创建对象的方法,创建另一个对象非常容易,把代码复制一份改改就OK了

var person = new Object()
person.name = "小花"
person.showName = function(){
    alert(this.name)
}
//调用对象上的方法
person.showName()

问题来了,如果想创建100或者更多的对象,我们不可能把代码复制100份,这样会产生非常多的冗余代码,这个时候最容易想到的就是把相同的代码进行封装,所以得到一个封装的函数

function createPerson(name){
    var person = new Object()
    person.name = name
    person.showName = function(){
        alert(this.name)
    }
    return person
}
//创建对象 小强

var p1 = createPerson("小强")
p1.showName()

//创建对象 小花

var p2 = createPerson("小花")
p2.showName()

上面封装函数的形式来节约代码,和工厂流水线生产非常相似,首先new一个对象出来,相当于工厂去买原材料,接下来,给对象加属性和方法,相当于工厂对原材料进行加工,最后,return一个对象,相当于工厂生产出了产品,我们把上面封装函数的这种方式叫做工厂方式,这里最主要要把我工厂方式的特点,流水线生产

1.2.5.构造函数

在上面工厂方式的基础上,我们再来做进一步的优化,当我们使用系统内置对象的时候,非常的方便,直接new一下就ok了,例如:new Date(), 这样就会创建出一个时间对象,那我们自定义的对象能不能也像内置对象一样直接使用new的形式来创建呢?答案是肯定的,具体写法如下:

function CreatePerson(name){
    this.name = name
    this.showName = function(){
        alert(this.name)
    }
}
//创建对象 小强
var p1 = new CreatePerson("小强")
p1.showName()

//创建对象 小花
var p2 = new CreatePerson("小花")
p2.showName()

上面函数能简写是因为new关键字,以new去调用一个函数的方式来创建对象,实际上会经历以下4个步骤:

1.创建一个新对象
2.将构造函数的作用域赋给新对象(因此,this就指向了这个新对象)
3.指向钩子函数中的代码(为这个新对象添加属性)
4.返回新对象

因此,new关键字的作用就是当new区调用一个函数的时候,这个函数里面的this会指向创建出来的对象并且这个函数的返回值就是这个创建出来的对象,总结起来就是说new可以让被调用的函数返回一个创建好的对象,并且这个函数里面的this都是指向这个对象的,同时还引出一个构造函数的概念,当new去调用一个函数的时候,这个被调用的函数就是构造函数

1.3.对象引用

在javascript中数据类型可以分为基本类型和引用类型,基本类型就是我们已经学过的Undefined、Null、Boolean、Number、String,引用类型有Object、Array、Date、RegExp、Function等,基本类型的值是简单的数据段,例如:数字5、字母A,引用类型值指那些可能由多个值构成的对象。

在从一个变量向另一个变量复制基本类型值和引用类型值时,有一些不同,如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

var num1 = 10
var num2 = num1

在上面代码中,num1存了变量10,在给num2赋值的时候,相当于拷贝了num1的副本然后赋值给了num2,这个时候,num1和num2可以参与任何操作而互相不会影响。这里需要注意的关键点就是num1和num2以后参与任何运算不会相互影响

如果从一个变量向另一个变量复制引用类型的值,同样还是会将存储在变量对象中的值复制一份放到新变量分配的空间中,不同的是,复制的这个副本是一个指针,并不是具体的对象,这个指针指向存储在堆内存中的一个对象,复制操作结束后,两个变量实际上将引用同一个对象

var arr1 = [1, 2, 3]
var arr2 = arr1

arr2.push(4)

console.log(arr1)

1.4.原型

上一节我们理解了对象的引用造成的影响就是两个变量引用一个对象的时候会相互影响,基于这个知识点,我们来看看构造函数方式创建对象有什么问题

function CreatePerson(name) {
    this.name = name
    this.showName = function () {
        alert(this.name)
    }
}
var p1 = new CreatePerson("小强")
var p2 = new CreatePerson("小李")

alert(p1.showName == p2.showName) //执行结果为false

从上面的代码中我们知道,虽然p1和p2都有showName这个方法,但是它们并不是相同的,那意味着如果要创建100个或者更多的对象出来,那么在内存中同样会有非常多的showName方法被创建,然而创建出这么多showName是没有必要的,非常的浪费资源,面对这种问题,我们希望做的事情就是让p1和p2共用一个showName,如果有更多的对象被创建,这个showName在内存中也是唯一的,这个时候,就需要学习js中的原型了。

我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,这个对象就是构造函数CreatePerson的原型对象,当调用CreatePerson创建出一个具体对象p1后,p1内部也有一个指针指向构造函数的原型对象,同样的道理,不管创建出多少对象,这些对象内部都会有个指针执行构造函数的原型对象,使用原型对象的好处是让所有被创建出来的对象共享这个原型对象所包含的属性和方法

function CreatePerson(name){
    this.name = name
}
CreatePerson.prototype.showName = function(){
    alert(this.name)
}
var p1 = new CreatePerson("小强")
var p2 = new CreatePerson("小花")
var p3 = new CreatePerson("小豆")
alert(p1.showName === p2.showName) // true
alert(p2.showName === p3.showName) // true

上面代码具体关系图如下:

1.5.实例-面向对象写选项卡

面向过程选项卡实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #wrap button{
            height: 30px;
            width: 60px;

        }
        #wrap div{
            width: 300px;
            height: 150px;
            border: 1px solid red;
            margin-top: 10px;
            display: none;
        }
        #wrap .active{
            display: block;
        }
    </style>
</head>
<body>
<div id="wrap">
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    <div class="active">nodeing_1</div>
    <div>nodeing_2</div>
    <div>nodeing_3</div>
    <div>nodeing_4</div>
</div>
<script>
    var oWrap = document.getElementById("wrap");
    var aBtn = oWrap.getElementsByTagName("button")
    var aDiv = oWrap.getElementsByTagName("div")
    for(var i=0; i<aBtn.length;i++){
        aBtn[i].index = i
        aBtn[i].onmouseover = function () {
            for(var j=0;j<aBtn.length;j++){
                aBtn[j].style.backgroundColor = "white"
                aDiv[j].style.display = "none"
            }
            this.style.backgroundColor = "orangered"
            aDiv[this.index].style.display = "block"
        }
    }
</script>
</body>
</html>

面向对象选项卡实现的时候可以按照以下步骤进行改写:

1、根据功能封装函数,把面向过程的代码整理出来
2、正式改写成面向对象,遵循以下原则
    1、全局变量就是属性
    2、函数就是方法
    3、既不是函数也不是全局变量的代码封装成一个初始化方法
3、调整this指向

按照步骤1,整理代码

首先,我们把选项卡改变这个功能单独封装出来


function changeTab(){
    for(var j=0;j<aBtn.length;j++){
                aBtn[j].style.backgroundColor = "white"
        aDiv[j].style.display = "none"
    }
    this.style.backgroundColor = "orangered"
    aDiv[this.index].style.display = "block"
}

整理后完整代码为:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #wrap button{
            height: 30px;
            width: 60px;

        }
        #wrap div{
            width: 300px;
            height: 150px;
            border: 1px solid red;
            margin-top: 10px;
            display: none;
        }
        #wrap .active{
            display: block;
        }
    </style>
</head>
<body>
<div id="wrap">
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    <div class="active">nodeing_1</div>
    <div>nodeing_2</div>
    <div>nodeing_3</div>
    <div>nodeing_4</div>
</div>
<script>
    var oWrap = document.getElementById("wrap");
    var aBtn = oWrap.getElementsByTagName("button")
    var aDiv = oWrap.getElementsByTagName("div")
    for(var i=0; i<aBtn.length;i++){
        aBtn[i].index = i
        aBtn[i].onmouseover = function () {
            changeTab(this)
        }
    }
    function changeTab(){
        for(var j=0;j<aBtn.length;j++){
            aBtn[j].style.backgroundColor = "white"
            aDiv[j].style.display = "none"
        }
        obj.style.backgroundColor = "orangered"
        aDiv[obj.index].style.display = "block"
    }
</script>
</body>
</html>

接下来具体改写成面向对象的形式

//1 创建构造函数
function Tab(){
    //2 把全局变量改成属性 
    this.oWrap = document.getElementById("wrap");
    this.aBtn = this.oWrap.getElementsByTagName("button")
    this.aDiv = this.oWrap.getElementsByTagName("div")
}

//3 函数就是方法,把方法添加到prototype上
Tab.prototype.changeTab = function(){
    for(var j=0;j<aBtn.length;j++){
        aBtn[j].style.backgroundColor = "white"
        aDiv[j].style.display = "none"
    }
    obj.style.backgroundColor = "orangered"
    aDiv[obj.index].style.display = "block"
}
//4 把既不是函数也不是变量的代码封装成初始化方法
Tab.prototype.init = function(){
    for(var i=0; i<this.aBtn.length;i++){
        this.aBtn[i].index = i
        this.aBtn[i].onmouseover = function () {
            this.changeTab(this)
        }
    }
}

//5.用构造函数Tab创建一个对象,并且调用init方法
var t1 = new Tab()
t1.init()   //运行报错,还需要执行最后一步,修改this指向

最终,完善的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #wrap button{
            height: 30px;
            width: 60px;

        }
        #wrap div{
            width: 300px;
            height: 150px;
            border: 1px solid red;
            margin-top: 10px;
            display: none;
        }
        #wrap .active{
            display: block;
        }
    </style>
</head>
<body>
<div id="wrap">
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    <div class="active">nodeing_1</div>
    <div>nodeing_2</div>
    <div>nodeing_3</div>
    <div>nodeing_4</div>
</div>

<script>


    function Tab() {
        this.oWrap = document.getElementById("wrap");
        this.aBtn = this.oWrap.getElementsByTagName("button")
        this.aDiv = this.oWrap.getElementsByTagName("div")
    }
    Tab.prototype.changeTab = function (obj) {
        for(var j=0;j<this.aBtn.length;j++){
            this.aBtn[j].style.backgroundColor = "white"
            this.aDiv[j].style.display = "none"
        }
        obj.style.backgroundColor = "orangered"
        this.aDiv[obj.index].style.display = "block"
    }
    Tab.prototype.init = function () {
        var That = this
        for(var i=0; i<this.aBtn.length;i++){
            this.aBtn[i].index = i
            this.aBtn[i].onmouseover = function () {
                That.changeTab(this)
            }
        }
    }
    var t1 = new Tab()
    t1.init()
</script>
</body>
</html>

接下来,我们就可以见证面向对象复用代码的优势了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #wrap button, #wrap2 button{
            height: 30px;
            width: 60px;

        }
        #wrap div, #wrap2 div{
            width: 300px;
            height: 150px;
            border: 1px solid red;
            margin-top: 10px;
            display: none;
        }
        #wrap .active, #wrap2 .active{
            display: block;
        }
    </style>
</head>
<body>
<div id="wrap">
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    <div class="active">nodeing_1</div>
    <div>nodeing_2</div>
    <div>nodeing_3</div>
    <div>nodeing_4</div>
</div>
<div id="wrap2">
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    <div class="active">nodeing_1</div>
    <div>nodeing_2</div>
    <div>nodeing_3</div>
    <div>nodeing_4</div>
</div>
<script>


    function Tab(id) {
        this.oWrap = document.getElementById(id);
        this.aBtn = this.oWrap.getElementsByTagName("button")
        this.aDiv = this.oWrap.getElementsByTagName("div")
    }
    Tab.prototype.changeTab = function (obj) {
        for(var j=0;j<this.aBtn.length;j++){
            this.aBtn[j].style.backgroundColor = "white"
            this.aDiv[j].style.display = "none"
        }
        obj.style.backgroundColor = "orangered"
        this.aDiv[obj.index].style.display = "block"
    }
    Tab.prototype.init = function () {
        var That = this
        for(var i=0; i<this.aBtn.length;i++){
            this.aBtn[i].index = i
            this.aBtn[i].onmouseover = function () {
                That.changeTab(this)
            }
        }
    }
    var t1 = new Tab("wrap")
    t1.init()
    var t2 = new Tab("wrap2")
    t2.init()
</script>
</body>
</html>

1.6.实例-面向对象写拖拽

面向过程拖拽功能实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #box{
            width: 100px;
            height: 100px;
            background-color: red;
            position: relative;
        }
    </style>
</head>
<body>
<div id="box"></div>
<script>
    var oBox = document.getElementById("box")
    oBox.onmousedown = function (ev) {
        var ev = ev || window.event;
        var difX = ev.clientX - oBox.offsetLeft
        var difY = ev.clientY - oBox.offsetTop
        document.onmousemove = function (ev) {
            oBox.style.left = ev.clientX - difX + "px"
            oBox.style.top = ev.clientY - difY + "px"
        }
        document.onmouseup = function () {
            document.onmousemove = null;
            document.onmouseup = null;
        }
    }

</script>
</body>
</html>

改写成面向对象的形式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #box{
            width: 100px;
            height: 100px;
            background-color: red;
            position: relative;
        }
    </style>
</head>
<body>
<div id="box"></div>
<script>

    function Drag() {
        this.oBox = document.getElementById("box")
        this.difX = null
        this.difY = null
    }
    Drag.prototype.fnMove = function (ev) {
        this.oBox.style.left = ev.clientX - this.difX + "px"
        this.oBox.style.top = ev.clientY - this.difY + "px"
    }
    Drag.prototype.fnUp = function () {
        document.onmousemove = null;
        document.onmouseup = null;
    }
    Drag.prototype.fnDown = function (ev) {
        var That = this
        this.difX = ev.clientX - this.oBox.offsetLeft
        this.difY = ev.clientY - this.oBox.offsetTop
        document.onmousemove = function (ev) {
            var ev = ev || window.event
            That.fnMove(ev)
        }
        document.onmouseup = function () {
            That.fnUp()
        }
    }
    Drag.prototype.init = function () {
        var That = this
        this.oBox.onmousedown = function (ev) {
            var ev = ev || window.event
            That.fnDown(ev)
        }
    }
    var d1 = new Drag()
    d1.init()
</script>
</body>
</html>