js之面向对象

Posted 许小墨~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js之面向对象相关的知识,希望对你有一定的参考价值。

系列文章目录

前端系列文章——传送门
javascript系列文章——传送门


文章目录


面向对象

一、对象的重要性

在js内部,也就是js的底层,对所有数据的处理,都是以对象形式来进行的,所以,js中所有数据都有两种定义方式:

  1. 字面量方法

    字面量方式,就是直接定义,让我们可以一目了然的看出,这个数据的类型。例如:

    var num = 10
    var str = 'abcdef'
    var bool = true
    ...
    

    这种定义方式,我们可以一眼就看出这是什么类型的数据。

  2. 构造函数方式

    var arr = new Array()
    var obj = new Object()
    var num = new Number()
    var str = new String()
    ...
    

    这种通过new的方式来定义对象的方式,就叫做构造函数方式。

    这种方式定义的数据,也是可以像字面量方式定义的数据一样,进行数学运算、字符串拼接等操作的。例:

    var num = new Number(10)
    num += 5
    console.log(num) // 15
    
    var str = new String('abcd')
    str += 'ef'
    console.log(str) // 'abcdef'
    

这也就造成了,在js中,对象是无所不能的,无处不在的,对象是js最重要的组成部分。

二、面向对象

1、介绍

面向:脸面朝向

对象:js的重要组成部分

连在一起,就是脸面要朝向对象,也就是要重视对象。

面向对象是一种更加注重对象编程思想,是一种超脱的、高级的编程思想。

用专业术语来讲,面向对象又叫做OOP(Object Oriented Programming)

我们之前的编程思想叫做面向过程编程思想POP(Procedure Oriented Programming)。

2、面向过程和面向对象比较

拿我们日常生活来举例子:

2.1、面向过程

下班回家,我们需要吃饭,想吃土豆丝,我们就去菜店买土豆,然后先削皮,用菜刀切成片,再切成丝,然后用水洗一洗,洗到没有淀粉,然后起锅烧油,放入佐料葱姜蒜,撒点十三香,佐料爆出香味,倒入土豆丝,翻炒到所有土豆丝发热后,撒盐,翻炒到干锅,倒入酱油,继续翻炒到土豆丝软化,撒入少许白糖,翻炒到有些粘性了,撒些鸡精,翻炒到鸡精消融,出锅。开吃。

总结:要吃到美味的土豆丝,需要自己亲力亲为,重点把握做菜的每个细节。

2.2、面向对象

下班回家,我们需要吃饭,想吃土豆丝,告诉保姆,我要吃土豆丝。等保姆做好以后,开吃。

总结:要吃到美味的土豆丝,不用亲力亲为,重点找好保姆即可。

通过对比,我们发现,面向对象确认要比面向对象更加高级,类似于一个屌丝跟富豪的区别。

所以说面向对象是更加高级的编程思想。

我们之前的编程思想:当做一个案例的时候,需要根据效果,分析实现效果的每个步骤,重点关注每个步骤的实现过程以及其中的逻辑。

面向对象编程思想:当做一个案例的时候,找到对应的对象,调用其中的方法即可。

3、创建对象

既然要面向对象,更加注重对象,就要先明白如何创建对象是最好的方式,如何给对象添加属性和方法才是最好的方式。

直接创建:

var obj = 

构造函数方式创建:

var obj = new Object();

Object是一个系统提供的构造函数,这个构造函数专门用来创建对象使用的。

如下:保存多个人的信息,需要这样创建多次。

var obj1 = 
    name: '张三',
    age: 12,
    sex: '男'


var obj2 = 
    name: '李四',
    age: 13,
    sex: '女'


var obj3 = 
    name: '王五',
    age: 11,
    sex: '女'

。。。

从上面的代码可以看出来,每次创建对象都是在重复动作

这时候,应该用函数来处理重复动作

4、工厂函数

定义一个函数,每次调用都能得到一个对象

