类
每个类都包含三个部分:
一:构造函数:供实例化对象用的
二:构造函数外的通过点语法添加的:这是供类使用的,实例化对象是访问不到的
三:类的原型中的:实例化对象可以通过其原型链进行间接的访问
封装
1、声明一个类:
首先声明一个函数保存在一个变量里,然后在这个函数(类)内部通过this(函数内部自带的一个变量,用于指向当前这个对象)变量添加属性或方法来实现对类添加属性或者方法。
new关键字可以看做是对当前对象this的不停赋值。
var Book = function(title,type,time){
this.title = title;
this.type = type;
this.time = time;
}
//安全模式
var Book = function(title,type,time){
//判断执行过程中this是否是当前这个对象
if(this instanceof Book){
this.title = title;
this.type = type;
this.time = time;
}else{
return new Book(title,type,time);
}
}
类中通过this和prototype添加方法和属性的区别
this添加的属性和方法是在当前对象上添加的,而JavaScript是一种基于原型prototype的语言,所以每创建一个对象时,它都有一个原型prototype用于指向其继承的属性、方法。这样通过prototype继承的方法并不是对象自身的,所以在使用这些方法时,需要通过prototype一级一级查找来得到。
你会发现通过this定义的属性和方法该对象自身拥有的,所以每通过创建一个类创建一个新对象时,this指向的属性和方法都会得到相应的创建,而通过prototype继承的属性和方法是每个对象通过prototype访问到。所以我们每次通过类创建一个新对象时这些属性和方法不会再次创建。
constructor是一个属性,当创建一个函数或者对象时都会为其创建一个原型对象prototype,在prototype对象中又会像函数中创建this一样创建一个constructor属性。constructor属性指向的就是拥有整个原型对象的函数或对象。
属性与函数的封装实现的原理: JavaScript的函数级作用域,声明在函数内部的变量以及方法在外界是访问不到的,通过此特性可以创建类的私有属性和函数。然而在函数内部通过this创建的函数和属性,在类创建对象时,每个对象自身都拥有一份并且在外部可以访问。因此通过this创建的属性和函数,可以看做是对象共有属性和共有函数。通过this创建的方法不但能访问这些对象的共有属性和函数,还可以访问私有属性和函数,可以将这些方法看做为特权方法。在创建对象时通过这些特权方法我们可以初始化实例化对象的一些属性,所以特权方法可以看做构造函数。
var Book = function(id,name,price,){
//私有属性
var num = 1;
//私有方法
function checkId(){
}
//特权方法
this.getName = function(){};
this.setName = function(){};
//对象公有属性
this.id = id;
//对象共有方法
this.copy = function(){}
//构造器
this.setName(name);
}
在类外通过点语法添加的函数和属性在使用new创建对象时是执行不到的。所以在新创建的对象中是无法访问的。但是可以通过类来使用。因此在类外添加的属性和方法被称为类的静态属性和静态函数。
使用prototype添加的属性和函数都可以在创建的对象中通过原型链进行访问,所以讲在prototype对象中的属性和函数称为共有属性和共有函数
//静态公有属性
Book.isChinese = false;
//静态公有方法
Book.restTime = function(){
}
Book.prototype = {
//公有属性
isJbBook:true;
//公有方法
disPlay:function(){
}
}
闭包
闭包是有权限访问另一个函数作用域中变量的函数,即在函数的内部实现另一个函数。我们将这个闭包函数作为创建对象的构造函数,这样他即是闭包又是可实例对象的函数
var Book = (function(){
//静态私有变量
var bookNum = 0;
//静态私有函数
function checkBook(){
}
//创建类
function _book(newId,newName,NewPrice){
//私有变量
var name,price;
//私有函数
function checkID(){
}
//特权方法
this.getName = function(){
}
this.setName = function(){
}
//公有属性
this.id = newId;
//公有方法
this.copy = function(){
}
//构造器
this.setName(newName)
}
//构造原型
_book.prototype = {
//静态公有属性
isJbook:false;
//静态公有寒素
disPlay:function(){
}
}
//返回类
return _book;
})()
继承
//类式继承
//实现父类
function SuperClass(){
this.superValue = true;
}
//为父类添加公有方法
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
}
//实现子类
function SubClass(){
this.subValue = false;
}
//继承父类
SubClass.prototype = new SuperClass();
SubClass.prototype.getSubValue = function(){
return this.subValue;
}
类原型对象的作用就是给类的原型添加函数和属性,但是类不能直接访问这些属性和方法,必须通过原型prototype进行访问,而我们实例化父类的时候,新创建的对象复制了父类的构造内的属性和函数并且将原型的_proto_指向父类的原型对象,这样就拥有父类的原型对象上属性与函数。并且这个新建的对象可以直接访问到父类原型对象的属性和函数。如果将新创建的对象赋值给子类原型,那么子类的原型就可以访问到父类的原型属性和方法。
上述的类继承有两个缺点,其一,由于子类通过其原型prototype对父类实例化,继承父类。如果父类中的共有属性是引用类型,就会在子类中被所有实例共用,因此子类的实例更改子类原型从父类构造函数中继承来的属性就会直接影响其他子类。其二,由于子类实现是继承靠原型prototype对父类的实例化实现,因此在创建父类的时候,是无法向父类传递参数的。因而在实例化父类的时候也无法对父类构造函数的属性进行初始化。
构造函数继承
//声明父类
function superClass(id){
//引用类型共有属性
this.book = {'javascript','html','css'};
//值类型共有属性
this.id = id;
}
superClass.prototype.showBooks = function(){
console.log(this.id)
}
//声明子类
function subClass(id){
//继承父类
superClass.call(this,id);
}
//实现
var instance1 = new subClass(10);
instance1.showBooks();
suberClass.call(this,id),这条语句是构造函数式继承的精华,由于call这个方法可以更改函数的作用环境,因此在子类中,对superClass调用的这个方法是将子类中的变量执行一遍,由于父类中是给this绑定属性的,因此子类自然就继承了父类的公有属性。但是由于这种继承没有涉及原型prototype,所以父类的原型方法自然不会被子类继承。而如果想被子类继承就必须放在构造函数中,这样创建的每个实例都会单独拥有一份而不能共用。
组合继承
组合继承就是类式继承和构造函数继承共同实现
function superClass(name){
this.name = name;
this.books = {'html','css','html'};
}
//父类原型共有方法
superClass.prototype.getName = function(){
console.log(this.name);
}
function subClass(name,time){
//构造函数式继承父类name属性
superClass.call(this,name);
this.time = time;
}
//类式继承 子类原型继承父类
subClass.prototype = new superClass();
subClass.prototype.getTime = function(){
console.log(this.time);
}
组合继承融合了类式继承和构造函数继承的优点
原型继承
借助原型prototype可以根据已有的对象创建一个新的对象,同时不必创建新的自定义对象类型
function inheritObject(o){
//声明过度对象
function F(){};
//过度对象原型继承父对象
F.prototype = o;
//返回一个父对象的实例,该实例原型继承父对象
return new F();
}
实际封装的类式继承,同样也有类式继承的缺点。优点:F过渡类中无内容所以开销比较小。
寄生式继承
//声明基类对象
var book = {
name:'js book',
alikeBook:{'cBook','hBook'}
}
//
function createBook(obj){
//通过原型继承方式创建新对象
var o = new inheritObject(obj);
//扩展新对象
o.getName = function(){
consolec.log(name);
};
//返回扩展后的新对象
return new o;
}
其实是对原型继承的二次封装,并且在二次封装中对继承对象进行扩展。
寄生组合式继承
组合式继承,存在一个问题:子类不是父类的实例,而子类的原型是父类的实例。
它处理的对象不是对象,而是类的原型。
//寄生式繼承 继承原型
//传递参数:subClass 子类
//传递参数:superClass 父类
function inheritPrototype(subClass,superClass){
//复制一份父类的原型副本保存在变量中
var p = inheritObject(superClass.propotype);
//修正因为重写子类原型导致子类的constructor属性修改
p.constructor = subClass;
//设置子类的原型
subClass.prototype = p;
}
相对于组合继承,我们需要继承的仅仅是父类的原型,不需要调用父类的构造函数。在构造函数继承中我们已经调用了父类的构造函数。因此我们需要的就是一个父类的原型对象的一个副本,而这个副本我们痛过原型继承救可以得到,但是直接这么赋值给子类会存在问题,因对父类原型对象的复制得到的复制对象p中的constructor指向的不是subClass的子类对象,因此寄生式继承中要对复制对象p做一次增强,修复其constructor属性指向不正确的问题,最后将复制对象p赋值给子类的原型,这样子类的原型就继承了父类的原型并且没有执行父类的构造函数。
多态
function Add(){
function zero(){
return 10
}
function one(num){
return num+10;
}
function two(num1,num2){
return num1+num2;
}
this.add = function(){
var arg = arguments;
var len = arg.length;
switch(len){
case 0:
return zero();
break;
case 1:
return one(arg[0]);
break;
case 2:
return two(arg[0],arg[1]);
break;
}
}
}
var A = new Add();
A.add() //10
A.add(1) //11
A.add(1,2) //3