文档章节

js继承的6种继承方式

学霸猫
 学霸猫
发布于 2017/03/23 23:28
字数 2564
阅读 665
收藏 1

 

①类式继承(构造函数)

JS中其实是没有类的概念的,所谓的类也是模拟出来的。特别是当我们是用new 关键字的时候,就使得“类”的概念就越像其他语言中的类了。类式继承是在函数对象内调用父类的构造函数,使得自身获得父类的方法和属性。call和apply方法为类式继承提供了支持。通过改变this的作用环境,使得子类本身具有父类的各种属性。

var father = function() {

this.age = 52;

this.say = function() {

alert('hello i am '+ this.name ' and i am '+this.age + 'years old');

}

}

var child = function() {

this.name = 'bill';

father.call(this);

}

var man = new child();

man.say();


 ②原型继承

原型继承在开发中经常用到。它有别于类继承是因为继承不在对象本身,而在对象的原型上(prototype)。每一个对象都有原型,在浏览器中它体现在一个隐藏的__proto__属性上。在一些现代浏览器中你可以更改它们。比如在zepto中,就是通过添加zepto的fn对象到一个空的数组的__proto__属性上去,从而使得该数组成为一个zepto对象并且拥有所有的方法。话说回来,当一个对象需要调用某个方法时,它回去最近的原型上查找该方法,如果没有找到,它会再次往下继续查找。这样逐级查找,一直找到了要找的方法。 这些查找的原型构成了该对象的原型链条。原型最后指向的是null。我们说的原型继承,就是将父对像的方法给子类的原型。子类的构造函数中不拥有这些方法和属性。

var father = function() {

}

father.prototype.a = function() {

}

var child = function(){}

//开始继承

child.prototype = new father();

var man = new child();

man.a();


可以看到第七行实现了原型继承。很多人并不陌生这种方式。通过在浏览器中打印man我们就可以查看各个原型的继承关系。

可以看到逐级的关系child->object(father实例化的对象)->father。child是通过中间层继承了father的原型上的东西的。但是为什么中间还有一层object呢,为什么不把child.prototype = father.prototype。答案是如果这样做child和father就没有区别了。大家应该还记得在prototype中有个constructor属性,指向的是构造函数。按照正常的情况我们要把constructor的值改回来指向child的构造函数。但如果直接把father.prototype赋值给child.prototype,那么constructor应该指向谁呢?所以很显然只能通过中间层才能使得child和father保持为独立的对象。

类式继承和原型继承的对比

构造函数(类)式继承

首先,构造函数继承的方法都会存在父对象之中,每一次实例,都会将funciton保存在内存中,这样的做法毫无以为会带来性能上的问题。

其次,类式继承是不可变的。无法复用,在运行时,无法修改或者添加新的方法,这种方式是一种固步自封的死方法。实践中很少单纯使用。

原型继承

优点:

原型链可改变:原型链上的父类可替换可扩展

可以通过改变原型链接而对子类进行修改的。另外就是类式继承不支持多重继承,而对于原型继承来说,你只需要写好extend对对象进行扩展即可。

但是原型链继承也有2个问题。

第一,包含引用类型值的原型属性会被所有实例共享(可以这样理解:执行sub1.arr.push(2);先对sub1进行属性查找,找遍了实例属性(在本例中没有实例属性),没找到,就开始顺着原型链向上找,拿到了sub1的原型对象,一搜身,发现有arr属性。于是给arr末尾插入了2,所以sub2.arr也变了)。

第二,在创建子类型的实例时,不能向超类型的构造函数中传递参数。(实际上,应该说没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数)实践中很少单纯使用原型链。

function Super(){
this.val = 1;
this.arr = [1];
}
function Sub(){
// ...
}
Sub.prototype = new Super(); // 核心

var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
alert(sub1.val); // 2
alert(sub2.val); // 1

alert(sub1.arr); // 1, 2
alert(sub2.arr); // 1, 2

总结:

类式继承在实例化时,父类可传参,不能复用(父类不可变,每一次实例都会将父类内容保存在内存中)

原型继承在实例化时,父类不可传参,可以复用(原型链可改变(父类可替换可扩展),父类不会保存在内存中,而是顺着原型链查找,但是结果是原型属性会被所有实例共享(尤其影响引用类型值))

 

③组合继承(最常用)


组合继承将原型链和借用构造函数的技术结合到一起,发挥两者之长的一种继承模式。

思路是使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。

function SuperType(name){

this.name = name;

this.numbers = [1,2,3];

}

SuperType.prototype.sayName = function(){

console.log(this.name);

}

