在学习js的面向对象的时候着实懵逼了,因为之前学过java,发现js的面向对象简直太扯淡了。但是没办法,还是要耐着性子学一学,不过有了ECMAScript6之后,相信以后学过强类型语言的同学再去学习js的面向对象会好一些。
首先要明确的是,面向对象是一种解决问题的思路,是一种编程思想。
早期的编程是面向过程的。“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想。就是把解决问题的关注点,放到解决问题的每一个详细的步骤上面。
而面向对象(Object Oriented,简称OO)是一种以事物为中心的编程思想。就是把解决问题的关注点放到事物(对象)上。
比如我想实现 点击按钮给div设置红色背景 这个功能。
如果是用面向过程的思想来解决问题的话,大致分为以下几个步骤。
1.获取button
2.给button添加点击事件
3.获取div
4.改变div的背景色
示例:
如果使用面向对象的思想来解决这个问题的话,我需要有两个对象
1.按钮对象
2.div对象
通过代码可以看到,实现的功能一样,只不过解决问题的思路不同。
面向过程的思想是我实现这个功能需要哪些步骤,然后按照步骤来走。
面向对象的思想是我实现这个功能需要什么对象。然后是对象中哪些属性和方法。
什么是对象
在之前的js基础中我们已经了解了什么是js对象。在这里我们了解一下什么是广义的对象。
万物皆对象。也就是说,世界上的所有事物都是对象。注意,对象是指一个具体的事物。
“路边的汽车”,这里的汽车不是对象,而是一类事物,因为路边汽车有很多。
“我的那台黑色的宝马汽车”,这里的汽车就是一个对象了,因为具体到一个事物了。
对象具有状态和行为,对应对象中的属性和方法。
js中的面向对象
在传统的面向对象中,比如java。是有class(类)的概念的。而在js中(ECMAScript6之前),是没有类(class)的概念的,传统的面向对象是基于类的面向对象,而js中的面向对象实际上是基于原型的。
也就是说,js种面向对象是跟一些强类型语言(比如java)是有一定区别的。
面向对象三大特性
封装
在js中,封装实际上就是把数据和方法封装在一起。
继承
在现实生活,最直接的继承就是财产继承。比如,小明的父亲有一辆车,小明自己没有车,小明的父亲去世后,小明通过继承得到了这辆车。并可以去使用它。
在js中,继承就是自己没有, 别人有,拿过来为自己所用, 并成为自己的东西。
多态
在js中实际上是没有多态的体现的,我们只需要了解大致意思即可。下面的java代码看不懂也没关系。
多态,顾名思义就是对象的多种状态。多态是发生在继承的基础上的。
以java语言为例。
多态分为2种情况
1.引用多态
首先一个Animal父类
package duotai;
public class Animal {
public void eat() {
System.out.println("吃");
}
}
Dog子类
package duotai;
public class Dog extends Animal {
}
Main方法
package duotai;
public class test {
public static void main(String[] args) {
Animal ani = new Animal();
// 父类引用指向子类对象
Animal dog = new Dog();
}
}
父类引用指向子类对象,这就是引用多态。
2.方法多态
调用的对象不同,执行的结果不同
创建本类的对象时,调用的本类的方法
创建子类对象时,调用的是子类重写的方法或者继承来的方法。
父类
package duotai;
public class Animal {
public void eat() {
System.out.println("动物会吃东西");
}
}
子类
package duotai;
public class Dog extends Animal {
public void eat() {
// TODO Auto-generated method stub
System.out.println("狗吃东西");
}
}
main方法
package duotai;
public class test {
public static void main(String[] args) {
Animal ani = new Animal();
// 父类引用指向子类对象
Animal dog = new Dog();
// 子类如果重写eat方法时就调用子类中的方法,否则调用父类的eat方法
dog.eat();
}
}
同一个方法不同对象调用结果不同,这就是方法多态。
js中创建对象的方式
在前面的js基础中,我们已经了解了三种创建对象的方式,下面我们来看看这三种方式的优缺点。
实际上js中的对象都是通过函数创建的。
1.对象字面量
var yzq = {
name: "yzq",
age: 23,
eat: function() {
console.log("吃");
}
}
这种方式只能创建一次对象,复用性较差,如果要创建多个对象,代码冗余度太高
2.内置构造函数
/*内置构造函数*/
var p=new Object();
p.name="yzq";
p.age=3;
p.eat=function () {
console.log("吃");
}
var p1=new Object();
p1.name="喻志强";
p1.age=18;
p1.play=function () {
console.log("玩")
}
同样的,这种方式写起来很麻烦。
3.自定义构造函数
function Person(name,age){
this.name=name;
this.age=age;
this.eat=function () {
console.log("吃");
}
}
var p2=new Person("yzq",23);
var p3=new Person("喻志强",18);
var p4=new Person("yuzhiqiang",24);
这种方法用的较多,也是比较合适的创建对象的方法。
注意:
自定义构造函数创建对象时一定要配合new关键字来使用
构造函数的执行过程
1.使用new关键字创建对象
2.调用构造函数,把新创建出来的对象赋值给构造函数内的this
3.在构造函数内使用this为新创建出来的对象新增成员
4.默认返回新创建的这个对象 (普通的函数,如果不写返回语句,会返回undefined)
构造函数的返回值
1.如果不写返回值,默认返回的是新创建出来的对象 (一般都不会去写这个return语句)
2.如果我们自己写return语句 return的是空值(return;),或者是基本类型的值或者null,都会默认返回新创建出来的对象
3.如果返回的是object类型的值,将不会返回刚才新创建的对象,取而代之的是return后面的值
如果像使用正常的函数一样使用构造函数
构造函数中的this将不再指向新创建出来的对象(因为根本就没有创建对象)
构造函数中的this这个时候指向的就是window全局对象
当使用this给对象添加成员的时候,全部都添加到了window上
前面我们提到了,js是基于原型的语言。
js所有的函数(function)都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。
首先我们来看看prototype到底是个什么东西。
打印prototype看看
可以看到,prototype是一个对象,这个对象默认有一个constructor属性,指向函数本身。
既然是一个对象,那么我们就可以给这个对象添加属性和方法。
例如:
给person函数中的原型对象添加属性和方法。
可以看到,我们给原型对象添加了一个skill属性和eat方法。这些属性和方法可以直接被new出来的对象使用。实际上,这就是js中继承的一种体现。所以,我们可以将一些重复的属性或方法放到prototype中去。
访问对象的属性和方法时,如果对象本身有,则访问对象本身的,如果没有,就去原型中找。
对象的__proto__属性
每个对象都有__proto__
属性,指向创建该对象的函数的prototype。
先看看__proto__
都有啥
可以看到,实例化后的对象的__proto__
和构造其的函数的prototype是一样的
也就是说对象的__proto__
指向的是创建它的函数的prototype。
__proto__
是一个隐藏的属性,javascript不希望开发者用到这个属性值,在实际开发中除非有特殊的需求,否则不要轻易的使用实例对象的__proto__
属性去修改原型的成员。
这个属性一般用在调试中,去查看实例化对象的原型:
console.log(p.constructor.prototype);
如果你觉得本文对你有帮助,麻烦动动手指顶一下,算是对本文的一个认可。也可以关注我web前端的博客专栏,我会不定期的更新,如果文中有什么错误的地方,还望指正,谢谢!