JavaScript语言精粹-笔记3-继承
继承(Inheritance)
在基于类的语言中, 对象是类的实例, 并且类可以从另一个类继承. JavaScript是一门基于原型的语言, 这意味着对象直接从其他对象继承.
伪类(Pseudoclassical)
文章没有对伪类做解释, 本人理解是JavaScript是没有类这个概念的, 但却实现了一套和类很相似的机制, 由于它本身并不是类的实现, 因此我们这里把这种实现称为伪类.
当一个函数对象被创建时, Function构造器产生的函数对象会运行类似这段代码: this.prototype = { constructor: this }
, 新函数对象被赋予一个prototype属性, 它的值是一个包含constructor属性且属性值为该函数的对象. 这个prototype对象是存放继承特征的地方. 因为JavaScript语言没有提供一种方法去确定哪个函数是打算用来做构造器的, 所以每个函数都会得到一个prototype对象. constructor属性没有什么用, 重要的是prototype对象.
说白了就是, this.prototype
是什么就是继承什么, 要实现继承关系只要将继承对象赋值给this.prototype
就可以完成继承
上面这段话需要仔细得品读, 我们做个试验:
// 创建函数字面量func
var Func = function() {};
// 验证上面的结论
Func.prototype.constructor === Func // true
// 实例化func赋值给funcer
var funcer = new func();
funcer.constructor === Func // true
// 可见 Func.prototype.constructor === funcer.constructor === Func
// 我们好好想想到底new做了什么
当使用构造器调用模式, new做了什么, 我们可以将new操作符转换成new方法看下:
// Function.method方法是前面文章定义好的
Function.method('new', function () {
// 创建一个新对象, 它继承自构造器函数的原型对象, 这一句就相当与new操作符了
var that = Object.create(this.prototype);
// 调用构造器函数, 绑定this到新对象上, 这一句用于捆绑参数
var other = this.apply(that, arguments);
// 如果它的返回值不是一个对象, 就返回该对象
return (typeof other === 'object' && other) || that;
});
接下来看看继承到底做了什么?
新建一个叫Mammal的伪类
// 定义一个伪类并扩充它的原型:
var Mammal = function (name) {
this.name = name;
};
Mammal.prototype.get_name = function () {
return this.name;
}
Mammal.prototype.says = function () {
return this.saying || '';
}
// 创造一个实例
var myMammal = new Mammal('Herb the Mammal');
var name = myMammal.get_name(); // Herb the Mammal
新建一个叫Cat的伪类并继承自Mammal
// 创建另一个伪类Cat
var Cat = function (name) {
this.name = name;
this.saying = 'meow';
}
// 实现Cat继承于Mammal
Cat.prototype = new Mammal();
// 扩充原型对象, 增加purr和get_name方法
Cat.prototype.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i ++) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
};
Cat.prototype.get_name = function () {
return this.say() + ' ' + this.name + ' ' + this.says();
}
var myCat = new Cat('Henrietta');
var says = myCat.say(); // meow
var purr = myCat.purr(5); // r-r-r-r-r
var name = myCat.get_name(); // meow Henrietta meow
上面的Cat通过修改prototype为new Mammal()
来继承Mammal伪类, 并可以看到新的get_name函数替代了Mammal的get_name函数, 且say函数可以继续使用, 不过代码但看起来有些格格不入, 我们做一些修改:
// 在Function上定义继承函数inherits
Function.method('inherits', function (Parent) {
this.prototype = new Parent();
return this;
});
// 通过之前定义的method和inherits函数对代码作变更
var Cat = function (name) {
this.name = name;
this.saying = 'meow';
}
.inherits(Mammal)
.method('purr', function (n) {
var i, s = '';
for (i = 0; i < n; i ++) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
})
.method('get_name', function () {
return this.say() + ' ' + this.name + ' ' + this.says();
})
对象说明符(Object Specifiers)
其实就是让我们将传参方式从一个一个传入变为传入一个对象
原型(Prototypal)
在一个纯粹的原型模式中, 我们会摒弃类, 转而专注于对象. 基于原型的继承相比基于类的继承在概念上更容易理解. 我们用基于原型的继承来实现和前面基于伪类的继承一样的功能:
// 定义对象字面量
var myMammal = {
name: 'Herb the Mammal',
get_name: function () {
return this.name;
},
says: function () {
return this.saying || '';
},
};
// 使用之前定义的Object.create实现继承, 即: myCat.prototype = myMammal
var myCat = Object.create(myMammal);
myCat.anme = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i ++) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
}
myCat.get_name = function () {
return this.say() + ' ' + this.name + ' ' + this.says();
}
函数化(Functional)
不管上面的伪类继承还是原型继承都有一个弱点就是无法保护隐私, 对象的所有属性都是可见的, 我们可以通过结合闭包概念对上面的示例进行优化:
// 定义对象生成函数mammal
var mammal = function (spec) {
var that = {};
that.get_name = function () {
return spec.name;
};
that.says = function () {
return spec.saying || '';
};
return that;
}
// 定义对象生成函数cat, 并且继承mammal
var cat = function (spec) {
spec.saying = spec.saying || 'meow';
var that = mammal(spec);
that.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i ++) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
};
that.get_name = function() {
return that.says() + ' ' + spec.name + ' ' + that.says();
};
return that;
}