关于JavaScript原型链的理解

310 阅读4分钟

JavaScript的原型链概念,发现没有一个统一的表述或者定义,都是通过举例子,打比方来表述,感觉不精确也不合理,所以这里研究一下.

我对原型链的定义

在JavaScript中,每一个实例对象都有一个私有属性__proto__,这个__proto__指向它的构造函数的原型对象,该原型对象又有自己的原型对象,层层链接,形成原型链.

关于原型链的一些问题

1. JavaScript的类

类是创建对象的模板.

(1) es6的类

es6中类提供了class原生API,它同样是基于原型的类.

(2) es5的类

es5中的类没有原生API,以下几种方法构造类.

>1 构造函数法

缺点:每个实例的公共方法都需要重新创建一份,并存入内存,数据量过大,性能不好.

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function () {
    alert(this.name);
  };
}
var p1 = new Person("ddd", 10);
>2 原型法

缺点:所有属性和方法都定义到原型上,跟单纯使用构造函数法的弊端类似.

function Person() {}
Person.prototype.name = "ddd";
Person.prototype.age = 10;
Person.prototype.sayHello = function () {
  alert(this.name);
};
var p2 = new Person();
>3 构造函数和原型结合法

优点: 避免了单独使用构造函数和原型法的弊端,使得实例的公共方法都能定义到原型上来共享,每个实例都分别创建自己的属性和方法.

var Person = function(name, age) {
        this.name = name;
        this.age = age;
};
Person.prototype.sayHello = function() {
    alert(this.name);
}
var p = new Person('s', 123);
>4 工厂函数法

优点: 解决了大量创建相似对象的问题

缺点: 不知道创建的对象的类型

function createPerson(name, job) {
 var o = new Object();
 o.name = name;
 o.job = job;
 o.sayName = function() {
  console.log(this.name);
 }
 return o
}
var person1 = createPerson('Mike', 'student')
var person2 = createPerson('X', 'engineer')
>5 极简主义法

荷兰程序员的方法

首先,它也是一个对象模拟类。在这个类里面,定义一个构造函数createNew()用来生成实例。

var Cat = {
  createNew :function(){
    var cat = {}
    cat.name = 'Tom'
    cat.makeSound = function(){
      alert("喵喵")
    }
    cat.sayName = function(){
      alert(this.name)
    }
    return cat
  }
}
// 得到类的实例
var cat1 = Cat.createNew();

类的继承

只需要在子类的createNew方法中调用父类的createNew方法即可

// 父类
var Animal = {
    createNew: function(){
      var animal = {};
      animal.sleep = function(){ alert("睡懒觉"); };
      return animal;
    }
  };

// 子类
var Cat = {
  createNew: function() {
    var cat = Animal.createNew()
    cat.name = 'Tom'
    cat.makeSound = function(){
      alert("喵喵")
    }
    cat.sayName = function(){
      alert(this.name)
    }
    return cat
  }
}

var cat1 = Cat1.createNew()
cat1.sleep()// 睡懒觉

私有属性

例如上面代码子类Cat的name属性就是私有的,外部获取不了,只能通过子类的内部方法获取.

var cat1 = Cat1.createNew()
cat1.name //undefined

数据共享

如果需要多个类的实例共享同一个数据,则放在类对象里面,createNew的同级即可实现.

var Cat = {
  sound: "喵喵"。
  createNew: function(){
    var cat = {}
    cat.makeSound = function(){
      alert(Cat.sound)
    }
    cat.changeSound = function(x){
      Cat.sound = x
    }
    return cat
  }
}
var cat1 = Cat.createNew()
var cat2 = Cat.createNew()
cat1.makeSound() // 喵喵

2. JavaScript的继承实现

JavaScript的继承有很多种,es6的相对简单,使用extends就行, es5的继承相对麻烦点.

父类如下:

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};
>1 原型链继承

缺点: 不能继承多个父类(多继承),不能在声明子类的时候,为父类传参.

function Cat() {}
Cat.prototype = new Animal("cat");
var cat = new Cat();
>2 构造函数继承

构造继承能够多继承,能够为父类传参;

不能继承父类的原型方法和属性,每个子类都有父类实例的副本,性能不好.

function Cat(name) {
  Animal.call(this, name);
  this.xxx = "xxx";
}
var cat = new Cat("cat");
>3 实例继承

缺点: 子类实例是父类的实例,不是子类的实例; 不能多继承

function Cat(name) {
  var instance = new Animal(name);
  return instance;
}
var cat = new Cat("cat");
>4 拷贝继承

缺点: 支持多继承; 实例不是父类的实例,是子类的实例;父类不可枚举的属性不能被继承.

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
>5 组合继承(原型链和构造函数结合)

优点: 可以多继承; 子类实例既是子类的实例,也是父类的实例; 能继承父类原型上的属性和方法

缺点: 子类实例上生成了两份实例,数据量大的话,性能不好

function Cat(name) {
  Animal.call(this, name);
}
Cat.prototype = new Animal();
var cat = new Cat("cat");
>6 寄生组合继承

寄生组合继承是通过将父类的原型对象寄生到一个空的对象的原型对象上,来避免子类生成两份实例对象.

缺点: 实现较为麻烦, 容易, 功能已经相对完善

function Cat(name) {
  Animal.call(this);
  this.name = name || "Tom";
}

// 创建一个没有实例方法的类
var Super = function () {};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
// 重新指定Cat的constructor
Cat.prototype.constructor = Cat;

// Test Code
var cat = new Cat();