天天看点

Javascript继承的最佳实践

什么是继承?

继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

在Javascript 中 没有 类的概念, 它是通过构造函数来产生 对象,

构造函数 就是一个普通的函数,通常当函数名 为 大写开头的,我们认为是构造函数,否则 就是普通的方法。

  1. function A() {
  2.     this.name = 'A Class instance';
  3. }
  4. function m1() {
  5. }

复制代码 既然 Javascript 是 通过构造函数来产生 对象,那我们怎么定义它的 属性、方法呢?

var a1 = new A();  是新创建一个A类对象,默认情况下,在构造函数中,使用this指向的是 新创建的对象;

而Javascript的对象属性 可以 晚绑定,即

  1. var obj = {};
  2. obj.name = 'obj1';
  3. obj.say = function say() {
  4.     console.log(this.name);
  5. }

复制代码 可以先产生对象,在需要增加属性时, 通过 obj.属性名  或 obj['属性名']  来添加属性。

所以我们在构造函数中 使用 this.属性名 来 定义产生的 对象 的 属性和方法。

  1. function A() {
  2.     this.name = 'A Class instance';
  3.     this.say = function() {
  4.         console.log('Hi,I am ' + this.name);
  5.     }
  6. }
  7. var a1 = new A();
  8. var a2 = new A();
  9. a1.say(); // Hi,I am A Class instance
  10. a2.say(); // Hi,I am A Class instance

复制代码 我们分析一下上面代码,

创建了 a1,a2 对象,在创建a1,a2 对象的时候 都为其添加一个say()方法,而这2个对象的 say方法 完全一样,

试想想 如果 创建n 个 A类对象,那是不是为这n个对象 添加一个say()方法,那是非常浪费内存。

所以在Javascript 中 引用了 prototype 原型的概念:

每一个构造函数都有一个prototype 对象,使用构造函数实例化一个对象,访问这个对象属性时,如果这个对象有该属性,则返回,否则就会在该对象的构造函数的prototype 上找,直到找到就返回,否则返回的是undefined

来验证一下:

Javascript继承的最佳实践

在上面代码中,A构造函数中没有定义say 方法,

但a1,a2 却 能够 调用say()方法,因为 A函数的prototype 默认指向的是 Object.prototype ;

此时内存中只保存Object.prototype.say,而A产生的对象 是通过原型机制,一层一层往上找,然后调用的。

所以通过 prototype原型机制,我们可以实现代码复用,和继承。

  1. A.prototype.say = function() {
  2.     console.log('Hi,I am ' + this.name);
  3. }
  4. function A() {
  5.     this.name = 'A Class instance';
  6. }
  7. var a1 = new A();
  8. var a2 = new A();
  9. a1.say(); // Hi,I am A Class instance
  10. a2.say(); // Hi,I am A Class instance
  11. function B() {
  12.     this.name = 'B Class instance';
  13. }
  14. B.prototype = new A();
  15. B.prototype.constructor = B;
  16. var b = new B();
  17. b.say();

复制代码

Javascript继承的最佳实践

分析一下上面代码 执行结果:

我们定义了一个B类,并把他的prototype 指向 A的实例对象,

然后产生一个 b 对象,调用b对象 say() ,也输出了内容。

这是为什么呢?

访问 b 对象属性时, 如果不存在 ,就会在其 prototype 访问,

就是 访问a 对象的 prototype ,但a对象 prototype  默认 是 指向Object.prototype 的,

而我们在 Object.prototype 定义了say 方法,b 对象也能访问 say()方法, 就好像b 继承了 父类中的 属性一样。

这里我们也可以看出,一旦 原型链 过长,会导致 访问多个对象的prototype。

所以 在设计 的时候 应该 不超过 3层原型链,可以考虑其他方式。

Javascript 最佳的实践:

  1. // 定义动物类
  2. function Animal(type, name) {
  3.     // 定义属性
  4.     this.type = type;
  5.     this.name = name;
  6. }
  7. // 定义方法
  8. Animal.prototype.say = function() {
  9.     console.log('Hi,My name is %s ,I am a %s', this.name, this.type);
  10. }
  11. // 定义Dog 类
  12. function Dog(type, name, hobby) {
  13.     // 这里的this 指向的是新创建的dog对象,而通过 Animal.apply 形式调用 Animal方法 ,
  14.     // 显示的指定其 this ,指向新创建的dog对象 ,再把对象的参数传进去
  15.     // 所以在 Animal 构造函数中 的 代码 是 为 新创建的dog对象 的属性 赋值
  16.     // 从而实现了 属性的继承
  17.     Animal.apply(this, [ type, name ]);
  18.     this.hobby = hobby;
  19. }
  20. Dog.prototype = new Animal();
  21. Dog.prototype.constructor = Dog;
  22. var d = new Dog('Dog', '大黄狗', '吃骨头');
  23. d.say();

复制代码 定义:

通过构造函数,定义 对象的 属性。

通过原型对象,定义 对象的 方法。

继承:

使用构造函数的 prototype,实现方法的继承

在构造函数使用 

父类构造函数.call(this,);

父类构造函数.apply(this,[])

从而实现属性的继承。

不要在Object.prototype 定义任何内容 

继承还可以有很多种 实现方式, 比如 属性的复制等等,应当灵活运用。

继续阅读