大家好,今天我带大家学习一下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的继承和封装,多态是无法实现的,上面两个方法在实际运用中能起到很不错的效果,让我们少码很多字,
今天就到这里,谢谢大家