原型

原型的背景

首先,你应该知道javascript是一门面向对象语言。

是对象,就具有继承性。

继承性,就是子类自动共享父类的数据结构和方法机制。

而prototype 和 __proto__ 的存在就是为了建立这种子类与父类间的联系。

我们将prototype称作原型,将通过__proto__来建立起来的对象与对象的关系称作原型链。

下面,通过创建一个简单对象,来探索原型和原型链到底是什么。

原型与原型链

首先,创建一个最简单的对象

function Foo(){}
var o = new Foo();

ps:这是刚从java转入JavaScript时,最令我费解的一段代码,凭什么一个function就可以用来创建一个对象。下面就是new 关键字的分解动作。。。这个关键字究竟做了什么,能创建一个对象。。

这个创建的过程,可以分解成下面代码

  function Foo(){}
 // 创建一个基本对象
 var o = new Object();
 // 创建对象和父类原型之间的链接 
 o.__proto__ = Foo.prototype;
 // 执行构造函数
 Foo.call(o); 

为了更好的理解这段代码,我们先理解这几个概念

  • 什么是构造函数constructor

    构造函数就是对象创建时,自动调用的方法

  • prototype

    prototype,长这样

    {
        constructor: f foo()
        __proto__: Object
    }

    它是一个对象,存储着一类事物的基本信息,所以可以将它称作类。

  • __proto__

    __proto__,这个属性用来建立对象和类之间的关系。

有了这些概念,我们来分析创建对象的过程中,究竟做了些什么.
创建一个对象,会做如下三件事。

  • 创建一个基本对象 new Object()
  • 建立新对象与原型(我把它理解为类)之间的连接
  • 执行构造函数

小结:prototype可以理解为类,也就是存储一类事物的基本信息。__proto__可以理解为一条线索,用来建立原型(类)和对象之间的关系。

原型、原型链和继承之间的关系。

继承,需要满足如下三个要求。

  • 子类继承父类的成员变量
  • 子类继承父类的方法
  • 子类继承父类的构造器,如果父类的构造函数带有参数,那么子类中应该显示调用

我们该如何实现继承呢?

// 创建一个构造函数,我认为 a.prototype就是父类对象。
function a(x,y) {
    a.prototype.x = x;
    a.prototype.y = y
}
// 为父类对象添加一个method 
a.prototype.console = function() {
    console.log(this.x);
    console.log(this.y);
}
//创建子类构造函数
function b(x,y) {
    // 子类显示的调用父类构造方法
    a.call(this,x,y);
}
// 子类继承父类的成员变量以及父类的方法
b.prototype = Object.create(a.prototype); = b.prototype.constructor = b;
// 创建对象
var c = new b(1,2);
 // 这里Object.create 是用来创建一个新的prototype,用来记录新类的信息,并与父类建立联系
 Object.create = function() {
  //创建一个基本对象
  var temp = new Object();
  //与父类的的原型建立联系
  temp.__proto__ = proto;
  //返回新类的原型
  return temp;
 }

小结:继承关系的实现,做了如下两件事情

  • 子类显示的调用父类的构造函数
  • 子类通过原型来与父类建立联系,从而能让子类拥有父类的成员变量和方法。

原型就是类,原型链就是来建立子类和父类之间的联系。

原型链的实际表现

先创建一个类

function people() {}
// 为父类对象添加一个method
people.prototype.run = function() {
   console.log("I am running");
}

通过类来创建一个对象

var p = new people();
p.run();
// i am running

这里p对象长这样

{
__proto__: Object
}

很显然,这个对象之中并没有run方法。

但是它却能调用run,因为它会通过__proto__(原型链)来寻找类中的方法。

经常有人这么问proto 和prototype有什么区别?

我想看到这里,你应该很明白了。

  • prototype 一个存储类信息的对象,只存在function中.(下图中绿块)
  • proto 单纯是对象用来指向上级的一链接。(看下图黄线)

那么又有人会问function中__proto__又是什么关系呢?