function SubType(name,age){

SuperType.call(this,name);

this.age = age;

}

SubType.prototype = new SuperType();

SubType.prototype.sayAge = function(){

console.log(this.age);

}

var instance1 = new SubType('aaa',21);

instance1.numbers.push(666);

console.log(instance1.numbers);

instance1.sayName();

instance1.sayAge();

var instance2 = new SubType('bbb',22);

console.log(instance2.numbers);

instance2.sayName();

instance2.sayAge();

 

把实例函数都放在原型对象上,通过Sub.prototype = new Super();继承父类函数,以实现函数复用。

保留借用构造函数方式的优点,通过Super.call(this);继承父类的基本属性和引用属性,以实现传参;

优缺点

优点:

  1. 可传参
  2. 函数可复用
  3. 不存在引用属性共享问题(图纸)

缺点:

子类原型上有一份多余的父类实例属性,因为父类构造函数被调用了两次,生成了两份,而子类实例上的那一份屏蔽了子类原型上的造成内存浪费。(下面会有方法解决这个问题)

④原型式继承

思想:在一个函数内部创建一个临时性的构造函数,将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新的实例。

function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
var person = {
  name:'wang',
  friends:['cc','jj','gg']
}
var anotherPerson = object(person);
anotherPerson.name = 'lee';
anotherPerson.friends.push('hh');

var anotherPerson2 = object(person);
anotherPerson2.name = 'joke';
anotherPerson2.friends.push('mm');

console.log(person.friends);//cc jj gg hh mm

es5中Object.create()方法规范了原型式继承,传入一个参数情况下是和object()方法行为相同,传入第二个参数可以为新对象添加新的属性,但会覆盖原型对象上的同名属性。

下面是Object.create方法的Polyfill

if (typeof Object.create != 'function') {
  // Production steps of ECMA-262, Edition 5, 15.2.3.5
  // Reference: http://es5.github.io/#x15.2.3.5
  Object.create = (function() {
    //为了节省内存,使用一个共享的构造器
    function Temp() {}

    // 使用 Object.prototype.hasOwnProperty 更安全的引用 
    var hasOwn = Object.prototype.hasOwnProperty;

    return function (O) {
      // 1. 如果 O 不是 Object 或 null,抛出一个 TypeError 异常。
      if (typeof O != 'object') {
        throw TypeError('Object prototype may only be an Object or null');
      }

      // 2. 使创建的一个新的对象为 obj ,就和通过
      //    new Object() 表达式创建一个新对象一样,
      //    Object是标准内置的构造器名
      // 3. 设置 obj 的内部属性 [[Prototype]] 为 O。
      Temp.prototype = O;
      var obj = new Temp();
      Temp.prototype = null; // 不要保持一个 O 的杂散引用(a stray reference)...

      // 4. 如果存在参数 Properties ,而不是 undefined ,
      //    那么就把参数的自身属性添加到 obj 上,就像调用
      //    携带obj ,Properties两个参数的标准内置函数
      //    Object.defineProperties() 一样。
      if (arguments.length > 1) {
        // Object.defineProperties does ToObject on its first argument.
        var Properties = Object(arguments[1]);
        for (var prop in Properties) {
          if (hasOwn.call(Properties, prop)) {
            obj[prop] = Properties[prop];
          }
        }
      }

      // 5. 返回 obj
      return obj;
    };
  })();
}

如果使用Object.create方法的话,那么代码就可以变为

function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
var person = {
  name:'wang',
  friends:['cc','jj','gg']
}
var anotherPerson = Object.create(person,{
  name:{
    value:'gre'
  }
});

优点:

引出了使用临时构造函数的这种思路,为后面要介绍的继承方式奠定基础

缺点:

这种继承方式和原型继承十分类似,也存在原型继承中引用类型属性共享的问题。当然,父类也不能传入参数。

⑤寄生式继承

思想:类似工厂模式,创建一个仅用于封装继承过程的函数,在该函数内部增强对象,最后返回对象

一种做法:在原型式继承基础上再封一层函数,在这个函数里面为原型式继承返回的对象进行补强

function createObj(o){
  var obj = Object.create(o);
  obj.say = function(){
    console.log('hi');
  }
  return obj;
}
var person = {
  name:'wang',
  friends:['aa','bb']
}
var subPerson = createObj(person);
subPerson.say();//hi

优点:

引出再次封装的思想

缺点:

这个封装的方法里面补强的内容是写死的,整个函数不能复用,降低了效率。

原型式继承和寄生式继承

这两种继承自身都有不少问题,但是介绍这两个只是为了介绍思想,引出下文的寄生式组合式继承,所以对两种继承的优缺点没有做太过详细的分析。这两种继承方式也不会经常用到。

