【JavaScript高级】面向对象编程

一、面向对象编程

1.1 面向对象概述

要理解面向对象,就要理解什么是 面向过程。

原生JavaScipt案例合集
JavaScript +DOM基础
JavaScript 基础到高级
Canvas游戏开发

面向过程注重的是解决问题的步骤,利用函数去实现每一步过程,没有类和对象的概念,常见的面向过程的语言 C 语言。

面向对象注重使用对象去解决某一个问题,具有类和对象的概念。类是抽象的,注重的是一类的东西,比如说人类、鸟类、猫科等等,从这些类中拿到具体的对象,对象就是一个比较明确的个体,利用这个对象去解决针对性的问题。面向对象语言目前比较流行,像 Java、C++、PHP、Python等等。

也就是面向过程需要调用着去实现各种功能函数,然后调用这些函数;而面向对象,是将一些功能方法在对象中实现,调用者只需要调用这个对象,而不需要了解对象中的功能实现的具体细节。

如:前面学过内置对象 数组Array 字符串String 算术Math 正则RegExp等等,对象中内置了很多的方法,我们在使用的时候,只需要根据对应的功能去调用这些方法即可,而不需要知道这些方法所实现的细节。

假设:给一个数组,进行倒叙排序 注意不能使用数组中reverse()方法

这个时候,就需要自己封装函数实现倒叙的功能。每个人去实现自己这个倒叙功能时,其中的实现细节是不一样的,这就是瞄向过程。而如果我们直接调用数组对象的reverse()方法,根本不需要管reverse()封装的细节,直接拿过来调用即可,这就是面向对象。

再举例:

​ 比方说你是一个单身狗,回家需要吃饭,吃饭就得做饭,吃上饭的流程:选择菜市场 --> 挑菜 --> 回家 --> 摘菜 --> 洗菜 --> 切菜 --> 炒菜 --> 上桌 --> 吃(一个人) --> 刷锅洗碗 此时,所有的步骤都需要自己去完成,这就是面向过程。

​ long long ago...,这个时候你终于有了对象,再差点有了好基友和闺蜜,这个时候两个人吃饭,吃饭的流程:你下班在家打游戏,让你哪个对象或好基友或闺蜜去菜市场 --> 挑菜 --> 回家 --> 摘菜 --> 洗菜 --> 切菜 --> 炒菜 --> 上桌 上桌之后,吃(两个人) 你去刷锅。此时整个操作,除了刷锅和吃是你自己完成,其它的只是说了一嘴,你去完成其它所有的操作,这个过程他买什么菜、去哪个菜市场、怎么切不用管,反正最后能吃上就行。这就是面向对象,一些功能交给另外的对象完成。

1.2 面向对象三大特性

封装: 把相关信息存储在对象中的能力。

继承:继承是面向对象语言中最显著的一个特性。从已有类中派生出新的类,新的类能吸收已有类的属性和方法,并能够拓展新的能力。

多态:多种形态,可以通过不同的接口去实现。

1.3 类和对象

类:具有相同的特性和行为的集合。如:人类的身高、姓名、年龄等属性,行为如吃饭、睡觉、学习等行为。人类具有这些行为特征,但是又不能具体说这些行为特征的详细信息。所以,类是抽象的。

对象:从类中,拿出来的具体的个体,如:张三、身高180、23岁等属性,爱吃煎饼果子、睡觉爱打人、喜欢学箜篌等行为。这些描述就比较具体了。所以说,对象是具体的。

在传统的JS中(一般指ES3.1),不存在类的概念,但是可以通过构造函数模拟类。所以我们称其为基于对象的类编程语言。

1.4 体会面向过程和面向对象

假设描述一个人:这个人的名字叫张三、年龄23、性别男、爱吃 鱼香肉丝

面向过程:

ini 复制代码
var name = "张三",
	age = 23,
	sex = '男',
	eat = function(){
		return "鱼香肉丝";
	}

面向对象:

javascript 复制代码
var obj = {
	name:'张三',
	age:23,
	sex:'男',
	eat:function(){
		return "鱼香肉丝";
	}
}

1.5 JS中对象的分类

内置对象:Array String Math RegExp Date

宿主对象:提供js运行环境的对象,目前指的是浏览器对象

自定义对象:大括号包裹的名值对 key-value

二、创建对象

2.1 原始方式

假设我们将猫科类的动物看作是一个对象,有猫科动物的品种、动龄,生活习性:

csharp 复制代码
var Felidae = {
	type:'',
	age:0,
	behavior:null
}

基于这个原型对象去创建具体的个体,这个原型可以看作模拟类,基于这个模拟类创建对象如下:

ini 复制代码
var tiger = {
	type:'东北虎',
	age:10,
	behavior:function(){
		return "喜欢独立捕捉猎物...";
	}
}

