前言
在ES6中出现了class
与extends
关键字,使js做类与继承容易了很多,但是在ES6之前如何实现类与继承是理解js重要的一部分。本篇文章将介绍ES6的类、继承与ES6之前的类、继承的写法,一篇文章介绍所有内容。
类
下面定义的类全部为Person
类,Person
类有age
与name
两个属性,sayName
与sayAge
两个方法。
ES5定义类
伴随着JS的发展,定义类的方式共有7种,最后2种除特殊场景外不推荐:
- 工厂模式
- 构造函数模式
- 原型模式
- 组合使用构造函数模式与原型模式(最常用)
- 动态原型模式
- 寄生构造函数模式(特殊场景)
- 稳妥构造函数模式(特殊场景)
接下来说明各种方式如何定义类,以及缺点。
工厂模式
示例代码如下:
function createPerson(name, age){
let tempPerson = new Object();
tempPerson.name = name;
tempPerson.age = age;
tempPerson.sayName = function(){
console.log(`My name is ${this.name}`);
}
tempPerson.sayAge = function(){
console.log(`My age is ${this.age}`);
}
return tempPerson;
}
const person1 = createPerson('小刘先森', 26);
person1.sayName(); // 输出:My name is 小刘先森
person1.sayAge(); // 输出: My age is 26
const person2 = createPerson('小刘儿', 26);
person2.sayName(); // 输出:My name is 小刘儿
person2.sayAge(); // 输出: My age is 26
缺点:没有解决对象识别的问题,即怎么样知道一个对象的类型
构造函数模式
示例代码如下:
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(`My name is ${this.name}`);
}
this.sayAge = function(){
console.log(`My age is ${this.age}`);
}
}
const person1 = new Person('小刘先森', 26) // 名字与年龄均为本作者 哈哈
person1.sayName(); // 输出:My name is 小刘先森
person1.sayAge(); // 输出: My age is 26
const person2 = new Person('小刘儿', 26)
person2.sayName(); // 输出:My name is 小刘儿
person2.sayAge(); // 输出: My age is 26
缺点:每个实例都会有一份没必要的方法副本,即person1.sayName !== person2.sayName
原型模式
示例代码如下:
function Person(){
}
Person.prototype.name = '小刘先森';
Person.prototype.age = '26';
Person.prototype.sayName = function(){
console.log(`My name is ${this.name}`);
}
Person.prototype.sayAge = function(){
console.log(`My age is ${this.age}`);
}
const person1 = new Person('小刘先森', 26) // 名字与年龄均为本作者 哈哈
person1.sayName(); // 输出:My name is 小刘先森
person1.sayAge(); // 输出: My age is 26
const person2 = new Person('小刘儿', 26)
person2.sayName(); // 输出:My name is 小刘儿
person2.sayAge(); // 输出: My age is 26
缺点:缺点很明显,属性全部写在了prototype上,并且为固定值,无论实例化几个类都会输出一样的值。
组合使用构造函数模式与原型模式(最常用)
示例代码如下:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function(){
console.log(`My name is ${this.name}`);
}
Person.prototype.sayAge = function(){
console.log(`My age is ${this.age}`);
}
const person1 = new Person('小刘先森', 26) // 名字与年龄均为本作者 哈哈
person1.sayName(); // 输出:My name is 小刘先森
person1.sayAge(); // 输出: My age is 26
const person2 = new Person('小刘儿', 26)
person2.sayName(); // 输出:My name is 小刘儿
person2.sayAge(); // 输出: My age is 26
这是最常用的自定义类的方式,每个实例都会有一份自己的实例属性副本,同时又共享着对方法的引用。
动态原型模式
示例代码如下:
function Person(name, age){
// 属性
this.name = name;
this.age = age;
// 方法
if( typeof this.sayName !== 'function'){
Person.prototype.sayName = function(){
console.log(`My name is ${this.name}`);
}
Person.prototype.sayAge = function(){
console.log(`My age is ${this.age}`);
}
}
}
const person1 = new Person('小刘先森', 26) // 名字与年龄均为本作者 哈哈
person1.sayName(); // 输出:My name is 小刘先森
person1.sayAge(); // 输出: My age is 26
const person2 = new Person('小刘儿', 26)
person2.sayName(); // 输出:My name is 小刘儿
person2.sayAge(); // 输出: My age is 26
其中方法定义的部分,判断sayName是否已经存在,不存在再去定义,if语句中的代码只会在初次实例化时执行,也同样达到了类的定义方式,但是却没有原型与构造函数组合方式代码简洁,不推荐使用。
缺点:不能使用对象字面量方式重写原型,如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系
寄生构造函数模式
示例代码如下:
function Person(name, age){
let tempPerson = new Object();
tempPerson.name = name;
tempPerson.age = age;
tempPerson.sayName = function(){
console.log(`My name is ${this.name}`);
}
tempPerson.sayAge = function(){
console.log(`My age is ${this.age}`);
}
return tempPerson;
}
const person1 = new Person('小刘先森', 26);
person1.sayName(); // 输出:My name is 小刘先森
person1.sayAge(); // 输出: My age is 26
const person2 = new Person('小刘儿', 26);
person2.sayName(); // 输出:My name is 小刘儿
person2.sayAge(); // 输出: My age is 26
这种模式除了在实例化时使用new
操作符,其余的与工厂模式完全相同。这种模式可以在特殊场景下为对象创建构造函数。
场景:创建一个具有额外方法的数组,由于不能修改Array
的构造函数,可以使用该模式
function SpecialArray(){
//创建数组
var values = new Array();
//添加值
values.push.apply(values, arguments);
//添加方法
values.toPipedString = function(){
return this.join("|");
};
//返回数组
return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"
稳妥构造函数模式
道格拉斯·克罗克福德(Douglas Crockford)发明了 JavaScript 中的稳妥对象(durable objects)这个概念,所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用 this 和new),或者在防止数据被其他应用程序改动时使用。
示例代码如下:
function Person(name, age){
let tempPerson = new Object();
tempPerson.sayName = function(){
console.log(`My name is ${name}`);
}
tempPerson.sayAge = function(){
console.log(`My age is ${age}`);
}
return tempPerson;
}
const person1 = new Person('小刘先森', 26);
person1.sayName(); // 输出:My name is 小刘先森
person1.sayAge(); // 输出: My age is 26
const person2 = new Person('小刘儿', 26);
person2.sayName(); // 输出:My name is 小刘儿
person2.sayAge(); // 输出: My age is 26
注意:
- 以上方式创建的类不引用
this
- 实例化时不使用
new
- 除了调用
sayName
或者sayAge
没有其它方法可以访问到构造函数中传入的数据,即使再添加其它方法也没办法访问到。
ES6定义类
ES6使用class
关键字定义一个类,定义一个Person
类代码如下:
class Person{
// 构造函数
constructor(name, age){
this.name = name;
this.age = age;
}
// 方法
sayName(){
console.log(`My name is ${this.name}`)
}
// 方法
sayAge(){
console.log(`My age is ${this.age}`)
}
}
const person1 = new Person('小刘先森', 26) // 名字与年龄均为本作者 哈哈
person1.sayName(); // 输出:My name is 小刘先森
person1.sayAge(); // 输出: My age is 26
const person2 = new Person('小刘儿', 26)
person2.sayName(); // 输出:My name is 小刘儿
person2.sayAge(); // 输出: My age is 26