js中的面向对象入门

什么是对象

咱们先来看高程三中是如何对对象进行定义的javascript

"无序属性的集合,其属性能够包括基本值、对象或者函数",对象是一组没有特定顺序的的值。对象的没个属性或方法都有一个俄名字,每一个名字都映射到一个值。前端

简单来理解对象就是由属性和方法来组成的java

面向对象的特色

-封装git

对于一些功能相同或者类似的代码,咱们能够放到一个函数中去,屡次用到此功能时,咱们只须要调用便可,无需屡次重写。github

在这里咱们能够理解为创造对象的几种模式:单例模式,工厂模式,构造函数模式,原型模式等。chrome

  • 继承浏览器

    子类能够继承父类的属性和方法dom

  • 多态(重载和重写)
    1. 重载:严格意义上说js中没有重载的功能,不过咱们能够经过判断函数的参数的不一样来实现不一样的功能来模拟重载。
    2. 重写:子类能够改写父类的属性和方法

javascript中的封装

  • 单例模式
    小王在一个小公司,就本身一个前端,因此他写js都是这样的
var a = 1;
function getNum(){
    return 1;
}

后来公司又招了个前端小明,因而变成他们2个一块儿写同一个js了。一天小王发现本身写的getNum方法出问题了,原来是小华写的js中也有个getNum的函数,代码合并后把他的覆盖掉了,因而便找小华理论去,通过一番妥协后,两人都把本身的代码改了改模块化

var xiaoming = {
    num:1,
    getNum:function(){
        return 1;
    }
}

var xiaohua = {
    num:2,
    getNum: function(){
        return 2;
    }
}

这就是咱们所谓的单例模式(命名空间)函数

咱们把描述同一个事物的方法或者属性放到同一个对象里,不一样事物之间的方法或者属性名相同相互也不会发生冲突。

单例模式的优劣

  1. 使用单例模式,咱们能够实现简单的模块化开发
var utils = {
    getCss:function(){
        //code
    },
    getByClass:function(){
         //code
    },
    setCss:function(){
        //code
    }
}

咱们能够把本身写好的工具方法放到一个单独的js文件中,而后直接引入便可。

  1. 避免了全局变量名的冲突
    须要注意的是,咱们在引入各个模块的时候,须要注意引入的顺序,引入顺序是按照各模块之间的相互依赖进行先后排列的;
  2. 缺点:
  • 单例模式只是必定程度上避免了变量名的冲突,但并不能完全解决此问题,并且在不一样的对象下,咱们可能会有不少功能相同的代码,最终形成大量的冗余代码。
  • 单例模式让每一个对象有了本身独立的命名空间,可是并不能批量生产的问题,每个新的对象都要从新写一份如出一辙的代码。
var person1 = {
    name:'小明',
    age:24,
    showName:function(){
          console.log('个人名字是:'+this.name)
    }
};
var person1 = {
    name:'小华',
    age:25,
    showName:function(){
          console.log('个人名字是:'+this.name)
    }
};
  • 工厂模式
  1. 工厂模式其实就是把须要一个个的编写的对象,放在一个函数中统一的进行建立,说白了就是普通函数的封装。
  2. 工厂模式总共3步骤:
    1)引进原材料 --- 建立一个空对象
    2)加工原材料 --- 加工对象:给对象添加属性和方法;
    3)输出产品 --- 返回对象:return 对象;
function CreatePerson(name,age){
        var obj={};//1.建立一个空对象
        //2.加工对象
        obj.name=name;
        obj.age=age;
        obj.showName=function(){
            console.log('个人名字是:'+this.name)
        };
        return obj;//3.输出对象;
}
var person1 = CreatePerson('小明',23)
var person2 = CreatePerson('小华',23)
person1.showName(); //个人名字是:小明
person2.showName(); //个人名字是:小华
  • 工厂模式的优缺点
    1. 既然叫工厂模式,它就和咱们周围的工厂同样,咱们只须要把原材料放进去,就能获得咱们须要的产品了。
    2. 工厂模式也解决了单例模式的批量生产的问题,避免了单例模式中的大量冗余代码,进行系统的封装,提升代码的重复利用率
    3. 不过工厂模式跟咱们js内置类的调用方法不一样
  • 构造函数模式
    1. 能够建立一个自定义的类,而且能够new出实例
    2. 构造函数作的就是类和实例打交道。
//构造函数:首字母大写(约定俗成);
    function CreatePerson(name,age){ //建立一个自定义的类
        //构造函数中的this,都是new出来的实例
        //构造函数中存放的都是私有的属性和方法;
        this.name=name;
        this.age=age;
        this.showName=function(){
             console.log('个人名字是:'+this.name)
        }
    }
   //实例1
    var person1 = new CreatePerson('小明',25)
   //实例2
    var person2 = new CreatePerson('小华',24)

这里说一下工厂模式和构造函数模式的区别:

1. 在调用的时候不一样:
工厂模式:调用的时候,只是普通函数的调用createPerson();
构造函数模式:new CreatePerson();
2. 在函数体内不一样:
工厂模式有三步:1)建立对象 2)加工对象 3)返回对象;
构造函数模式只有1步: 只有加工对象; 由于系统默认会为其建立对象和返回对象;
3. 构造函数默认给咱们返回了一个对象,若是咱们非要本身手动返回的话:
    (1)手动返回的是字符串类型:对之前实例上的属性和方法没有影响;
    (2)手动返回的是引用数据类型:之前实例身上的属性和方法就被覆盖了;实例没法调用属性和方法;

构造函数的方法都是私有方法,每一个实例调用的都是本身私有的方法,一样也会有许多重复的代码。

咱们可使用原型模式来解决每一个实例中都有相同方法的函数的问题

  • 原型模式
function CreatePerson(name,age){ 
        this.name=name;
        this.age=age;
    }
    // 咱们把公有的方法放到函数的原型链上
    CreatePerson.prototype.showName = function(){
             console.log('个人名字是:'+this.name)
    }  
    var person1 = new CreatePerson('小明',25) 
    var person2 = new CreatePerson('小华',24)
    person1.showName() //小明

###### 原型模式的关键:

1)每一个函数数据类型(普通函数,类)上,都有一个属性,叫prototype。
2)prototype这个对象上,天生自带一个属性,叫constructor:指向当前这个类;
3)每一个对象数据类型(普通对象,prototype,实例)上都有一个属性,
   叫作__proto__:指向当前实例所属类的原型;

这3句话理解了,下边的东西就能够不用看了 //手动滑稽

经过例子咱们来看这几句话是什么意思

function CreatePerson(name,age){
        this.name=name;
        this.age=age
}
CreatePerson.prototype.showName=function(){
             console.log('个人名字是:'+this.name)
}
var person1 = new CreatePerson('小明',25);
console.dir(person1)

在chrome浏览器控制台中显示

chrome

从图中能够看出,person1这个对象上有name和age两个属性,
person1的__proto__指向了它的构造函数(CreatePerson)的prototype上,
并且还有一个showName的方法。
而且它们中有一条链关联着: person1.__proto__ === CreatePerson.prototype

接着来看

function Foo(){
    this.a=1;
}
Foo.prototype.a=2;
Foo.prototype.b=3;
var f1 = new Foo;  //没有参数的话括号能够省略
console.log(f1.a) //1
console.log(f1.b) // 3

以这个为例,
当咱们查找f1.a时,由于f1中有这个属性,因此咱们得出 f1.a=1;
当咱们查找f1.b时,f1中没有这个属性,咱们便顺着f1.__proto__这条链去
它的构造器的prototype上找,因此咱们得出了 f1.b = 3;

原型图1

接着来讲,Foo.prototype是个对象,那么它的__proto__指向哪里呢
还记的刚刚说的那句
每一个对象数据类型(普通对象,prototype,实例)上都有一个属性,叫作__proto__:指向当前实例所属类的原型
此外,咱们应该知道
每个对象都是function Object这个构造函数的实例
obj

因此咱们能够接着还原这个原型图
原型图2

等等,图上貌似多了个个Object.prototype.__proto__ 指向了null,这是什么鬼?

咱们这么来理解,Object.prototype是个对象,
那么它的__proto__指向了它的构造函数的prototype上,
最后发现了仍是指向它自身,这样转了个圈貌似是无心义的,因而便指向了null

还没完,咱们发现对象都是函数(构造器)创造出来的,那么函数是谁创造的呢?石头里蹦出来的么?
在js中,function都是由function Function这个构造器创造的,每个函数都是Function的实例

Function

如今基本上咱们就能得出了完整的原型图了

原型图

是否是有点乱?根据咱们刚刚讲的是能把这个图理顺的,
这里须要注意下,Function.__proto__是指向它的prototype的

多说一点,判断数据类型的方法时,咱们知道有个instanceof的方法
好比

A instanceof B

instanceof判断的规则就是:

沿着A的__proto__这条线查找的同时沿着B的prototype这条线来找,若是两条线能找到同一个引用(对象),那么就返回true。若是找到终点还未重合,则返回false。

再来看咱们以前的那个例子

function Foo(){
    this.a=1;
}
Foo.prototype.a=2;
Foo.prototype.b=3;
var f1 = new Foo;  //没有参数的话括号能够省略
console.log(f1.a) //1
console.log(f1.b) // 3

当咱们查找f1.a时,由于f1中有这个属性,因此咱们得出 f1.a=1;
当咱们查找f1.b时,f1中没有这个属性,咱们便顺着f1.__proto__这条链去它的构造器的prototype上找,因此咱们得出了 f1.b = 3;

当咱们查找一个对象的属性时,先在这个对象的私有空间内查找,若是没找到,就顺着对象的__proto__这条链去它的构造器的ptototype上查找,若是还没找到,接着沿__proto__向上查找,直到找到Object.prototype尚未的话,这个值就为undefined,这就是所谓的原型链

列举下网页中的一些相关的原型链

dom

有兴趣的同窗可自行经过浏览器控制台看看咱们经常使用的方法都是在哪一个类上定义的,好比getElementsByTagName,addEventListener等等

继承

在这里就主要说一下组合继承(call + 原型链)

function Father(){
    this.xxx= 80;
    this.yyy= 100;
    this.drink = function(){}
}
Father.prototype.zzz= function(){}
var father = new Father;
function Son(){
    this.aaa = 120;
    this.singing = function(){}
    Father.call(this);
}
Son.prototype = new Father;
Son.prototype.constructor = Son;
var son = new Son
console.dir(son)

组合继承

这么写有个很差的地方就是:子类私有的属性中有父类私有的属性,子类公有的属性中也有父类私有的属性;
根据咱们前边的知识,咱们能够这么来改写

function Father(){
    this.xxx= 80;
    this.yyy= 100;
    this.drink = function(){}
}
Father.prototype.zzz= function(){}
var father = new Father;
function Son(){
    this.aaa = 120;
    this.singing = function(){}
    Father.call(this); //利用call继承了父类的私有属性
}
Son.prototype.__proto__ = Father.prototype
var son = new Son
console.dir(son)

最后来一张思惟导图

面向对象入门

图片若是放大也看不清的话 下载地址

若有错误,欢迎指正!

相关文章
相关标签/搜索