说来话长的 Javascript 原型链

栏目: JavaScript · 发布时间: 5年前

内容简介:为了不被罚200块钱,不得不强迫自己在今天之前写完这篇博客,人就是要对自己狠一点,想些招数来强迫自己,也许是件好事。JS的原型链总是被端上前端面试桌上的一盘经典菜,不同的人从不同的角度去品鉴。今天我想从构造函数模式到原型模式到原型链再到原型链来阐述我的理解。以前的我只是知道构造函数就是定义一个函数,函数名大写,函数里面给隐式返回的this对象添加属性和方法,这个函数就是构造函数。

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

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

构造函数模式

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

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

为什么要用构造函数?

首先因为面向对象的程序设计需要很多对象,很多相似又不同的对象,有对象才好办事嘛。

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

我们试着来实现一下

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, '清秀俊朗,风度翩翩');
    
复制代码

你看我们能通过给这个函数传入我们期望的参数就能找到对的那个人吧。

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

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

随着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. 构造函数创建的对象friend1、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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版)

程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版)

左程云 / 电子工业出版社 / 109.00元

《程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版)》是一本程序员代码面试"神书”!书中对IT名企代码面试各类题目的最优解进行了总结,并提供了相关代码实现。针对当前程序员面试缺乏权威题目汇总这一痛点,本书选取将近300道真实出现过的经典代码面试题,帮助广大程序员的面试准备做到接近万无一失。"刷”完本书后,你就是"题王”!《程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版)》......一起来看看 《程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具