资讯专栏INFORMATION COLUMN

Javascript 设计模式读书笔记(三)——继承

cangck_X / 421人阅读

摘要:的继承方式属于原型式继承,非常灵活。当使用关键字执行类的构造函数时,系统首先创建一个新对象,这个对象会继承自构造函数的原型对象新对象的原型就是构造函数的属性。也就是说,构造函数用来对生成的新对象进行一些处理,使这个新对象具有某些特定的属性。

继承这个东西在Javascript中尤其复杂,我掌握得也不好,找工作面试的时候在这个问题上栽过跟头。Javascript的继承方式属于原型式继承,非常灵活。因此Javascript的继承方式除了基于类的继承之外还有基于原型的原型式继承。

继承是什么

看了这个词的第一反应我联想到了财产继承,在一般情况下,父母的遗产由子女继承,也就是说子女将会获得财产的使用权。而Javascript中的继承与现实生活中的财产继承还是有区别的。首先,Javascript中的父类和子类可以同时并存,而遗产么,什么样的财产叫遗产呢?其次,财产是花了就没了,而JS里的继承则是子类能够继承父类的方法,可以重复使用,减少代码量。当几个类都需要一个相似的方法时,使用继承可以从同一个类中继承相同的方法,而不用对每个类重复地复制粘贴了。

不用在定义上纠缠过久,下面讨论一下实现继承的一些方法。

继承的基本方法

开头便说到了JS的继承方式分为基于类的继承(简称类式继承)和基于原型链的继承(简称原型式继承)。

类式继承

类式继承的特点就是使用函数声明类,通过new关键字创建实例。

创建类的方法很简单,一般可以写成这样:

function Blog(address) {
  this.address = address;
}

Blog.prototype.getAddress = function() {
  return this.address;
}

创建该类的实例通过new关键字即可:

var blog = new Blog("classicemi.github.io");
blog.getAddress(); // "classicemi.github.io"

当使用new关键字时,它和通过一般方式执行函数的区别在于函数的执行方式会改变。当使用new关键字执行Blog类的构造函数时,系统首先创建一个新对象,这个对象会继承自构造函数Blog的原型对象(新对象的原型就是构造函数的prototype属性)。再将this关键字绑定到新对象上,再返回该新对象。也就是说,构造函数用来对生成的新对象进行一些处理,使这个新对象具有某些特定的属性。

创建一个继承Blog的类就需要手工完成和new运算符相似的工作了。

function MyBlog(address, author) {
  Blog.call(this, address);
  this.author = author;
}

当通过new关键字调用MyBlog构造函数时,系统会创建一个新对象(这步是自动的,this即为这个新对象),然后在this上调用超类Blog的构造函数,再给this添加一些MyBlog类特有的不是继承自Blog类的属性,最后将新对象this返回(这步也是自动的)。

下面为了继承Blog类的方法,需要设置原型链,使MyBlog类能够在原型链上找到继承自Blog类的方法。

MyBlog.prototype = new Blog(); // MyBlog类的原型是Blog类的一个实例
MyBlog.prototype.constructor = MyBlog; // 上一步执行后prototype的constructor属性会变成Blog,需要修改回来
MyBlog.prototype.getAuthor = function() { // 给MyBlog添加自己的方法
  return this.author;
}

通过这些操作后,MyBlog类就声明好了,它继承了Blog类的属性和方法,创建MyBlog类实例的方法和创建Blog类实例的方法一样,直接使用new关键字调用构造函数即可。

原型式继承

之前的类式继承是为了模仿其他一些面向对象语言的特点而创造的,并没有真正体现Javascript语言本身的特点,下面要说的原型式继承则是利用JS的原型特性而实现的继承方式。

使用原型式继承时,不需要像类式继承一样用一个类(构造函数)来定义对象的结构,而可以用对象字面量的方式直接创建一个对象,这个对象是作为原型存在的,被称作原型对象(prototype object)。就像工厂生产车间里的模具一样,为以后生产出的零件提供了参考原型。

还是以之前的BlogMyBlog类为例:

// Blog原型对象
var Blog = {
  address: "classicemi.github.io", // 属性只是作为默认值,一般都会被改写
  getAddress: function() {
    return this.address;
  }
};

这里没有像用一个构造函数来定义Blog类的结构,将方法添加在Blog.prototype上。这里定义的Blog对象只是作为原型存在,为继承Blog类的对象提供一些方法。

现在原型对象有了,要创建继承该原型对象对应的类的新类应该怎么做呢?利用JS的原型链特性,只要将新类的原型设为该原型对象即可。按照这种思路可以写出子类MyBlog的创建方法:

function MyBlogConstrucFunc() {}
MyBlogConstrucFunc.prototype = Blog;
var MyBlog = new MyBlogConstrucFunc();

通过new运算符调用MyBlogConstrucFunc函数,返回的是一个空对象,这个空对象的prototype属性指向原型对象Blog。在返回的空对象中,还可以添加MyBlog类自有的属性和方法。

不过通过这三行代码实现子类对超类的继承还是有些冗余,我们可以实现一个方法来实现对超类的继承,将超类作为该方法的参数传入并在最后将空对象返回即可。

function clone(object) {
  function F() {}
  F.prototype = object;
  return new F();
}
Mixin Class

以上所讨论的是比较严格的继承方式,有的时候,我们可能只想对某个函数进行重用,并不需要完全的继承,那么我们可以将函数以扩充的方式在类之间进行共享。对于重用频率比较高的方法,我们可以将它们归并在一个类中,然后用这个类去扩充其他的类。这种方法称为掺元类(mixin class)。这种处理方法在很多JS库(比如jQueryUnderscore)中都有用到,是一种扩充工具函数的好方法。

