玩转JavaScript OOP - 类的实现详解

概述

当我们在谈论面向对象编程时,我们在谈论什么?

我们首先谈论的是一些概念:对象、类、封装、继承、多态。

对象和类是面向对象的基础,封装、继承和多态是面向对象编程的三大特性。

JavaScript提供了对象却缺乏类,它不能像C#一样能显式地定义一个类。

但是JavaScript的函数功能非常灵活,其中之一就是构造函数,结合构造函数和原型对象可以实现”类”。

对象和类的概念

对象

“对象”是面向对象编程中非常重要的一个概念,一个对象是一个“东西”(某个人或某件事)的描述。

人和事都来源于现实生活,我们对现实世界的认知就是对人和事的认知。

在编程的领域,代码对于常人来说是抽象的,代码构成的应用是为了更好地解决现实世界的问题。

在分析和设计阶段,使用“对象”的概念能够更好地反应现实世界的问题。

反过来说,代码是包含一些逻辑的,这些逻辑用于描述业务,业务是包含一些业务知识的,业务知识是通过对现实世界的理解和分析总结出来的,这些问题是由现实世界的“对象”构成的。

对象包含特征和行为,用OOP的术语来说,特征是对象的属性,行为是对象的方法。

在现实世界中,相似的对象可以按照一定的标准来分组。例如“蜂鸟”和“老鹰”都被划分到鸟类,鸟类不是一个具体的对象,它是人们根据“蜂鸟”、“老鹰”等那些具体的鸟分析出相似的特征和行为后,归纳出来的一个概念。类相当于一个模板,我们可以基于这个模板创建不同的具体的对象。

在C#中,我们可以定义一个鸟类。

/// <summary>
/// 鸟类
/// </summary>
public class Bird
{
  public void Fly()
  {
    Console.WriteLine("I can fly!");
  }
}

虽然JavaScript是一门面向对象编程语言,但它没有提供class的语法支持。

在JavaScript中,一切都是基于对象的,即使后面要讲的“原型”也都是对象,JavaScript的继承和重用也都是通过原型来实现的。

但是结合构造函数和原型对象可以实现JavaScript的“类”。

构造函数

之前我们使用new Array()创建一个数组,使用new Object()创建一个对象,Array()和Object()是JavaScript内置的两个构造函数,尽管JavaScript没有提供类,但我们可以将Array和Object理解为“类”的概念。

需要注意的是,JavaScript的“类”是由构造函数实现的。

定义构造函数

构造函数也是函数,定义构造函数和其他函数并没有语法上的区别。

唯一的区别是构造函数的首字母应该大写,这也是JavaScript的编程规范。

以下定义了一个Person()构造函数,我们可以将它理解为Person类。

function Person(){
	console.log('I am keepfool.');
}

JavaScript的“类”和构造函数是同时被定义的,在JavaScript中定义“类”时,就同时定义了构造器。

使用构造函数

JavaScript使用类的方式和C#一样,new关键字后面跟着构造函数。

var p = new Person();

定义属性和方法

现在我们已经定义好了Person类,可以为Person类添加一些属性和方法。

定义属性

在讲JavaScript对象时,我们讲了对象的属性设置和访问。
这段代码展示了定义对象属性的两种方式:

var cat = {
	color: 'black'
};
cat.name = 'Tom';
console.log(cat.color);
console.log(cat.name);

使用this定义属性

JavaScript类的属性定义方式则有些不同,在构造函数中使用this关键字定义属性:

function Person(name){
	this.name = name;
}

•第一行代码,定义了Person类,并定义了构造函数。

•第二行代码,定义了name属性。

创建并使用对象

以下2行代码创建了两个Person类的对象

var p1 = new Person('James');
var p2 = new Person('Cury');

在Chrome控制台中输出p1.name和p2.name

p1和p2是两个不同的对象,修改p1.name不会影响p2.name。

p1.name = 'Lebron James';

定义方法

首先,我们区分一下术语“函数”和“方法”,“函数”是独立的单元,而“方法”是依赖于类这个主体存在的。

使用this定义方法

在JavaScript中,类的方法是定义在构造函数中的函数,在构造函数中使用this关键字定义方法:

function Person(name) {
	// 定义属性
	this.name = name;
	// 定义方法
	this.sayHello = function() {
		return 'Hello, I am ' + this.name;
	}
}

使用方法

在Chrome控制台分别调用p1和p2对象的sayHello()方法

constructor属性

当创建一个对象时,一个特殊的属性被JavaScript自动地分配给对象了,这个属性就是constructor属性。
在chrome控制台输入p1.constructor,可以看到p1对象的constructor属性指向一个函数。

瞧瞧这个函数的内容,这不正是Person()构造函数吗?

这表示我们也可以通过p1.constructor属性创建对象,