function 本身是对象,所以当然也有自己原型。function继承于Function.(看下图蓝线)。

clipboard.png

下面介绍一下原型继承和类继承的关系。

原型继承和类继

An example that shows the difference between creating a JavaScript class and subclass in ES5 and ES6.

ES5

'use strict';

/**
 * Shape class.
 * 
 * @constructor
 * @param {String} id - The id.
 * @param {Number} x  - The x coordinate.
 * @param {Number} y  - The y coordinate.
 */
function Shape(id, x, y) {
    this.id = id;
    this.setLocation(x, y);
}

/**
 * Set shape location.
 * 
 * @param {Number} - The x coordinate.
 * @param {Number} - The y coordinate.
 */
Shape.prototype.setLocation = function(x, y) {
    this.x = x;
    this.y = y;
};

/**
 * Get shape location.
 * 
 * @return {Object}
 */
Shape.prototype.getLocation = function() {
    return {
        x: this.x,
        y: this.y
    };
};

/**
 * Get shape description.
 * 
 * @return {String}
 */
Shape.prototype.toString = function() {
    return 'Shape("' + this.id + '")';
};

/**
 * Circle class.
 * 
 * @constructor
 * @param {String} id     - The id.
 * @param {Number} x      - The x coordinate.
 * @param {Number} y      - The y coordinate.
 * @param {Number} radius - The radius.
 */
function Circle(id, x, y, radius) {
    Shape.call(this, id, x, y);
    this.radius = radius;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;

/**
 * Get circle description.
 * 
 * @return {String}
 */
Circle.prototype.toString = function() {
    return 'Circle > ' + Shape.prototype.toString.call(this);
};

// test the classes
var myCircle = new Circle('mycircleid', 100, 200, 50); // create new instance
console.log(myCircle.toString()); // Circle > Shape("mycircleid")
console.log(myCircle.getLocation()); // { x: 100, y: 200 }

ES6

'use strict';

/**
 * Shape class.
 * 
 * @constructor
 * @param {String} id - The id.
 * @param {Number} x  - The x coordinate.
 * @param {Number} y  - The y coordinate.
 */
class Shape(id, x, y) {
    constructor(id, x, y) { // constructor syntactic sugar
        this.id = id;
        this.setLocation(x, y);
    }
    
    /**
     * Set shape location.
     * 
     * @param {Number} - The x coordinate.
     * @param {Number} - The y coordinate.
     */
    setLocation(x, y) { // prototype function
        this.x = x;
        this.y = y;
    }
    
    /**
     * Get shape location.
     * 
     * @return {Object}
     */
    getLocation() {
        return {
            x: this.x,
            y: this.y
        };
    }
    
    /**
     * Get shape description.
     * 
     * @return {String}
     */
    toString() {
        return `Shape("${this.id}")`;
    }
}

/**
 * Circle class.
 * 
 * @constructor
 * @param {String} id     - The id.
 * @param {Number} x      - The x coordinate.
 * @param {Number} y      - The y coordinate.
 * @param {Number} radius - The radius.
 */
function Circle extends Shape {
    constructor(id, x, y, radius) {
        super(id, x, y); // call Shape's constructor via super
        this.radius = radius;
    }
    
    /**
     * Get circle description.
     * 
     * @return {String}
     */
    toString() { // override Shape's toString
        return `Circle > ${super.toString()}`; // call `super` instead of `this` to access parent
    }
}

// test the classes
var myCircle = new Circle('mycircleid', 100, 200, 50); // create new instance
console.log(myCircle.toString()); // Circle > Shape("mycircleid")
console.log(myCircle.getLocation()); // { x: 100, y: 200 }

这段代码,自己体会。。。。


zhiwei
1.4k 声望18 粉丝

急招:现盒马优选业务启动,前端人才紧缺,HC 多多(目前未限制,P6起步),面试流程极速(平均 3 天走完技术面),在阿里 HC 全面收紧的情况下,机会难得,欢迎大家加入盒马体验技术团队!