var Mixin = function() {}
Mixin.prototype = {
  serialize: function() {
    var output = [];
    for(key in this) {
      output.push(key + ": " + this[key]);
    }
    return output.join(", ");
  }
  ...
};

为了能方便地将Mixin类中的方法添加到其他类中,我们可以扩展工具函数augment

function augment(receivingClass, givingClass) {
  if (arguments[2]) { // 可接受三个参数,第三个参数为需添加的方法,多个方法可用数组将方法名传入
    if (arguments[2] instanceOf Array) {
      for (var i = 0, len = arguments[2].length; i < len; i++) {
        if (!receivingClass.prototype[arguments[2][i]]) {
          receivingClass.prototype[arguments[2][i]] = givingClass.prototype[arguments[2][i]];
        }
      }
    } else if (typeof arguments[2] === "string") {
      if (!receivingClass.prototype[arguments[2]]) {
        receivingClass.prototype[arguments[2]] = givingClass.prototype[arguments[2]];
      }
    }
  } else {
    for (methodName in givingClass.prototype) {
      if (!receivingClass.prototype[methodName]) {
        receivingClass.prototype[methodName] = givingClass.prototype[methodName];
      }
    }
  }
}

这时我们如果要给其他类添加Mixin中的方法的话可以直接这样写:

augment(MyBlog, Mixin);
类式继承和原型式继承的对比

类式继承存在的意义很大一部分是为了满足对Javascript的特性还不熟悉的程序员,毕竟这种方法是强行为了模仿其他面向对象语言的特性而创造的。Javascript的原型式特征在父类和子类之间建立了一种双向的联系,这是JS区别于其他语言的特点。
原型式继承发挥了JS的特性,所有的子类继承的方法会通过原型链逐级向父类查找,因此用于继承的方法在内存中只会保存一份,这样可以节约内存。只有在对子类的某个方法进行直接设置,将继承而来的方法覆盖的时候才会对新方法多带带生成副本。

封装对继承的影响

一个经过封装的类,它的公用方法和特权方法可以被继承下来,因为它们是添加在原型链上的,在作为构造函数的时候可以继承给子类。而私用方法相当于作为了闭包中的变量,与原型链无关,因此不会被继承。

父类中的特权方法可以访问父类中的私用属性,而特权方法会被子类继承,因此子类也可以通过继承的特权方法间接访问父类的私用属性。但子类中新添加的特权方法不能访问父类中的私用属性,因为缺少了到达父类内部的原型链“通道”

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87521.html

相关文章

  • JavaScript 语言精粹》读书笔记 - 函数

    摘要:语言精粹读书笔记第四章函数函数字面量函数字面量包含个部分第一部分,保留字第二部分,函数名,它可以被忽略。这个超级延迟绑定使得函数对高度复用。构造器调用模式一个函数,如果创建的目的就是希望结合的前缀来调用,那它就被称为构造器构造。 《JavaScript 语言精粹》 读书笔记 第四章 函数 Functions 函数字面量 函数字面量包含4个部分: 第一部分, 保留字 function...

    wdzgege 评论0 收藏0
  • javascript高级程序设计》第六章 读书笔记javascript继承的6种方法

    摘要:继承的是超类型中构造函数中的属性,如上继承了属性,但没有继承原型中的方法。上述造成的结果是子类型实例中有两组超类型的构造函数中定义的属性,一组在子类型的实例中,一组在子类型实例的原型中。 ECMAScript只支持实现继承,主要依靠原型链来实现。与实现继承对应的是接口继承,由于script中函数没有签名,所以无法实现接口继承。 一、原型链 基本思想:利用原型让一个引用类型继承另一个引用...

    孙吉亮 评论0 收藏0
  • 读书笔记-1【javascript语言精粹】继承

    摘要:使用构造器有个严重的危害,如果在调用构造器函数的时候忘记使用前缀,不仅不会绑定到新对象,还会污染全局变量原型模式原型模式中,我们采用对象来继承。 构造器调用模式 当一个函数对象被创建时,Function构造器会运行类似这样的代码: this.prototype = {constructor: this} new一个函数事会发生: Function.method(new, functio...

    malakashi 评论0 收藏0
  • 《编写可维护的 JavaScript读书笔记

    摘要:最近阅读了编写可维护的,在这里记录一下读书笔记。禁止使用,,,的字符串形式。避免使用级事件处理函数。让事件处理程序成为接触到对象的唯一函数。检测函数是检测检测函数的最佳选择。为特定浏览器的特性进行测试,并仅当特性存在时即可应用特性检测。 最近阅读了《编写可维护的 JavaScript》,在这里记录一下读书笔记。书中主要基于三个方向来讲解怎么增加代码的可维护性:编程风格、编程实践、自动化...

    tuniutech 评论0 收藏0
  • JavaScript模式读书笔记(二)字面量和构造函数

    摘要:对象字面量定义一个空对象这里的空指的是其自身属性为空,对象继承了的属性和方法添加属性方法完全删除属性方法自定义构造函数用操作符调用构造函数时,函数内部会发发生以下情况创建一个新对象,并且引用了该对象并继承了该函数的原型属性和方法被加入到的引 对象字面量 //定义一个空对象,这里的空指的是其自身属性为空,dog对象继承了Object.prototype的属性和方法 var dog={} ...

    _Zhao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<