LI@NG

JS 原型继承

什么是继承?

学过“面向对象”的同学们是否还记得,老师整天挂在嘴边的面向对象三大特征:封装,继承,多态。今天我们就来白话一下javascript中的原型继承,没学过的同学们也不用担心,跟着往下走,我相信你会明白的。

继承,当然是面向对象其中的一种思想和概念了,所谓继承,顾名思义就是继承了。。。

比如说小明是老明唯一的儿子,老明有一个亿的资产,这一个亿的资产虽然不在小明手里,但是小明是老明儿子,小明在一定程度上“继承”了他老爸给他提供的这么多的资产,小明不仅可以享用自己赚来的钱,也可以随时消费他父辈的资产。

在程序中,其实也是类似的,如果一个对象A继承了对象B,那么对象A不仅可以使用自己的属性和方法,同时也可以使用父级B中的属性和方法。

为了能够说明白Javascript 中的原型继承,就不得不说javascript中的对象。

javascript对象创建(声明)方式

在javascript中你可以使用以下方式创建一个javascript 对象(object).

  1. 对象字面量(object literal)方式
  2. 构造函数(constructor)方式 // es6中可以用class创建一个真正的对象
  3. Object.create(prototype) //常用此方法进行对象间继承

第一种对象字面量方式很简单粗暴:

var a = {}; // 我创建了一个空对象a

第二种“构造函数”方式来“创建对象”。

“构造函数”只是创建一个对象的第一步!有了构造函数之后,第二部就是用它创建一个对象!

所以, 第一步,来一个构造函数,你可以把它当成一个普通函数,比如:

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

这个函数也可以接受参数,如果大家对this关键字不了解,请先看《详解javascript this关键字》这个教程,这里我就不啰嗦了。

我们有了构造函数之后,第二步开始使用它构造一个函数。

在javascript中,我们使用 “new” 操作符 + 构造函数 来创建一个对象。

var a = new People('xiaoming');
var b = new People;

顺便提一下,如果你不想给构造函数传入参数,那么带不带括号都是一样的,也就是说:

var a = new People;
var b = new People();

这两种创建对象方式都正确。


小提示:其实第一种对象字面量方式只是构建对象的语法糖(syntax sugar),底层还是使用构造函数方式构造的:

new Object()

大家不相信的话,可以打开你的chrome的控制台,直接输入:

Object

是不是看到了下面这一行?

function Object() { [native code] }

这个Object也是个构造函数,只不过这里原生代码。

原型prototype

现在大家知道了javascript中对象创建的两种方式。

var a = {};
var xiaoming = new People();

接下来这句话请大家重复三遍:

所有的javascript对象都从一个叫“原型”的地方继承了所有的属性和方法!

这个“原型”是个啥?就是个对象!你可以把它想象成:{}。

我们前面说了创建对象的两种方法: 1, 对象字面量

var a = {};
// 其实等于
var a = new Object;

2, 构造函数

function People(){
}

var a = new People();

我们说过了:

所有的javascript对象都从一个叫“原型”的地方继承了所有的属性和方法!

上面这两个例子中对象是:

a

那么原型(prototype)在哪呢?大家试着和我一样做一下:

console.log(Object.prototype);
console.log(People.prototype);

是不是都输出了一个对象?

我们之前也说了,原型(prototype)就是一个对象而已,现在大家也知道它在哪了吧?原型就是构造函数的一个属性,这里可能听着有点别扭,函数的属性?对,javascript中的构造函数也是对象!

重要的事情说三遍:javascript的原型(prototype)是什么?就是“构造函数”下的一个对象叫做“原型”(prototype)

再添一句,这个原型对象中又有个名为“constructor”的属性,指向了该函数本身。

function People(){}

People.prototype.constructor === People // true

再提醒一下,原型只存在在“构造函数”中,有些同学会误解的去找一个“对象”或“对象字面量”的原型(prototype),因为我们说了原型(prototype)只存在在“构造函数”中,所以去对象或对象字面量里找原型的找不到的,只能返回undefined。比如:

var a = {};
console.log(a.prototype) // 返回undefined

// 或者

function People() {}
var p = new People;
console.log(p.prototype) // 依然返回undefined

// 但是
console.log(People.prototype) // 就返回一个{} 当然这里面有好多原生的方法

但是,还有一个特别属性

__proto__

可以让你“向父级”查看,当前对象继承的“原型”是谁?就是孩子找爸爸。 打开你的chrome浏览器的控制台,接着上面的代码继续试验:

a.__proto__ // Object {}
p.__proto__ // Object {}
a.__proto__ === Object.prototype // true
p.__proto__ === People.prototype // true

// 我们发现两个对象的原型都是对象。
// 那么我们再来看看构造函数“继承”的原型是谁?

Object.__proto__ // function () {}
People.__proto__ // function () {}

看到这里,希望你能明白,不管是javascript自带的,还是我们自定义的构造函数依然继承自匿名函数的原型!(希望你没晕)

原型继承

有了上面的铺垫,我们终于能够开始说一下原型继承了。如果你上面的知识点都理解了,理解原型继承不在话下。

来个例子:

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

People.prototype.sayHi = function () {
	console.log('hi, my name is ', this.name);
};


var a = new People('xiaoming');
a.sayHi(); // hi, my name is xiaoming

var b = new People('laoming');
b.sayHi(); // hi, my name is laoming

这种方式很简单也很直接,你在构造函数的原型上定义sayHi方法,那么用该构造函数实例化出来的对象都可以通过原型继承链访问到定义在构造函数原型上的方法。

理解了上述内容,你可以直接利用javascript的原型继承,也可以用它为基础构造自己“类”感觉小库。