var p3 = new p1.constructor('Steve Nash');

这行代码阐述了一句话:“我不关心p1对象是怎么创建的,但我想让另一个对象如p1一样创建!”

在Chrome控制台使用instanceof操作符,可以看到p1、p2、p3都是Person类的实例

另外,当我们以{}方式创建对象时,实际上也调用了Object()构造函数。

var o = {};

这行代码声明了一个对象,尽管我们没有设置任何属性和方法,但JavaScript引擎默认给它设置了constructor属性。
o.constructor指向的是Object()构造函数,[native code]显示了Object()是JavaScript内置的函数。

原型对象

在JavaScript中,定义一个函数时,函数就会拥有prototype属性,构造函数也不例外。
下图说明了Person()构造函数的prototype属性是一个对象,它是属于函数的,我们称这个属性为原型对象。
从Person类的角度出发,我们也可理解为prototype属性是属于Person类的。

同时Person类的实例是没有prototype属性的,上图的p1.prototype是undefined,这说明prototype属性是共享的,这有点像C#中的静态属性。

设置prototype

既然prototype是一个对象,那就可以为它添加属性和方法。

在函数的protpotype属性上定义属性和方法,与设置普通对象的属性和方法没什么区别。
下面的代码为Person.prototype定义了属性和方法。

function Person(name){
	this.name = name;
	this.sayHello = function() {
		return 'Hello, I am ' + this.name;
	}
}

// 在构造函数的prototype对象上定义属性和方法
Person.prototype.height = 176;
Person.prototype.run = function(){
	return 'I am ' + this.name + ', I am running!';
}

var p1 = new Person('James');

 使用prototype

在Person.prototype中定义的属性和方法,可以直接被Person类的实例使用,仍然是以object.property的方式使用。

需要特别注意的是,namesayHello()是属于Person类的实例,而heightrun()是不属于Person类的实例。

小技巧:通过hasOwnProperty方法可以查看对象是否包含某个属性或方法。

自有属性 vs. prototype的属性

Person类的实例既可以使用Person类中的属性,又可以使用Person.prototype中的属性。
那么Person类的属性和Person.prototype的属性有什么差别呢?

首先,我们可以将Person类中的属性和方法理解为“实例属性”。
由于prototype是共享的,我们可以将prototype中的属性和方法理解为“共享属性”。

“实例属性”和“共享属性”的差别主要体现在性能上。
每创建一个Person的实例,就会产生一个name属性和sayHello()方法的副本,而height属性和run()方法则是所有实例共享一个副本。

既然如此,这意味着sayHello()方法可以提到prototype中。
另外,不同的Person实例height可能会不一样,应将它放到Person类中更合理。

function Person(name,height){
	this.name = name;
	this.height = height;
}

Person.prototype.sayHello = function(){
	return 'Hello, I am ' + this.name + ', my height is ' + this.height + 'cm.';
}
Person.prototype.run = function(){
	return 'I am ' + this.name + ', I am running!';
}

var p1 = new Person('James',203);
var p2 = new Person('Cury',190);

类的实现总结

JavaScript没有类,但构造函数可以实现“类”。

按照JavaScript编程规范,构造函数的首字母应该大写。

“类”的属性和方法是用this.property方式定义在构造函数中的。

在对象创建时JavaScript分配了constructor属性给对象,constructor属性是对象构造函数的一个引用。

函数在定义时就已经有了prototype属性,prototype属性也是一个对象。

prototype是共享的,定义在prototype上的属性和方法可以被“类”的实例使用。

如果属性或方法能够定义在prototype上,就不要定义在构造函数上,使用prototype可以减少内存开销。

