19_怎么理解js中的原型链?如何实现继承?实现继承常用的方式有哪些

一、理解js的原型

1、函数和对象的关系

函数也是对象的一种,通过instanceof可以判断出来。但是函数和对象的关系并不是简单的包含和被包含的关系

对象都是通过函数创建的

var obj = {a:10,b:20};
var arr = [5, 'x', true];

但是,其实上面这段代码的实质是下面这样的:

//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];

 var obj = new Object();
 obj.a = 10;
 obj.b = 20;

 var arr = new Array();
 arr[0] = 5;
 arr[1] = 'x';
 arr[2] = true;

而Object和Array都是函数,可以自己用typeof函数进行验证。

所以,可以得出:对象都是通过函数创建的

2、原型prototype

在前言中,我们说了函数也是一种对象,所以函数也是属性的集合,同时,也可以对函数进行自定义属性。
每个函数都有一个属性——prototype。这个prototype的属性值是一个对象(属性的集合),默认只有一个叫做constructor的属性,指向这个函数本身。 如下图所示:

上图中,SuperType是一个函数,右侧的方框就是它的原型。

原型既然作为对象(属性的集合),除了constructor外,还可以自定义许多属性,比如下面这样的:

当然了,我们也可以在自己定义的方法的prototype中增加我们自己的属性,比如像下面这样的:

function Fn() { }
    Fn.prototype.name = '张三';
    Fn.prototype.getAge = function () {
       return 12;
};

那么问题来了:函数的prototype到底有何用呢?

在解决这个问题之前,我们还是先来看下另一个让人迷糊的属性:_proto_

3、“隐式原型”proto

我们先看一段非常常见的代码:

function Fn() { }
   Fn.prototype.name = '张三';
    Fn.prototype.getAge = function () {
       return 12;
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getAge ());

即,Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。

但是,因为每个对象都有一个隐藏的属性——“proto”,这个属性引用了创建这个对象的函数的prototype。即:fn.proto === Fn.prototype
那么,这里的proto到底是什么呢?

其实,这个proto是一个隐藏的属性,javascript不希望开发者用到这个属性值,有的低版本浏览器甚至不支持这个属性值。

var obj = {};
console.log(obj.__proto__);
console.log(Object.prototype);

从上面来看,obj.proto和Object.prototype的属性一样!为什么呢?

原因是:obj这个对象本质上是被Object函数创建的,因此obj.proto=== Object.prototype。我们可以用一个图来表示。

即,每个对象都有一个proto属性,指向创建该对象的函数的prototype。

说一下自定义函数的prototype:
自定义函数的prototype本质上就是和 var obj = {} 是一样的,都是被Object创建,所以它的proto指向的就是Object.prototype。

但是,Object.prototype确实一个特例——它的proto指向的是null。

另外一个问题:函数也是一种对象,函数也有proto吗?
答:当然也不例外啦!
下面用一段代码和一张图来说明这个问题,看完相信就有个比较直观的理解啦!

function fn(x, y) {
        return x+y;
}
console.log(fn(10,20));

//以下只是为了演示函数被Function创建的
var fn1 = new Function("x","y","return x+y;");
console.log(fn1(5,6));

用图表示就是:

从上图可以看出:自定义函数Foo.proto指向Function.prototype,Object.proto指向Function.prototype。

但是,为什么有Function.proto指向Function.prototype呢?
其实原因很简单:Function也是一个函数,函数是一种对象,也有proto属性。既然是函数,那么它一定是被Function创建。所以Function是被自身创建的。所以它的proto指向了自身的Prototype

最后一个问题:Function.prototype指向的对象,它的proto是不是也指向Object.prototype?
答案是肯定的。因为Function.prototype指向的对象也是一个普通的被Object创建的对象,所以也遵循基本的规则。如下图:

说了这么多,我们将上面这些图片整合到一整个图片,便于整体理解,图片如下:

4、instanceof

