JavaScript面向对象编程
JavaScript面向对象编程
面向对象编程主要涵盖:封装、继承、多态
1.封装
将属性和方法组合在一起形成一个类,通过new实例一个对象。这就是封装
先看一下js的一阶封装
<script> var Cat = { name : '', color : '' }; var cat1 = {}; // 创建一个空对象 cat1.name = "大毛"; // 按照原型对象的属性赋值 cat1.color = "黄色"; var cat2 = {}; cat2.name = "二毛"; cat2.color = "黑色"; </script>
上面cat1和cat2都是参考Cat的属性设置的,但是两者之间似乎没有任何关系。
再来看看二阶封装——构造函数
<script> function Cat(name,color){ this.name = name; this.color = color; } var cat1 = new Cat("小明","黑色"); var cat2 = new Cat("小红","红色"); alert("cat1.constructor == Cat如果返回true,表示cat1的构造函数时Cat,也表示Cat就是构造函数:"+cat1.constructor == Cat); alert(cat2.constructor == cat1.constructor); alert(cat2.constructor); alert(cat1.constructor); </script>
上下文:this
this永远指向当前代码的上下文
<script> var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var than = this; return function(){ return than.name; }; } }; alert(object.getNameFunc()()); </script>
大家自己在火狐上面运行看看得到的结果。如果把上面的var than=this;去掉结果又是什么呢。好好体会this关键字。
Prototype形式的封装
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是将该对象的实例共享所有属性和方法。那么,prototype就可以通过调用构造函数来创建那些对象实例的原型对象。
翻译白话:写一个对象函数,然后可以通过对象函数的prototype来定义其他的属性和方法,并且这个属性和方法都会被该对象的实例直接共享。
使用原型的好处是可以让对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中添加定义对象信息,而是可以直接将这些信息添加到原型中。使用构造函数的主要问题就是每个方法都要在每个实例中创建一遍。
直接使用构造函数来创建方法
<script> function Cat(name,color){ this.name = name; this.color = color; this.eat = function(){ alert("吃鱼"); } } var cat1 = new Cat("小明","黑色"); var cat2 = new Cat("小红","红色"); cat1.eat(); alert(cat1.eat == cat2.eat) </script>
用prototype来添加方法
<script> function Cat(name,color){ this.name = name; this.color = color; this.eat2 = function(){ alert(1); } //return this; } //我们在2.html中已经说过,Cat就是指构造函数。而js里面有个特殊的的对象prototype对象。每一个函数都有prototype属性,这个属性指向prototype对象 Cat.prototype.eat=function(){ alert(this.name+"吃鱼"); } Cat.prototype.aa = "aa"; var cat1 = new Cat("小明","黑色"); var cat2 = new Cat("小明","黑色"); //alert(cat1); alert(cat1.aa); Cat.prototype.aa = "bb"; alert(cat1.aa); alert(cat2.aa); /** var cat2 = new Cat("小红","红色"); //alert(Cat.eat()); //TypeError: Cat.eat is not a function cat1.eat(); cat1.eat2(); alert(cat1.eat == cat2.eat); alert(cat1.eat == cat2.eat); */ </script>
好好体会一下上面的图。
①。因为所有函数都会有一个prototype属性,这个属性指向一个prototype对象(原型对象)。所以上面Cat对象的prototype属性指向原型对象。然后我们在原型对象上添加了一个eat方法。
②。所有通过Cat new出来的实体都会有一个匿名属性__proto__这个属性指向Cat指向的prototype对象。这个cat1和cat2就都具有eat方法。
③。原型对象具有一个constructor属性,这个属性指向自己的构造函数,那么上面的原型对象就会指向Cat方法
构造函数,实例,原型对象的区别
实例就是通过构造函数创建的。实例创造出来就具有constructor属性(指向构造函数)和__proto__属性(指向原型对象)。
构造函数中有一个prototype属性,这个属性是一个指针,指向它的原型对象。
原型对象内部也有一个指针(constructor属性)指向构造函数:Cat.prototype.constructor = Cat;
实例可以访问原型对象上定义的属性和方法。
继承
JavaScript实现继承的方式有很多,我们介绍几种简单并且使用的比较多的方式
继承是什么?
答:子类能够调用父类的方法和属性
上面的例子中,猫是动物。那么可以创建一个动物类。猫继承动物。前面描述了prototype的含义,那么是不是可以将猫的构造函数的prototype赋值为动物的对象。
继承方式一:
<script> function Animal(typeName){ this.typeName = typeName; } Animal.prototype.eat = function(){ alert("吃"); }; function Cat(name,color){ this.name = name; this.color = color; /** this.eat = function(){ alert("吃鱼"); } */ } Cat.prototype.eat = function(){ alert("吃鱼"); } Cat.prototype = new Animal("aa"); Cat.prototype.constructor = Cat; var cat1 = new Cat("小明","黑色"); cat1.eat(); alert("Cat.prototype.constructor:"+Cat.prototype.constructor) alert(cat1.typeName); alert(cat1.name) </script>
在上面可以看出一个问题,Cat.prototype.constructor指向的是Animal这就有点奇怪了,Cat明明是猫为什么会是动物呢,所以我们要重新将Cat的构造函数指向Cat。即:Cat.prototype.constructor=Cat
继承方式二:
<script> function Animal(){ this.typeName = "动物"; } Animal.prototype.eat = function(){ alert("吃"); }; Animal.prototype.attr = "属性" function Cat(name,color){ this.name = name; this.color = color; this.eat = function(){ alert("吃鱼"); } } Cat.prototype = Animal.prototype; var cat1 = new Cat("小明","黑色"); cat1.eat(); alert(cat1.typeName); alert(cat1.attr); var animal = new Animal(); alert("查看cat1的构造函数是谁(cat1.constructor):"+cat1.constructor); alert("查看Cat的构造函数是谁(Cat.prototype.constructor):"+Cat.prototype.constructor); alert("查看cat1的构造函数是谁(animal.constructor):"+animal.constructor); alert("查看Cat的构造函数是谁(Animal.prototype.constructor):"+Animal.prototype.constructor); Cat.prototype.constructor = Cat; alert("查看cat1的构造函数是谁(cat1.constructor):"+cat1.constructor); alert("查看Cat的构造函数是谁(Cat.prototype.constructor):"+Cat.prototype.constructor); alert("查看cat1的构造函数是谁(animal.constructor):"+animal.constructor); alert("查看Cat的构造函数是谁(Animal.prototype.constructor):"+Animal.prototype.constructor); </script>
查看例子:好处是可以不用创建Animal实例节约内存,坏处是Cat的原型对象和Animal的原型对象相同,对其中一个修改都会反映到另一个对象。而且Animal的构造函数不能有自己的属性和方法,不然Cat对象将继承不到这些属性
继承方式三:
<script> function Animal(){ this.typeName = "动物"; } Animal.prototype.eat = function(){ alert("吃"); }; Animal.prototype.attr = "属性" function Cat(name,color){ this.name = name; this.color = color; this.eat = function(){ alert("吃鱼"); } } //Cat.prototype = Animal.prototype; var F = function(){}; F.prototype = Animal.prototype; Cat.prototype = new F(); var cat1 = new Cat("小明","黑色"); cat1.eat(); alert(cat1.typeName); alert(cat1.attr); var animal = new Animal(); alert("查看cat1的构造函数是谁(cat1.constructor):"+cat1.constructor); alert("查看Cat的构造函数是谁(Cat.prototype.constructor):"+Cat.prototype.constructor); alert("查看animal的构造函数是谁(animal.constructor):"+animal.constructor); alert("查看Animal的构造函数是谁(Animal.prototype.constructor):"+Animal.prototype.constructor); Cat.prototype.constructor = Cat; alert("查看cat1的构造函数是谁(cat1.constructor):"+cat1.constructor); alert("查看Cat的构造函数是谁(Cat.prototype.constructor):"+Cat.prototype.constructor); alert("查看animal的构造函数是谁(animal.constructor):"+animal.constructor); alert("查看Animal的构造函数是谁(Animal.prototype.constructor):"+Animal.prototype.constructor); </script>