javascript几种面向对象的设计模式和原型/原型链详解

面向对象 #

对象:js中万物皆对象,是泛指
类:对象的具体细分
实例:某一个类别中具体的一个事物
对象是一个抽象的概念
内置类:Array,Number,String,Boolean,Null,Undefined,Object,RegExp,Date,Function
HTMLCollection元素集合类,通过getElementByTagName获取的元素集合都是它的实例
NodeList节点集合类,通过getElementsByName获取的节点集合都是它的实例
通过对一个实例的研究,得出这一个类的知识,认为所有的类的实例都具有这些特征,这个过程叫面向对象
所有编程语言都是面向对象开发的,类的继承、封装、多态

  • 1).继承:子类继承父类中的属性和方法
  • 2).多态:当前方法的多种形态(后台语言中,多态包含重载和重写)
  • 3).重载:方法名相同,参数不一样(参数个数,参数类型);JS中不存在重载
  • JS中函数方法名一样的话,后边的会把前边的覆盖掉,最后只保留一个

js中类似重载的操作:根据传递单数的不同,实现不同的功能

重写:子类重写父类的方法

~~~~~

1.设计模式 #

1.1 单例模式 #

对象数据类型的作用:把描述同一件事物的属性和方法放在同一段堆内存中,起到分组的作用,防止冲突;这样不同事物间即使属性名一样也不会发生冲突.这种分组的编写代码模式叫做单例模式
在单例模式中把对象名叫做命名空间
单例模式是一种项目开发中经常使用的模式,可以使用单例模式进行模块化开发。
模块化开发:对于一个较大的项目,需要多人协同开发,根据项目需求划分成几个功能板块,每个人负责一部分,同时开发,最后把每个人的代码进行合并

var person = {
  name: "Amy",
  age: 12,
  write: function(){
    alert(this.name,this.age);
  }
};
person.write();

1.2 工厂模式 #

单例模式虽然解决了分组的问题,但是不能实现批量的生产,属于手工作业模式。
工厂模式把实现同一件事情的代码放到一个函数中,如果以后想实现这个功能,就不需要重新编写代码了,只需执行当前函数就可以了,这就叫做“函数的封装”。
函数的封装,是为了低耦合高内聚,减少页面中的冗余代码,提高代码的重复利用率

function factory(name,age){
  var obj = {};
  obj.name = name;
  obj.age = age;
  obj.write = function(){
    alert(this.name+"can write JS!");
  }
  return obj;
}
var p1 = factory("Amy",12);
var p2 = factory("Bob",14);
p1.write();
p2.write();

2.构造函数 #

2.1 构造函数的目的 #

构造函数模式的目的就是为了创建自定义类,并且创建这个类的实例
构造函数模式拥有了类和实例的概念,并且实例和实例之间是相互独立的,在JS中叫做实例识别

2.2 构造函数的特点 #

  1. 从形式上看,一个函数被作为构造函数还是普通函数执行的唯一区别,看是否使用了new运算符。
    执行的时候,构造函数new Person();而createPerson()只是普通函数执行。
    通过new执行后,Person就是一个类了。
    约定俗成的规范是首字母大写:new Person();newCreatePerson();
    回顾数组的创建方式:
    //字面量创建方式
    var arr = [];
    //实例创建方式--构造函数执行方式
    var arr = new Array();
    

    不管这两种哪种方式,arr都是Array类的实例
    JS中所有的类都是函数数据类型的,通过new执行变成一个类,但是他本身也是一个普通函数
    JS中所有的实例是对象数据类型的

  2. 函数代码执行的时候,
    相同点:都是形成一个私有作用域,然后形参赋值,然后预解析,代码从上至下执行(类和普通函数一样,类也具有普通函数的一面);
    不同点:构造函数在代码执行之前,不用手动创建对象,浏览器会默认创建一个对象数据类型的值(这个对象就是当前类的实例,以当前实例为主体,将属性名和属性值赋给实例,this代表当前实例),浏览器会默认将创建的实例返回

    function CreatePerson(name,age){
    //浏览器默认创建的对象就是实例p1
     this.name = name;
     this.age = age;
     this.write = function(){
       console.log(this.name,this.age);
     }
    //浏览器默认将创建的实例返回
    }
    var p1 = new CreatePerson('Amy',18);
    var p2 = new CreatePerson('Bob',18);
    
    var p3 = CreatePerson("Cindy",20);
    //这样就是普通函数执行方式,并非构造函数执行,则函数中this指window(方法名前没有点),p3没有返回值,则为undefined
    
  3. this在构造函数中,是当前类的实例(this的第 4 种情况)

  4. 虽然各个实例p1,p2都是CreatePerson的实例,都拥有write方法,但是不同实例之间的方法是不一样的;实例和实例之间是单独的个体,所以私有属性之间不相等
    console.log(p1.write === p2.write); //false
    

3.构造函数-扩展 #

