JS基础整理(2)—定义类和继承的几种方法
1. 创建类
1.1. 简单的类
类指一组对象从同一个原型对象继承属性,原型对象
是类的核心特征。
定义一个原型对象,然后用Object.create()创建一个继承它的对象,我们就定义了一个JavaScript类。
//工厂函数,用于创建Range对象
function range(from, to) {
//使用Object.create()创建一个对象,继承原型对象
let r = Object.create(range.methods);
r.from = from;
r.to = to;
return r;
}
// 定义一个原型对象
range.methods = {
includes(x){
//通过this引用调用from和to的对象
return this.from<=x && x<=this.to;
},
// 生成器函数:让这个类的实例可迭代
*[Symbol.iterator](){
for(let x = Math.ceil(this.from); x<=this.to; x++){
yield x;
}
},
toString(){return "("+this.from +"..."+this.to+")";}
}
let r = range(1,10); // 创建一个对象
console.log(r.includes(2));
console.log(r.includes(11));
console.log(r.toString());
console.log([...r]);
1.2. 使用构造函数的类
上面的方法定义了JavaScript类,但是它没有定义构造函数。构造函数
是专门用于初始化新对象的函数,使用new
关键字调用构造函数会自动创建新对象。构造函数调用的关键在于构造函数的prototype
属性被用作新对象的原型。
只有函数对象才有prototype属性,同一个构造函数创建的所有对象都继承同一个对象。
在不支持的ES6 class关键字的JavaScript版本中,用以下的方法创建类。
// 构造函数
function Range(from, to){
this.from = from;
this.to = to;
}
// 所有Range对象都继承这个对象,prototype这个名字是强制的
Range.prototype={
//不要使用箭头函数,因为箭头函数没有prototype属性,this是从定义它的上下文继承的
includes:function(x) {
return this.from<=x && x<=this.to;
},
[Symbol.iterator]:function*() {
for(let x = Math.ceil(this.from); x<=this.to; x++){
yield x;
}
},
toString:function(){return "("+this.from +"..."+this.to+")";}
}
//以new关键字调用构造函数
let r = new Range(1,10);
console.log(r.includes(2));
console.log(r.includes(11));
console.log(r.toString());
console.log([...r]);
对比以上两个例子,有以下区别:
- 工厂函数命名为range(),构造函数命名为Range();
- 创建对象的时候,工厂函数使用raneg(),构造函数使用new Range()
函数体内有一个特殊表达式new.target
用于判断函数是否作为构造函数,如果new.target不是undefined,说明函数作为构造函数,会自动创建新对象
function F(){
if(!new.target) return new F();
}
上面提到,构造函数调用的关键在于构造函数的prototype
属性被用作新对象的原型。所以,每个普通JavaScript函数自动拥有一个prototype
属性,这个属性有一个不可枚举的constructor
属性。
constructor
属性的值就是该函数对象本身
let F = function (x) {this.x = x}
let p = F.prototype;
let c = p.constructor;
console.log("c === F: ", c === F); // true, F.prototype.constructor === F
注意,上面的Range的例子中,由于用自己定义的对象Range.prototype = {}重写了预定义的Range.prototype对象,所以Range的实例都没有constructor属性。
let o = new F();
console.log(o.constructor === F); // true
console.log(r.constructor === Range); // ?
上面r.constructor === Range返回的是false。
常用的方法是使用预定义的原型对象及其constructor属性,然后通过以下方式添加方法:
Range.prototype.includes = function(x){}
1.3. 使用ES6的class
ES6引入class
关键字,可以使用新语法创建类
class Range{
//实际上定义的函数不叫constructor
//class会定义一个新变量Range,并将这个特殊构造函数的值赋给改变量
constructor(from, to){
this.from = from;
this.to = to;
}
//methods
//方法之间没有逗号
//不支持key:value形式
includes(x){
//通过this引用调用from和to的对象
return this.from<=x && x<=this.to;
}
*[Symbol.iterator](){
for(let x = Math.ceil(this.from); x<=this.to; x++){
yield x;
}
}
toString(){return "("+this.from +"..."+this.to+")";}
}
2. 继承的几种方法
// 父类
function Animal(name){
this.name = name || "cat";
this.sleep = function(){console.log(`${this.name} is sleeping.`);}
}
- 原型链继承
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
let cat = new Cat();
cat.sleep();
- 构造继承
function Cat2(name){
//使用父类的构造函数来增强子类实例, 复制父类的实例属性给子类
Animal.call(this);
this.name = name;
}
//实例并不是父类的实例,只是子类的实例
//只能继承父类的属性和方法,不能继承原型链的
//无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
let c2 = new Cat2("cat2");
c2.sleep();
- 实例继承
function Cat3(name){
//为父类实例添加新特性,作为子类实例返回
let instance = new Animal();
instance.name = name||'Tom';
return instance;
}
//实例是父类的实例,不是子类的实例
let c3 = new Cat3();
c3.sleep();
- 拷贝继承
function Cat4(name){
let a = new Animal();
//拷贝父类的属性和方法
//效率较低,内存占用高
//无法获取父类不可枚举的方法(不能使用for in访问)
for(let p in a){
Cat4.prototype[p] = a[p];
}
this.name = name;
}
let c4 = new Cat4('cat4');
c4.sleep();
- 组合继承
function Cat5(name){
Animal.call(this);
this.name = name ||'Tom';
}
//通过调用父类构造,可以继承实例属性/方法,也可以继承原型属性/方法
//将父类实例作为子类原型,实现函数复用
//调用了两次父类构造函数,生成了两份实例
Cat5.prototype = new Animal();
Cat5.prototype.constructor = Cat;
//既是子类的实例,也是父类的实例
let c5 = new Cat5('cat5');
c5.sleep();
- 寄生组合继承
//调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Cat6(name){
Animal.call(this);
this.name = name || "Tom";
}
(function(){
//通过寄生方式,砍掉父类的实例属性
let Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat6.prototype = new Super();
})();
Cat6.prototype.constructor = Cat6;
let c6 = new Cat6('cat6');
c6.sleep();
- 基于ES6的class的继承
class Span extends Range{
constructor(start, length){
if(length>0){
super(start, start+length);
}else{
super(start+length, start);
}
}
}
最近在看高频面试题,经常看到继承的问题,之后再来补充一下~~~~
还有类涉及对象和原型链的问题,可能也会总结一下
发表评论 (审核通过后显示评论):