小谈javascript原型链

  javascript不像java是基于类(class)继承的,子类可以继承父类的属性,而是基于原型来实现继承的。要理解原型继承的原理,关键在于理解原型链,本帖将先介绍与之相关的对象、原型对象(prototype)和原型指针(__proto__),有了这些基础之后进而阐述原型链的构成,最后以一个应用实例来启发读者怎么在实际当中使用它。

1.什么是对象?

  按照官方的解释是:Object in Javascript is a “name-value pair” which is a name that maps to an unique value。意思是javascript中的对象是“键值对”的集合,每一个键(key)都对应一个唯一的值(这个值也可以是一个函数),它的样子如下

//定义一只猫对象,它有名字,颜色,抓老鼠等属性
var cat = {
    name: "kitty",       //key为name,value为“kitty”
    color: "white",
    skill: function(){   //key为skill,value为一个函数对象
        console.log("我会抓老鼠!");
    }
};

  而定义对象有两种方法:一种是通过大括号直接创建;另一种是使用new来创建,JavaScript 提供了多个内建对象(就是javascript已经定义好了,可以直接使用的对象,比如 String、Date、Array、Object 等等),栗子如下

//1.通过大括号定义对象
var a = {
    name:"mike",
    age:20
};

//2.通过new操作符创建
//通过new内建对象来创建
var b = new Object();  //创建一个空对象b
b.name = "mike";       //给对象b添加name属性
b.age = 20;            //给对象b添加age属性

//或者先自定义一个构造函数,然后再通过new来创建
var C = function(name, age){   //创建一个构造函数C,需两个参数name,age
    this.name = name;
    this.age = age;
}
var c1 = new C("mike", 20);   //通过new C并传入两个参数来创建对象c1

2.prototype 和 __proto__

2.1 原型指针__proto__
  每个对象都有一个__proto__属性 ,它是一个指针,指向(等于)它父对象的prototype 。如果我们通过new操作符来创建一个对象,则该对象的__proto__指向创建它的函数的prototype。可以举个栗子来看一下

var Programmer = function(){}                    //定义一个“程序员”构造函数
var FProgrammer = new Programmer();              //创建一个“女程序媛”对象
FProgrammer.__proto__ === Programmer.prototype;  //->true

  其中FProgrammer (女程序员)对象是由自定义的构造函数Programmer (程序员)通过new操作符创建的,所以女程序媛的__proto__指向她的父对象(程序员)的prototype。

2.2 原型对象 prototype
  prototype是每一个函数类型自带的属性,而其它非函数类型并没有。那我们就定义一个函数a,然后看一下它的prototype长什么样,如下图所示

这里写图片描述

  如上图红框部分所示,prototype本身是Object类型,所以我们称prototype为原型对象,既然prototype是个对象,那自然就包含了__proto__属性,除此之外,它还包含了一个constructor构造函数,该函数指向函数a本身,验证如下

var Programmer = function(){}
Programmer.prototype.constructor === Programmer;  //-> true

  那么为什么原型对象里会有constructor,它有什么用呢?
  答案是在通过new操作符创建对象时,就是通过这个constructor来实例化对象的。例如以 var o = new Person(“aty”,25); 形式创建对象的过程实际上可以分为下面几步,如下图所示

这里写图片描述

  所以这个constructor是new一个对象的大功臣,是必不可少的。

2.3 prototype 和 __proto__的关系

  由var FProgrammer = new Programmer();这句代码,我们可以得到__proto__与prototype的关系如下图所示

这里写图片描述

3.原型链

  弄清楚了原型对象(prototype)与原型指针(__proto__)之后,我们再来理解原型链。当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去__proto__指向的“父类”prototype去找这个属性,由于prototype也是一个对象,所以它又有自己的__proto__,于是就这样一直找下去,这就形成了我们平时所说的原型链的概念。
  还是以上面“程序员”的代码为例,FProgrammer的__proto__指向了Programmer 的prototype,而Programmer 的prototype也是一个对象啊,那它的__proto__又指向谁呢?我们来看下下面的代码

FProgrammer.__proto__.__proto__ === Object.prototype;      //->true
// 即 Programmer.prototype.__proto__ === Object.prototype;  

  由此可见,Programmer.prototype的__proto__指向的是Object的prototype,说明函数是特殊的对象,是继承自Object的,那么Object原型的__proto__指向的是谁呢?