3.1 构造函数的本质 #

  1. 在构造函数中,new Fn()执行,如果Fn()中不需要传递参数的话,后边的()可以省略

    function Fn(){
    this.x = 100;
    this.getX = function(){
     console.log(this.x);
    }
    }
    var f = new Fn;
    
  2. 在类中出现的this.xxx=xxx中,this都是当前类的实例,而某一个属性值(方法),方法中的this则需要看方法前面的点才能知道this是谁

    var ss = f.getX;
    ss();  //undefined-->window下没有x属性
    
  3. 类有普通函数一面,也有构造函数一面
    function Fn(){
    var num = 10;
    //this.num 
    this.x = 100;
    this.getX = function(){
     console.log(this.x);
    }
    }
    var f1 = new Fn();
    console.log(f1.num); //undefined
    

    var num只是当前形成私有作用域中的一个私有变量,只有this才能给当前实例增加属性和方法

  4. 在构造函数模式中,浏览器会默认把实例返回,返回的是对象数据类型的值;如果手动写返回值了,若返回基本数据类型的值,当前实例是不变的;若返回引用数据类型的值,当前实例会被返回的值替换掉
    function Fn(){
    this.x = 100;
    this.getX = function(){
     console.log(this.x);
    }
    //浏览器默认返回实例
    //return 100;  基本数据类型
    //return {"name":100}  引用数据类型,会替换原有返回值
    }
    

3.2 检测方法 #

  1. 检测某个实例是否属于这个类–>instanceof
    var f1 = new Fn();
    console.log(f1 instanceof Fn);  //true
    console.log(f1 instanceof Array);  //false
    console.log(f1 instanceof Obejct);  //true-->所有实例都是对象数据类型的,而每一个对象数据类型都是Object内置类的实例
    

    对于检测数据类型,typeof有自己的局限性,不能细分Obejct下的对象,例如数组,正则

  2. “in”检测某个属性是否属于这个对象–> attr in object,不管是公有属性还是私有属性,只要存在则返回true
    console.log('getX' in f1); //若返回true,则证明getX是f1的私有方法
    
  3. hasOwnProperty用来检测某一属性是某对象的私有属性,只能检测私有属性
    console.log(f1.hasOwnProperty(getX)); //true-->说明getX是f1的私有属性
    

    检测某一属性为该对象的公有属性

    function hasPubProperty(obj,attr){
    return (attr in obj) && !obj.hasOwnProperty(attr);
    }
    
  4. isPrototypeOf来判断指定对象object1是否存在于另一个对象object2的原型链中,是则返回true,否则返回false。
    object1.isPrototypeOf(object2);
    

4.原型链模式 #

4.1 原型链构成 #

  1. 每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型),并且这个属性是一个对象数据类型的值
  2. 在prototype上,浏览器自动给它增加了一个constructor(构造函数)属性,属性是当前函数或类本身
  3. 每一个对象数据类型(普通对象,实例,prototype)也自带一个属性:_ proto _ (两边各自两个下划线),属性值是指向当前实例所属原型
    示意图

4.2 提取共有 #

基于构造函数的原型模式,解决方法或属性共有的问题,把实例之间相同的属性和方法提取为共有的

function CreatePerson(name,age){
  this.name = name;
  this.age = age;
}
CreatePerson.prototype.write = function(){
  console.log(this.name,this.age);
}
var p1 = new CreatePerson("Amy",12);
var p2 = new CreatePerson("Bob",14);

想让谁公有就把谁提取到CreatePerson.prototype上即可

console.log(p1.write === p2.write); //true

将新生成的对象的_ prop 属性赋值为构造函数的prototype属性,使得通过构造函数创建的所有对象可以共享相同的原型,这意味着同一个构造函数创建的所有对象都继承自一个相同的对象,因此它们都是同一个类的对象。
在JavaScript中,每一个函数都有默认的原型对象属性prototype,该对象默认包含了两个成员属性:constructor和
proto _。
原型链

4.3 基类Object #

  1. Object是所有对象数据类型的基类(最顶层的类),在它的原型上没有_ proto _属性
  2. f1通过_ proto _可以向上级查找,不管找到多少级,最后总能找到Object

    console.log(f1 instanceof Object); //true
    
  3. f1.hasOwnProperty(x) ==> hasOwnProperty是f1的属性。
    但是f1的私有属性上并没有这个方法,如何处理的呢?
    通过对象名.属性名的方式获取属性值的时候,首先在对象的私有属性上进行查找,如果私有存在这个属性,则获取的是私有的属性值;如果私有的没有这个属性,则通过proto找到所属类的原型,原型上定义的属性和方法都是当前实例的公有属性和方法,原型上存在的话,获取的是公有的属性值,如果原型上也没有,则通过原型上的proto继续向上查找,一直找到Object的prototype为止
    这种查找模式叫原型链模式

  4. 在IE浏览器中,原型模式也是同样原理,但是IE浏览器害怕通过_ proto 把公有的属性修改,所以禁止使用 proto _查找
    f1.sum = function(){}  //修改私有的
    f1.__proto__.sum = function(){}  //修改公有的 //IE禁用
    Fn.prototype.sum = function(){}  //修改公有的
    

