[关闭]
@bornkiller 2015-05-27T14:27:16.000000Z 字数 2937 阅读 2918

javascript类继承浅析

javascript


前言

高级语言基本上都有类的概念,而javascript因为各种原因相对比较特别,并没有明确的class类声明方式(ES6),而是通过构造函数变相实现。推荐《javascript高级程序设计》,对类继承有详细介绍。书中涉及继承方式多达数种,意味着继承的灵活性。但灵活性,有时也意味着复杂性。总结来说,继承方案本文介绍即可覆盖大部分场景。

关于原型链

javascript的世界,可以认为变量(原始类型[1],引用类型)皆对象[2]。当声明一个普通变量,即可调用多种方法。

  1. love = ['one', 'two', 'three']
  2. ["one", "two", "three"]
  3. love.slice(1,2)
  4. ["two"]
  5. Object.keys(love)
  6. ["0", "1", "2"]

这里声明一个数组,即可调用slice方法。既然未手工定义slice方法,为么能够使用,就需要需要提到原型继承。

对象可访问变量由两部分构成,属性和原型对象。属性优先级高于原型对象,如上所述,love数组的可枚举属性为"0", "1", "2",当调用slice方法时,会先在属性里面寻找slice键对应的值,此例中显然不存在。未找到,则到_proto_指针指向的原型对象中寻找。love变量指向的原型对象即为Array.prototype,键命中,所以不会undefined。注意的一点,原型对象也是对象,即原型对象也存在_proto_指针指向相应原型对象,在原型对象中匹配键时,也遵循先属性,后原型的法则。这样的匹配方式就实现了原型链[3]

对象继承

某些场合中,父类仅对子类的prototype存在影响,此时应考虑使用对象继承。假设Organization类和Employee类。

  1. function Organization(boss, belief) {
  2. this.boss = boss;
  3. this.belief = belief;
  4. }
  5. Organization.prototype.speak = function() {
  6. console.log("%s powers the company, who believes \"%s\"", this.boss, this.belief);
  7. };
  8. function Employee(name, age, belief) {
  9. this.name = name;
  10. this.age = age;
  11. }

属性继承

如果Employee指代自由职业员工,即同时充当公司跟雇员的身份,又不需要公司运作方式(原型方法),则Organization的仅属性对该类产生影响,则继承方式如下,可以称之为“属性继承”。

  1. function Employee(name, age, belief) {
  2. Organization.call(this, name, belief);
  3. this.name = name;
  4. this.age = age;
  5. }

原型继承

如果Employee指代特定公司的员工,则Organization的属性跟原型同时对该类产生影响,则继承方式如下,原型直接指定公司信息。如此,Employee实例都默认具备相同的公司信息。

  1. Employee.prototype = new Organization('bruce wayen', 'make business easy');
  2. Employee.prototype.constructor = Employee;

对于这种相关属性写死的方式,还有另外一种实现方式:

  1. Employee.prototype = Object.create(Organization.prototype, {
  2. name: {
  3. configurable: true,
  4. enumerable: true,
  5. writable: true,
  6. value: 'bruce wayen'
  7. },
  8. belief: {
  9. configurable: true,
  10. enumerable: true,
  11. writable: true,
  12. value: 'make business easy'
  13. }
  14. });
  15. Employee.prototype.constructor = Employee;

针对上述的自由职业者问题,通过直接扩充Employee的属性,在构造函数内部直接指定公司信息,但需要公司运作方式(原型方法),则实现如下:

  1. function Employee(name, age, belief) {
  2. this.name = name;
  3. this.boss = name;
  4. this.age = age;
  5. this.belief = belief
  6. }
  7. Employee.prototype = Organization.prototype;
  8. Employee.prototype.constructor = Employee;

伪类继承

伪类继承与传统的面向对象语言更为相似,同时继承属性和原型,则自由工作者,又需要公司运作方式,则可以在不调整原始代码的基础上,如下实现:

  1. function Employee(name, age, belief) {
  2. Organization.call(this, name, belief);
  3. this.name = name;
  4. this.age = age;
  5. }
  6. Employee.prototype = Organization.prototype;
  7. Employee.prototype.constructor = Employee;

如果你使用Nodejs的话,大部分应用场景都会使用伪类继承,如下代码为gulp脚本部分代码,即使用伪类继承方式。

  1. var Orchestrator = require('orchestrator');
  2. function Gulp() {
  3. Orchestrator.call(this);
  4. }
  5. util.inherits(Gulp, Orchestrator);

ES6 class

目前为止,iojs 2.0+以基本实现class功能。

  1. "use strict";
  2. class Organization {
  3. constructor(boss, belief) {
  4. this.boss = boss;
  5. this.belief = belief;
  6. }
  7. speak() {
  8. console.log("%s powers the company, who believes \"%s\"", this.boss, this.belief);
  9. }
  10. }
  11. class Employee extends Organization {
  12. constructor(name, age, belief) {
  13. super(name, belief);
  14. this.name = name;
  15. this.age = age;
  16. }
  17. }

use strict 必不可少,否则会抛出异常。之前以为class是完全新面向对象语法,后来确认更像是语法糖的东西,实现伪类继承。

总结

记录比较零散,如有机会,阅读《javascript高级程序设计》可以收获更多,暂且记录到这里。


[1] Boolean, String, Number
[2] 原始类型理论上并不是对象,不存在proto指针。之所以能调用各种方法,是为了保证语言体验一致性,执行引擎做预处理,与引用对象差别明显。
[3] 引用对象的基类为Object
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注