JS 继承

来源:互联网 发布:js格式化日期函数 编辑:程序博客网 时间:2024/06/05 05:54

最近在阅读红宝书,刚好重新过来一遍继承,拿来和大家分享下,继承是JS核心之一,面试必问。
在继承之前大家需要了解的就是js创建对象,常见的创建对象方式有工厂模式,构造模式,原型模式。

                    **理解原型对象**

1.只要创建了一个函数,就会为该函数创建一个prototype属性,指向函数的原型对象
2.原型对象会自动获得一个constructor属性,指向所在的构造函数
3.每个对象实例都包含一个内部属性,该属性指向原型对象
4.通过构造函数,还可以继续为原型对象添加其他属性和方法。
5.当给对象实例添加一个属性时,这个属性会屏蔽原型对象中保存的同名属性,
即添加这个属性只会阻止我们访问原型中的属性,而不会修改那个属性


最主要的创建对象的方法
组合使用构造函数模式和原型模式

创建自定义类型最常见的方式,定义引用类型的一种默认模式。

思想:

构造函数模式:用于定义实例属性
原型模式:用于定义方法和共享的属性
每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。支持向构造函数传递参数。

function Person(name,age){    this.name = name;    this.age = age;    this.friends = ["小红","小明"];}Person.prototype = {    constructor : Person,    getAge : function(){        console.log(this.age);    }}var zhangsan = new Person("zhangsan",23);var lisi = new Person("lisi",30);zhangsan.friends.push("小三");console.log(zhangsan.friends); //["小红","小明","小三"]console.log(lisi.friends); //["小红","小明"]console.log(zhangsan.friends === lisi.friends); //falseconsole.log(zhangsan.getAge === lisi.getAge); //true

有了以上这些知识我们再来看继承

由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承,而实现继承主要是依靠原型链来实现。

继承:

原型链 借用构造函数 组合继承 原型式继承 寄生式继承 寄生组合式继承

JavaScript主要通过原型链实现继承。使用最多的是组合继承。

原型链:

通过将一个类型的实例赋值给另一个构造函数的原型实现。(问题对象实例共享所有继承的属性和方法,不适宜单独使用)

借用构造函数:

在子类型构造函数的内部调用超类型构造函数。(每个实例都具有自己的属性)

组合继承:

使用原型链继承共享的属性和方法,通过借用构造函数继承实例属性。

原型式继承:

可以不必预先定义构造函数的情况下继承(执行对给定对象的浅复制,复制得到的副本还可以进一步改造)

寄生式继承:

与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,返回对象。(为了解决组合继承由于多次调用超类构造函数而导致低效率问题,可将这个模式与组合继承一起使用。)

寄生组合式继承:

集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。

原型链

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数想指针(constructor),而实例对象都包含一个指向原型对象的内部指针(proto)。如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针(proto),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条。

function animal(){      this.type = "animal";  }  animal.prototype.getType = function(){      return this.type;  }  function dog(){      this.name = "dog";  }  dog.prototype = new animal();  dog.prototype.getName = function(){      return this.name;  }  var xiaohuang = new dog();//原型链关系  xiaohuang.__proto__ === dog.prototype  dog.prototype.__proto__ === animal.prototype  animal.prototype.__proto__ === Object.prototype  Object.prototype.__proto__ === null  

缺点:

包含引用类型值的原型。
在创建子类型的实例时,不能向超类型的构造函数中传递参数。(没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数)

借用构造函数

优点:

解决原型中包含引用类型值带来的问题。
可以在子类型构造函数中向超类型构造函数传递参数。

思想:
在子类构造函数内部调用超类构造函数。
通过apply()和call()方法可以在新建的对象上执行构造函数。