function createObj(name,age,sex)
    var obj = 
        name,
        age,
        sex
    
    return obj;

var obj1 = createObj("张三",12,"男");
var obj2 = createObj("李四",13,"女");
var obj3 = createObj("王五",11,"女");

这种调用就能创建对象的函数,叫做工厂函数。创建出来的每个对象的结构一致,如:电商网站中的商品对象。

优点:可以同时创建多个对象

缺点:创建出来的没有具体的类型(比如是Array和Number),都是object类型的,但我们看到自己的对象只是object,不知道具体是什么类型。

代码:

function factory(name,age)
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    return obj;

var zs = factory("张三",12);
var ls = factory("李四",20);
console.log(zs);
console.log(ls);

5、构造函数

从使用构造函数方式来看数字类型、字符串类型。。。

当我们定义好不同的数据,从外观上就能看出,这个数据的具体类型,例:

var arr = new Array();
var obj = new Number();
var str = new String();
console.log(arr);
console.log(obj);
console.log(str);

这种专门用于new来创建对象的函数,叫做构造函数。

Number、String这种函数,除了可以用new创建对象外,还可以进行类型转换。

其实这样用new创建对象的函数,我们自定义的函数也是可以的:

function fn() 
var f = new fn()
console.log(f)

每个函数都可以这样做。当一个函数被用new来创建对象的时候,这个函数就叫做构造函数了。

通过上面代码,我们发现,如果使用自己定义的函数来创建对象的话,就可以立马看到这个对象的具体类型了。

所以,解决工厂函数的问题,就可以使用自定义的构造函数来实现。

例:

function Person()
   

var zs = new Person();
function fn()


var ls = new fn();
console.log(zs);
console.log(ls);

6、自定义构造函数

function Person(name,age,sex)
    this.name = name;
    this.age = age;
    this.sex = sex;

var obj1 = new Person("张三",12,"男");
var obj1 = new Person("李四",13,"女");
var obj1 = new Person("王五",11,"女");

这时候,我们会奇怪,因为函数并没有被对象调用,那this指的是谁?函数并没有返回值,得到的是什么?

构造函数和普通函数不同的地方在于使用new的时候,中间发生了很多看不见的过程:

  1. 创建了一个新对象
  2. this指向了这个新对象
  3. 执行构造函数中的代码,给对象添加属性和方法
  4. 返回这个新对象

使用new构造函数来创建对象的过程称之为实例化

构造函数注意事项:

  1. 构造函数天生就是用来创建对象的,所以必须和new配合使用,否则就不具备创建对象的能力
  2. 构造函数内部不能有return关键字,因为构造函数会自动返回对象。如果返回基本数据类型,和不加效果一样,如果返回复杂数据类型,构造函数就没意义了。
  3. 如果new的时候,不需要参数,那么小括号可以省略
  4. 人们通常将构造函数的首字母大写

此时的构造函数有个缺点:

从一个构造函数中创建出的对象,实现的是同一种功能,他们拥有的方法应该是一样的,也就是说,同一个构造函数中new实例化得到对象,应该具备同样的方法。

例:

function Person(name)
	this.name = name;
    this.say=function()
        console.log("说话");
    

var obj1 = new Person("张三");
var obj2 = new Person("李四");
console.log(obj1.say == obj2.say); // false  表示这是两个空间

通过比较发现,方法是不一样的。造成了内存浪费。

如果将方法定义在全局,可以解决这个问题。

function Person(name,age)		
	this.name = name;
	this.age = age;
	this.sayHi = fn;

function fn()
	console.log("我是" + this.name + ",今年" + this.age + "岁");

var zs = new Person("张三",12);
var ls = new Person("李四",13);

定义在全局的变量容易被覆盖,所以这个解决方案解决不彻底。

7、原型

7.1、原型概念

任何一个对象提升自带一个属性:__proto__,这个属性对应的值是一个对象,这个对象叫做对象的原型,或叫做对象的原型对象。

var obj = 
console.log(obj)

