JavaScript面向对象

Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)—–es6以前是这样的。所以es5只有使用函数模拟面向对象。

面向对象的三大基本特征

  • 封装:就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。我们平时所用的方法和类都是一种封装,当我们在项目开发中,遇到一段功能的代码在好多地方重复使用的时候,我们可以把他单独封装成一个功能的方法,这样在我们需要使用的地方直接调用就可以了。
  • 继承:继承的过程,就是从一般到特殊的过程。它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。继承在我们的项目开发中主要使用为子类继承父类。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
  • 多态:对象的多功能,多方法,一个方法多种表现形式。

三大特征的优点

封装:封装的优势在于定义只可以在类内部进行对属性的操作,外部无法对这些属性指手画脚,要想修改,也只能通过你定义的封装方法;

继承:继承减少了代码的冗余,省略了很多重复代码,开发者可以从父类底层定义所有子类必须有的属性和方法,以达到耦合的目的;

多态:多态实现了方法的个性化,不同的子类根据具体状况可以实现不同的方法,光有父类定义的方法不够灵活,遇见特殊状况就捉襟见肘了。

创建对象的方式

1.原始模式

var person = new Object() //创建一个Object 对象
person.name = 'sun' //创建一个name 属性并赋值
person.age = 20 //创建一个age 属性并赋值
person.say = function () { //创建一个say()方法并返回值
    return this.name + this.age
}
alert(person.say()) //输出属性和方法的值

这样的写法有两个缺点,一是如果想生成多个实例,写起来就非常麻烦,而且会产生大量重复代码,这是不可取的;二是实例与原型之间,没有任何办法,可以看出有没有什么联系。

为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。

2.工厂模式

工厂,简单来说就是投入原料、加工、出厂。

通过工厂模式来生成对象,将重复的代码提取到一个函数里面,避免像第一种方式写大量重复的代码。这样我们在需要这个对象的时候,就可以简单地创建出来了。

function createPerson(name, age){
    var person = new Object()           
    person.name = name
    person.age = age      
    person.say = function(){
        alert("姓名:"+this.name+"年龄:"+this.age)
    }
    //出厂
    return person
}
//创建两个对象
var p1 = createPerson("zhang", 22);
var p2 = createPerson("yu", 20);       
//调用对象方法
p1.say()
p2.say()

工厂模式解决了重复实例化的问题,但是它也有许多问题,创建不同对象时,其中的属性和方法都会重复建立,消耗内存;还有函数识别问题等等。

3.构造函数模式

为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数模式。 
所谓”构造函数”,其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
构造函数的方法有一些规范:
(1)函数名和实例化构造名相同且大写(非强制,但这么写有助于区分构造函数和普通函数);
(2)通过构造函数创建对象,必须使用new 运算符。
function Person(name, age){
    this.name = name
    this.age = age
    this.say = function(){
        alert("姓名:"+this.name+"年龄:"+this.age)
    }
    //不需要自己return了
}             
//创建两个对象
var p1 = new Person("zhang", 22)
var p2 = new Person("yu", 20)
alert(p1.say == p2.say) //false

构造函数可以创建对象执行的过程:
(1)当使用了构造函数,并且new 构造函数(),那么就后台执行了new Object();
(2)将构造函数的作用域给新对象(即new Object()创建出的对象),而函数体内的this 就代表new Object()出来的对象,而不是指向方法调用者;
(3)执行构造函数内的代码;
(4)返回新对象(后台直接返回)。

构造函数方法很好用,解决了函数识别问题,但是存在一个浪费内存的问题。每一次生成一个实例,都会生成重复的内容,每个实例对象还是有自己的一套方法。这样既不环保,也缺乏效率。

4.在原型(prototype)上进行扩展 

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype是通过调用构造函数而创建的那个对象的原型对象。使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。

我们经常把属性(一些在实例化对象时属性值改变的),定义在构造函数内;把公用的方法添加在原型上面,也就是混合方式构造对象(构造方法+原型方式)

function Person(name, age){
    //属性:每个对象的属性各不相同
    this.name = name
    this.age = age
}
//在原型上添加方法,这样创建的所有对象都是用的同一套方法
Person.prototype.say= function(){
    alert("姓名:"+this.name+"年龄:"+this.age)
} 
//创建两个对象
var p1 = new Person("zhang", 22)
var p2 = new Person("yu", 20)           
alert(p1.say == p2.say) //true
//这里为什么两个对象的方法是相等的呢,原因如下
alert(p1.say == Person.prototype.say) //true

所以,它解决了消耗内存问题。通过prototype我们还可以很方便的扩展系统对象,按照自己的需求来扩展,而且又能适用于所有地方,又不会浪费资源。

总结:

实例对象的__proto__指向,其构造函数的原型;构造函数原型的constructor指向对应的构造函数。

有时某种原因constructor指向有问题,可以通过constructor:构造函数名;重新指向。

继承

利用call()for in继承 :

给对象的constructor.prototype添加方法属性,对象就会继承,如果要实现一个对象继承其他对象,采用如下方法:

function Person(name,age){
    this.name = name
    this.age = age
}
Person.prototype.say = function(){
    console.log('说话')
}
function Man(name,age){
Person.call(this,name,age)
this.sex = "男" } for(var key in Person.prototype){ Man.prototype[key] = Person.prototype[key] } //添加自己的方法 Man.prototype.fn = function(){ console.log('aaa') } var m = new Man('s',20)

采用中介:

function ClassA(name){
    this.name = name
}
ClassA.prototype.showName = function(){
    console.log(this.name)
}
//中继来做准备工作
function Ready(){}
Ready.prototype = ClassA.prototype //引用

//需要来继承ClassA
function ClassB(name){
    ClassA.call(this,name)
}
ClassB.prototype = new Ready()
ClassB.prototype.constructor = ClassB
var b = new ClassB('小明')

es6继承的书写方法:

class Father {   
     constructor(name) {
         this._name = name
     }   
    //实例方法,通过实例对象调用
     getName() {
         console.log(this._name)
     }   
     // 静态方法不会被继承,并且是通过类名去调用的   
     static run() {        
          console.log("run")
     } 
}
class Son extends Father {
    constructor(name, age) {
        //实例化子类的时候把子类的数据传给父类(这里的super必须有,super里的参数是所继承的父类实例化所需要的数据)
        super(name)
        this._age = age
    } 
}
var p = new Father('人') Father.run()//run p.getName() // var m = new Son('小明',20) m.getName() //小明

注意:继承会继承父类的实例属性和实例方法,并不会继承静态属性和静态方法,且静态方法只能通过类名去调用。

多态

同一个方法,面对不同的对象有不同的表现形式就叫做多态。
实现多态,有两种方式:方法重载,方法重写。

方法重载:重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。

方法重写:重写(也叫覆盖)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。

 

posted @ 2020-12-22 16:36  奥利奥ALA  阅读(147)  评论(0)    收藏  举报