JavaScript JS如何实现面向对象和继承机制?

🌙
手机阅读
本文目录结构

问题

JavaScript JS如何实现面向对象和继承机制?

答案

面向对象:常用的创建对象的方式:

通过Object构造函数创建对象

var school = new Object();
school.name = '阿西河前端教程';
school.teacher = '朱安邦';
school.sayName = function () {
  console.log(this.name);
}
school.sayName();

对象字面量创建对象(单例模式)

var school = {
  name: '阿西河前端教程',
  teacher: '朱安邦',
  sayName: function () {
    console.log(this.name);
  }
};

school.sayName();

注意

虽然Object构造函数和对象字面量都可以创造单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。

工厂模式

var createSchool = function (name, teacher) {
  var o = new Object();
  o.name = name;
  o.teacher = teacher;
  o.sayName = function () {
    console.log(this.name);
  }
  return o;
}

var school = createSchool("阿西河前端教程", "朱安邦");
school.sayName();

注意

注意:工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

构造函数模式

var School = function (name, teacher) {
  this.name = name;
  this.teacher = teacher;
  this.sayName = function () {
    console.log(this.name);
  }
}

var school = new School("阿西河前端教程", "朱安邦");
school.sayName();

注意:使用构造函数的主要问题是每个方法都要在每个实例上重新创建一遍,所以上面sayName与下面代码是逻辑上等价的:

this.sayName = new Function('console.log(this.name)')

因此,不同实例上的同名函数是不相等的:

var school1 = new School("阿西河前端教程", "朱安邦");
var school2 = new School("阿西河前端教程", "朱安邦");
console.log(school1.sayName === school2.sayName)

然后,创建两个完成相同任务的Function实例的确没有必要,如果把函数转移到构造函数外部,会解决此问题:

var School = function (name, teacher) {
  this.name = name;
  this.teacher = teacher;
  this.sayName = sayName;
}

function sayName () {
  console.log(this.name);
}
var school1 = new School("阿西河前端教程", "朱安邦");
var school2 = new School("阿西河前端教程", "朱安邦");
console.log(school1.sayName === school2.sayName)

注意

上面的修改虽然解决了多次实例化Funtion对象,但是全局函数过多的话就没有封装性可言了。

原型模式

我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是所有实例共享属性和方法:

var School = function () { }
School.prototype.name = '阿西河前端教程';
School.prototype.teacher = '朱安邦';
School.prototype.sayName = function () {
  console.log(this.name);
}

var school1 = new School();
var school2 = new School();
console.log(school1.sayName === school2.sayName)

注意

注意:school1.sayName===school2.sayName比较结果为true,因为指向内存中同一地址。

大多数情况下并不是所有属性方法都要共享,比如nameteacher,所以更多情况用构造函数和原型模式的组合方式。

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

var School = function (name, teacher) {
  this.name = name;
  this.teacher = teacher;
  this.domain = 'axihe.com';
  this.students = [
    '朱一',
    '朱二'
  ];
}
School.prototype = {
  //因为是字面量类型定义原型,相当于重写原型,要重新设置构造函数的指向
  constructor: School,
  sayName: function () {
    console.log(this.name);
  }
}

var school1 = new School('阿西河前端教程', '朱安邦');
var school2 = new School('阿西河前端教程', '朱安邦');
school1.students.push('朱三');

console.log(school1.students);//[ '朱一', '朱二', '朱三' ]
console.log(school2.students);//[ '朱一', '朱二' ]
console.log(school1.students === school2.students);//false
console.log(school1.sayName === school2.sayName);//true

注意

  1. 实例属性都是在构造函数中定义的,而所有实例共享的属性和方法都是在原型中定义的。

  2. 这种构造函数与原型混成的模式,是目前在ECMAScript中使用的最广泛、认同度最高的一种创建自定义的方法。可以说,这是用来定义引用类型的一种默认模式。

继承机制继承是面向对象语言的三大特性之一(继承、封装、多态),许多面向对象语言都支持两种继承方式:接口集成和实现集成。

