原型链和继承

      • 1. 原型链
      • 2. __proto__ vs prototype
        • 2.1 __proto__
          • 字面量构造法
          • 构造函数构造法
          • Object.create构造法
          • 隐式原型
        • 2.2 prototype
          • 显式原型
        • 2.3 prototype和__proto__关系
        • 2.4 constructor
        • 2.5 画个图吧
      • 3. 继承
        • 3.1 构造函数继承
        • 3.2 原型链继承
        • 3.3 组合式继承
        • 3.4 原型式继承
        • 3.5 寄生式继承
        • 3.6 寄生组合式继承
      • 4.参考

1. 原型链

每一个object对象都有自己[[prototype]]属性,它指向自己的原型对象(prototype),该原型对象又有自己的[[prototype]]属性,指向自己的原型对象,层层向上直到一个对象的原型对象是nullnull没有原型,作为这个原型链的最后一节。

访问一个object对象的属性的时候,先去查找对象本身有没有同名的属性,如果没有,就去对象的原型上去找,原型的原型,层层向上查询,直到找到同名的属性,或者直到找到原型链的终点:null

2. __proto__ vs prototype

2.1 __proto__

每一个object对象都有一个[[prototype]]属性,它是一个隐藏属性。它指向对象的原型。在很多浏览器的实现中,把[[prototype]]实现为__proto__

所以,__proto__指向的原型是什么呢?这就由它的构造方法来决定。

对象的三种构造方法:

字面量构造法
var a = {
    name: 'lc'
}

这个a对象,其__proto__指向Object.prototype

实际上,字面量构造法是一个语法糖,本质上也是依靠构造函数创建的。

var a = Object.create();
a.name = 'lc';
构造函数构造法
function A(name) {
    this.name = name;
}
var a = new A();

这个a对象,其__proto__指向A.prototype
new内部是这样实现的:

// b = new A()
{
    var obj = {};
    obj.__proto__ = A.prototype;
    A.call(obj);
    return obj;
}
Object.create构造法
var b = {
    name: 'lc'
}
var a = Object.create(a);

这个a对象,其__proto__指向b

Object.create内部是这样实现的:

Object.create = function(b) {
    function fn(){};
    fn.prototype = b;
    return new fn();
}

实际上,a也是由new方法创建,只是,a的构造函数fn只是存在了一瞬间。在外部,我们看不到a的构造函数,只能看到它的原型是b

隐式原型

__proto__的作用:可以在访问一个object对象的属性的时候,先去查找对象本身有没有同名的属性,如果没有,就去对象的原型上去找,原型的原型,层层向上查询,直到找到同名的属性,或者直到找到原型链的终点:null。我们称它为隐式原型

我们可以通过Object.getPrototypeOf获得。

2.2 prototype

每一个函数在创建之后都有一个prototype属性,它指向函数的原型对象。

ps: 通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性。

显式原型

prototype的作用:用来实现基于原型的继承与属性的共享。我们把它称之为显式原型

2.3 prototype__proto__关系

a的构造函数是A,a的隐式原型指向A的prototype。

2.4 constructor

所有对象都会从它的原型对象上继承constructor属性,它会指向对象的构造函数。

2.5 画个图吧

原型链和继承_第1张图片

3. 继承

3.1 构造函数继承

function A(name) {
    this.name = name;
}

function B(name) {
    A.call(this, name);
}

var a = new A('qq')
var b = new B('lc');

缺点: 子类父类没有公用的方法。函数复用无从谈起。

3.2 原型链继承

function A(name) {
    this.name = name;
    this.age = 3;
}
A.prototype.getName = function() {
  return this.name; 
}

function B(name) {
    this.name = name;
}
B.prototype = new A();
B.prototype.setName = function(name) {
    this.name = name;
}

var b = new B('ls');
b.getName();
b.setName('qq');

缺点: 父类的构造函数的属性变成子类的__proto__下的属性了。
所以,上述代码

b.age   //3

// 实际上,b不应该由age属性

原型链和继承_第2张图片

如图,B.prototypeconstructor属性指向A而不是B

假设,两个子类child1child2,两个子类都继承一个父类parent。父类的构造函数下的引用类型,就会被两个child共享。并且共享的是引用值,实例之间的属性会互相干扰。

3.3 组合式继承

function A(name) {
    this.name = name;
    this.age = 3;
}
A.prototype.getName = function() {
  return this.name; 
}

function B(name) {
    A.call(this, name);
}
B.prototype = new A();
B.prototype.setName = function(name) {
    this.name = name;
}

var b = new B('ls');

上面两种方法的组合。目前比较完美了。子类之间的属性既不会互相影响,又可以实现方法的共享。

缺点: 构造函数被执行了两次,new的时候执行了一次,call的时候执行了一次。

实际上解决这个问题,有一个可以优化的地方:

function A(name) {
    this.name = name;
    this.age = 3;
}
A.prototype.getName = function() {
  return this.name; 
}

function B(name) {
    A.call(this, name);
}
// 去掉这行
- B.prototype = new A();
// 添加这行
+ B.prototype = A.prototype;
B.prototype.setName = function(name) {
    this.name = name;
}

var b = new B('ls');

这个可以解决构造函数被执行了两次,但是实际上,到组合式继承目前为止,都没有解决子类对象原型constructor指向的问题。

3.4 原型式继承

function A(name) {
    this.name = name;
    this.age = 3;
}
A.prototype.getName = function() {
  return this.name; 
}
var a = new A('ls');
var b = Object.create(a);

对象的浅复制。

b.__proto__ = a;

这个思路好清奇,没有了构造函数,原型,怪怪的。

子类不能创建子类的方法,只能继承父类的方法。

3.5 寄生式继承

function A(name) {
    this.name = name;
    this.age = 3;
}
A.prototype.getName = function() {
  return this.name; 
}
var a = new A('ls');
function clone(a) {
    var obj = Object.create(a);
    obj.setName = function(name) {
        this.name = name;
    }
    return obj;
}
b = clone(a);

寄生式继承在创建函数内部创建的函数(如上例sayName),不能做到函数复用。

3.6 寄生组合式继承

function A(name) {
    this.name = name;
    this.age = 3;
}
A.prototype.getName = function() {
  return this.name; 
}

function B(name) {
    A.call(this, name);
}
B.prototype = new A();
B.prototype.constructor = B;
B.prototype.setName = function(name) {
    this.name = name;
}

var b = new B('ls');

组合式继承的更进一步,解决了组合式继承的子类原型constructor不指向子类构造函数的问题。

目前是最好的继承方式。

4.参考

《javascript》高级程序设计
https://www.zhihu.com/question/34183746

你可能感兴趣的:(javascript)