js继承

伪类

首先要记住一件事,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'
  }
}

你可能感兴趣的:(js继承)