深入解读 Js 中的面向对象编程

时间:2024-01-03 14:15:56

前言:今天看了一篇文章觉得很不错,所以给大家分享一下,也许很多人都看过面向对象编程甚至写过这样博客,觉得面向对象编程就那样,没啥好说的,那可能是因为你对这方面知识已经了解,可以选择性跳过。那如果有更通俗易懂的解读欢迎分享给我。或者有更好的学习技术书籍推荐也可以在评论下方留言,谢谢。

面向对象的几个概念

在进行面向对象编程,首先了解传统的面向对象编程(例如Java)中常会涉及到的概念,大致可以包括:

  • 类:定义对象的特征。它是对象的属性和方法的模板定义。
  • 对象(或称实例):类的一个实例。
  • 属性:对象的特征,比如颜色、尺寸、大小等。
  • 方法:对象的行为,比如走路、说话等。
  • 构造函数:对象初始化的瞬间被调用的方法。
  • 继承:子类可以继承父类的特征。例如,猫继承了动物的一般特性。
  • 封装:一种把数据和相关的方法绑定在一起使用的方法。
  • 抽象:结合复杂的继承、方法、属性的对象能够模拟现实的模型。
  • 多态:不同的类可以定义相同的方法或属性。

在 JavaScript 的面向对象编程中大体也包括这些。不过在称呼上可能稍有不同,例如,JavaScript 中没有原生的“类”的概念,
而只有对象的概念。因此,随着你认识的深入,我们会混用对象、实例、构造函数等概念。

对象的创建

在js里,我们通常可以使用构造函数来创建特定类型的对象。诸如 Object 和 Array 这样的原生构造函数。此外,我们也可以创建自定义的构造函数。例如:

function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
var person1 = new Person('Excellent', 18, 'Student');
var person2 = new Person('wangli', 20, 'Doctor');