7.2、原型作用

原型中的属性和方法,默认就能被对象所访问。

var arr = []
console.log(arr)

我们平常使用的数字方法,其实都在数组的原型上,字符串方法也是一样的。

7.3、构造函数访问原型

任何函数天生自带属性prototype,访问到的也是原型。

function fn()
console.log(fn.prototype);

构造函数对应的原型,和实例化对象的原型,是同一个对象。

function Person()
    

var obj = new Person();
console.log(obj.__proto__ === Person.prototype) // true

7.4、三者关系

构造函数、实例对象、原型对象三者之间的关系如下图:

8、面向对象代码

到这里,我们也就找到最佳的定义对象和给对象添加属性和方法的最好的方式了,即:

  • 创建对象使用自定义构造函数
  • 给对象添加属性,就在构造函数中进行
  • 给对象添加方法,就利用构造函数将方法添加到原型上

例:

function Person(name, age) 
	this.name = name
    this.age = age

Person.prototype.say = function()
    console.log(this.name + '会说话')

var p1 = new Person('张三', 12)
var p2 = new Person('李四', 13)
console.log(p1.say === p2.say) // true

9、面向对象版Tab

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<style>
    .box
        height:430px;
        width:500px;
        border:1px solid #000;
        overflow:hidden;
        cursor:pointer;
    
    .box ul,.box ol
        list-style: none;
        padding: 0;
        margin: 0;
        width:500px;
    
    .box ul
        height:30px;
        background:#ccc;
        line-height: 30px;
        display:flex;
        justify-content: space-evenly;
        color:#fff;
    
    .box ul li
        float:left;
        width:150px;
        background:#0f0;
        text-align:center;
       
    .box ol
        height:400px;
    
    .box ol li
        display:none;
    
    .box ol li.current
        display:block;
    
    .box ol li img
        width:500px;
        height: 400px;
    
    .box ul li.current
        background:#999;
        display:block;
    
</style>
<body>
<div class="box">
    <ul>
        <li class="current">绿色</li>
        <li>粉色</li>
        <li>蓝色</li>
    </ul>
    <ol>
        <li class="current"><img src="images/1.jpg"></li>
        <li><img src="images/2.jpg"></li>
        <li><img src="images/3.jpg"></li>
    </ol>
</div>
</body>
<script type="text/javascript">
function Tab(className)
    var box = document.querySelector("."+className);
    this.ulis = box.querySelectorAll(".box ul li");
    this.olis = box.querySelectorAll(".box ol li");
    for(var i=0;i<this.ulis.length;i++)
        this.ulis[i].index = i;
        var ele = this;
        this.ulis[i].onclick=function()
            ele.toggle(this);
        
    

Tab.prototype.toggle=function(ele)
    for(var i=0;i<this.ulis.length;i++)
        this.ulis[i].className = '';
        this.olis[i].className = '';
    
    ele.className = 'current';
    this.olis[ele.index].className = 'current';

var TabBox = new Tab("box");
</script>
</html>

10、构造器

每一个原型对象天生带有一个属性叫做constructor,这个属性指的是这个原型对象所对应的构造函数。

function Person()
    

var obj1 = new Person();
console.log(obj1.__proto__.constructor === Person); // true  

11、原型链

11.1、概念

任何对象都会有原型,原型本质上是一个对象,只要是对象就会有原型。

所以,对象有原型,原型也有原型,原型的原型也有原型,。。。,这样形成一条链式结构就叫原型链。

例:

function Person() 
    

var p = new Person()
console.log(p)

通过输出对象p,查看原型链。

通过一张图来描述原型链:

这样向上的一条链式结构,我们称之为原型链。

11.2、作用

当我们访问对象上的属性和方法时,先在自己身上找,如果有,就访问到了,如果没有,顺着原型链往上找,找到了就使用,找不到就继续往上找,。。。,如果找到了null,都没有的话,就返回undefined;

但是给对象属性赋值和原型没关系,有就修改,没有就增加。