var cat = {
	type:"暹罗猫",
	age:8,
	behaivor:function(){
		return "喜欢晚上行动..."
	}
}

var cheetah = {};
cheetah.type = "亚洲豹";
cheetah.age = 6;
cheetah.behaivor = function(){
	return "抓到猎物后喜欢挂到树上..."
}

var cat2 = new Object();
cat2.type = "中华田园猫";
cat2.age = 4;
cat2.behaivor = function(){
	return "卖萌和睡觉..."
}

这种利用原型的方式创建对象有两个问题:
	1. 上面三个独立的个体,程序不能识别出具体的个体(实例的对象)与原型(模拟类)之间的关系;
	2. 书写的代码太冗余,每次基于原型去创建一个新的实例个体时,都要重复的去书写原型中那些属性和方法。

2.2 利用工厂函数进行改进

javascript 复制代码
//利用函数对其进行优化
// function factory(type,age,behaivor){
//     var Felidae = {
//         type:type,
//         age:age,
//         behavior:function(){
//             return behaivor;
//         }
//     }
//     return Felidae;
// }

function factory(type,age,behaivor){
    return {
        type:type,
        age:age,
        behavior:function(){
            return behaivor;
        }
    }
}
var tiger = factory('东北虎',10,"喜欢独立捕捉猎物...")
var cat = factory('暹罗猫',8,"喜欢晚上行动...")

利用工厂函数,解决了代码冗余的问题。但是还是不能看出对象和原型之间的关系。
利用关键字obj instanceof Fn  或者利用 consstructor 查看构造函数 

2.3 利用构造函数再次优化

构造函数和普通函数最明显的区别:构造函数首字母大写。利用使用this关键字,this指向实例化对象。

构造函数在实例化对象时,需要用到new关键字,语法 new 构造函数()

javascript 复制代码
//定义一个构造函数,在JS中,使用构造函数来模拟类
function Felidae(type,age,behaivor){
   this.type = type;
   this.age = age;
   this.behaivor = function(){
       return behaivor;
   }
}

//实例化对象 利用关键字new
var tiger = new Felidae('东北虎',10,"喜欢独立捕捉猎物...");
var cat = new Felidae('暹罗猫',8,"喜欢晚上行动...");

//确认实例和原型之间的关系
console.log(tiger instanceof Felidae);//true
console.log(tiger.constructor);//ƒ Felidae(type,age,behaivor){...}


构造函数的方式,解决了代码冗余的问题,也能确认出实例和原型之间的关系。
但是,也存在一定需要优化的问题,见原型优化。

注:构造函数执行时(实例化)步骤

​ 1. 开辟一个新的内存空间 ------ 在内存中新建一个空对象

​ 2. 改变 this 指向 ------ 指向内存中的空对象

​ 3. 执行函数中的代码,为 this 赋值 ------ 根据定义的键值和传入的参数,依次给这个空对象添加上键值对

​ 4. 返回 this ------ 把指向内存中刚刚创建的新对象的指针 return 出去,传址赋值给变量

如果构造函数中,仅使用return,那么它还是作为一个普通函数存在的。

当然,如果有this和return都共存的情况下,不同的数据类型,函数最终的处理结果不同。如果return返回的是一个基本数据类型的数据,则忽略。如果返回的是一个引用数据类型的数据,那么函数无论怎么调用,返回的都是这个引用数据类型的数据。

2.4 原型优化

假设,有如下构造函数:

ini 复制代码
function Felidae(type,age,behaivor){
   this.type = type;
   this.age = age;
   this.behaivor = behaivor;

   this.des = "猫科动物多数善攀缘及跳跃。大多喜独居。肉食,常以伏击方式捕杀其他温血动物。分布于欧亚大陆 、非洲、美洲的寒带到热带地区,由野猫(Felis silvestris)驯化来的家猫(F.s.catus)已被人为带到全世界,很多成为当地入侵物种。";
   this.sayHi = function(){
        return "吼..."
   };
   thsi.sayHello = function(){
        return "动物的品种:" + this.type + ", 动物年龄:" + this.age + ",猫科动物的描述:" + this.des + ", 动物行为:" + this.behaivor;
   }
}
//实例化对象 利用关键字new
var tiger = new Felidae('东北虎',10,"喜欢独立捕捉猎物...");
var cat = new Felidae('暹罗猫',8,"喜欢晚上行动...");
console.log(tiger == cat);//false
console.log(tiger.des == cat.des);//true
console.log(tiger.sayHi() == cat.sayHi());//true
console.log(tiger.sayHi == cat.sayHi);//false  不同个体中的相同函数

上面每创建一个实例对象,都会重复创建des、sayHi、sayHello,它们所实现或获取的值都是一样的。这样重复创建会占用过多的内存,为了优化,我们开辟一个独立空间,作为其调用的地址。这样可以节约内存,利用的就是prototype这个原型。

javascript 复制代码
function Felidae(type,age,behaivor){
   this.type = type;
   this.age = age;
   this.behaivor = behaivor;
}

Felidae.prototype.des = "猫科动物多数善攀缘及跳跃。大多喜独居。肉食,常以伏击方式捕杀其他温血动物。分布于欧亚大陆 、非洲、美洲的寒带到热带地区,由野猫(Felis silvestris)驯化来的家猫(F.s.catus)已被人为带到全世界,很多成为当地入侵物种。",
Felidae.prototype.sayHi = function(){
        return "吼..."
};
Felidae.prototype.sayHello = function(){
        return "动物的品种:" + this.type + ", 动物年龄:" + this.age + ",猫科动物的描述:" + this.des + ", 动物行为:" + this.behaivor;
};
//实例化对象 利用关键字new
var tiger = new Felidae('东北虎',10,"喜欢独立捕捉猎物...");
var cat = new Felidae('暹罗猫',8,"喜欢晚上行动...");
console.log(tiger.sayHi == cat.sayHi);

三、原型和原型链

3.1 原型概述

原型prototype是每一个函数都默认自带的属性,它的值是一个对象,叫做原型对象。

对于构造函数来讲,意义更大。因为通过构造函数可以实例化化对象,而每一个实例化的对象,都默认含有一个隐式的属性 proto 来指向该构造函数的原型对象。

原型的作用:为实例化对象提供共享的属性和方法。

也就是说,在内存中开辟一块共享的空间,供构造函数的实例化对象在调用时指向这一个共享的地址,节约内存。

3.2 原型链概述

每一个实例化对象,都有原型对象,原型对象本质还是一个对象,所以说原型对象上面可能还有原型对象。

当一个实例化对象,去调用某个属性或方法时,它会先从自身查找,如果找到直接调用;如果找不到则去它的原型中查找,在原型中查找到则使用;如果它的原型中也没有找到,那么继续向上去原型的原型中查找,就这样一层层的查找,形成一条链,这条链我们叫做原型链。一直找到,原型链的终点是 Object 的原型,如果还是找不到则返回undefined.

javascript 复制代码
function Cat(name,age){
    this.name = name;
    this.age = age;
    this.sayHi = function(){
        return "我们的天性是抓老鼠!!!"
    }
}

Cat.prototype.name = "猫";
Cat.prototype.sayHello = function(){
    return "爱睡觉!!"
}

var cat = new Cat("加菲猫",5);

console.log(cat.__proto__ === Cat.prototype);//true

3.3 几个相关属性和方法

  • prototype 属性:允许您向对象添加属性和方法

    注:1. 这个属性随着函数的声明而自动生成

    ​ 2. 构造函数通过调用这个属性自定义的共享属性和方法可以被该构造函数的实例化对象调用

  • constructor 属性:获取实例化对象的构造函数

  • instanceof 运算符:用于判断实例化对象的原型链中是否出现过指定的构造函数

  • isPrototypeOf() 方法用于判断原型对象和某个实例之间的关系

  • hasOwnProperty() 方法判断指定属性是不是这个实例化对象的本地属性

    注:构造函数中定义的属性和方法使本地属性,随着new的实例化对象的生成而生成,也就是对象本身具备的属性和方法

    ​ 返回值 是一个布尔值,本地属性返回true 非本地属性返回false

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XhEfPdPQ-1692844162882)(01_面向对象编程.assets/image-20200518103512795.png)]

  • in 运算符 判断某个属性可不可以被某个实例调用

in运算符还可以用于对对象属性和数组的遍历

css 复制代码
var arr = ["banana","orange","pear","apple"];
for(i in arr){
    console.log(i,arr[i],arr)
}


var obj = {
    name:"Jack",
    age:23,
    sex:"男"
}
for(key in obj){
    console.log(key);
}

四、安全类

无论外部如何调用(使用new关键字 或 当作普通函数)该类,都会返回一个类的实例化对象

解决办法:利用this指向

ini 复制代码
function Cat2(name,age){
    if(this === window){
        return new Cat2(name,age);
    }else{
        this.name = name;
        this.age = age;
        this.sayHi = function(){
            return "我们的天性是抓老鼠!!!"
        }
    }
}
var catObj = new Cat2("暹罗猫",4);
console.log(catObj);
catObj = Cat2("短耳猫",7);
console.log(catObj);

五、练习

利用面向对象的思想,完成一个气球移动效果

相关推荐
迷雾漫步者1 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-28 分钟前
验证码机制
前端·后端
燃先生._.1 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖2 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235242 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240253 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar3 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人4 小时前
前端知识补充—CSS
前端·css
GISer_Jing4 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试