`
bolinyang
  • 浏览: 75337 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

JavaScript中的面向对象思想

阅读更多
一.概述

   任何一门面向对象的编程语言都有如下三个特性:封装,继承和多态。封装的意思就是把相关的方和和变量封装在一起,并且赋予这些方法和变量一些访问的权限,简单的说就是明确了那些变量或者方法只能在对象内部或者派生类中使用,那些变量或者方法可以在类的外部使用,封装的好处可以不把接口的实现细节暴漏给使用者,比如开关电脑,我不知道里面的实现到底是什么样的,我只要按一下开关电脑就开了,点两下鼠标电脑就关了,使用者没有必要关心其实现也可以达到自己的目的。继承也是面向对象的一大特色,由于继承的存在,这样就可以在设计的站在一个更加抽象的层面去设计,比如看Spring的设计,我们看到都是一些interface以及一些抽象类,同时继承可以实现代码的复用,把一些公共的代码放在基类中。多态,简单的说就是同类型的对象在不同的环境或者时刻表现出不同的行为来。关于面向对象的思想说起来简单,但是要想真正把其理解掌握,的确需要花很大的功夫去理解去实践,我还没有理解其本质。下面我们就围绕着封转,继承和多态来看一下JS中的面向对象思想。

二.JS之封装
1.变量的封装
变量封装的方式一
<script>
   var o = new Object();
   o.name = "alibaba";
   console.log(o.name);	//输出alibaba
</script>


这里直接创建一个Object类型的对象,Object是JS的内置函数(不是类,这个我们后面再解释),o.name相当于给这个对象增加了一个name的成员变量,增加一个变量的方式太简单了,不过个人觉得有点随意,这样我们可以到处给o增加成员变量了,今天A增加一个明天B增加一个,到后来我们自己都不知道o的成员变量到底有那些了。这里有个小细节,如果对象o已经拥有了成员变量name,那么o.name=”alibaba”就是对成员变量的复制了而不是创建成员变量了。

变量封装的方式二
<script>
   var o = {
       name:"alibaba",
       age:10
    }; // 这里的分号不是必须的,但是习惯加个分号
	
    console.log(o.name);	//输出alibaba
    console.log(o.age);		//输出10
</script>


这里没有使用new关键字,感觉是直接定义了一个对象,然后封装了name和age两个成员变量,我们可以在浏览器的Console中看到原来o也是一个Object类型的对象,不过这里的缺陷也很明显,对象成员变量的值怎么在定义的时候被缺省创建了,就是说我们在创建对象的时候无法干预成员变量的赋值,只有在对象创建结束后,我们才有修改成员变量值的机会。

变量封装的方式三
<script>
		function createCompany(name, age) {
			var o = new Object();
			o.name = name;
			o.age = age;
			return o;
		}
	
		var o = createCompany("alibaba", 10);
		console.log(o.name);	//输出alibaba
		console.log(o.age);		//输出10
</script>


这里有点工厂设计模式的味道,其实说到这里,我也曾经在网上搜索过,怎么没有写JS设计模式的书,我觉得JS也应该有一些设计模式出来,模式其实就是一些通用问题的解决方案,感觉模式和算法发挥的作用有点像。这时候我们可以干预创建对象是成员变量的赋值了,遗憾的是,我们只知道从我们这个“伪工厂”中创建出来的对象其类型只能是Object,我们不知道其具体类型,感觉不好。

变量封装的方式四
<script>
		function Company(name, age) {
			this.name = name;
			this.age = age;
		}
	
		var company = new Company("nuaa", 10);
		console.log(company.name);		//输出alibaba
		console.log(company.age);		//输出10
</script>


这里首先定义一个Company的构造函数,这个构造函数有两个参数,这样我们可以在创建对象的时候既可以控制成员变量的复制,也可以知道对象是什么类型的了,所以这是一个比较完美的解决方案。

2.方法的封装
在采用面向对象思想时,我们一边都把变量的访问权限设置为私有的,然后提公有的接口去获取成员变量的值,不过在JS中我们按照上面的方式定义的成员变量都是公有的,就是可以直接通过.的方式来访问。但是这并不能说我们就不需要去封装方法了,方法的封装还是必须的。下面我们就来看一下各种方法封转的方式,其实和上面变量的封装是一一对应的

方法封装方式一
<script>
		var o = new Object();
		o.name = "alibaba";
		o.getName = function() {
			console.log(this.name);
		};
	
		o.getName();		//输出alibaba
</script>


这里我们先创建一个Object类型的对象o,然后给这个对象增加一个方法属性getName,这里的问题还是一样的,方法的封装可能会零散的分布在程序的各个地方,导致最后我们自己都不知道到底有那些方法

方法的封装方式二
<script>
		var o = {
			name:"alibaba",
			getName:function() {
				console.log(this.name);
			}
		};
	
		o.getName();		//输出alibaba
</script>


在定义变量o的时候定义其方法属性getName,缺点也是一样的,就是在定义对象的时候无法控制成员变量的复制

方法的封装方式三
<script>
		function createCompany(name, age) {
			var o = new Object();
			o.name = name;
			o.age = age;
			o.getName = function() {
				console.log(this.name);
			};
			o.getAge = function() {
				console.log(this.age);
			};
			return o;
		}
	
		var o = createCompany("alibaba", 10);
		o.getName();
		o.getAge();
</script>


这里采用的是在“伪工厂”中创建对象的时候给创建对象的方法属性,其实和方式一的创建手段有点像,只是这里把对象方法属性的创建集中起来,但是我们还是没有办法知道所创建的对象类型。

方法的封装方式四
<script>
		function Company(name, age) {
			this.name = name;
			this.age = age;
		
			this.getName = function() {
				console.log(this.name);
			};
		
			this.getAge = function() {
				console.log(this.age);
			};
		}
	
		var company = new Company("alibaba", 10);
		company.getName();
		company.getAge();
</script>


这里我们在构造函数中创建方法属性,这样上面的那些缺点就全部没有了,这种解决策略是不是一定就是完美的呢?其实未必,我们来看个例子,首先我们创建一个对象var c1 = new Company(“nuaa”, 60); 接下来我们再来创建一个对象var c2 = new Company(“alibaba”, 10);这时候我们有两个对象c1和c2,这两对象除了成员变量的值不一样,其方法属性是一样的,但是现在c1拥有的getName应用和c2拥有的getName引用是不一样的,在JS中,其实每个方法属性都是对一个变量的引用,我们可以在程序中验证一下c1的getName对应的值和c2的getName对应的值是不一样的,下面我们给出验证程序:

<script>
		function Company(name, age) {
			this.name = name;
			this.age = age;
		
			this.getName = function() {
				console.log(this.name);
			};
		
			this.getAge = function() {
				console.log(this.age);
			};
		}
	
		var c1 = new Company("alibaba", 10);
		var c2 = new Company("nuaa", 60);
		console.log(c1.getName == c2.getName);	//输出为false
</script>


上述程序段说明c1的getName和c2的getName是不同的引用,好,问题来了,虽然c1和c2是不同的引用,但是它们指向的代码段是一样的,所以这里的不同引用有点不合理,因此就出现的原型的概念,下面我们将看看JS中的原型。

3.JS中的原型

原型(prototype)是JS中每个函数的都有的属性,这里一定要理解清楚,原型是函数的属性,首先我们来看一段小程序

<script>
    function Person(){
    }
</script>


这段JS程序只定义了一个函数Person(),我们可以在浏览器的Console查看函数的prototype属性



这时我们发现原来函数的prototype属性就是一个对象,这个对象有两个属性,分别是constructor属性和_proto_属性,其中构造函数属性指向了其本身,下面我们通过一个示意图来描述一下这种关系,方便理解,关于这里的_proto_我们后面再讨论,它是一个引用,指向了当前对象的父类。



通过上面的示意图,我们很清晰的看到函数的prototype属性其实就是一个对象,关于对象的_proto_属性我们后面再介绍。其实原型有点类似我们经常所说的静态成员变量,为所有实例所共享。这里我们并没有显示的地去定义函数的原型属性,此时,原型属性的constructor属性就自动会获取到构造函数Person()。下面我们来看看在使用原型的使用:

<script>
			/*在构造函数中定义成员变量*/
			function Company(name, age) {
				this.name = name;
				this.age = age;
			}
	
			/*在原型中定义方法*/
			Company.prototype = {
				getName:function() {
					console.log(this.name);
				},
		
				getAge:function() {
					console.log(this.age);
				}
			}
	
			var c1 = new Company("NUAA", 60);
			var c2 = new Company("alibab", 10);
	
			console.log(c1.getName == c2.getName);		//输出是true
			console.log(c1.getAge == c2.getAge);			//输出是true
</script>

在chrome中的运行结果如下:


这里我们首先来看1,此时原型的constructor属性已经不再是Company()了,就是说如果我们定义了函数的原型属性,那么原型属性的constructor属性要是我们不显示指定的话,就不会自动指向Company(),如果这个constructor属性对你来说比较重要的话,那么就应该在原型中指定一下这个属性,例如constructor:Company。接下来我们来看看原型属性,这个原型属性是一个Object类型的对象,同时具有getName()和getAge()两个方法,通过观察2出,我们发现原来getName()和getAge()也有原型属性,再一次验证了原型属性是每个方法固有的属性,同时方法也有__proto__属性,这个我们后面再研究。最后需要注意的是程序的输出,打印出两个true,这就说明c1和c2拥有函数引用是相同的。这样的写法更加合理一些,这也是我们在前端开发中经常用到的一种写法。

三.JS之继承
1.实例的_proto_属性学习

在前面我们就说过每个实例都有一个_proto_属性,那么这个_proto_属性到底是什么呢?其实就是实例对应构造函数的原型对象,在这个原型对象中有我们在原型属性中定义的一些方法。下面我们首先来看一段JS代码

<script>
		//构造函数的定义
		function Company() {
			this.name = "NUAA";
		}
	
		//构造函数原型属性的定义
		Company.prototype = {
			sayHi:function() {
				console.log("Hello " + this.name);
			}
		}
	
		var o = new Company();
		o.sayHi();
		console.log(o);
</script>


上述代码很简单,就是定义了一个构造函数,同时也定义了构造函数的原型属性,接下来把实例打印出来。这段程序的运行结果如下:


对于箭头(1),我们发现实例对象的_proto_属性,其实根据上面的信息,我们可以看到_proto_是一个实例,其实就是原型对象实例,在这个原型对象实例中有我们在原型属性中定义的sayHi()方法,这个我们可以看一下构造函数Company()的原型属性,箭头(3)所指。同时在原型对象中,我们又看到了_proto_属性,箭头(2)所指,原型对象的_proto_属性执行的是Object()构造函数的原型对象,和箭头(4)处我们打印的Object的原型对象是一样的,那么Object原型对象的_proto_属性呢?没有,这是原型链的终点,原型链到Object后就停止发展了。其实这里我们已经看清楚了JS的继承是如何实现的了,对象实例中拥有一个_proto_属性,这个属性也是一个对象实例,这个对象实例中有一系列方法,同时原型对象实例中也有自己的_proto_属性,这时候使用我们创建的实例调用一个方法后,首先就会在实例本身的_proto_属性中查找这个方法,如果找到,就会执行,如果找不到,就会到实例拥有的原型对象的_proto_属性中去找,这样一直按照原型链向上搜索,直至找到方法或者原型链到达终点。

2.方法的_proto_属性学习

在JS中每个函数都是一个对象实例,既然函数也是对象实例,那么函数也应该有自己的_proto_属性,没错,函数也有自己的_proto_属性,函数的_proto_属性中保存是一些函数的内部属性,比如call()方法,用来调用函数自己等等。下面我们来看一个小例子。

<script>
	function Sum(num1, num2) {
		this.num1 = num1;
		this.num2 = num2;
	}
	
	Sum.prototype = {
		add:function() {
			console.log(this.num1 + this.num2);
		}
	}
	
	var o = new Sum(1, 2);
	//直接调用函数
	o.add();
	//使用函数的内部属性调用函数
	o.add.call(o, 1, 2);
</script>


这段JS程序很简单,创建一个对象实例,然后调用对象实例的add方法,不过这里展示两种调用add方法的方式,第一种是通过对象实例来调用的,第二种时通过函数的内部属性来调用的,在使用call方法时,我们传递了一个o参数,就是把o传进去,这时候在add方法中的this引用就是o了。下面是这段JS程序的运行结果:



看看这段JS代码的运行结果,我们发现函数有自己的_proto_属性,上图B箭头所指,函数的_proto_属性指向了一个对象,这个对象包含了函数的一些内部属性,例如call方法等,同时每个函数都有自己的原型属性,上图A箭头所指就是函数的原型属性。

3.继承的实现

在JS 继承是通过原型链来实现的,其基本思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法。从这个基本思想出发,我们可以设计这样一个简单的基于继承的JS程序。

<script>
	function Parent() {
		this.superName = "Parent";
	}

	Parent.prototype = {
		getSuperName:function() {
			console.log(this.superName);
		}
	}

	function Child() {
		this.subName = "Child";
	}
	
	//子引用的构造函数的原型属性指向父引用对象,这样子引用就继承了父引用中的方法
	Child.prototype = new Parent();
	
	//在继承父引用方法的同时又有自己不同的特点
	Child.prototype.getSubName = function() {
		console.log(this.subName);
	}
	
	var child = new Child();
	
	//调用继承而来的方法
	child.getSuperName();
	//调用自己的方法
	child.getSubName();
	
</script>


上述代码利用JS中的原型链实现了面向对象中的继承理念,子引用在继承父引用的中的方法后,又有自己的方法。下面我们来看一下上述代码在Chrome中的运行结果如下:



通过上面的运行结果,我们可以看到child实例有一个subName的成员变量,它的_proto_属性(箭头A所指)指向了一个Parent类型的实例(如箭头B所指),这个Parent类型的实例中首先有我们给Child增加的一个getSubName方法,其次,因为这个实例是Parent类型的,那么它就拥有Parent类型中定义的成员变量superName,其次,它(child的_proto_所指的引用)是一个实例,那么就应该有自己的_proto_属性,这个_proto_属性就指向了Parent类型的原型对应的一个实例,这里面就包含了Parent原型属性中定义的getSuperName方法了,从而形成一个原型继承链,当然箭头B所指的实例也有自己的_proto_属性了,这个属性其实就是原型链的终点了,即Object类型的实例。接下来,我们来按照这个原理来分析一下child.getSuperName()方法的调用过程,首先会在child的_proto_属性中查找这个方法,发现child的_proto_属性中没有这个方法,这时候child的_proto_属性指向了一个引用,这个引用也有自己的_proto_属性,这时候就会在这个引用的_proto_属性中查找getSuperName()方法,结果找到了这个方法,因为这个引用的类型是Parent类型的,那么它的_proto_属性一定指向Parent的原型对象了,这时候肯定就会找到getSuperName()方法,如果找不到,就沿着继承链继续向前找找到Object中,如果在Object中还是找不到的话,那么就报错没有相关的方法。如果我们在child中重写了父类的一个方法,那么使用child来调用这个方法的时候,调用是Child原型中的方法,而不是Parent原型中的方法,这可以理解为是JS继承链中的就近原则。

三.关于多态
找了一些资料看了看,觉得JS中多态的研究没有太大的意义,所以就没有继续往下纠结。

四.JS&&JQuery实践分享
前些日子利用晚上的时间折腾了一个表达式计算器,利用数据结构中堆栈思想实现的,有一个操作符栈和一个操作数栈,通过扫描表达式以及比较算符优先级来实现,这个算法大家都学过,我就不详细介绍了,下面就是最终的Demo:


注意:1.JS中浮点数的计算有点问题

2.源代码https://github.com/yangbolin/web-calculator.git
  • 大小: 4.4 KB
  • 大小: 16.1 KB
  • 大小: 16.5 KB
  • 大小: 50.7 KB
  • 大小: 29.6 KB
  • 大小: 21 KB
  • 大小: 11.6 KB
  • 大小: 2.4 KB
分享到:
评论
1 楼 huangningren 2013-03-01  

相关推荐

    老生常谈javascript的面向对象思想

    在进行JavaScript编程的过程中,面向对象思想一直是一个核心的概念。面向对象编程(OOP)是通过创建对象来模拟现实世界的一种编程范式。在JavaScript中,对象可以通过不同的方法创建和定义。面向对象有三大基本特性...

    JavaScript面向对象编程指南.pdf

    JavaScript作为一门浏览器语言的核心思想;面向对象编程的基础知识及其在... 《JavaScript面向对象编程指南》着重介绍JavaScript在面向对象方面的特性,展示如何构建强健的、可维护的、功能强大的应用程序及程序库

    JavaScript面向对象编程指南

    《JavaScript面向对象编程指南》内容包括:JavaScript作为一门浏览器语言的核心思想;面向对象编程的基础知识及其在JavaScript中的运用;数据类型、操作符以及流程控制语句;函数、闭包、对象和原型等概念,以代码...

    javascript面向对象编程

    面向对象编程的基础知识及其在JavaScript中的运用;数据类型、操作符以及流程控制语句;函数、闭包、对象和原型等概念,以代码重用为目的的继承模式;BOM、DOM、浏览器事件、AJAX和JSON;如何实现JavaScript中缺失的...

    javascript面向对象编程.pdf

    总而言之,学习现代JavaScript面向对象编程,有助于开发者在认识这门语言演化的基础上,运用面向对象的设计和编程模式来构建更加健壮和可维护的JavaScript应用程序。同时,测试和调试是保证代码质量不可或缺的环节,...

    JavaScript的面向对象特性浅析与范例

    传统方式对JavaScript的应用基本上是基于过程模型的,若在JavaScript中利用面向对象的思想进行代码编写,将会使得代码具有良好的结构和逻辑性,更便于管理和维护。本文让读者看到JavaScript如何实现面向对象编程并...

    第15章 javascript面向对象与原型

    在深入讲解JavaScript面向对象与原型的知识点之前,首先需要了解JavaScript的基础知识。在JavaScript中,面向对象编程(OOP)的概念虽然存在,但是它的实现与传统基于类的语言有所不同。ECMAScript,也就是...

    Javascript面向对象编程

    ### JavaScript面向对象编程详解 #### 一、引言 JavaScript作为一种广泛使用的脚本语言,在Web开发领域占据着举足轻重的地位。尽管JavaScript本质上是一种基于原型的语言,但它也支持面向对象编程的一些特性,使得...

    JavaScript面向对象精要(英文版)

    《JavaScript面向对象精要》这本书不仅介绍了JavaScript面向对象的基础概念,还深入探讨了其实现机制及其在实际开发中的应用。对于希望提高自己JavaScript技能水平的开发者来说,本书是一本不可多得的好书。通过学习...

    面向对象javascript

    面向对象JavaScript是一种编程范式,它将JavaScript脚本编写转换为面向对象的思想。面向对象的JavaScript开发可以极大地提高开发效率和代码健壮性。 面向对象的JavaScript的特征包括: * 继承:允许子类继承父类的...

    JavaScript程序设计课件:面向对象概述.pptx

    面向过程与面向对象 6.1.1 面向过程与面向对象 1、概念 面向过程(Procedure Oriented)也可称之为“面向记录”,是一种以过程为中心的编程思想。它注重的是具体的步骤,只有按照步骤一步一步执行,才能够完成某件...

    网页开发面向对象思想深入理解

    网页开发面向对象思想深入理解的知识点涵盖了JavaScript...以上知识点从面向对象编程思想的普及到JavaScript的特有实现方式,再到编程哲学的深层次探讨,都为我们提供了理解和运用面向对象思想在网页开发中的深刻见解。

    JavaScript中使用面向对象思想处理cooke思路

    在这个场景中,我们将探讨如何使用面向对象的思想处理Cookie,一种在Web开发中用于存储用户数据的机制。 首先,我们需要了解Cookie的基本概念。Cookie是由服务器端发送到客户端(浏览器)的一小段文本信息,浏览器...

Global site tag (gtag.js) - Google Analytics