接口继承只继承方法签名,而实现集成则继承实际的方法。

由于函数没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现继承。原型链原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

代码如下:

function SuperType () {
  this.name = '阿西河前端教程';
  this.teacher = ['朱1', '朱2'];
};
function SubType () { };

//继承SuperType
SubType.prototype = new SuperType();//共享了实例

var instance1 = new SubType();
instance1.teacher.push('朱3');
console.log(instance1.teacher);//[ '朱1', '朱2', '朱3' ]


var instance2 = new SubType();
console.log(instance2.teacher);//[ '朱1', '朱2', '朱3' ]

原型链的问题

  1. 共享了引用类型属性和方法。

  2. 创建子类型的实例时,不能向超类型的构造函数中传递参数。借用构造函数这种技术的基本思想是在子类型构造函数的内部调用超类型构造函数。

function SuperType (name) {
  this.name = name;
  this.teacher = ['朱1', '朱2'];

  // 每实例化一次,创建一次Function对象
  this.sayName = function () {
    console.log(this.name)
  }
};
function SubType (name) {
  //继承了SuperType
  SuperType.call(this, name);
};

var instance1 = new SubType('阿西河前端教程');
instance1.teacher.push('朱3');
console.log(instance1.teacher);//[ '朱1', '朱2', '朱3' ]


var instance2 = new SubType();
console.log(instance2.teacher);//[ '朱1', '朱2' ]

借用构造函数的问题

  1. 如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了

  2. 在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。组合继承将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。也是JavaScript中最常用的继承模式。

function SuperType (name) {
  this.name = name;
  this.teacher = ['朱1', '朱2'];
};

SuperType.prototype.sayName = function () {
  console.log(this.name)
}
function SubType (name, address) {
  //继承了SuperType
  SuperType.call(this, name);
  this.address = address;
};
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAddress = function () {
  console.log(this.address)
};

var instance1 = new SubType('阿西河前端教程', 'axihe.com');
instance1.teacher.push('朱3');
console.log(instance1.teacher);//[ '朱1', '朱2', '朱3' ]
instance1.sayName();//阿西河前端教程
instance1.sayAddress();//axihe.com


var instance2 = new SubType('阿西河', 'AXIHE.COM');
console.log(instance2.teacher);//[ '朱1', '朱2' ]
instance2.sayName();//阿西河
instance2.sayAddress();//AXIHE.COM

因为SubType原型是一个SuperType实例,

此时SubType.prototype.constructor指向SuperType构造函数,所以需要重新给SubType.prototype.constructor定义指针指向SubType的构造函数;

更多面试题

如果你想了解更多的前端面试题,可以查看本站的WEB前端面试题 ,这里基本包涵了市场上的所有前端方面的面试题,也有一些大公司的面试图,可以让你面试更加顺利。

面试题
HTML CSS JavaScript
jQuery Vue.js React
算法 HTTP Babel
BootStrap Electron Gulp
Node.js 前端经验相关 前端综合
Webpack 微信小程序 -

这些题库还在更新中,如果你有不错的面试题库欢迎分享给我,我整理后放上来;人人为我,我为人人,互帮互助,共同提高,祝大家都拿到心仪的Offer!

AXIHE / 精选资源

浏览全部教程

面试题

学习网站

前端培训
自己甄别

前端书籍

关于朱安邦

我叫 朱安邦,阿西河的站长,在杭州。

以前是一名平面设计师,后来开始接接触前端开发,主要研究前端技术中的JS方向。

业余时间我喜欢分享和交流自己的技术,欢迎大家关注我的 Bilibili

关注我: Github / 知乎

于2021年离开前端领域,目前重心放在研究区块链上面了

我叫朱安邦,阿西河的站长

目前在杭州从事区块链周边的开发工作,机械专业,以前从事平面设计工作。

2014年底脱产在老家自学6个月的前端技术,自学期间几乎从未出过家门,最终找到了满意的前端工作。更多>

于2021年离开前端领域,目前从事区块链方面工作了