伪类
首先要记住一件事,js中没有类的概念。
function Animal(name){
this.name = name
}
Animal.prototype.say = function(){
console.log("I'm", this.name)
}
function Bird(name, type){
Animal.call(this, name)
this.type = type
}
Bird.prototype = Object.create(Animal.prototype)
上面的代码中,我们创建了两个“类”,Bird继承了Animal,我们创建的bird都会继承到animal中的say方法。
当一个函数对象被创建时,Function构造器产生的函数对象会运行一些代码,类似
this.prototype = {constructor: this}
所有的函数对象在被创建时都会得到一个prototype属性。
但是这种方法不是纯粹的原型继承。在构造对象的时候,我们不能忘记使用new关键字,不然this将无法指向我们新创建的对象,造成全局变量污染。
原型继承
正确的做法是,不要在写构造函数了(不要使用伪类),直接写需要的对象。比如当我们需要一个animal的时候,就直接写一个animal出来,而当你需要一个bird的时候,先用Object.create创建一个原型链到animal的原型上的对象,然后再添加bird需要的属性。当你需要一个鸭子的时候,先用Object.create创建一个原型链到bird的原型上的对象,然后再添加duck需要的属性。
var myAnimal = {
name : 'myAnimal',
get_name: function(){
console.log("I'm",this.name)
}
}
var myBird = Object.create(myAnimal)
myBird.name = "myBird"
myBird.type = "Duck"
myBird.get_name()
var mytanglaoya = Object.create(myBird)
mytanglaoya.name = "唐老鸭"
mytanglaoya.get_name()
为啥不直接连过去
有的同学可能会问,上面的代码好像很麻烦哎,为什么不能直接原型连过去,就像下面这样
var foo = {bar:"bar1"}
foo.prototype = Object.prototype
foo.prototype.getBar = function(){
return this.bar
}
var baz = {bar:"bar2"}
baz.prototype = foo.prototype
console.log(baz.getBar()) //bar2
看上去一切都很美好,但是有一天,baz不爽了,它觉得自己应该和其他的foo区分开,于是,它修改了自己的原型,在每次getBar的时候先打上自己的名号
baz.prototype.getBar = function(){
return "baz: " + this.bar
}
console.log(foo.getBar()) //baz: bar1
然而没有想到的是,baz的原型和foo是同一个,他在修改自己原型的时候,其实修改了foo的原型,于是...
我们可以用Object.getPrototypeOf来查看对象的原型
console.log(Object.getPrototypeOf(foo) === Object.getPrototypeOf(baz)) //true
console.log(baz.__proto__ === foo) //true
Object.create
其实Object.create的原理非常简单,我们找个“中介”F,并把这个构造函数的原型指向待继承的原型,当调用new F()的时候,我们创建的新对象的原型和传入的prototype被隔离开了。
Object.create = function(prototype){
var F = function(){}
F.prototype = prototype
var result = new F()
return result;
}
上面的代码中,首先创建了一个构造函数F,F将被用来构造一个对象,但是在这之前,我们要把他的原型连接到待继承对象的原型上,然后创建对象并返回。
事实上,
function Foo(){}
var foo = new Foo()
等价于
var foo = Object.create(Foo.prototype);
下面我们来测试一下
a = {p1:"p1"}
b = Object.create(a)
console.log(Object.getPrototypeOf(a) === Object.getPrototypeOf(b)) //false
works great! 现在原型得到了很好的隔离
functional
现在我们学会了伪类继承和原型继承。在伪类继承中,我们首先设计“类”,其实就是构造函数,并且将构造函数的原型链加以连接。但是伪类继承需要使用new关键字,而且这种设计有点不伦不类。更好的方式是使用纯粹的原型继承,我们不在通过构造函数来实现继承,而是通过Object.create函数,把被继承的对象的prototype复制到后代对象的原型上,然后在做差异化。
但是这两种方法都没有解决一个问题:私有变量。
在面向对象编程中,有一个很重要的特点就是封装。我们需要把一些特定的属性变成私有属性,只有通过特定的方法(接口)才可以访问这些属性。这里我们需要用到的就是所谓的functional。
var animal = function(spec){
var that = {};
that.get_name = function(){
return spec.name
}
that.says = function(){
return spec.saying || '';
};
return that;
}
var myAnimal = animal({name:"Herb"})
console.log(myAnimal.name) //undefined
console.log(myAnimal.get_name()) //Herb
我们的代码中,animal是一个标准的函数,我们将一个对象传入animal函数作为animal的变量,并返回一个拥有两个函数的对象。我们无法直接通过myAnimal来访问name,因为myAnimal对象只有两个方法,但是,我们可以通过myAnimal的两个方法来访问name,这样我们就得到了对象的私有环境。
当我们想要实现cat并让其继承animal的时候,一切都非常简单,我们只需要写一个cat函数,并在其中执行animal函数就可以了。一切都是最纯粹的函数而已!
var cat = function(spec){
spec.saying = spec.saying || 'meow'
var that = animal(spec)
that.get_name = function(){
return spec.name + 'cat'
}
}