function Animal(){    this.colors = ["red","blue"];}function Dog(){    //继承Animal    Animal.call(this);}var xiaohuang = new Dog();xiaohuang.colors.push("yellow");console.log(xiaohuang.colors): // ["red","blue","yellow"]var xiaohei = new Dog();xiaohei .colors.push("black");console.log(xiaohei .colors); // ["red","blue","black"]//Dog的每个实例都会有自己的colors属性副本//传递参数//优点:在子类构造函数中向父类构造函数传递参数(相比于原型链的优点)function Animal(name){    this.name = name;}function Dog(){    //继承Animal,同时还传递了参数    Animal.call(this,"xiaohuang");    this.age = 10; //实例属性}var xiaohuang = new Dog();console.log(xiaohuang .name);  //"xiaohuang”console.log(xiaohuang .age);    //10

缺点:

构造函数模式存在的问题—方法都在构造函数中定义,函数复用无从谈起。
在超类型的原型中定义的方法,在子类型是不可见的。
结果所有的类型都只能使用构造函数模式
借用构造函数很少单独使用

组合继承

组合继承/伪经典继承–最常用的继承模式

优点:

将原型链和借用构造函数组合到一起,发挥两者之长。
思想:
使用原型链实现对原型属性和方法的继承,(实现了函数的复用)
通过借用构造函数实现对实例属性的继承 (保证每个实例都有自己的属性)

function Animal(name){    this.name = name;    this.colors = ["red","blue"];}Animal.prototype.getName = function(){    console.log(this.name);}function Dog(name,age){    //继承属性    Animal.call(this,name);    this.age = age;}Dog.prototype = new Animal();Dog.prototype.constructor = Dog;Dog.prototype.getAge = function(){    console.log(this.age);}var xiaohuang = new Dog("xiaohuang",10);xiaohuang.colors.push("yellow");console.log(xiaohuang.colors); //"red","blue","yellow"xiaohuang.getAge(); //10xiaohuang.getName(); //"xiaohuang"var xiaohei = new dog("xioahei",3);console.log(xiaohei.colors); //"red","blue"xiaohei.getAge(); //3xiaohei.getName(); //"xioahei"

解释:
animal的构造函数定义了name和colors两个属性

dog的原型定义了getName()方法。
dog构造函数在调用animal构造函数时传入了name参数,
又定义了自己的属性age

将animal实例的赋值给dog的原型,又在新原型上添加方法getAge(),两个不同的实例可以分别拥有自己的属性name,colors,又可以使用相同的方法getName()

instanceof,isPrototypeOf()也能够识别基于组合继承创建的对象。

缺点:

无论什么情况下,都会调用两次超类型构造函数。
(1.创建子类原型的时候 2.子类型构造函数内部) 看 寄生组合式继承
有两组 name和colors属性,dog的原型中有,dog的实例中有,实例中会屏蔽原型的的两个同名属性。

原型式继承

思想:
没有严格意义上的构造函数。
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
必须要有一个对象可以作为另一个对象的基础。

function object(o){    function F(){};    F.prototype = o;    return new F();};//object对传入的对象进行了一次浅复制var xiaoming = {    name : "xiaoming",    friends : ["f1","f2"]};var xiaohong = object(xiaoming);xiaohong.__proto__ === xiaoming; //truexiaohong.name = "xiaohong";xiaohong.friends.push("f3");var xiaohua = "xiaohua";xiaohua.friends.push("f4");console.log(xiaoming.friends);//["f1","f2","f3","f4"]

缺点 :

和原型模式一样,包含引用类型值的属性始终都会共享相应的值

寄生式继承

思路:
与寄生构造函数和工厂模式类似,
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象。

function createAnother(o){    var clone = object(o);  //通过调用函数创建一个新对象    clone.sayHi = function(){ //以某种方式来增强这个对象        alert("hi");    };    return clone;  //返回这个对象}var person = {    name : "fangfang",    friends : ["f1","f2"]};var anotherperson = createAnother(person);anotherperson.sayhi(); //"hi"//anotherperson具有person的所有属性和方法,还有自己的sayhi方法

缺点:
适用场景:在主要考虑对象,而不是自定义类型和构造函数的情况

寄生组合式继承

最理想的继承方式

优点:

不必为了指定子类型的原型而调用父类型的构造函数(解决了组合继承至少两次调用超类构造函数)
(只调用了一次animal构造函数,避免了dog.prototype上面创建多余的属性)
(还能正常使用instanceof和isPrototypeOf())
思想:
通过借用构造函数来继承属性,
通过原型链的混成形式来继承方法

不必为了指定子类型的原型而调用父类型的构造函数,
我们所需的只是父类原型的一个副本。
就是使用寄生式继承来继承父类的原型,然后将结果指定给子类型的原型

function inheritPrototype(Dog,Animal){    var prototype = Object(Animal.prototype); //创建父类原型的一个副本    prototype.constructor = Dog; //副本添加constructor属性    Dog.prototype = prototype;  //把副本赋值给子类原型}function Animal(name){    this.name = name;    this.colors = ["red","blue"];}Animal.prototype.getName = function(){    console.log(this.name);}function Dog(name,age){    Animal.call(this,name);    this.age = age;}inheritPrototype(Dog,Animal);Dog.prototype.getAge = function(){    console.log(this.age);}//此时 Animal.prototype === Dog.prototype

https://github.com/zgfang1993/blog/issues

原创粉丝点击