JS 学习笔记 (七) 面向对象编程OOP
1、前言
创建对象有很多种方法,最常见的是字面量创建和new Object()创建。但是在需要创建多个相同结构的对象时,这两种方法就不太方便了。
如:创建多个学生信息的对象
let tom = {
name: "Tom",
age: 20,
sex: "boy",
height: 175
};
let marry = {
name: "Marry",
age: 22,
sex: "girl",
height: 165
}
2、对象工厂
2.1 实例
使用对象工厂改进上述代码,如:
function person(name, age, sex, height) {
return {
name, age, sex, height
}
}
let tom = new person("Tom", 20, "boy", 175)
let marry = new person("Marry", 22, "girl", 165)
console.log(tom);
console.log(marry);
打印出结果:
{ name: 'Tom', age: 20, sex: 'boy', height: 175 }
{ name: 'Marry', age: 22, sex: 'girl', height: 165 }
对象工厂函数创建返回的是一个新对象。
2.2 缺陷以及解决方法
- 对象工厂本身是一个普通函数,用于表达对象结构时,描述性不强
- 对象工厂没有解决对象标识的问题,即创建的对象是什么类型。
- 利用构造函数可以解决这些问题
3、构造函数
3.1 实例详解
更改上面的代码,并添加一个say函数:
function person(name, age, sex, height) {
this.name = name;
this.age = age;
this.sex = sex;
this.height = height;
this.say = function(){
console.log(`你好,我是${this.name}`);
}
}
let tom = new person("Tom", 20, "boy", 175)
let marry = new person("Marry", 22, "girl", 165)
tom.say()
marry.say()
打印出结果:
你好,我是Tom
你好,我是Marry
其中,代码中的this是对new Person的空对象进行扩展。
每个对象都具有constructor属性,用于标识对象的“类型”,如:
console.log(Tom.constructor == Person); // true
console.log(Tom.constructor == Object); // false
若要判tom和marry对象的类型,推荐使用instanceof方法。如:
// 使用instanceof方法判断对象类型
console.log(Tom instanceof Person); // true
console.log(Tom instanceof Object); // true
但是这个解决方法还是有一些缺陷。
// 判断实例所对应的方法是否相同
console.log(Tom.say == Jerry.say); // false
输出是false的原因是因为同一个构造函数的实例都会创建一个自己的方法。这样可能会极大的增加的内存的负荷。而且,同一个方法应该是完成相同的任务,没有必要创建多个相同的方法。
我们可能会想到的解决方案是将它的say()方法提取出来,如:
function Person(name, age, sex, height) {
this.name = name;
this.age = age;
this.sex = sex;
this.height = height;
}
function say() {
console.log(`你好,我是${this.name}`);
}
let tom = new person("Tom", 20, "boy", 175)
let marry = new person("Marry", 22, "girl", 165)
tom.say()
marry.say()
输出结果:
你好,我是Tom
你好,我是Marry
这样处理的好处就是say()方法不会被多次创建,但会产生一定的问题。即:
say() 为全局函数,会导致作用域混乱。而且只有Person创建的对象才能调用该方法,由于该方法是放在全局的,可能会产生内存泄漏,会让全局的函数更加臃肿。
解决方法:
利用原型模式,将方法定义在构造函数的原型对象上,可以解决这个问题
- 每个函数都有一个prototype属性,指向一个对象。
该对象包含应该由特定引用类型的实例共享的属性和方法。
该对象就是通过调用构造函数创建的对象的原型。
看下列代码:
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function () {
console.log(`你好,我是${this.name}`);
}
let Tom = new Person("Tom", 12)
Tom.say()
let Marry= new Person("Jerry", 10)
Marry.say()
console.log(Tom.say == Marry.say); // true
输出结果:
你好,我是Tom
你好,我是Marry
若要对person的原型进行属性扩展,可直接使用Person.prototype。因为当在构造函数原型上创建属性(或方法)时,会被改构造函数的额所有对象所共享。如:
Person.prototype.from = "China"
console.log(Tom.from); // China
console.log(Marry.from); // China
若Marry对象本身有from属性,则继承自Person的from就不起作用了(原型的from属性被屏蔽掉了),起作用的是Marry自身的from属性,如:
Marry.from = "America"
console.log(Tom.from); // China
console.log(Marry.from); // America
若要判断对象的原型,对象原型的构造函数,可枚举的属性:
console.log(Object.keys(Marry)); // 可枚举的自身的属性 [ 'name', 'age' ]
console.log("from" in Marry); // 可枚举的自身的属性和继承的属性 true
console.log(Object.getOwnPropertyNames(Marry)); // 可枚举的自身的属性 [ 'name', 'age' ]
console.log(Object.getPrototypeOf(Marry).constructor == Person); // true
3.2 基本原理总结
- 在内存中创建一个新对象。
- 这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性。
- 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
- 执行构造函数内部的代码(给新对象添加属性)。
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
4、原型继承
4.1 概要
- 利用构造函数构建对象结构(模板,近似于类),从语义上较为清晰的表达对象结构。
- 利用构造函数原型扩展,能方便的为该构造函数所创建的对象进行基于原型的扩展。
- 利用构造函数还可以进行基于原型对象的继承
4.2 构造函数、原型和实例的关系
- 每个构造函数都有一个原型对象。
- 原型有一个属性指回构造函数
- 实例有一个内部指针[[Prototype]]指向原型。
4.3 原型链
当对象原型是另一个构造函数的实例,如此迭代,形成了一连串的继承关系,即为原型链。原型链表达了对象与对象之间的继承关系。
举个例子:
function Person(name, age) {
this.name = name
this.age = age
}
let Marry = new Person("Marry", 10)
console.log(Marry instanceof Person); // true
console.log(Marry instanceof Object); // true
console.log(Object.getPrototypeOf(Marry)); // {}
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Marry))); // [Object: null prototype] {}
4.4 原型链的问题
function Animal() {
this.colors = ["white", "black"];
}
function Mouse(name, age) {
this.name = name;
this.age = age;
}
Mouse.prototype = new Animal();
let m1 = new Mouse("Mickey", 10);
console.log(m1.name, m1.colors);
m1.colors.push("red");
let m2 = new Mouse("Miney", 9);
console.log(m2.colors);
输出结果:
Mickey [ 'white', 'black' ]
[ 'white', 'black', 'red' ]
这是因为当原型中包含引用值,在实例件共享的是该引用值的引用,当修改实例中的该属性时,会影响全部实例。
存在的问题:子类型在实例化时不能给父类型传递参数。
解决方案:盗用构造函数
4.5 盗用构造函数
在子类构造函数中调用父类构造函数,并将子类当前实例只定为构造函数的上下文。
function Animal(type) {
this.colors = ["white", "black"];
this.type = type
}
function Mouse(name, age, type = "Mouse") {
Animal.call(this, type) // 父类构造函数"盗用"(仅仅把Animal当做普通函数调用)
this.name = name;
this.age = age;
}
Mouse.prototype = new Animal()
let m1 = new Mouse("Mickey", 20)
m1.colors.push("red")
console.log(m1.name, m1.colors);
let m2 = new Mouse("Miney", 18)
console.log(m2.name, m2.colors);
console.log(m1 instanceof Mouse);
console.log(m1 instanceof Animal);
输出结果:
Mickey [ 'white', 'black', 'red' ]
Miney [ 'white', 'black' ]
true
true
存在的问题:
无法访问父类原型上的方法,或者说是没有父类
解决方法:
将原型链与盗用构造函数结合起来
4.6 原型链与盗用构造函数的组合
将两者优点集中起来,如:
function Animal(type) {
this.colors = ["white", "black"];
this.type = type
}
Animal.prototype.show = function () {
console.log(this.type, this.colors); // Mouse [ 'white', 'black', 'red' ]
}
function Mouse(name, age, type = "Mouse") {
Animal.call(this, type) // 父类构造函数“盗用”,解决传参问题
this.name = name;
this.age = age;
}
Mouse.prototype = new Animal() // 强制指定原型对象,表达继承关系
let m1 = new Mouse("Mickey", 20)
m1.colors.push("red")
console.log(m1.name, m1.colors); // Mickey [ 'white', 'black', 'red' ]
m1.show() // 通过原型继承获取
let m2 = new Mouse("Miney", 18)
console.log(m2.name, m2.colors); // Miney [ 'white', 'black' ]
m2.show()
console.log(Object.keys(m1));// [ 'colors', 'type', 'name', 'age' ]
存在的问题:
console.log(m1 instanceof Mouse); // false
console.log(m1 instanceof Animal);// true
构造函数的指向不正确,问题在于Mouse.prototype = new Animal(),将构造函数指向了Animal
解决方法:强制指定构造函数的指向为原型构造函数
Mouse.prototype.constructor = Mouse
console.log(m1.constructor == Mouse); // true
console.log(m1 instanceof Mouse); // true
4、类
4.1 概述
- ECMAScript 6 新引入的 class 关键字具有正式定义类的能力。
- 类( class)是ECMAScript 中新的基础性语法糖结构
- 虽然 ECMAScript 6 类表面上看起来可以支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念
4.2 实例一
- [ ] 要求:
- 使用ES5实现
- 创建一个Person类,其中实例属性包括:姓名name,年龄age。
- 创建一个数组people,向该列表中存入4个Person的实例 按照年龄升序对people列表进行排序,显示排序后的姓名和年龄。
- 为Person类添加一个方法setAttr(attr, value),可以动态的为Person类的实例添加属性和属性值。**
function Person(name, age) {
this.name = name;
this.age = age
}
// 在Person的原型上复写toString方法
Person.prototype.toString = function () {
return `${this.name}:${this.age}`
}
// 在Person的原型上定义setAttr方法
Person.prototype.setAttr = function (attr, value) {
this[attr] = value
}
let people = [];
// 定义people空数组,实例化4个Person对象,并添加到people数组中
let p1 = new Person("tom", 20)
let p2 = new Person("henry", 19)
let p3 = new Person("mark", 21)
let p4 = new Person("jeorge", 23)
people.push(p1, p2, p3, p4)
// 对people数组的age属性的值从大到小的排序
people.sort((a, b) => a.age - b.age)
console.log(people);
// 输出每个实例的name和age组成的语句
people.forEach((item) => console.log(item.toString()))
// 给第一个实例添加gender属性并赋值
people[0].setAttr("gender", "Male")
console.log(people[0]);
// 输出第二个实例化对象与第一个对象进行比较,看添加gender属性是否成功
console.log(people[1]);
输出结果:
[
Person { name: 'henry', age: 19 },
Person { name: 'tom', age: 20 },
Person { name: 'mark', age: 21 },
Person { name: 'jeorge', age: 23 }
]
henry:19
tom:20
mark:21
jeorge:23
Person { name: 'henry', age: 19, gender: 'Male' }
Person { name: 'tom', age: 20 }
4.2 实例二
- [ ] 要求:
- 使用ES6实现实例一的功能
// 定义一个Person类
class Person {
// 在Person类里面定义的constructor构造函数,并传入参数name,age
constructor(name, age) {
// 定义name属性和age属性,并把传入的值赋给它
this.name = name;
this.age = age
};
// 定义的toString方法
toString() {
// 直接返回name和age的属性值
return `${this.name}:${this.age}`
};
// 定义setAttr方法,传入参数attr和value,表示属性和属性值
setAttr(attr, value) {
// 给当前的attr属性赋value值
this[attr] = value
}
}
let people = [
new Person("tom", 20),
new Person("henry", 19),
new Person("mark", 21),
new Person("jeorge", 23)
];
// 从大到小排序
people.sort((a, b) => a.age - b.age)
console.log(people);
// 返回每个实例的toString方法return的值
people.forEach((item) => console.log(item.toString()))
// 给第一个person实例对象添加gender属性并赋Male值
people[0].setAttr("gender", "Male")
// 打印出添加gender属性后的实例和未添加的实例进行比较
console.log(people[0]);
console.log(people[1]);输出结果:
[
Person { name: 'henry', age: 19 },
Person { name: 'tom', age: 20 },
Person { name: 'mark', age: 21 },
Person { name: 'jeorge', age: 23 }
]
henry:19
tom:20
mark:21
jeorge:23
Person { name: 'henry', age: 19, gender: 'Male' }
Person { name: 'tom', age: 20 }
4.3 实例三
- [ ] 要求:
- 使用ES6实现,在 实例二 Person类的基础上,编写以下两个类继承自Person。
- Teacher类除了具有Person类的姓名和性别,还具有一个“课程”course属性。
- Student类除了具有Person类的姓名和性别,还具有一个“分数”score属性
- 通过代码分析一个Teacher实例与Person类、Teacher类、Student类以及object之间的关系
class Person {
constructor(name, age) {
this.name = name;
this.age = age
};
toString() {
return `${this.name}:${this.age}`
}
}
// 这里实现Teacher子类继承Person父类
class Teacher extends Person {
constructor(name, age, course) {
// super 子类Teacher调用父类Person的属性
super(name,age)
// 添加course属性
this.course = course
}
}
// 实例化一个Teacher对象
let Liming = new Teacher("Liming",22,"语文")
console.log(Liming);
// 这里实现Student子类继承Person父类
class Student extends Person{
constructor(name, age, score) {
// super 子类Student调用父类Person的属性
super(name,age)
// 添加score属性
this.score = score
}
}
// 实例化一个Student对象
let Tom = new Student("Tom",22,100)
console.log(Tom);
// 判断Tom 是否是Student的实例
console.log(Tom instanceof Student);
console.log(Tom instanceof Person);
console.log(Tom instanceof Object);
// 判断实例化对象Liming和Tom的原型对象
console.log(Object.getPrototypeOf(Liming));
console.log(Object.getPrototypeOf(Tom));
// 判断Student类和Teacher类的原型对象
console.log(Object.getPrototypeOf(Student));
console.log(Object.getPrototypeOf(Teacher));
输出结果:
Teacher { name: 'Liming', age: 22, course: '语文' }
Student { name: 'Tom', age: 22, score: 100 }
true
true
true
Person {}
Person {}
[class Person]
[class Person]
4.4 实例四
- [ ] 要求
- 使用ES6实现,在 实例3 中Student类的基础上进行修改。
- 为Student类添加属性grade,用于表示该学生实例的年级(如:Grade One等)。
- 为学生实例的grade属性赋值时,能自动的将输入的信息转换为所有字母大写进行保存在对象中。
- 读取学生实例的grade属性时,如果该属性没有信息,则返回“NO GRADE”。
解法一:
class Person {
constructor(name, age) {
this.name = name;
this.age = age
}
}
class Student extends Person {
constructor(name, age, score) {
super(name, age)
this.score = score;
};
// 存取器属性取出数据,若没有则显示NO GRADE
get grade() {
return this.__level || "NO GRADE"
};
// 存取器属性存入数据,并转为大写
set grade(value) {
this.__level = value.toUpperCase()
}
}
let Tom = new Student("Tom", 22, 100)
let Jerry = new Student("Jerry", 22, 100)
Tom.grade = "grade one"
console.log(Tom.grade);
console.log(Jerry.grade);
输出结果:
GRADE ONE
NO GRADE
解法二:
// 在es5构造函数中定义存储器方法
let Person = function(){
return function(name,age){
this.name = name;
this.age = age;
}
}
class Students extends Person(){
#gd; //实例私有字段
constructor(name,age,score){
super(name,age)
this.score = score
}
get grade(){
return this.#gd || "No GRADES"
}
set grade(value){
this.#gd = value.toUpperCase().trim()
}
}
let p1 = new Person("Jerry",20)
let p2 = new Person("Jerry")
p1.grade = "abc"
console.log(p1);
console.log(p2);
输出:
[Function (anonymous)] { grade: 'abc' }
[Function (anonymous)]
JS 学习笔记 (七) 面向对象编程OOP的更多相关文章
- python学习笔记(七):面向对象编程、类
一.面向对象编程 面向对象--Object Oriented Programming,简称oop,是一种程序设计思想.在说面向对象之前,先说一下什么是编程范式,编程范式你按照什么方式来去编程,去实现一 ...
- python 学习笔记7 面向对象编程
一.概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发"更快更好更强..." ...
- 2016/1/17 笔记 1,面向对象编程OOP 2,类 全
面向对象编程OOP 编程方式的发展 1,面向过程 重用性低 维护工作量大 2,面向对象 重用性高 维护容易 概念 1,对象 Object 一个具体的事物 是类的实例 2,类Class 同一 ...
- javascript 学习笔记之面向对象编程(一):类的实现
~~想是一回事,做是一回事,写出来又是一回事~~一直以来,从事C++更多的是VC++多一些,从面向过程到面向对象的转变,让我对OO的编程思想有些偏爱,将一个客观存在的规律抽象出来总是让人比较兴奋,通过 ...
- JavaSE学习笔记05面向对象编程01
面向对象编程01 java的核心思想就是OOP 面向过程&面向对象 面向过程思想: 步骤清晰简单,第一步做什么,第二步做什么...... 面向过程适合处理一些较为简单的问题 面向对象思想: 物 ...
- javascript 学习笔记之面向对象编程(二):继承&多态
~~接上篇~~上一篇实现了类的实现以及类成员变量和方法的定义,下面我们来了解下面向对象中两个最重要的特性:继承和多态. 继承 js中同样可以实现类的继承这一面向对象特性,继承父类中的所有成员(变量和属 ...
- python自动化测试学习笔记-7面向对象编程,类,继承,实例变量,邮件
面向对象编程(OOP)术语: class TestClass(object): val1 = 100 def __init__(self): self.val2 = 200 ...
- C++ Primer 学习笔记_67_面向对象编程 --转换与继承、复制控制与继承
面向对象编程 --转换与继承.复制控制与继承 I.转换与继承 引言: 由于每一个派生类对象都包括一个基类部分,因此能够像使用基类对象一样在派生类对象上执行操作. 对于指针/引用,能够将派生类对象的指针 ...
- C++ Primer 学习笔记_69_面向对象编程 --继承情况下的类作用域
面向对象编程 --继承情况下的类作用域 引言: 在继承情况下,派生类的作用域嵌套在基类作用域中:假设不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义. 正是这样的类作用域的层次嵌套使 ...
随机推荐
- 解决git报错
解决git报错:fatal: unable to access "https://github.com/.../.git/" 1.在git中执行(记得分开执行) git confi ...
- 概述:基于事件的优化方法 / 事件驱动优化 / Event-Based Optimization / EBO
大家好,我是月出 本文基于这篇综述,介绍了 事件驱动优化(Event-Based Optimization, EBO). 事件驱动优化,是一种建模现实场景.做优化的思路,理论和 MDP / 强化学习很 ...
- KingbaseES R6 通过脚本构建集群案例
案例说明: KingbaseES V8R6部署一般可采用图形化方式快速部署,但在生产一线,有的服务器系统未启用图形化环境,所以对于KingbaseES V8R6的集群需采用手工字符界面方式部署,本 ...
- 004-GoingDeeperConvolutions2014(googLeNet)
Going Deeper with Convolutions #paper 1. paper-info 1.1 Metadata Author:: [[Christian Szegedy]], [[W ...
- 2021年1月-第02阶段-前端基础-HTML+CSS阶段-Day01
HTML5 第一天 一.什么是 HTML5 1.HTML5 的概念与定义 定义:HTML5 定义了 HTML 标准的最新版本,是对 HTML 的第五次重大修改,号称下一代的 HTML 两个概念: 是一 ...
- 创建Elasticsearch集群并为它们配置TLS安全通信
文章转载自:https://elasticstack.blog.csdn.net/article/details/105636302 文章开头讲述的是两台es主机构建一个集群,其中有关的配置可以借鉴 ...
- docker-compose安装harbor
目录 Harbor 安装环境说明 获取安装包(离线安装方式) 安装harbor 用docker-compose查看Harbor容器的运行状态 Harbor访问测试 上传镜像到Harbor服务器 Har ...
- suse 安装mysql5.7
1.上传包到home目录下 2.安装 1,解压下载的文件: tar -xvf mysql-5.7.29-1.sles12.x86_64.rpm-bundle.tar 解压后: 3.安装libatomi ...
- Java之POI导出Excel(二):多个sheet
相信在大部分的web项目中都会有导出导入Excel的需求,之前我也写过一篇导出单个sheet工作表的文章,没看过的小伙伴可以去看哈,链接也给大家放出来了:导出单个sheet 但是在我们日常的工作中,需 ...
- JavaScript根据参数获取url中参数名的值
//假设ulr如下var localhost="http://127.0.0.1?name=tom&sex=男&id=1";//正则方法封装function Get ...