以上这篇玩转JavaScript OOP - 类的实现详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Javascript oop设计模式 面向对象编程简单实例介绍

    Javascript oop设计模式 面向对象编程 最初我们写js代码的时候是这么写 function checkName(){ //验证姓名 } function checkEmail(){ //验证邮箱 } function checkPassword(){ //验证密码 } 这种方式会造成全局变量的严重污染,再过渡到 var checkObject = { checkName : function(){}; checkEmail: function(){}; checkPassword:

  • javascript延时重复执行函数 lLoopRun.js

    function lLoopRun(sFuncLoop,sFuncEnd,nDelay) { //writen by http://fengyan.iecn.cn //sFuncLoop >> 字符串型,需要重复执行的Javascript函数或语句(多个函数或语句请用;分隔) //sFuncEnd >> 字符串型,用于中止重复执行动作(sFuncLoop)的Javascript函数或语句 //nDelay >> 数字型,重复执行的时间间隔(毫秒数) var vinter

  • JavaScript运行机制之事件循环(Event Loop)详解

    一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,否则会带来很复杂的同步问题.比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线

  • Nodejs全栈框架StrongLoop推荐

    StrongLoop是一个基于Nodejs的强大框架,几乎包含了移动开发全栈所需要的所有功能.2013年成立,很少的员工,一个技术驱动,执行力强大的团队.也是在13年我开始接触StrongLoop,当时是为了做nodejs方面的技术选型,看了许多框架,LoopBack是我觉得最酷的一个.我还记得当时是觉得LoopBack的文档太差(主要是跟在线的版本不一样),不知道能活多久所以才放弃了它.时隔一年回来看到这个绿油油的框架,这一年可真是突飞猛进呢. 全栈框架StrongLoop StrongLoo

  • Node.js事件循环(Event Loop)和线程池详解

    Node的"事件循环"(Event Loop)是它能够处理大并发.高吞吐量的核心.这是最神奇的地方,据此Node.js基本上可以理解成"单线程",同时还允许在后台处理任意的操作.这篇文章将阐明事件循环是如何工作的,你也可以感受到它的神奇. 事件驱动编程 理解事件循环,首先要理解事件驱动编程(Event Driven Programming).它出现在1960年.如今,事件驱动编程在UI编程中大量使用.JavaScript的一个主要用途是与DOM交互,所以使用基于事件

  • javascript oop开发滑动(slide)菜单控件

    这里使用原生的javascript,用面向对象的方式创建一个容易维护使用方便的滑动菜单,调用方式如下: 复制代码 代码如下: var $sliding = document.getElementById("silding"); var s1 = new Sliding(); s1.commands = $sliding.getElementsByTagName("dt"); s1.panels = $sliding.getElementsByTagName(&quo

  • javascript 原型模式实现OOP的再研究

    复制代码 代码如下: 复制代码 代码如下: function A() { this.v1 = 10; } A.prototype.print = function() { alert(this.v1); } function B() { } B.prototype = new A(); new B().print(); 运行这段代码输出是10,看起来好像是类B继承了类A的方法print,并产生了正确的输出,实际上的执行流程是在类B生成的对象中,不能直接得到方法print,于是在它的prototy

  • JavaScript OOP类与继承

    类:分为公开的和私有的 复制代码 代码如下: function Person(n){ var name=n; //私有属性 function hello(){} //私有方法一 var hello2() = function(){} //私有方法二 this.Name = "张三"; //公开成员一 this.Hello = function(){ //公开方法一 this.Name; //公开方法中可以调用私有方法与属性 name; } } Person.prototype.Age=

  • 延时重复执行函数 lLoopRun.js

    公司的一个项目中,有许多地方需要延时执行一些可重复性的函数(动作),就写了下面这段函数. 呵呵,不知道取什么意思更为确切些,就定为了:LoopRun,意为"重复执行"  function lLoopRun(sFuncLoop,sFuncEnd,nDelay) {   var vintervalId = null;   var runString  = sFuncLoop;   var stopString  = sFuncEnd;   var delayTime  = nDelay;

  • javascript基于prototype实现类似OOP继承的方法

    本文实例讲述了javascript基于prototype实现类似OOP继承的方法.分享给大家供大家参考,具体如下: 这里要说明的是,公有属性(使用this.修饰符)可以被覆盖,私有属性(使用var 修饰符)不能被覆盖 子类不能访问父类的私有属性,父类的方法正常访问父类的私有变量. function Vegetable(){ this.taste='delicious'; var a = 'I\'m Vegetable\'a!' this.fun1 = function(){ alert('Veg

  • Javascript OOP之面向对象

    面向对象程序设计(Object-oriented programming,OOP)是一种程序设计范型,同时也是一种程序开发的方法.对象指的是类的实例.它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性.灵活性和扩展性.--维基百科 一般面向对象包含:继承,封装,多态,抽象 对象形式的继承 浅拷贝 var Person = { name: 'allin', age: 18, address: { home: 'home', office: 'office', } sclools:

  • JavaScript OOP面向对象介绍

    OOP 语言使我们有能力自定义对象和变量类型. 面向对象编程 JavaScript 是面向对象的编程语言 (OOP).OOP 语言使我们有能力定义自己的对象和变量类型.对象拥有属性和方法. 属性 属性指与对象有关的值. 举例: 复制代码 代码如下: <script type="text/javascript"> var txt="Hello World!" document.write(txt.length) //使用字符串对象的长度属性来计算字符串中的

  • JS OOP包机制,类创建的方法定义

    复制代码 代码如下: /** * 定义包 * @param {} ns * @return {} */ Xu.pkg = function(ns) { if (!ns || !ns.length) { return null; } var levels = ns.split("."); var nsobj = Xu; for (var i = (levels[0] == "Xu") ? 1 : 0; i < levels.length; ++i) { nsob

随机推荐