主要是说明下instanceof的判断规则是如何进行的。先看如下代码和图片:

function fn() {
}
var f1 = new fn();

console.log(f1 instanceof fn);//true
console.log(f1 instanceof Object);//true

instanceof的判断规则为:
假设instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。

instanceof的判断规则是:沿着A的proto这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。

结合这个判断规则,上面的代码和图示相信很容易看懂了。

二、原型继承

原型链的定义:访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着proto这条链向上找,这就是原型链。

示例:在实际应用中如何区分一个属性到底是基本的还是从原型中找到的呢?
答案就是:hasOwnProperty这个函数,特别是在for…in…循环中,一定要注意。

但是!!f1本身并没有hasOwnProperty这个方法,那是从哪里来的呢?答案很简单,是从Object.prototype中来的。看下图:

对象的原型链是沿着proto这条线走的,因此在查找f1.hasOwnProperty属性时,就会顺着原型链一直查找到Object.prototype。

由于所有对象的原型链都会找到Object.prototype,因此所有对象都会有Object.prototype的方法。这就是所谓的“继承”。

三、原型继承的几种方式

  • 原型链继承
  • 构造函数继承(对象冒充继承)
  • 组合继承(原型链继承+构造函数继承)
  • 原型式继承
  • 寄生组合式继承

1、原型链继承

function Show(){
    this.name="run";
}

function Run(){
    this.age="20"; //Run继承了Show,通过原型,形成链条
}
Run.prototype=new Show();
var show=new Run();
console.log(show.name)//结果:run

2、构造函数继承(对象冒充继承)

为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题

function Box(age){
    this.name=['Lee','Jack','Hello']
    this.age=age;
}
function Desk(age){
    Box.call(this,age); //对象冒充,给超类型传参
}
var desk = new Desk(200);
console.log(desk.age);//200
console.log(desk.name);//['Lee','Jack','Hello']
desk.name.push('AAA'); //添加的新数据,只给 desk
console.log(desk.name)//['Lee','Jack','Hello','AAA']

3、组合继承(原型链继承+构造函数继承)

借用构造函数虽然解决了刚才两种问题, 但没有原型, 复用则无从谈起。 所以, 我们需要原型链+借用构造函数的模式,这种模式成为组合继承。

function Box(age) {
    this.name = ['Lee', 'Jack', 'Hello']
    this.age = age;
}
Box.prototype.run = function () {
    return this.name + this.age;
};
function Desk(age) {
    Box.call(this, age); //对象冒充
}
Desk.prototype = new Box(); //原型链继承
var desk = new Desk(100);
console.log(desk.run()); //Lee,Jack,Hello100

4、原型式继承

这种继承借助原型并基于已有的对象创建新对象,
同时还不必因此创建自定义类型

function obj(o) { //传递一个字面量函数
    function F() {} //创建一个构造函数
    F.prototype = o; //把字面量函数赋值给构造函数的原型
    return new F(); //最终返回出实例化的构造函数
}
var box = { //字面量对象
    name : 'Lee',
    arr : ['哥哥','妹妹','姐姐']
};
var box1 = obj(box); //传递
console.log(box1.name); // Lee
box1.name = 'Jack';
console.log(box1.name); // Jack
console.log(box1.arr); // (3) ["哥哥", "妹妹", "姐姐"]
box1.arr.push('父母');
console.log(box1.arr); // (4) ["哥哥", "妹妹", "姐姐", "父母"]
var box2 = obj(box); //传递
console.log(box2.name); // Lee
console.log(box2.arr); //引用类型共享了 (4) ["哥哥", "妹妹", "姐姐", "父母"]

5、寄生组合式继承

寄生组合式继承解决了两次调用的问题,组合式继承就会有两次调用的情况

基本模型如下:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype);  //创建对象
    prototype.constructor = subType;              //增强对象
    subType.prototype = prototype;                //指定对象
}

四、参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容