构造函数应该以一个大写字母开头(和Java中定义的类一样),普通函数则小写字母开头。
要创建 Person 的新实例,必须使用 new 操作符。
以这种方式调用构造函数实际上会经历以下4个步骤:

  1. 创建一个新对象(实例)
  2. 将构造函数的作用域赋给新对象(也就是重设了this的指向,this就指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

在上面的例子中,我们创建了 Person 的两个实例 person1 和 person2 。

原型模式定义对象的方法

  //创建面向对象
function Person(age,name,job){
this.name=name;
this.age=age;
this.job=job;
}
   Person.prototype.sayName = function () {
      console.log(this.name);
    };
     /*Person.prototype={
constructor:Person,// 这里重新将构造函数指回Person构造函数
sayname:function(){
console.log(this.name);
}
}*///也可以这样定义
var person1=new Person("38","黎明","明星");
var person2=new Person("18","Excellent","前端开发");
console.log(person1.sayname===person2.sayname);//true
person1.sayname();//黎明
person2.sayname();//Excellent

通过原型模式定义的方法sayName()为所有的实例所共享。也就是,
person1person2访问的是同一个sayName()函数。同样的,公共属性也可以使用原型模式进行定义:

function Chinese (name) {
this.name = name;
}
Chinese.prototype.shcool= '实验中学'; // 公共属性,所有实例共享

组合使用构造函数模式和原型模式

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,
而原型模式用于定义方法和共享的属性。

继承

大多的面向对象语言都支持两种继承方式:接口继承和实现继承。ECMAScript只支持实现继承,而且其实现继承主要依靠原型链来实现。

原型链继承

使用原型链作为实现继承的基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。首先我们先回顾一些基本概念:

  • 每个构造函数都有一个原型对象(prototype
  • 原型对象包含一个指向构造函数的指针(constructor
  • 实例都包含一个指向原型对象的内部指针([[Prototype]]

如果我们让原型对象等于另一个类型的实现,结果会怎么样?显然,此时的原型对象将包含一个指向另一个原型的指针
相应的,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,
如此层层递进,就构成了实例与原型的链条。可参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

简单的例子,演示了使用原型链实现继承的基本框架:

//原型继承
function Father(name){
this.name=name;
}
Father.prototype.sayfname=function(){
console.log(this.name);
}
function Child(cname){
this.cname=cname;
}
Child.prototype=new Father('父级');//继承父级Father
Child.prototype.constructor=Child;
//会切断原型链,导致无法进行继承,变成 Child -> Object
/*Child.prototype={
saycname:function(){
console.log(this.cname);
}
}*/
Child.prototype.saycname=function(){
console.log(this.cname);
}
var child1=new Child('子级');
child1.saycname();
child1.sayfname();

原型链继承的核心语句是Child.prototype = new Father('父级');

,它实现了ChildFather的继承,
而继承是通过创建Father的实例,并将该实例赋给Child.prototype实现的。

深入解读 Js 中的面向对象编程

ps:原型链的问题:

1.首先是顺序,一定要先继承父类,然后为子类添加新方法。

2.其次,使用原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链.如上面注释一样。

借用构造函数继承

借用构造函数(constructor stealing)的基本思想如下:即在子类构造函数的内部调用超类型构造函数。

 //构造函数
function Parent(name){
this.name=name;
this.colors=['blue','pink','yellow']
}
function Childs(name){
Parent.call(this,name);
}
var instance1 =new Childs('Excellent');
instance1.colors.push('black');
console.log(instance1.colors); // [ 'blue', 'pink', 'yellow', 'black' ]
console.log(instance1.name); // Excellent
var instance2 = new Childs("wangli");
console.log(instance2.colors); // ['blue','pink','yellow']
console.log(instance2.name); // wangli

 缺点:同构造函数一样,无法实现方法的复用(所有的方法会被重复创建一份)

组合使用原型链和借用构造函数,混合模式

我们会组合使用原型链继承和借用构造函数来实现继承。也就是说,使用原型链实现对原型属性和方法的继承,
而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

//组合使用原型链和借用构造函数
// 父类构造函数
function Person1(name,age,job){
this.name=name;
this.age=age;
this.job=job;
}
// 父类方法
Person1.prototype.saypname=function(){
console.log(this.name);
}
// 子类构造函数
function Student(name,age,job,school){
Person1.call(this,name,age,job);//继承父级的属性
this.school=school;
}
//createObject()对传入其中的对象执行了一次浅复制。
function createObject(proto) {
function F() { }
F.prototype = proto;
return new F();
}
// 继承父类的原型方法(获得父类原型链上的方法)
Student.prototype = createObject(Person1.prototype);
Student.prototype.constructor=Student;// 设置 constructor 属性指向 Student
// 子类方法
Student.prototype.sayschool=function(){
console.log(this.school);
}
var person1 = new Person1('wangli', 27, 'Student');
var student1=new Student('Lili','16','学生','第一实验中学');
var student2=new Student('Excellent','17','学生','第二实验中学');
person1.saypname();//wangli
student1.saypname();//Lili
student1.sayschool();//第一实验中学
student2.saypname();//Excellent

我们用些更简单的东西去解决,比如ES6中的面向对象语法

S6中引入了一套新的关键字用来实现class
但它并不是映入了一种新的面向对象继承模式。JavaScript仍然是基于原型的,这些新的关键字包括class
constructor
static
extends
super

class关键字不过是提供了一种在本文中所讨论的基于原型模式和构造器模式的面向对象的继承方式的语法糖(syntactic sugar)

前面继承修改

//ES6 class关键字 面向对象的继承方式的语法糖(syntactic sugar)。
"user strict";
class Person2{
constructor(name,age,job){
this.name=name;
this.age=age;
this.job=job;
}
sayName(){
console.log(this.name);
}
}
class Student2 extends Person2{
constructor(name,age,job,school){
super(name,age,job,"Studen2");
this.school=school;
}
saySchool(){
console.log(this.school);
}
}
var stu1 = new Student2('weiwei', 20,'学生','Southeast University');
var stu2 = new Student2('lily', 22, '学生','Nanjing University');
stu1.sayName(); // weiwei
stu1.saySchool(); // Southeast University
stu2.sayName(); // lily
stu2.saySchool(); // Nanjing University

如果还有不太了解ES6的,可以看看阮一峰Es6入门:http://es6.ruanyifeng.com/

了解更多js知识,也可以到这里看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript