JS中OOP之模拟封装和继承和this指向详解

大家好,今天我带大家学习一下js的OOP,

大家都知道,面向对象有三个基本特征,继承,封装和多态,面向对象的语言有那么几种,C++,PHP,JAVA等,而功能强大的JS可以模拟实现面向对象的两大特征,继承,和封装,无法实现多态,所以当有人对问你,js是一门面向对象的语言,你应该反驳他,js是一门基于对象的语言,不是面向对象,他就会觉得你很有学问。哈哈!

首先,我们学习一下,什么是对象呢,那就先了解一下类和对象是什么?

1、类:一类具有相特征(属性)和行为(方法)的集合
eg:人类--->属性,身高,体重,性别,方法:吃饭,说话,走路
2、对象:从类中,拿出具有确定属性值和方法的个体。
eg:张三--->属性,身高180 体重180,方法:说话-->我叫张三,身高180

大家知道什么是类什么是对象了吗?

两者有什么关系呢?

类是抽象的,对象是具体的,类是对象的抽象化,对象是类的具体化
类是一个抽象的概念只能说类有属性和方法,但是不能给属性附具体的值,
对象是一个具体的个例,是将类中的属性,进行具体赋值而来的个体
eg:张三是人类的一个个体,可以说张三的姓名叫张三,也就是张三对人类的每一个属性进行了具体的赋值,那么张三就是由人类产生的一个对象。

是不是通俗易懂呢。

下面我来使用一下类和对象:

首先创建一个类,这里要注意的是,类名必须使用大驼峰法则,即每一个单词的首字母都要大写,这是我说知道唯一一个使用大驼峰法则的地方

方法:

function 类名(属性1){
this.属性1=属性1;
this.方法=function(){
方法中要调用自身属性,必须使用this.属性。
}
}

function Person(name,age){
    this.name=name;
    this.age=age;            
    this.say=function(content){
        //在类中,要访问类自身的属性,必须使用this,属性调用。
        alert("我叫"+this.name+",今年"+this.age+"了!"+content);
    }
}

这样我们就创建好了一个类

下面我们实例化出一个对象,

通过 obj=new类名(属性1的具体值);
obj.属性;调用属性
obj.方法();调用方法

var zhangsan = new Person("张三",18);

下面我们要学习一下三个属性和三个方法

【成员属性和成员方法】
1、在构造函数中,使用this.属性声明。或者在实例化出对象以后,使用“对象.属性追加的,都属于成员属性或成员方法”。也叫实例属性,和实例方法。
成员属性和方法是属于由类new出的对象的。需要使用“对象名.属性名”调用。

【静态属性与静态方法】
2、 通过“类名.属性”、“类名.方法”声明的属性和方法,称为静态属性,静态方法。也就类属性和类方法:

类属性/类方法,是属于类的(属于构造函数的)
通过“类名.属性名”调用

3、成员属性时属于实例化出的对象的,只能使用对象调用。
静态属性时属于构造函数的,只能使用类名调用。

【私有属性和私有方法】
4、在构造函数中,使用var声明的变量称为私有属性;
在构造函数中,使用function声明的函数,称为私有方法;
function Person(){
var num = 1;//私有属性
function func(){}//私有方法
}
私有属性和私有方法的作用域,只能在构造函数内容有效,即,只能在构造函数内部使用,在构造函数外部,无论使用对象名还是类名都无法调用。

举个栗子,

function Person(name){
            this.name=name;//声明成员属性
            var sex = "男";
            this.satTime=function(){
                alert("说出当前时间是"+getTime());
            }
            this.writeTime = function(){
                alert("我写了当前时间是"+getTime());
            }
            function getTime(){
                return new Data();
            }
            
        }
        var zhangsan = new Person("张三");
        zhangsan.age = 14;//追加成员属性
        
        //alert(zhangsan.age);//调用成员属性
        Person.count = "60亿";//声明静态属性
        console.log(Person.count);//调用静态属性
        var lisi = new Person("李四");
        console.log(lisi.count);//undefined 静态属性时属于类的,只能用类名调用
        console.log(lisi.sex);
        console.log(Person.sex);

通俗易懂的,好了下面我们重点来了,就是标题的两大内容之一,封装

 

  封装

1、 什么叫封装?
① 方法的封装: 将类内部的函数进行私有化处理,不对外提供调用接口,无法在类外部使用的方法,称为私有方法,即方法的封装。
② 属性的封装: 将类中的属性进行私有化处理,对外不能直接使用对象名访问(私有属性)。 同时,需要提供专门用于设置和读取私有属性的set/get方法,让外部使用我们提供的方法,对属性进行操作。 这就叫属性的封装。
2、 注意: 封装不是拒绝访问,而是限制访问。 要求调用者,必须使用我们提供的set/get方法进行属性的操作,而不是直接拒绝操作。
因此,单纯的属性私有化,不能称为封装!必须要私有化之后,提供对应的set/get方法。

上面是知识点,大家知道了吗,简单的讲,就是将属性保存起来,只让用户通过提供的方法去访问,修改数据,当然,如果你是前端高手,你就可以无视,直接修改源码。这当然是外话了 --!!

下面我们来一段实例代码,让你们感受下封装的过程

function Person(name, age1) {
          this.name = name;
 //       this.age = age;
          var age = 0;
          this.setAge = function(ages) {
                if(ages > 0 && ages <= 120) {
                    age = ages;
                } else {
                    alert("年龄赋值失败!");
                }
            }
       // 当实例化类拿到对象时,可以直接通过类名的()传入年龄,设置私有属性
            if(age1 != undefined) this.setAge(age1);
            this.getAge = function() {
              return age;
            }
          //私有化的方法,只能在类内部被其他方法调用,而不能对外提供功能。 这就是方法的封装!
                 
             function getTime() {
               return new Date();
             }
 }
            //    var zhangsan = new Person("张三");
            //    zhangsan.setAge(99);
            //    alert("张三的年龄是:"+zhangsan.getAge());

            var lisi = new Person("李四", 999);
            //lisi.setAge(110);
            alert("李四的年龄是:" + lisi.getAge());
            
            function Person(){
                var age = 0;
                this.getAge=function(){
                    return age;
                }
                this.setAge=function(age1){
                    age = age1;
                }
                function func(){}
            }

封装其实很简单,当我们把属性封装起来之后,我们就不能直接访问类的私有属性,我们只可以通过getAge(),setAge()访问设置age属性,这样就模拟实现了第一个功能,封装,

继承

等等,我忽略了一个重要的问题,那就是this指向问题,大家可能问上面我们出现了this,this到底是什么呢,现在我们详细解释一下

简单来说

1、谁最终调用这个函数,this就指向谁!
①this指向谁,不应该考虑函数在哪里声明,应该考虑函数在哪里调用;
②this指向的永远只可能是对象,不可能是函数;
③this指向的对象,叫做函数的上下文,也叫函数的context;也叫函数的调用者。

2、this指向的规律!!!(跟函数的调用方式息息相关)
①通过函数名()调用的this永远指向window;
②通过对象.方法调用的,this指向这个对象;
③函数作为数组中一个元素,通过数组下标调用的,this指向这个数组;
④函数作为window内置函数的回调函数使用;this指向window;
setInterval setTimeout 等
⑤函数作为构造函数时new关键字调用,this指向新new出的对象。

以上是我所总结的this指向,this也就这几中情况了,其他还没遇到过,如果有其他,请大佬在评论告诉我,先谢了,

在举个栗子

    var fullname = 'John Doe';
    var obj = {
       fullname: 'Colin Ihrig',
       prop: {
          fullname: 'Aurelio De Rosa',
          getFullname: function() {
             return this.fullname;
          }
       }
    };
    console.log(obj.prop.getFullname()); 
    // 函数的最终调用者 obj.prop 
    
    var test = obj.prop.getFullname;
    console.log(test());  
    // 函数的最终调用者 test()  this-> window
    
    obj.func = obj.prop.getFullname;
    console.log(obj.func()); 
    // 函数最终调用者是obj
    
    var arr = [obj.prop.getFullname,1,2];
    arr.fullname = "JiangHao";
    console.log(arr[0]());
    // 函数最终调用者数组

 

吃了这个栗子,是不是清楚多了,不要被歪的所迷惑,你就找谁最终调用了这个函数就行,

然后是原型和原型链,这个是很重要的知识点,大家一定要掌握好,因为继承需要准备很多知识点,大家耐心学完,

【__proto__与prototype】
1、prototype,函数的原型对象;
①只有函数才有prototype,而且所有函数,必有prototype
②prototype本身也是一个对象!
③prototype指向了当前函数所在的引用地址!

2、__proto__:对象的原型
①只有对象才有__proto__,而且所有对象必有__proto__
②__proto__也是一个对象,所以也有自己的__proto__,顺着这条线向上找的顺序,就是原型链
③函数、数组都是对象,都有自己的__proto__;

3、实例化一个类,拿到对象的原理?
实例化一个类,实际上是将新对象的__proto__,指向构造函数所在的prototype。
也就是说:zhangsan.__proto__==Person.prototype √

4、所有对象的__proto__延原型链向上查找,都将指向Object的prototype
Object的prototype的原型,指向null

【原型链的指向问题】
研究原型链的指向,就是要研究各种特殊对象的__proto__的指向问题。
1、通过构造函数,new出的对象。新对象的__proto__指向构造函数的prototype
2、函数的__proto__,指向了function()的prototype
3、函数的prototype的__proto__指向Object的prototype
(直接使用{}字面量声明。或使用new Object拿到的对象的__proto__ 直接指向Object的prototype)
4、Object的prototype的__proto__,指向null
Object作为一个特殊函数,他的__proto__指向function()的prototype

这是点先放这里了解释的很清楚,大家看了之后就清楚原型了,继承就需要几个知识点而已,

一、扩展Object实现继承

     ①声明父类
         function Parent(){}
     声明子类
         function Son(){}
    ②通过prototype给object类添加一个扩展方法:
        Object.prototype.extend=function(parent){
            for(var i in parent){
                this[i]=parent[i];
            }
        }
    ③分别拿到父类对象,和子类对象;
        var p = new Person();
        var s = new Student();
    ④用子类对象,调用扩展方法,实现继承操作;
        s.extend(p);
 3、实现继承的原理:
     通过循环,将父类对象的所有属性和方法全部付给子类属性,关键点在for-in循环,即使不扩张object,也能通过简单的循环实现操作
 4、扩展Object继承的缺点,
     ①,无法通过一次实例化,直接拿到完整的子类对象,而需要先拿到父类对象和子类对象两个对象,在手动合并
     ②扩展Object的继承方法,也会保留只在子类的对象上
/**/
function Person(name,age){
    this.name = name;
    this.age = age;
    this.say = function(){
        alert("我叫"+this.name);
    }
}

function Student(no){
    this.no = no;
    this.Study = function(){
        alert("我在学习!");
    }
}

Object.prototype.extend1 = function(parent){
    for(var i in parent){
        this[i]=parent[i];
    }
}
var p = new Person("张三",12);
var s = new Student("123456");
s.extend1(p);
console.log(s);

扩展继承我感觉很简单,以后要用的时候,只需要拿出扩展的extend1方法,就可以继承了,这个名字大家不要抄啊,可以.a.b都行,高兴就好

 使用原型实现继承

     ①定义父类:
         function Parent(){}
      定义子类:
             function Son(){}
      ②将父类对象,赋值给子类的prototype
             son.prototype = new Person();
      ③拿到子类对象,就会将父类对象的所有属性和方法,添加到__proto__
             var s = new Son();
     使用原型继承的原理:
          将父类对象,赋值给子类的prototype,那么父类对象的属性和方法就会出现在子类的prototype中。那么。实例化子类时,子类的prototype又回到子类对象的__proto__中,最终,父亲对象的属性和方法,会出现在子类的对象的__proto__中;
    这种继承的特点
          ①子类自身的所有属性,都是成员属性,父类继承过来的属性,都是原型属性;
         ②依然无法通过一步实例化拿到完整的子类对象。
        function Person(name,age){
            this.name = name;
            this.age = age;
            this.say = function(){
                alert("我叫"+this.name);
            }
        }
        function Student(no){
            this.no = no;
            this.Study = function(){
                alert("我在学习!");
            }
        }
            
        Student.prototype = new Person("zhangsan",14);
        var s = new Student();
        console.log(s);

 

原型继承是比较简单的,子类的Prototype指向父类new的对象,就可以实现继承啦!

三,

[call/bind/apply]
1、三个函数的作用:通过函数名调用着三个函数,可以强行将函数中的this指定为某个对象。
2、三个函数的写法(区别):
call写法:func.call(func的this指向的obj,func参数1,func参数2,...);
apply写法:func.apply(func的this指向的obj,[func参数1,func参数2,...]);
bind写法:func.bind(func的this指向的obj)(func参数1,func参数2,...);
3、三个函数的唯一区别,在与接受func的参数列表的方式不同,除此之外,功能上没有任何差异!!

[使用call/bind/apply实现继承]
1、实现步骤:
①定义父类
function Perent(name){}
②定义子类时,在子类中使用三个函数,调用父类,将父类函数的this,指向为子类函数的this:
function Son(no,name){
this.no=no;
Person,call(this,name);
}
③实例化子类时,将自动继承父类属性,
var s = new Son(12,"zhangsan")

function Person(name,age){
                this.name = name;
                this.age = age;
                this.say = function(){
                    alert("我叫"+this.name);
                }
            }
            function Student(no){
                this.no = no;
                this.study=function(){
                    alert("我在学习");
                }
                Person.call(this.name,age);
            }
            var s =new Student(12,"张三",24);
            console.log(s);

 

以上就是js的继承和封装,多态是无法实现的,上面两个方法在实际运用中能起到很不错的效果,让我们少码很多字,

今天就到这里,谢谢大家

转载于:https://www.cnblogs.com/zhangxinlei/p/7737528.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值