浅谈JS面向对象之创建对象

        hello,everybody,今天要探讨的问题是JS面向对象,其实面向对象呢呢,一般是在大型项目上会采用,不过了解它对我们理解JS语言是很大的意义。

       首先什么是面向对象编程(oop),就是用对象的思想去写代码,那什么是对象呢,其实我们一直在用,像数组 Array  时间 Date 都是对象,并且这些对象是系统创建的,所以叫系统对象,而我们自己当然也可以创建对象,一般对象有两部分组成:

1 方法 (动态的 对象下面的函数)比如Array 的push(),sort()方法

2  属性 (静态的,相当于对象下面的变量)Array的 length属性

OK,既然存在面向对象的编程,那么一定有它的优点,那么我们来看看它的四大特点:

抽象:抓住核心问题
封装:只能通过对象来访问方法
继承:从已有对象上继承出新的对象
多态:多对象的不同形态

这些特点呢,一开始理解是比较抽象的,只有我们在实践起来更能理解它的意义。不过我们做简单的介绍。

抽象:我们知道呢,不同的对象下面的方法是不一样的,比如数组有属于数组的方法属性,时间对象有属于它的方法属性,我们不能把时间对象的方法加给数组,把很多的方法分门别类,放到不同的对象下面,就是一种抽象,更好的管理。

封装:这个就比较好理解了,就是每个方法必须得在指定的对象下用。

继承:假如我们有两个弹窗,第二个弹窗相比第一个弹窗仅仅是多了一个功能,这个时候,就适合用继承,第二个可以继承第一个的功能,然后在这基础上再增加功能,继承提高了我们对代码的复用。

多态:也是代码复用的一种形式,用的不太多,比如有一个电脑,我们只用一个接口,然后连接不同的设备,这些设备可以实现不同的功能。听起来比较抽象,一般后端语言用的比较多。

下面我们来具体看面向对象的创建以及创建模式的演变

1 创建一个面向对象

//创建一个面向对象
var obj = new Object(); //创建一个空对象
obj.name = ‘haha‘;
obj.showName = function(){ 
   alert(obj.name);

}
obj.showName();

这个栗子很简单,存在的问题是,当我们有多个面向对象的时候,重复代码过多,需要封装,所以有了下面的方法

2  工厂方式

function CreatePerson(name){ 
    //原料
    var obj = new Object();
    //加工
    obj.name = name;
    obj.showName = function(){ 
       alert(this.name);
   }
   //出厂
   return obj;
}

var p1 = CreatePerson(‘haha‘);
p1.showName();
var p2 = CreatePerson(‘hehe‘);
p2.showName();

这其实就是简单的封装函数,整个过程像工厂的流水线,所以叫工厂方式,但为了与我们的系统对象(Array等)保持一致,所以我们要进行改造,成为构造函数模式

3 构造函数模式

我们要通过这三个方面来改变:1 函数名首字母大写  2 New 关键字提取  3  this指向为新创建的对象 

function CreatePerson(name){   //函数名首字母大写
     //var this=new Object(); //系统偷偷替咱们做:不用写
     this.name = name; 
this.showName = function(){
alert(
this.name);
}

//return this;//而且函数的默认返回值就是this即这个对象(隐式返回 不用自己再写返回值

}
var p1 =new CreatePerson(‘haha‘); 
//当用new去调用一个函数:这个时候this指的就是创建出来的对象 而且函数的默认返回值就是this即这个对象(隐式返回 不用自己再写返回值)
p1.showName();
var p2 = new CreatePerson(‘hehe‘);
p2.showName();

所以最终的代码是这样的

function CreatePerson(name){ 
     this.name = name; 
     this.showName = function(){ 
        alert(this.name); 
     } 
} 
var p1 =new CreatePerson(‘haha‘); 
 p1.showName();
var p2 = new CreatePerson(‘hehe‘);
 p2.showName();

但是函数构造模式也存在相应的问题:

alert(p1.showName==p2.showName);//false

测试这个代码,两个方法是不相同的,也就是说这两个对象并不是共用一个方法,每new一次,系统都会新创建一个内存,这两个对象各自有各自的地盘,但他们具有相同的功能,还不共用,肯定不是我们所希望的。所以就有了下一种方法,原型模式

4 原型模式

我们创建的每个函数都有原型(prototype),从字面意思看,就是指最原始的,原型是加在构造函数下面的,这个时候,通过这个构造函数创建出来的对象,都能共享这个原型下的方法和属性。

看个栗子(原型+构造)

function CreatePerson(name){ 
    this.name = name;
}
CreatePerson.prototype.showName = function(){ 
    alert(this.name);
}
var p1 =new CreatePerson(‘haha‘);
p1.showName();
var p2 = new CreatePerson(‘hehe‘);
p2.showName();

alert(p1.showName==p2.showName);//true

通过最后一句的测试为true,可以看到在构造函数的原型下面加的方法showName()方法是所有通过这个构造函数创建出来的对象所共享的,也就是说他们共用一个内存,更进一步的说它们存在引用关系,也就是说你更改了p1的showName也会影响p2。

所以我们在构造对象的时候,一般是原型模式和构造模式组合使用,变化的用构造模式 不变的用原型模式,就像上面的这个栗子,属性用的构造函数,因为一般不同对象属性都不同,方法用原型模式。

 

在这里我要介绍一个重要的属性就是constructor

1  它指的是对象的构造函数

2  每个自定义的构造函数都有的属性,当你写完一个构造函数的时候,系统程序会自动在函数的原型下添加对应的构造函数,比如有个自定义构造函数Aaa,系统会自动帮你添加这句话:

Aaa.prototype.construnctor = Aaa;(每个函数都有 都是自动生成的)

所以每个构造函数出生后,原型下面都有这样一个属性了。可以测试一下

 function Aaa(){}
 var a1 = new Aaa();
 a1.constructor  //Aaa函数

3 所以既然是原型下面的,证明构造函数也是可以被修改的。

 function Aaa(){}
 Aaa.prototype.construnctor=10;//添加
 var a1 = new Aaa();
 a1.constructor  //10

不过要避免修改这个属性。

4 不过有一种情况,我们需要修改,为了创建对象的代码更方便,你一定见过这样的代码,采用Json的方式在原型下加方法,也就是字面量法:

function Aaa(){}
Aaa.prototype = {

//json 相当于重新赋值了Aaa的原型 ,所以也覆盖掉了
//Aaa.prototype.construnctor = Aaa; 

    num1:function(){alert(10);},
    num2:function(){alert(20);}
 } var a1 = new Aaa(); a1.constructor //object

这个时候弹出的构造函数竟然变成了object,这是为什么呢,首先我们要知道object是所有对象的最初始的大boss,所有的对象都属于object,其次上面有讲过,每个最开始的构造函数的原型下面都有这样一句话,Aaa.prototype.construnctor = Aaa;这个例子的赋值方式,已经不是在原型上增加方法了,它属于一种赋值的方式,所以就把原来的构造函数给覆盖了,因此我们在写的时候需要修正一下原型的指向。

function Aaa(){}
Aaa.prototype = {
     constructor:Aaa,
     num1:function(){alert(10);},
     num2:function(){alert(20);}
} 
var a1 = new Aaa(); 
a1.constructor
// Aaa

 

 

关于JS面向对象之构造对象就先说到这里,其实还有别的不常用的模式,后续会继续补充,如果疏漏,欢迎补充,我是沐晴,不见不散

 

以上是关于js之面向对象的主要内容,如果未能解决你的问题,请参考以下文章

前端之JavaScript面向对象开发

前端之JavaScript面向对象开发

前端 之 JavaScript : JS的面向对象; 定时器; BOM

web前端(15)—— JavaScript的数据类型,语法规范2

前端之JavaScript基础

前端之JavaScript面向对象开发