FProgrammer.__proto__.__proto__.__proto__ === null;        //->true
//Object.prototype.__proto__ === null;

  所以原型链的尽头是一个null对象,将这条原型链画出来会更加清楚,如下图所示
这里写图片描述

4.原型链的应用

  从上面可以看出原型链可以达到继承的效果,也就是说我们可以通过原型链来实现原型继承,下面来看一下原型继承在实际中是怎么应用的,比如说我们要定义一个男程序员对象,他有写代码(code)和以打游戏为乐(fun)这两个属性,还有一个女程序员对象,她有写代码(code)和看电视剧娱乐(fun)这两个属性,一般的方法的写法如下

//男程序员
function MProgrammer(){
    this.code = function(){
        console.log("写代码!!");
    },
    this.fun = function(){
        console.log("我以打游戏为乐,哈哈!!");
    }
}

//女程序员
function FProgrammer(){
    this.code = function(){
        console.log("写代码!!");
    },
    this.fun = function(){
        console.log("我就是喜欢追剧,吼吼!!");
    }
}

  这样的写法明显有重复代码,既然他们有共同的工作(写代码),何不把这个方法提出来放到一个公共对象里作为“父类”供他们继承呢,这里就可以用到原型链了,优化后的代码如下

//“父类”
var Programmer = function(){}
Programmer.prototype.code = function(){
    console.log("写代码!!");
}

//以“父类”通过new操作符创建的男程序员对象
var MProgrammer = new Programmer();
MProgrammer.fun = function(){
    console.log("我以打游戏为乐,哈哈!!");
}

//以“父类”通过new操作符创建的女程序员对象
var FProgrammer = new Programmer();
FProgrammer.fun = function(){
    console.log("我就是喜欢追剧,吼吼!!");
}

MProgrammer.code();
MProgrammer.fun();

FProgrammer.code();
FProgrammer.fun();

运行结果如下图所示
这里写图片描述

  这种以原型继承的方式来写是不是看着舒服多啦,既没有重复代码,又简洁,那如果男程序员和女程序员的共同方法不止那一个敲代码code方法呢,比如还有上下班打卡和开会等,那样在“父类”的prototype上分别加上每个属性会比较繁琐,用以下方式写会更简洁一点

//“父类”
var Programmer = function(){}
Programmer.prototype = {
    code:function(){
        console.log("写代码!!");
    },
    clock:function(){
        console.log("上下班打卡!");
    },
    meeting:function(){
        console.log("每天早上9点开早会!");
    }

    // ...
}

  如果公司职员有一个共同的日常:每天都要写日报,那写日报这个属性是属于全体员工的,不能加在程序员的属性里,而应该加在程序员的“父类”(职员)里,那代码应该怎么写呢,如下所示

//程序员的“父类”——职员
var Staff= function(){}
Staff.prototype.daily = function(){
console.log("公司职员每天都要写日报!");
}
//女程序员的“父类”——程序员
var Programmer = function(){}
//将程序员的原型对象实例化为职员就可以拥有职员的所有属性了
Programmer.prototype = new Staff();
//另外给程序员的原型对象附加写代码的属性
Programmer.prototype.code = function(){
    console.log("写代码!!");
}

//以“父类”(程序员)通过new操作符创建的男程序员对象
var MProgrammer = new Programmer();
MProgrammer.fun = function(){
    console.log("我以打游戏为乐,哈哈!!");
}

//以“父类”通过new操作符创建的女程序员对象
var FProgrammer = new Programmer();
FProgrammer.fun = function(){
    console.log("我就是喜欢追剧,吼吼!!");
}

MProgrammer.code();
MProgrammer.fun();
MProgrammer.daily();

FProgrammer.code();
FProgrammer.fun();
FProgrammer.daily();

结果如下图所示
这里写图片描述

  以上代码中为了继承职员Staff的属性不能写成var Programmer = new Staff();因为这样写的话Programmer就只是一个普通的对象了,并不是函数,下句Programmer.prototype.code = function(){}这里就会报错,因为普通对象并没有prototype属性,只有函数才有,此时Programmer只有一个__proto__属性了,如下图所示
这里写图片描述

总结

  既然javasript是一门面向对象语言,那我们在写js代码时就应该有面向对象的思想,发扬它的原型继承特性,多使用原型链,而不应该以面向过程的方式去编写代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值