设计模式
今天开始初学设计模式,在此记录以便日后复习。
什么是设计模式
一个模式就是一个可重用的方案,可应用于在软件设计中的常见问题,另一种解释就是一个我们如何解决问题的模板 - 那些可以在许多不同的情况里使用的模板。 --w3cschool
我为什么要学习设计模式
- 高级工程师面试必考知识点
- 写出更好的代码,设计模式是必经之路
- 掌握设计模式更容易阅读流行框架源码
- 想要成为项目负责人,从零架构工程,设计模式是基石
- 没事装个逼
- ......我学习设计模式的四个阶段
- 巩固面向对象相关知识,es6 class语法,封装,继承,多态等
- 掌握设计原则
- 掌握设计模式
- 最后做一个练习案例面向对象 学习设计模式,面向对象是基础,那就先来复习一下面向对象和函数式编程的区别 数据存放方式
- 对于面向对象,数据存放在对象的属性里面,以及全局变量。
- 对于函数式,数据存放在各级作用域里面,作用域包括全局作用域。
数据访问方式
数据存放方式决定了访问的方式。
- 对于面向对象来说,访问数据(全局变量除外)需要先获取对象的引用,然后再进行操作(直接访问——公共属性,或者调用成员函数/方法访问——私有属性)
- 对于函数式,访问数据是直接访问(通过函数入参或者作用域链查找)基于类的面向对象和基于原型的面向对象 基于类的面向对象 类定义了所有用于具有某一特征对象的属性。类是抽象的事物,而不是其所描述的全部对象中的任何特定的个体。另一方面,一个实例是一个类的实例化,是其中的一个成员
基于原型的面向对象
构造函数(constructor),实例(instance),原型(prototype)本身都是对象。基于原型的语言具有所谓的原型对象的概念,新对象可以从中获得原始的属性。
在JavaScript
中有一个很有意思的__proto__
属性用于访问其原型对象,构造函数,实例,原型本身都有__proto__
指向原型对象。最后顺着原型链都会指向Object
这个构造函数,然而Object
的原型对象的原型是null
通过原型添加方法
代码语言:javascript复制Student.prototype.study = function() {
console.log('我在学习' this.subject);
}
为什么要将方法挂载到原型上?
对于每一个实例对象,其方法的函数体是一模一样的,方法的执行结果只根据其实例对象决定,然而生成的每个实例都需要生成一个方法去占用一份内存。
ES6之前的面向对象
在ES6
之前创建对象有两种方式,这两种方式在特定的使用场景分别有其优点和缺点, 下面我们来分别介绍这两种创建对象的方式。
- 第一种是使用对象字面量的方式
- 第二种是使用构造函数的方式使用对象字面量的方式
我们通过对象字面量的方式创建两个
student
对象,分别是student1
和student2
。
var student1 = {
name: '小呆',
age: 22,
subject: '前端开发'
};
var student2 = {
name: '小傻',
age: 22,
subject: '后端开发'
};
对象字面量的方式在创建单一简单对象的时候是非常方便的。但是,它也有其缺点:
- 在生成多个实例对象时, 我们需要每次重复写很多属性,写起来特别的麻烦。
- 虽然都是学生的对象, 但是看不出两个对象之间有什么联系。使用构造函数的方式
构造函数就其实就是一个普通的函数,当对构造函数使用
new
进行实例化时,会将其内部this
的指向绑定到实例对象上.
我们来创建一个Student
构造函数(构造函数通常使用大写字母开头,和普通函数做区分)。
function Student (name, age, subject) {
this.name = name;
this.age = age;
this.subject = subject;
console.log(this);
}
我们在构造函数中打印出this
的指向。因为构造函数其实就是一个普通的函数, 那么我们便可以使用普通函数的调用方式去调用Student
。
Student('小呆', 22, '前端开发'); //window{}
可以看到,采用普通方式调用Student
时, this
的指向是window
。下面使用new
来实例化该构造函数, 生成一个实例对象student1
。
let student1 = new Student('小呆', 22, '前端开发');
//Student {name: "阿辉", age: 22, subject: "前端开发"}
可以看到,当我们采用new
生成实例化对象student1
时, this
不再指向window
, 而是指向的实例对象本身。
上面的就是采用构造函数的方式生成实例对象的方式, 并且当我们生成其他实例对象时,由于都是采用Student
这个构造函数实例化而来的, 我们能够清楚的知道各实例对象之间的联系。
new都做了什么?
- 创建一个新对象
- 分配内存,将构造函数的作用域赋值给新对象,即this指向这个新对象
- 执行构造函数中的代码
- 返回新对象
ES6中的面向对象
ES6
中提供了基于类class
的语法。但class
本质上是ES6提供的一颗语法糖,但实际上JavaScript
是一门基于原型的面向对象语言。
ES6
中使用class
来创建对象
//定义类
class Student {
//构造方法
constructor(name, age, subject) {
this.name = name;
this.age = age;
this.subject = subject;
}
//类中的方法
study(){
console.log('我在学习' this.subject);
}
}
//实例化类
let student3 = new Student('小痴', 24, '前端开发');
student3.study(); //我在学习前端开发
上面的代码定义了一个Student
类, 可以看到里面有一个constructor
方法, 这就是构造方法,而this
关键字则代表实例对象。也就是说,ES5
中的构造函数Student
, 对应的是Es6
中Student
类中的constructor
方法。
Student
类除了构造函数方法,还定义了一个study
方法。方法之间不要用逗号分隔,加了会报错。而且,类中的方法全部是定义在原型上的,我们可以用下面的代码进行验证。
console.log(student3.__proto__.study === Student.prototype.study); //true
console.log(student3.hasOwnProperty('study')); // false
上面的第一行的代码中, student3.__proto__
是指向的原型对象,其中Student.prototype
也是指向的原型的对象,结果为true
就能很好的说明类中的方法全部是定义在原型上的。
第二行代码是验证student3
实例中是否有study
方法,结果为false
, 表明实例中没有study
方法,这也更好的说明了上面的结论。
而将study
方法挂载到prototype
原型对象,所有的实例都能继承该方法,多个实例的方法指向了同一个内存地址。
实例与类
javascript中实例方法与类方法的区别
- 类属性(静态属性):通过类直接访问,不需要声明类的实例来访问
- 类方法(静态方法):通过类直接访问,不需要声明类的实例来访问
- 实例属性(动态属性):不能通过类直接访问,需要通过该类声明的实例才能访问
- 实例方法(动态方法):不能通过类直接访问,需要通过该类声明的实例才能访问
Person = function(){
}
Persion.sex = "woman"; //类属性
Person.eat= function(){ //类方法
alert("eat");
}
Person.prototype.age = 10; //实例属性
Person.prototype.dance = function(){ //实例方法
alert("dance");
}
const person = new Person();
person.age;//实例属性
person.dance();//实例方法
Person.sex;//静态属性
Person.eat();//静态方法
ES6中的实例与类
静态属性和实例属性
静态属性需要使用static关键字
代码语言:javascript复制class Foo {
static num = 1;
age = 2
}
const foo = new Foo();
console.log(Foo.num);// 1
console.log(foo.num);// undefined
console.log(Foo.age);// undefined
console.log(foo.age);// 2
如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
静态方法需要使用static关键字
代码语言:javascript复制class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
const foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
静态方法可以与非静态方法重名
如果静态方法包含this
关键字,这个this
指的是类,而不是实例。
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
const foo = new Foo();
Foo.bar() // hello
foo.bar() // world
父类的静态方法,可以被子类继承。
代码语言:javascript复制class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
静态方法也是可以从super对象上调用的
代码语言:javascript复制class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() ', too';
}
}
Bar.classMethod() // "hello, too"
封装,继承,多态
面向对象三大特性:封装、继承、多态
封装
封装就是将数据操作的细节隐藏起来,只暴露对外的接口,外界调用端不需要也不可能知道细节,只能通过通过对外暴露的接口来访问该对象
代码语言:javascript复制class School {
// 构造器:创建对象完成初始化操作
constructor(id, name) {
this.id = id
this.name = name
}
// 实例方法
schoolName() {
console.log(this.name)
}
// 类方法
static schoolOnly() {
console.log('我是类方法只可通过函数本身调用')
}
}
继承
继承表示子类继承父类,子类除了拥有父类所有的特征以外,还有一些更具体的特性;
代码语言:javascript复制class Student extends School {
constructor(sId, sName, id, name) {
super(id, name)
this.sId = sId
this.sName = sName
}
studentName() {
console.log(this.sName)
}
say() {
console.log('I am a student')
}
}
多态
由继承产生的多个不同的类,对同一个方法可以有不同的响应,比如学生和老师都可以继承自学校,但是它们分别实现了自己的say
方法;此时针对某一个实例,我们无需了解他是学生还是老师,我们可以直接调用say
方法,程序会自动判断应该如何执行这个方法;
class Teacher extends School {
constructor(tId, tName, id, name) {
super(id, name)
this.tId = tId
this.tName = tName
}
TeacherName() {
console.log(this.tName)
}
say() {
console.log('I am a teacher')
}
}
测试
let school = new School(1, '第一小学')
let student = new Student(10, 'Daming', 1, '第一小学')
let teacher = new Teacher(100, 'MrLi', 1, '第一小学')
console.log(student) //Object Student {id: 1, name: "第一小学", sId: 10, sName: "Daming"}
console.log(teacher) //Object Teacher {id: 1, name: "第一小学", tId: 100, tName: "MrLi"}
student.studentName() //Daming
student.schoolName() //第一小学
student.say() //I am a student
teacher.say() //I am a teacher
School.schoolOnly() //我是类方法只可通过函数本身调用
小伙伴们觉的对你有帮助的请点赞