说来话长的 Javascript 原型链

为了避免被罚200块钱,不得不强迫本身在今天以前写完这篇博客,人就是要对本身狠一点,想些招数来强迫本身,也许是件好事。前端

JS的原型链老是被端上前端面试桌上的一盘经典菜,不一样的人从不一样的角度去品鉴。今天我想从构造函数模式到原型模式再到原型链来阐述个人理解。面试

构造函数模式

之前的我只是知道构造函数就是定义一个函数,函数名大写,函数里面给隐式返回的this对象添加属性和方法,这个函数就是构造函数。chrome

至于为何要用构造函数呢?就有些迷茫了,因此再次翻开了红包书那些曾经使人头疼的面向对象章节(不得不说JS高级程序设计真是一本好书)设计模式

为何要用构造函数?

首先由于面向对象的程序设计须要不少对象,不少类似又不一样的对象,有对象才好办事嘛。浏览器

类似的地方抽离出来就成了抽象对象,好比你要找个身高多少、体重多少、长相如何......的异性。那每一个人的标准就不同,这些标准就成了对象的特殊性,而相同的就是都是某我的的异性朋友app

咱们试着来实现一下函数

function sexFriend(height, weight,appearance){
    var o = new Object();
    o.height=height;
    o.weight=weight;
    o.appearance=appearance;
    o.saySth=function(){
        alert('我愿意')
    }
    return o
}
var friend1 = new sexFriend(169, 90, '沉鱼落雁,闭月羞花');
var firend2 = new sexFriend(182, 150, '清秀俊朗,风度翩翩');
    
复制代码

你看咱们能经过给这个函数传入咱们指望的参数就能找到对的那我的吧。ui

这就是设计模式之——工厂模式,由于JS语言没有其余语言中的类的概念, 因此用函数来封装以特定接口建立对象的细节。this

可是这种工厂模式仅仅解决了建立多个类似对象的问题,但却没有解决对象识别的问题,即没有办法仅仅经过这些对象知道他们属于什么类型的对象,即是这些对象和构建他们的函数缺乏了关联,就好比说有我的说上面建立的对象是猪的时候,你拿什么证据去反驳他呢?spa

随着JS的发展,构造函数模式应运而生 构造函数模式就可以解决工厂模式所带来的问题,由于构造函数建立的对象与建立的函数之间创建了直接的联系。经过constructor

function SexFriend(height, weight, appearance){
    this.height=height;
    this.weight=weight;
    this.appearance=appearance;
    this.saySth=function(){
        alert('我愿意')
    }
}
var friend1 = new SexFriend(168, 90, '倾国倾城');
var friend2 = new SexFriend(182, 150, '风流倜傥');
复制代码

仔细比较上面的构造函数与工厂函数,他们有不少代码都是类似的,如下是他们的不一样:

  1. 没有显式地建立对象
  2. 直接将属性和方法赋给了 this 对象
  3. 没有 return 语句
  4. 按照惯例构造函数名字首字母大写(虽然小写并不会有不一样,为了与普通函数作区分)
  5. 构造函数建立的对象friend一、friend2都有一个constructor的属性
    friend1.constructor == SexFriend; // true
    friend2.constructor == SexFriend; // true
    复制代码

这就使得建立的对象经过constructor属性找到了标识他的对象类型

构造函数的不足之处

仅仅经过构造函数建立的对象实例是不够的,每建立一个实例对象,属性和方法都会隔离存在,没法共用,从而致使资源的浪费。咱们来看一种解决方案

function SexFriend(height, weight, appearance){
    this.height=height;
    this.weight=weight;
    this.appearance=appearance;
    this.saySth=saySth;
}
function saySth=function(){
    alert('我愿意')
}
var friend1 = new SexFriend(168, 90, '倾国倾城');
var friend2 = new SexFriend(182, 150, '风流倜傥');
复制代码

上面的栗子中,把saySth方法放到全局做用域就能实现共用,若是对象须要定义许多方法,那么就要定义不少的全局函数,使得这些自定义的引用类型丝毫没有封装可言。

原型模式就是解决上面的问题的

原型模式

咱们建立的每个函数都有一个prototype(原型)属性,这个属性是一个指针指向一个对象(了解这个很重要

咱们在浏览器打印上面的构造函数SexFriend的prototype出来看 它是这样的

console.log(SexFriend.prototype);
/* { constructor: ƒ SexFirend(height, weight, appearance), __proto__: Object } */
复制代码

这个对象的用途就是由其构造函数建立的全部对象实例均可以共用这个原型对象的属性。 这个对象的constructor 属性 就是指向的构造函数自己

SexFriend.prototype.constructor === SexFriend // true
复制代码

想一想前面咱们说构造函数模式的时候,建立的每一个实例都有一个 constructor 属性,事实上并非实例自己的属性,而是共用了原型对象上的 constructor 属性。

在chrome 浏览器去打印前面构造函数建立的实例对象frend1和friend2就会知道,这两个对象除了拥有在构造函数内添加的属性和方法以外,还有一个属性 proto 这个属性也是一个指针,就是指向原型对象的,所以这两个实例对象都能访问到共同的 constructor 属性。

因此当咱们须要共用某些方法和属性的时候就能够利用原型模式将方法属性绑定到原型对象上

来看具体实现:

function SexFriend(height, weight, appearance){
    this.height=height;
    this.weight=weight;
    this.appearance=appearance;
    // 将共用的方法绑定到原型对象里
    SexFriend.prototype.saySth=function(){
    alert('我愿意')
    }
}
var friend1 = new SexFriend(168, 90, '倾国倾城');
var friend2 = new SexFriend(182, 150, '风流倜傥');
复制代码
总结上面所说,要隔离的属性方法就在构造函数内建立,要彻底共用的方法属性就经过原型对象。

原型链

咱们在前面说到的原型对象本质上就是一个普通的对象,只不过这个对象与构造函数之间经过 constructor 属性建立了联系。

假如咱们将构造函数的 prototype 的指针指向另一个构造函数的实例对象,以下:

function A(){
    this.a = 1;
}
function B(){
    this.b=2;
}
var b = new B();
A.prototype = b

var a = new A()
a.constructor === A // false

a.__proto__ === b // true
a.constructor === b.constructor // true
b.constructor=== b.__proto__.constructor; // true
b.__proto__.constructor=== B.prototype.constructor; // true
B.prototype.constructor=== B; // true
复制代码

咱们把构造函数 A 的 prototype 属性的指针指向了构造函数 B 的实例对象 b; 那么再经过 A 建立的 a 的__proto__属性指向的就是 b 了,即原型对象的指针指向了 b ,这时 a 与 A 之间的链接就断了,可是 A 本来的Prototype(原型)对象仍然存在,只是指针再也不指向它了,

而且 a.constructor 已经指向了B

若是再将 B prototype 属性指针指向构造函数 C 的实例 c , 那么就有 a.constuctor === C了,由__proto__构成链接的经常的链子就是原型链了,使得 a.constuctor 找到最后的 C 。也使得实例对象 a 能沿着原型链继承到全部链上的方法和属性,从最近的原型到最远的原型直到找到相应的属性方法为止。而最顶级的原型对象就是Object

这就是我所理解的原型链,但愿对你有所帮助。

感恩红宝书。

相关文章
相关标签/搜索