- A+
1.基础
类,是一种抽象的,并不实际存在的,表示一种事物共有特征的描述
对象,是具体的,实际存在的,类中的某一个体
JavaScript是一个典型的“面向过程思想”的语言,语言中不存在类和对象的概念,
但是,由于JavaScript经常要解决“面向对象思想”的问题,所以JavaScript使用一些方法模拟面向对象思想的特征。
2.类的创建
构造函数,通过 new命令生成对象的函数称为构造函数,构造函数一般首字母大写
new命令的作用是先创建一个对象,然后让对象调用构造函数,因此,构造函数中的 this指向的是 new创建的对象
语法示例:
function Student(name, age, sex){
this .name=name;
this .age=age;
this .sex=sex;
};
var Lili = new Student('Lili', 20, 'female');
上述代码中,函数 Student代表类名,通过传递实参向类的属性赋值,new命令将这些键值对构造为对象,然后赋值给变量
注意,这种方法只是 js中众多创建类的方式之一,且并不是最优的创建方式
3.面向对象思想
面向对象(oop),创建一个对象,让对象拥有做某件事的能力(给对象属性和方法),然后命令对象做某件事(封装、继承、多态)
面向过程(pop),分析出解决问题的所有步骤,将其构建为一个一个函数,然后将这些步骤按照一定顺序实现(顺序、选择、循环)
oop的核心就是如何构建一个对象,也就是“对象的封装”!
4.封装
是指构造具有某种特征的类,以通过对其进行实例化,来获得满足需求的对象的过程
封装的特征:
公有,对象中的属性和方法,在对象外部能够直接访问的,称为公有属性和方法
私有,对象中的属性和方法,仅在对象内部才可以使用的,称为私有属性和方法
在构造函数中,通过“this .属性”的方式为类添加公有的属性和方法
this.property所添加的内容在对象外部能够直接被访问
在构造函数中,添加局部变量和闭包的方式为类添加私有的属性和方法
局部变量保证了对象外部无法直接获取,闭包保证了对象外部可以间接获取
对于所有对象都具有的共同属性,为了减少在构造对象时重复传递相同属性值,通过 prototype属性进行统一定义
语法:类名 .prototype .相同属性值的属性 = 属性值;
5.原型
js中给函数提供了一个对象类型的属性,叫作 prototype(原型)
原型归函数所有,不用创建,是默认就存在的
语法:Class .prototype .property = ' value ';
注意,js中提供了一种机制,如果是通过类创建的对象,当访问的属性在对象中没有找到时,
会去找创建这个对象的类的原型属性,如果能找到,则视为当前对象拥有这个属性。
本质上,原型的存在就是为了给类的对象添加共同的属性
作用,使用原型能够有效地节约内存空间,通过原型创建的属性和方法,能够被所有这个类创建的对象访问
6.构造函数语法小结
function ClassName(para1,para2,...) { //类名首字母须大写
var privateProperty = 'value'; //定义私有属性
var privateFunc = function () { } //定义私有方法
this.publicProperty1 = para1; //定义公有属性
this.publicProperty2 = para2;
this.publicFunc = function(){ }; //定义公有方法(特权函数,可以读写privateProperty)
... ...
}
ClassName.prototype.publicProperty3 = 'value3'; //定义原型(共有)属性及属性值
ClassName.prototype.publicFunc=function () { }; //定义原型(共有)方法
var obj = new ClassName(实参1,实参2,…); //通过类创建对象
7.原型属性
结构:原型是一个对象,在原型中通常有两个属性
① 构造器Constructor,该属性指向了这个类本身(表明当前原型归属于哪个类所有)
② 原型指向_proto_,该属性指向原型本身,提供给通过类创建的对象使用
作用:创建类的公有属性和公有方法,为创建对象服务
节约内存空间,不必为每一个对象都分配公有属性和公有方法的内存
缺点:原型中不能保存数组这类引用类型的数据
因为地址传递的问题会导致出现修改的连锁变化
比如,通过 obj .publicProperty .pop(); 删除共有属性数组中的元素,
会导致其他对象访问 prototype .publicProperty 数组时也缺少了这个元素
8.原型链
原型链构成,由对象的“_proto_”属性和对象的构造函数的原型的“_proto_”属性构成的链式结构称为原型链
原型链的顶端是 Object对象,Object对象没有“_proto_”属性,或者说它的“_proto_”属性指向了自身
原型链作用,访问对象的属性或方法时,首先在对象本身中查找是否拥有这个属性或方法,
如果没有找到,那么就沿着原型链逐级向上查找,直到 Object为止
在任何一级寻找到这个属性或方法都视为对象拥有这个属性或方法(继承)
原型链创建,函数的原型(prototype) 设置为 另一个函数的对象(实例)
语法示例:ClassName101 .prototype = new ClassName1 ;
9.继承
在面向对象的语言中,子类能够在不声明的情况下,使用父类的属性和方法的特性叫作继承
而JavaScript语言本质上并不是一门面向对象的语言,所以,需要通过某种手段来模拟继承,这个方法就是“原型链”
链式继承示例:
<script> function RichMan() {} RichMan.prototype.money='billions of pounds'; var father=new RichMan(); //创建父类的实例化对象 function Son() {} Son.prototype=father; //创建子类继承关系 var boy=new Son(); //创建子类实例化对象 console.log(boy.money); //billions of pounds </script>
存在的问题:
① 原型链继承,子类实例化不能向父类构造函数传参,但是可以直接访问父类的原型属性
构造继承,只能访问到父类实例属性,实例化时可以向父类构造函数传参,但是不能访问父类的原型属性和方法
② 原型链继承,子类 prototype的 constructor属性,实际上就是父类 prototype的 constructor属性,这样并不合理
构造函数,子类 prototype的 constructor属性是子类本身,父类也是同样,但构造继承的子类无法享有父类的prototype属性和方法
构造继承示例:
function RichMan(fcash,fhouse,fcar) { this.cash=fcash; this.house=fhouse; this.car=fcar; } /* RichMan.prototype.money='billions of pounds'; var father=new RichMan(); //创建父类的实例化对象*/ function Son(scash,shouse,scar) { RichMan.call(this,scash,shouse,scar); //创建子类构造继承关系 } var john=new Son(1,2,3); //可以访问父类实例属性,but not 原型属性 var dancy=new Son(4,5,6);
10.组合继承
为了解决“原型链继承”和“构造继承”的各自缺点,在创建子类继承时同时使用两种继承方式,即组合继承
1 <script> 2 function RichMan(fcash,fhouse,fcar) { 3 this.cash=fcash; 4 this.house=fhouse; 5 this.car=fcar; 6 } 7 RichMan.prototype.money='billions of pounds'; 8 // var father=new RichMan(); //创建父类的实例化对象 9 10 function Son(scash,shouse,scar) { 11 RichMan.call(this,scash,shouse,scar); //创建“构造继承”关系 12 } 13 Son.prototype=new RichMan(); //创建“链式继承”关系 14 var john=new Son(1,2,3); 15 var dancy=new Son(4,5,6); 16 console.log(john); //Son {cash: 1, house: 2, car: 3} 17 console.log(dancy.money); //billions of pounds 18 19 /* Son.prototype=father; //创建子类继承关系 20 var boy=new Son(); //创建子类实例化对象 21 console.log(boy.money); //billions of pounds*/ 22 </script>
组合继承的弊端:
在子类中调用了两次父类构造函数,一次用于构造继承,一次用于链式继承,也就是对实例属性初始化了两次,
但这一弊端不太致命,子类在实例化时,构造继承的实例属性覆盖了链式继承的实例属性,只是多消耗了一些内存。
寄生组合继承
核心思想:通过寄生方式,砍掉父类的实例属性,这样就能在调用两次父类的构造的时候,不会再次实例属性/方法。
1 <html lang="en"> 2 <head> 3 <meta charset="UTF-8"> 4 <title>继承</title> 5 </head> 6 <body> 7 8 <script> 9 function RichMan(fcash,fhouse,fcar) { 10 this.cash=fcash; 11 this.house=fhouse; 12 this.car=fcar; 13 } 14 RichMan.prototype.money='billions of pounds'; 15 // var father=new RichMan(); //创建父类的实例化对象 16 17 function Son(scash,shouse,scar) { 18 RichMan.call(this,scash,shouse,scar); //创建子类“构造继承”关系 19 } 20 // Son.prototype=new RichMan(); //创建“链式继承”关系 21 Son.prototype.constructor=Son; //将子类原型属性‘constructor’指向子类本身! 22 23 (function () { //创建自执行函数,并嵌套一个空的构造函数 Medi, 24 function Medi() { } //将空构造函数插入到原型链作为中间节点,即原子类变为孙类 25 Medi.prototype=new RichMan(); //这样就避免在每一次子(孙)类实例化时进行两次父类实例化属性和方法 26 Son.prototype=new Medi(); //也就是用这种寄生方式替代原来的直接“链式继承”关系 27 }()); 28 29 var john=new Son(1,2,3); 30 var dancy=new Son(4,5,6); 31 console.log(john); //Son {cash: 1, house: 2, car: 3} 32 console.log(dancy.money); //billions of pounds 33 34 /* Son.prototype=father; //创建子类继承关系 35 var boy=new Son(); //创建子类实例化对象 36 console.log(boy.money); //billions of pounds*/ 37 </script> 38 </body> 39 </html>
11.设计模式
设计模式(Design Pattern)是一套被反复使用的、多数人知晓的、经过分类的代码设计经验的总结(模板)
作用:提高代码可重用性、让代码更容易被他人理解、提高代码的可靠性,
设计模式使代码编写真正工程化,是软件工程的基石脉络,如同大厦的结构一样
常见种类:
① 工厂模式
② 构造函数模式
③ 原型模式
④ 混合模式
⑤ 动态原型模式
12.工厂模式
<script> function RichMan(fcash,fhouse,fcar) { var richMan={}; //定义局部变量,类型为Object对象 richMan.cash=fcash; //直接定义内部对象的属性 richMan.house=fhouse; richMan.car=fcar; return richMan; //将内部对象作为调用函数的返回值 } var man=RichMan('muchCash','bigHouse','luxuryCar'); console.log(man); console.log(man instanceof RichMan); //返回值为 false </script>
工厂模式是软件开发中经常被使用的一种设计模式
instanceof 方法一个实例是否归属于一个类。
通过工厂模式创建的对象,最大的问题是无法确定其属于哪一个类!
13.构造函数模式
<script> function RichMan(fcash,fhouse,fcar) { this.cash=fcash; //定义this属性 this.house=fhouse; this.car=fcar; } //通过 new命令创建对象 var man=new RichMan('muchCash','bigHouse','luxuryCar'); console.log(man); console.log(man instanceof RichMan); //返回值为 true </script>
构造函数和工厂模式最大的区别是:
没有显示创建一个对象,而是通过 new命令隐式创建一个对象
然后让隐式对象来实际执行构造函数,因此构造函数中的 this指向的是这个隐式对象
通过构造函数创建的对象可以明确判断其归属于哪一个类
构造函数创建对象必须使用 new命令,函数名的首字母通常大写
构造函数模式最大的问题是面对子类对象共有的属性值,不能有效地节约内存占用!
14.原型模式
<script> function RichMan(fcash,fhouse,fcar) {} //使用 prototype方法定义类的共有属性 RichMan.prototype.cash='muchCash'; RichMan.prototype.house='bigHouse'; RichMan.prototype.car='luxuryCar'; //通过 new命令创建对象 var man=new RichMan(); man.house='manyHouse'; //对于相同属性不同属性值时单独赋值 console.log(man); console.log(man instanceof RichMan); //返回值为 true </script>
弊端:在处理不同属性值的公有属性时,增加了内存的空间占用!
15.混合模式
<script> function RichMan(fcash,fhouse,fcar) { this.cash=fcash; this.house=fhouse; this.car=fcar; } RichMan.prototype.advantage=function () { console.log('数钱数到手抽筋') } //通过 new命令创建对象 var man=new RichMan('muchCash','bigHouse','luxuryCar'); console.log(man); man.advantage(); </script>
16.动态原型模式
<script> function RichMan(fcash,fhouse,fcar) { this.cash=fcash; this.house=fhouse; this.car=fcar; //使用“懒加载”的方式,定义共有属性的原型 if (typeof RichMan._initialized=='undefined'){ RichMan.prototype.advantage=function () { console.log('数钱数到手抽筋') } RichMan._initialized=true; } } //通过 new命令创建对象 var man=new RichMan('muchCash','bigHouse','luxuryCar'); console.log(man); man.advantage(); </script>
懒加载:使用时才加载和占用内存空间,在没有使用之前相当于不存在
动态原型模式和混合模式很相似,二者都是为了解决原型模式中所有内容都公有的问题
动态原型模式的特点在于,
通过判断一个类的 “._initialized” 属性的类型(typeof),进而判断这个类有没有被实例化过,
如果没有被实例化过,在第一次调用(初始化)时就会将其释放,并将属性值写为 true。
“._initialized”属性是每一个类都拥有的私有属性,它仅用来表示类是否被实例化过,是Boolean类型的可读写属性。