JavaScript面向对象
今天我们来介绍一下JavaScript的面向对象部分,那么学过js的朋友应该都知道,关于js面向对象部分是有一点乱的,并没有像java,python之类的有一些比较明确的规范,当然,在之后的ES6的部分还是做了一些完善。
创建对象的几种方式
js不同于强类型语言,关于创建对象它有以下几种方式:
一、字面量方式创建对象
字面量创建对象方式实际上就是实例化创建,代码如下:
var obj = {
name : '张三',
age : 20,
sex : '男',
run : function(){
console.log('我正在运动')
}
}
obj.run();
console.log(obj);
//不需要使用new,因为它本身便是实力化对象
//这种方式创建对象时较为繁琐,当需要实例的对象过多时不适用
二、自定义构造函数创建对象
function obj(name,age){
this.name = name;
this.age = age;
this.run = function (){
console.log('我正在运动');
}
}
var a = new obj("张三",'20');
a.run();//'我正在运动'
console.log(a.name);//张三
console.log(a.age);//20
三、工厂模式创建对象
工厂方式实际上是利用了js原生的Object对象
function Person(name,age){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function (){
console.log('我正在运动');
}
return obj;//返回该对象
}
var a = new Person("张三",'20');//创建新的对象
a.run();//‘我正在运动’
console.log(a.name);//张三
console.log(a.age);//20
四、 使用ES6规范创建对象
关于ES6的创建对象的语法格式就和java之类的强类型语言相似了,或者说js进行了一个模仿,代码格式如下:
class Person{
constructor (name,age){
this.name = name;//属性
this.age = age;
}//构造函数
run(){//方法
console.log(this.name + '正在运动')
}
}
var a = new Person('张三',20);//实例化对象
console.log(a.age);//20
console.log(a.name);//张三
a.run();//张三正在运动
js在ES6面向对象当中引入了class关键字,用来进行关于类的定义,那么constructor里面是构造函数的书写,相信大家都理解,这里不过多说,关于run方法的书写,这里是es6语法当中的一种字面量的简要写法。
当然除了以上几种方式,还有些混合模式之类的,不常用也就不做解释。
对象的继承
对于面向对象的语言来说,面向对象的三个部分:封装,继承,多态是必不可少的。但是在js当中并不具备多态这个特性,它并不像java等强类型语言具有方法的重载和重写等功能,因此并不具备多态,因此它是基于面向对象的语言,但并不面向对象,这一点大家要理解清楚。
那么接下来我们讲解一下在js中关于继承的几种方式:
一、通过原型链实现继承
function Animal(age){
this.age = age;
this.eat = function(){
console.log('吃点什么东西吧')
}
}
function CatClass(name,color){
this.name = name;
this.color = color;
this.sleep = function (){
console.log('睡一觉吧,太累了')
}
}
CatClass.prototype = new Animal(18);//对原型链进行赋值,更改
var b = new CatClass('小黑','black');
b.sleep();//睡一觉吧,太累了
b.eat();//吃点什么东西吧
console.log(b.age)//18
大家看到了,上面的代码就是通过对原型链的更改实现了,使用b对象调用eat()方法,b对象是CatClass创建出来的,却调用了Animal当中的eat()方法,因此实现了继承。原型链赋值那里需要注意,赋的值是一个新对象。
但是这个方式会有一个问题,因为他们的原型链指向的是同一个内存地址,所以如果父亲和儿子有相同的引用数据类型属性和方法,对其中的一个进行更改,那么另一个也会进行相应的更改,如果我们需要父亲儿子做不一样的事情,那么我们需要做一个小小的改动。就是利用一个介质或者说一个变量来对这个原型链进行一个保存,然后再将这个变量赋值给另一个原型链,那么这样他们就会互不干扰。
二、使用call或者apply改变this指向实现继承
function Father (name,age){
this.name = name;
this.age = age;
this.introduce = function (){
console.log('我是父亲我现在'+this.age+'岁');
}
}
function Son (name,age){
Father.call(this,name,age);//改变this指向
}
var father = new Father();
var son = new Son('张三',48);//注意这边,如果父亲的方法中有参数的话,那么改变了this指向以后就变成了儿子的方法,那么就需要儿子接收参数,再通过call传递给父亲然后调用这个方法
son.introduce();//儿子调用父亲的introduce方法
那么apply方法和call方法的不同就只是参数的不同,需要通过[ ]数组的形式来传递参数
function Son (name,age){
Father.apply(this,[name,age]);
}
其实说到这里,还有一种方法bind,使用方法参照call,但是由于占内存和浏览器兼容性的问题,不常用。因此我们记住call和apply就完全够用了。
虽然这种方式解决了引用数据类型的关联改变,但是使用call或者apply这样的方式会破坏代码的复用,降低效率
三、组合方式实现继承(利用原型链加上call或者apply)
在上述中的一、二两种方式我们了解过后都有一些缺陷,那么我们现在使用组合办法解决这些缺陷,实现私有属性方法不可以更改,共有属性方法都可以访问,代码示例如下:
function Father (name,age){
this.name = name;
this.age = age;
this.list = [1,2,3]
}
Father.prototype.introduce = function (){
console.log('我是父亲我现在'+this.age+'岁');
}//在prototype中设置公有方法
function Son (name,age){
Father.call(this,name,age);//改变this指向
}
Son.prototype = new Father();//原型链继承
Son.prototype.constructor = Son;//因为上一行代码实现原型链继承更改了构造函数,所以需要将父亲的改成自己的
var son = new Son('张三',48);//注意这边,如果父亲的方法中有参数的话,那么改变了this指向以后就变成了儿子的方法,那么就需要儿子接收参数,再通过call传递给父亲然后调用这个方法
son.introduce();//儿子调用父亲的introduce方法
son.list.push(4);
console.log(son.list);
var son1 = new Son();
console.log(son1.list);
结果如下:
这也是组合类型的优势,也是比较完善的继承,也是比较常用的方法。
四、ES6利用extends继承
如果学过java的朋友们那就会觉得很熟悉了,没错js中ES6的使用方式和java当中的继承相同,详细代码如下:
class Father{
constructor (name,age){
this.name = name;//属性
this.age = age;
}//构造函数
introduce(){//方法
console.log(this.name + '正在运动')
}
}
class Son extends Father{
constructor(name,age) {
super();//super 关键字用于访问父对象上的函数。当有了参数以后会访问父亲的constructor,并且返回当前this
this.name = name;//属性
this.age = age;
}
}
var son = new Son('父亲',48);
// console.log(son);
son.introduce();//儿子调用父亲的introduce方法
那么关于js面向对象我们就先说到这里。
若有错误或缺陷请评论指出,将及时修改。