今天我们要说的是面向对象中的原型链,在说原型链之前,我们先一步一步来了解一下面向对象中的一些基本概念。
一.类
类是指具有相同特征(属性)和行为(方法)的事物的集合。
二.对象
对象的目的实际上是将具有相同属性和行为的代码整合在一起,方便我们的管理。
起初,我们是这样来创建一个对象的,
js代码:
var person1 = { name:"张三", age:18, height:165, walk:function(){ console.log("i can walk!"); } }
但是这样的话,会有一个问题,我们每调用一个对象都要重复上面的代码,就比如,我们再定义一个person2我们就需要再重复上面的代码,name,age。。。那么怎么来解决这个问题呢?
三.构造函数
为了解决上面的问题,我们就可以想,我们能不能定义一个函数来解决这个问题呢?因为作为“人”这个类,它们都具有上面的属性(name,age,height)和方法(walk),OK,我们来看一下下面这段代码
js代码:
var person = function(_name,_age,_height){ var obj = new Object(); obj.name = _name; obj.age = _age; obj.height = _height; obj.walk = function(){ console.log("i can walk!"); } return obj; } var person2= new person("小花",8,140); //创建新对象
这个是我们所立刻就能想到的一般的创建函数的方法,它确实比我们第一种创建对象的方法简单得多,但是。。。这还不够简单,下面我们就来引进“构造函数”这个概念,因为它比上面那种我们一般创建函数的方法更简单,我们来看一下
function Person(_name,_age,_height){ this.name = _name, this.age = _age, this.height=_height, this.walk = function(){ console.log("i can walk!"); } } var person2 = new Person("阳阳",18,160);
没错,上面这段代码中的函数就是我们创建的构造函数,它的机制在于,会自动创建一个空对象,然后与this相关联,并作为默认返回值返回,通过构造函数我们就不用反复定义属性和方法,是不是比上面简单了一点。需要注意的是,我们在创建构造函数的时候,构造函数的首字母一定要大写。
四.原型
在上面的构造函数中,每个对象的属性(name,age,height)都是不一样的,我们每创建一个对象都要去调用构造函数去分配一定的内存空间进行存储,这个可以理解,但是,对于walk方法,它是每个对象都共有的方法,我们还要每创建一个对象都要去调用构造函数去分配一定的内存空间去存储它,好像是有点浪费资源对不对,没关系,我们接下来就引入另一个概念--原型。
我们先来说一下原型究竟是个什么意思?我们刚说过,对于一些共有方法,我们每次创建对象都要重复的去为它们分配空间,很占用空间。而原型就是为了解决空间被过多占用的问题,每一个函数在创建的时候都会默认的分配一个prototype属性,构造函数也不例外。prototype是一个指针,它指向一个对象,它的特点就是在内存中只有一份。总结起来就是:
将方法定义到函数的prototype上,这样的好处是,通过该构造函数生成的实例所拥有的方法都是指向一个函数的索引,这样可以节省内存。看下代码
function Person(_name,_age,_sex){ this.name = _name; this.age = _age; this.sex = _sex; } Person.prototype = { walk:function(){ console.log("i can walk!"); } } var person3 = new Person("盈盈",20,160); var preson4 = new Person(“笑笑”,22,167);
在上面的代码中,我们虽然也是定义了两个对象,但是我们在调用方法的时候,他们都是去prototype中调用的这个函数,而这个方法在内存中只有一份,就是通过这种方法我们节省了内存。
五,面向对象中的继承
在说原型链之前,我们还要提的就是面向对象中的继承问题,是不是心很累,但是这个对下面我们理解原型链是非常重要的。面向对象有三大特征,封装,多态,还有就是继承,这里,我们只看继承,我们先来看一下,面向对象中的继承机制是怎样的。这里的继承包括构造函数的继承,和原型的继承。
首先,我们来看一下,构造函数的继承,
function Student(_name,_age,_sex,_grade){ this.name = _name; this.age = _age; this.sex = _sex; this.grade = _grade; } var stu1 = new Student("盈盈",22,"女",100);//创建第一个对象 console.log(stu1.name); //盈盈
上面是我们新定义的一个构造函数。它跟上面我们定义的Person有一些共同的属性name,age,height,下面我们就通过构造函数的继承方式创建构造函数Student
function Student(_name,_age,_sex,_grade){ //Person.call(this,_name,_age,_sex); //方法一 Person.apply(this,[_name,_age,_sex]);//方法二 this.grade = _grade; } var stu1 = new Student("盈盈",22,"女",100); console.log(stu1.name);//盈盈
下面我们再来看一下原型的继承
原型的继承是通过将
原型的继承是通过将要创建的原型作为已创建原型的一个实例化对象,这样他就可以继承已创建构造函数的方法,并且还可以在此基础上去扩展自己的方法
下面我们通过具体的完整代码来看一下
function Person(_name,_age,_height){ this.name = _name, this.age = _age, this.height=_height } Person.prototype = { walk:function(){ console.log("i can walk!"); } } var person1 = new Person("阳阳",18,165); function Student(_name,_age,_sex,_grade){ this.name = _name; this.age = _age; this.sex = _sex; this.grade = _grade; } Student.prototype = new Person("姣姣",18,"女"); var stu1 = new Student("盈盈",22,"女",100); stu1.walk(); //i can walk!
在上面这个代码中,stu1就继承了Person的walk方法。
除了可以继承它还可以扩展自己的方法
Student.prototype = new Person("姣姣",18,"女"); Student.prototype.info = function(){ console.log("i can dance!"); } var stu1 = new Student("盈盈",22,"女",100); stu1.walk(); //i can walk! stu1.info(); //i can dance!
需要注意的是,上面扩展Student原型中的方法只能通过Student.prototype.info=function(){}不能通过Student.prototype = function(){}因为后者会产生覆盖!
六.原型链
真是千呼万唤始出来啊,终于到我们的原型链了,通过上面的介绍原型链的理解对我们来说就不难了。
当对象尝试获取某个属性或者方法时,若该对象的构造函数没有会到该构造函数的原型中去找,若原型也没有,会去原型对象的原型去找,最终到达Object.prototype.
通过上面所讲解的面向对象中的继承,我们应该很容易就理解这句话了吧。