5.原型扩展 #

5.1 批量设置原型上的公有属性和方法 #

1.起别名

var pro = Fn.prototype;  //把原来原型指向的地址赋给pro,他们操作的是同一个内存空间
pro.getY = function(){}

2.重构原型对象

Fn.prototype = {
    a: function(){

    },
    b: function(){

    }
};
f.a();
f.b();
console.log(f.constructor); //function Object

自己新开辟一个堆内存,存放公有属性和方法,把浏览器原来提供的Fn.prototype替换掉,并在空闲时销毁
1). 只有浏览器天生提供的Fn.prototype堆内存中才会有constructor,而自己开辟的堆内存中没有这个属性,所以f的构造方法会找到Object;constructor的指向就不是Fn而是Object
为了和原来的保持一致,需要手动增加constructor指向

contrunstor: Fn

2).用这种方式给内置类增加公有属性

Array.prototype.unique = function(){
//数组驱重的方法
}
Array.prototype = {
constructor : Array,
unique : function(){

}
//这样做会把之前存在于原型上的方法和属性给替换掉,所以用这种方式修改内置类的话浏览器会屏蔽掉,无法修改
};

但是可以一个个的修改内置的方法,当方法名和内置方法名重复的时候,会替换掉内值方法
所以在内置类原型上增加方法一定要加上特殊前缀,以示区别,如:

Array.prototype.sort = function(){
console.log("OK");
}
//这样将修改内值方法sort,失去排序功能

5.2 原型中的this #

function Fn(){
    this.x = 100;
    this.y = 200;
    this.getY = function(){
        console.log(this.y);
    }
}
Fn.prototype = {
    constructor: Fn,
    y : 300,
    getX : function(){
        console.log(this.x);
    },
    getY : function(){
        console.log(this.y);
    }
};
var f = new Fn;
f.getX();
f.__proto__.getX();

在类中的this:this.xxx=xxx ==>this指当前类的实例
某个方法中的this,看执行的时候”.”前边是谁就是谁

f.getX();  //this->f 
console.log(f.x);  //100
f.__proto__.getX();  // this->f.__proto__  
//f.__proto__.x->undefiend先向公有方法中查找,然后向Object中查找,没有则为undefined
Fn.prototype.getX(); //undefined
f.getY(); //200
f.__proto__.getY();  //300 this->f.__proto__.getY(),即公有方法中的getY(),公有中y:300
  1. 先确定this的指向
  2. 把this替换成对应的代码
  3. 按照原型链查找机制查找结果

5.3 链式写法 #

执行完成一个方法后继续执行下一个方法

  1. arr.sort(function(a,b){return a-b}).reverse().pop();
  2. 原理:arr执行sort方法->因为sort是Array.prototype上的公有方法,arr是Array的一个实例->数组才能使用Array原型上定义的属性和方法
  3. sort方法执行完成后返回的是一个数组,可以继续执行下面的方法,reverse返回的也是一个数组;pop返回的是被删除的那个元素

5.4 原型链继承 #

for(var key in obj){
  //默认可以把私有的和在所属类原型上扩展的方法和属性都能遍历到,但是一般情况下遍历对象只遍历私有的即可
  if(obj.propertyIsEnumerable){
    console.log(key);
    //只将私有属性和方法遍历出来,自定义的方法和属性以及公有的方法和属性将不被遍历
  }
  if(obj.hasOwnProperty(key)){
    //console.log(key);
    //只遍历私有的
  }
  //后者方式更普遍
}

Object.create(proto);创建一个拥有指定原型和若干指定属性的对象
proto一个对象,作为新创建对象的原型
Object.create(proto);创建一个新对象,把proto作为这个对象的原型
propertyIsEnumerable该值指示指定属性是否为对象的一部分以及该属性是否是可枚举的。

5.5 原型继承方式 #

function A(){
    this.x = 100;
}
A.prototype.getX = function(){
    console.log(this.x);
}
function B(){
    this.y = 200;
}
B.prototype = new A;
  1. 子类B继承父类A的所有属性和方法(私有和公有的)的方式,A的实例既有私有属性方法,又有公有属性和方法,所以用A的实例作为B的原型地址
  2. 继承特点:把父类的私有的和公有的都继承到子类原型上,都是子类公有的
  3. 核心:原型继承并不是把父类的属性和方法克隆一份给子类,而是让父类和子类之间增加了原型链的链接,以后子类的实例想要用父类的方法,需要一级级的向上查找来使用

6.除原型链外的其他继承方式 #

  • call继承
  • 把父类的私有的属性和方法克隆一份,作为子类私有的属性
  • 冒充对象继承
  • 把父类私有的和公有的克隆一份给子类私有的
  • 混合模式继承
  • 原型继承+call继承
  • 寄生组合式继承
  • 私有的只拿私有的(call),公有的拿公有的(Object.create())
  • 中间类继承法(不兼容)

~~~~~

未经允许不得转载:WEB前端开发 » javascript几种面向对象的设计模式和原型/原型链详解

赞 (0)