⑥寄生组合式继承(最理想的继承范式)

思想:子类的原型直接指向拥有父类原型的对象(当然subType.prototype = superType.prototype是不行的),这个对象还不会拥有父类的自有属性。

方法:通过寄生式继承中的原型式继承方法获取没有父类自有属性的原型对象并补强对象(重写construct属性),通过构造函数实现属性的继承。

function inheritPrototype(subType,superType){
  var prototype = Object.create(superType.prototype);
  prototype.construct = subType;//(重写原型失去了默认的construct属性)
  subType.prototype = prototype;
}
function SuperType(name){
  this.name = name;
  this.colors = ['aa','bb','cc'];
}
SuperType.prototype.sayName = function(){
  console.log(this.name);
}
function SubType(name,age){
  SuperTyep.call(this,name);
  this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
  console.log(this.age);
}

优点:

既可以复用又可以传参

参考资料:

《JavaScript高级程序设计》

博客:http://blog.csdn.net/liuqiao1123/article/details/51330214

博客:http://www.cnblogs.com/ayqy/p/4471638.html

© 著作权归作者所有

共有 人打赏支持
学霸猫
粉丝 5
博文 93
码字总数 39472
作品 0
深圳
程序员
私信 提问
JS 总结之原型继承的几种方式

在之前的总结中,我们详细分析了原型《JS 总结之原型》,原型很大作用用于模拟继承,这一次,我们来聊原型继承的几种方式。 🌶 前提 以一个父类为前提条件,列举 js 继承的继承方式: 🍖...

Karon_
2018/12/24
0
0
JS高级之面试必须知道的几个点

深圳市人人聚财招前端啦 ~~~ 招前端,招前端,招前端,欢迎扫描加群,砸简历过来: 前言 这段时间突然发现JS原生好多东西都忘记了,但有些东西确实很重要,所以又重新再梳理一次。主要有函数的...

小小小
2018/06/08
0
0
JavaScript 中的继承:ES3、ES5 和 ES6

选择一种继承方式 JavaScript 是一门动态语言,动态意味着高灵活性,而这尤其可以体现在继承上面。JavaScript 中的继承有很多种实现方式,可以分成下面四类: Mixin 模式,即属性混入,从一个...

天方夜
2018/10/30
0
0
全面理解面向对象的 JavaScript

简介: JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来人们对这一门语言的误解,即认为 JavaScript 不是一门面向对象的语言,或者只是部分具备一些面向对象的特征。本...

IBMdW
2013/04/21
1K
6
再谈 javascript 面向对象编程

前言:虽有陈皓《Javascript 面向对象编程》珠玉在前,但是我还是忍不住再画蛇添足的补上一篇文章,主要是因为javascript这门语言魅力。另外这篇文章是一篇入门文章,我也是才开始学习Javascr...

aoniao
2012/02/28
4.4K
22

没有更多内容

加载失败,请刷新页面

加载更多

搜索引擎(Elasticsearch搜索详解)

学完本课题,你应达成如下目标: 掌握ES搜索API的规则、用法。 掌握各种查询用法 搜索API 搜索API 端点地址 GET /twitter/_search?q=user:kimchy GET /twitter/tweet,user/_search?q=user:...

这很耳东先生
38分钟前
6
0
浅谈如何减少GC的次数

GC会stop the world。会暂停程序的执行,带来延迟的代价。所以在开发中,我们不希望GC的次数过多。 本文将讨论如何在开发中改善各种细节,从而减少GC的次数。 (1)对象不用时最好显式置为 Nu...

浮躁的码农
40分钟前
1
0
jpa 自定义返回对象

任何ORM框架都少不了开放自定义sql的问题。jpa自然也不例外,很多场景需要写复杂sql的。 首先定义一个方法签名,然后打上@Query注解。像下面这样,需要注意nativeQuery,这个表示query中的字...

朝如青丝暮成雪
今天
3
0
驰骋工作流引擎-批量审批设置

批量审批设置 关键词: 工作流引擎批量审批 Java工作流批量审批设计 应用场景 批量审批最多应用于代办中有很多相同的流程需要审批,若是把一些不是特别重要的审批做批量处理这样就可以提高很...

孟娟
今天
2
0
springmvc源码解析之DispatcherServlet四

说在前面 本次主要介绍DispatcherServlet,关注”天河聊架构“更多精彩。 springmvc配置解析 进入方法org.springframework.web.bind.annotation.support.HandlerMethodInvoker#resolveHttpE...

天河2018
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部