function Animal() { this.name = "aa"; } Animal.prototype.fly = function(){ return this.name; }; function Dog() { this.value = "bb"; } // 继承Animal Dog.prototype = new Animal(); // 重写原型的方法 Dog.prototype = { fly: function(){ return this.name; }, // 给自身添加新方法 cry: function(){ return false; } }; var dog1 = new Dog(); console.log(dog1.fly()); // undefined console.log(dog1 instanceof Animal); // false
如上代码所示:打印dog1.fly()方法 打印出undefined, 打印 dog1 instanceof Animal 打印false,可知:不能使用对象字面量的方法来实现重写原型的方法,因为这样做会切断与原型Animal的关系,比如现在dog1 不是 Animal的实例,且dog1的实例没有fly这个方法,因为它现在不是继承了;
使用原型链的缺点如下:
1. 我们都知道原型链中所有的属性和方法都会被所有实例共享,虽然原型可以解决共享的问题,这是他的优点,但也是他的缺点,比如我给A实例添加一个属性,当我实例化的B的时候,B也有这个属性,如下代码:
function Animal() { this.values = ["aa",'bb']; } function Dog(){}; Dog.prototype = new Animal(); var dog1 = new Dog(); dog1.values.push("cc"); // 添加cc值 console.log(dog1.values); // [“aa”,”bb”,”cc”]; var dog2 = new Dog(); console.log(dog2.values); // [“aa”,”bb”,”cc”];
2. 在创建子类型的实例中,不能向超类型中的构造函数传递参数。
理解借用构造函数
针对上面2点,因此我们需要借用于构造函数;其基本思想是:在子类型构造函数的内部调用超类型的构造函数,因此我们可以使用call或者apply的方法来调用,如下代码:
function Animal() { this.values = ["aa",'bb']; } function Dog(){ // Dog继承于Animal Animal.call(this); }; var dog1 = new Dog(); dog1.values.push("cc"); // 添加cc值 console.log(dog1.values); // ['aa','bb','cc'] var dog2 = new Dog(); console.log(dog2.values); // ['aa','bb']
如上代码:使用call或者apply的方法实现继承,可以得到自己的副本values,因此第一次打印出[“aa”,'bb','cc'] 第二次打印出 [“aa”,'bb'];
我们也可以传递参数,代码如下:
function Animal(name) { this.values = ["aa",'bb']; this.name = name; } function Dog(){ // Dog继承于Animal Animal.call(this,"dog22"); this.age = 22; }; var dog1 = new Dog(); console.log(dog1.name); // dog22 console.log(dog1.age); // 22
但是呢,借用构造函数也有缺点;
借用构造函数的缺点:
1.构造函数不能复用;
2.在超类型中定义的属性或者方法,在子类型中是不可见的,结果所有类型都只能使用构造函数的模式;
理解组合继承
需要解决上面的2个问题,我们可以考虑使用组合继承的方式来实现,就是指构造函数模式与原型模式组合起来一起使用,其思想就是:使用原型链实现对原型的属性和方法的继承,而借用构造函数来实现对实例中的属性的继承;这样,既可以通过在原型上定义的方法实现函数的复用,又能保证每个实例都有自己的属性;如下代码:
function Animal(name) { this.values = ["aa",'bb']; this.name = name; } Animal.prototype.sayName = function(){ return this.name; } function Dog(name,age){ // Dog继承属性 Animal.call(this,name); this.age = age; }; // 继承方法 Dog.prototype = new Animal(); Dog.prototype.constructor = Dog; Dog.prototype.sayAge = function(){ return this.age; } var dog1 = new Dog("dog111",'12'); dog1.values.push("cc"); console.log(dog1.values); // ['aa','bb','cc'] console.log(dog1.sayAge()); // 12 console.log(dog1.sayName()); // dog111 var dog2 = new Dog("dog222",'14'); console.log(dog2.values); // ['aa','bb'] console.log(dog2.sayAge()); // 14 console.log(dog2.sayName());// dog222
如上代码:Animal构造函数定义了2个属性,name和values,Animal原型中定义了一个方法sayName; Dog构造函数继承Animal是传递了参数name,然后又定义了自己的age参数,最后将Dog.prototpye = new Animal实例化Animal,让其Dog继承与Animal中的方法,这样的设计使Dog的不同的实例分别有自己的属性,同时又共有相同的方法,也节省了内存;
如上代码通过方法继承后,重写给Dog的constructor指向与Dog;如下代码:
Dog.prototype.constructor = Dog;
所以最后的Dog的实例对象的constructor都指向与Dog,我们可以打印如下:
console.log(dog1.constructor === Dog) // true
如果我们把上面的 Dog.prototype.constructor = Dog 注释掉的话,那么
console.log(dog1.constructor === Dog) // false
就返回false了;
理解原型式继承
其思想是:创建一个临时性的构造函数,然后将其传入的对象作为该构造函数的原型,最后返回这个临时构造函数的一个新实例,如下代码演示:
function object(obj) { function F() {}; F.prototype = obj; return new F(); }
我们现在可以做一个demo如下: