js面向对象

前言

今昔 JavaScript 大行其道,各种以对那据日深。web
程序员已渐习惯用各种优质之 JavaScript 框架迅速开 Web
应用,从而忽视了针对原生 JavaScript
的学习与深入理解。所以,经常出现的景象是,很多开了多年 JS
开发之程序员对闭包、函数式编程、原型总是说不清道不明,即使使用了框架,其代码组织也殊不好。这还是针对原生
JavaScript 语言特征理解不够的显现。要掌握好
JavaScript,首先一点是得抛弃一些其它高档语言如 Java、C#
等类式面向对象思维的干扰,全面地由函数式语言的角度理解 JavaScript
原型式面向对象的风味。把握好马上一点事后,才发或逾使好就宗语言。本文适合群体:使用了
JS 框架而对 JS 语言本质缺乏了解的程序员,具有 Java、C++
等语言开发经历,准备攻读并采取 JavaScript 的程序员,以及一直对
JavaScript 是否面向对象模棱两但,但巴了解真相的 JS 爱好者。

重新认识面向对象

为验证 JavaScript
是相同宗清底面向对象的言语,首先有必要从面向对象的定义在手 ,
探讨一下面向对象中之几独概念:

  • 漫天事物都对象
  • 目标拥有封装和后续特性
  • 对象及目标期间以信息通信,各自在信息隐藏

因为当下三点召开呢依据,C++
是半面向目标半面向过程语言,因为,虽然他实现了仿佛的包、继承和多态,但在非对象性质的大局函数和变量。Java、C#
是了的面向对象语言,它们通过类似的样式组织函数和变量,使的匪克脱目标是。但此间函数自己是一个过程,只是依附在某类及。

但是,面向对象仅仅是一个概念或者编程思想而已,它不应负让有语言是。比如
Java
采用面向对象思想构造其语言,它实现了近乎、继承、派生、多态、接口等机制。但是这些机制,只是实现面向对象编程的平等种植手段,而休要。换言之,一流派语言可以因该自特色选择相当的章程来兑现面向对象。所以,由于大部分程序员首先学或用的凡近似
Java、C++ 等高档编译型语言(Java
虽然是半编译半解释,但貌似举行为编译型来讲课),因而先称为主地受了“类”这个面向对象实现方式,从而以念书脚本语言的早晚,习惯性地用类式面向对象语言中的定义来判定该语言是否是面向对象语言,或者是不是富有面向对象特性。这为是阻止程序员深入上并控制
JavaScript 的关键原因有。

事实上,JavaScript
语言是通过同样栽叫做 原型(prototype)的章程来兑现面向对象编程的。下面就是来讨论 据悉类的(class-based)面向对象和 根据原型的
(prototype-based) 面向对象
就半栽艺术以组织客观世界的艺术达成之差距。

 

冲类的面向对象和因原型的面向对象方式比较

在因类的面向对象方式受到,对象(object)依靠类(class)来产生。而在根据原型的面向对象方式遭,对象(object)则是依赖 构造器(constructor)利用 原型(prototype)结构出的。举个成立世界之例子来证实二栽艺术认知的距离。例如工厂造一模一样部车,一方面,工人必须参照一布置工图纸,设计规定及时辆车当怎么样打造。这里的工程图纸就好比是言语中之 
(class)
,而车即使是以这个 类(class)造出来的;另一方面,工人和机器
( 相当给 constructor) 利用各种零部件如发动机,轮胎,方向盘 ( 相当给
prototype 的各个属性 ) 将汽车构造出。

实则关于这简单种植艺术谁更加彻底地发挥了面向对象的思,目前尚有争执。但作者觉得原型式面向对象是同等种植更加彻底底面向对象方式,理由如下:

首先,客观世界中之目标的有都是其余东西对象组织之结果,而空虚的“图纸”是不克发出“汽车”的,也就是说,类是一个抽象概念而并非实体,而目标的发生是一个实体的产生;

说不上,按照合事物都对象是极度基本的面向对象的规律来拘禁,类 (class)
本身并无是一个目标,然而原型方式遭到的组织器 (constructor) 和原型
(prototype) 本身吗是任何对象通过原型方式组织出的目标。

重新,在类式面向对象语言中,对象的状态 (state) 由对象实例 (instance)
所具有,对象的所作所为方式 (method)
则是因为声明该对象的类所持有,并且只有靶的构造与办法能让接续;而当原型式面向对象语言中,对象的行事、状态还属对象自我,并且能同吃连续(参考资源),这吗再次靠近客观实际。

终极,类式面向对象语言比如
Java,为了弥补无法使用面向过程语言中全局函数和变量的不方便,允许以相近中宣称静态
(static)
属性和静态方法。而其实,客观世界不存所谓静态概念,因为任何事物都对象!而于原型式面向对象语言中,除内建对象
(build-in object)
外,不同意全局对象、方法要性质之留存,也未曾静态概念。所有语言元素
(primitive)
必须靠对象在。但鉴于函数式语言的风味,语言因素所倚之对象是随着运行时
(runtime) 上下文 (context) 变化而别的,具体体现于 this
指针的生成。正是这种特征又近
“万物皆有所属,宇宙乃万物生存之从”的当观点。在 程序清单
1中 window 便类似与宇宙的定义。

清单 1. 目标的上下文依赖
 <script> 
 var str = "我是一个 String 对象 , 我声明在这里 , 但我不是独立存在的!"
 var obj = { des: "我是一个 Object 对象 , 我声明在这里,我也不是独立存在的。" }; 
 var fun = function() { 
    console.log( "我是一个 Function 对象!谁调用我,我属于谁:", this ); 
 }; 

 obj.fun = fun; 

 console.log( this === window );     // 打印 true 
 console.log( window.str === str );  // 打印 true 
 console.log( window.obj === obj );  // 打印 true 
 console.log( window.fun === fun );  // 打印 true 
 fun();                              // 打印 我是一个 Function 对象!谁调用我,我属于谁:window 
 obj.fun();                          // 打印 我是一个 Function 对象!谁调用我,我属于谁:obj 
 fun.apply(str);                   // 打印 我是一个 Function 对象!谁调用我,我属于谁:str 
 </script>

于收受了面向对象存在一样栽叫做基于原型实现的办法的实后,下面我们虽可来深入探讨
ECMAScript 是何等根据这无异于术组织自己之言语的。

 

极端中心的面向对象

ECMAScript 是同等宗清的面向对象的编程语言(参照资源),JavaScript
是里的一个转移种 (variant)。它提供了 6 种植基本数据列,即
Boolean、Number、String、Null、Undefined、Object。为了促成面向对象,ECMAScript计划有了同一种植好成功的数据结构

  • JSON(JavaScript Object Notation),
    这同经结构已经足以脱离语言而改为平等种广泛应用的数据交互格式
    (参照资源)。

应当说,具有核心数据列以及 JSON 构造语法的 ECMAScript
已经主导可以实现面向对象的编程了。开发者可以随心所欲地用 字面式声明(literal
notation)
艺术来布局一个靶,并对准其不存在的性质直接赋值,或者用
delete 将性删除 ( 注:JS 中的 delete
关键字用于去对象属性,经常吃误作为 C++ 中的
delete,而后人是用来释放不再采取的靶子 ),如 程序清单
2。

清单 2. 字面式 (literal notation) 对象声明
 var person = { 
    name: “张三”, 
    age: 26, 
    gender: “男”, 
    eat: function( stuff ) { 
        alert( “我在吃” + stuff ); 
    } 
 }; 
 person.height = 176; 
 delete person[ “age” ];

于事实上支出过程被,大部分新家或对 JS
应用尚未最好强要求的开发者也大半只有所以到 ECMAScript
定义之就等同有些情节,就会满足基本的付出需要。然而,这样的代码复用性非常弱,与任何实现了累、派生、多态等等的类式面向对象的强类型语言比较起来显得有些单调,不能够满足复杂的
JS 应用开发。所以 ECMAScript 引入原型来化解对象继承问题。

 

 

动函数构造器构造对象

除了 字面式声明(literal notation)方外,ECMAScript
允许通过 构造器(constructor)创建对象。每个构造器实际上是一个 函数(function)
对象
,
该函数对象涵盖一个“prototype”属性用于落实根据原型的继续(prototype-based
inheritance)
共享属性(shared properties)靶好由“new
关键字 + 构造器调用”的点子来创造,如 程序清单
3:

清单 3. 运用结构器 (constructor) 创建对象
 // 构造器 Person 本身是一个函数对象
 function Person() { 
     // 此处可做一些初始化工作
 } 
 // 它有一个名叫 prototype 的属性
 Person.prototype = { 
    name: “张三”, 
    age: 26, 
    gender: “男”, 
    eat: function( stuff ) { 
        alert( “我在吃” + stuff ); 
    } 
 } 
 // 使用 new 关键字构造对象
 var p = new Person();

由前期 JavaScript 的发明者为了要这宗语言和红的 Java 拉达涉及 (
虽然现在大家知晓两岸是雷锋同雷锋塔的涉及
),使用了 new 关键字来限定构造器调用并创建对象,以使该以语法上及
Java
创建对象的法子看上去好像。但待指出的凡,这简单门语言的 new意思毫无关系,因为该目标组织之机理完全不同。也多亏因为这里语法上之近乎,众多习惯了类式面向对象语言中目标创建方式的程序员,难以透彻理解
JS 对象原型构造的不二法门,因为她们连不知晓在 JS
语言中,为什么“函数叫做好当作类名”的面貌。而精神上,JS
这里仅是假了重要字 new,仅此而已;换句话说,ECMAScript
完全好为此任何 非new 表达式来为此调用构造器创建对象。

 

穷了解原型链 (prototype chain)

当 ECMAScript 中,每个由构造器创建的靶子有一个针对性构造器 prototype
属性值的 隐式引用(implicit
reference)
,这个引用称之为 原型(prototype)。进一步,每个原型可以具备对自己原型的 隐式引用(即该原型的原型),如此下来,这便是所谓的 原型链(prototype
chain)
 (参照资源)。在切实可行的语言实现中,每个对象都产生一个 __proto__
属性
来实现对原型的 隐式引用。程序清单
4说明了当时一点。

清单 4. 对象的 __proto__ 属性和隐式引用
 function Person( name ) { 
    this.name = name; 
 } 
 var p = new Person(); 
 // 对象的隐式引用指向了构造器的 prototype 属性,所以此处打印 true 
 console.log( p.__proto__ === Person.prototype ); 

 // 原型本身是一个 Object 对象,所以他的隐式引用指向了
 // Object 构造器的 prototype 属性 , 故而打印 true 
 console.log( Person.prototype.__proto__ === Object.prototype ); 

 // 构造器 Person 本身是一个函数对象,所以此处打印 true 
 console.log( Person.__proto__ === Function.prototype );

有了 原型链,便可定义一种植所谓的 性隐藏机制,并由此这种体制实现连续。ECMAScript
规定,当要于某对象的习性赋值时,解释器会查找该对象原型链中第一个包含该属性之对象(注:原型本身就是是一个靶,那么原型链即为同样组对象的链。对象的原型链中的率先个目标是拖欠对象自我)进行赋值。反之,如果要取得有对象属性之价,解释器自然是归该目标原型链中第一具有该属性的对象属性值。图
1说名了即受到隐藏机制:

图 1. 原型链中的特性隐藏机制

以图 1 吃,object1->prototype1->prototype2 构成了 对象 object1
的原型链,根据上述属性隐藏机制,可以了解地观看 prototype1 对象吃之
property4 属性和 prototype2 对象被的 property3
属性都给隐形。理解了原型链,那么以非常容易理解 JS
中因原型的累实现原理,程序清单
5 是利用原型链实现连续的简易例子。

清单 5. 采取原型链 Horse->Mammal->Animal 实现持续
 // 声明 Animal 对象构造器
 function Animal() { 
 } 
 // 将 Animal 的 prototype 属性指向一个对象,
 // 亦可直接理解为指定 Animal 对象的原型
 Animal.prototype = { 
    name: animal", 
    weight: 0, 
    eat: function() { 
        alert( "Animal is eating!" ); 
    } 
 } 
 // 声明 Mammal 对象构造器
 function Mammal() { 
    this.name = "mammal"; 
 } 
 // 指定 Mammal 对象的原型为一个 Animal 对象。
 // 实际上此处便是在创建 Mammal 对象和 Animal 对象之间的原型链
 Mammal.prototype = new Animal(); 

 // 声明 Horse 对象构造器
 function Horse( height, weight ) { 
    this.name = "horse"; 
    this.height = height; 
    this.weight = weight; 
 } 
 // 将 Horse 对象的原型指定为一个 Mamal 对象,继续构建 Horse 与 Mammal 之间的原型链
 Horse.prototype = new Mammal(); 

 // 重新指定 eat 方法 , 此方法将覆盖从 Animal 原型继承过来的 eat 方法
 Horse.prototype.eat = function() { 
    alert( "Horse is eating grass!" ); 
 } 
 // 验证并理解原型链
 var horse = new Horse( 100, 300 ); 
 console.log( horse.__proto__ === Horse.prototype ); 
 console.log( Horse.prototype.__proto__ === Mammal.prototype ); 
 console.log( Mammal.prototype.__proto__ === Animal.prototype );

知晓清单
5 中目标原型继承逻辑实现的关键在于
Horse.prototype = new Mammal() 和 Mammal.prototype = new Animal()
这半句代码。首先,等式右边的结果是构造出一个现对象,然后用此目标赋值给等式左边对象的
prototype
属性。也就是说将右侧新建的目标作为左边对象的原型。读者可用随即片独等式替换到相应的程序清单
5 代码最后两执行之等式中自动领悟。

 

JavaScript 类式持续的兑现方式

由代码清单 5
可以观看,基于原型的继承方式,虽然实现了代码复用,但其行文松散且不够流畅,可阅览性差,不利被贯彻扩大和对源代码进行实用地组织管理。不得不承认,类式继承方式以言语实现达标再有健壮性,且在构建而复用代码和组织架构程序方面具有鲜明的优势。这让程序员们愿意找到平等种能够当
JavaScript
中坐类式继承风格进行编码的法门路子。从空洞的角度来讲,既然类式继承与原型继承都是为落实面向对象而设计之,并且她们各自实现之载体语言在算能力上是相当价格的
( 因为图灵机的乘除能力跟 Lambda 演算的计能力是等价格的
),那么能免可知找到同样种植转移,使得原型式继承语言由此该换实现有类式继承编码的风骨也?

时部分主流的 JS 框架还提供了这种转换机制,也就类式声明方法,比如
Dojo.declare()、Ext.entend()
等等。用户采取这些框架,可以擅自而协调地组织协调之 JS
代码。其实,在成千上万框架出现之前,JavaScript 大师 Douglas
Crockford
 最早采用三单函数对 Function
对象开展扩张,实现了这种转移,关于它的贯彻细节可以(参考资源)。此外还有由于Dean
Edwards
落实的资深的
Base.js(参考资源)。值得一提的凡,jQuery
之父 John Resig 在搏众家的丰富后,用不至 30
行代码便实现了友好的Simple Inheritance。使用该提供的 extend
方法声明类非常简单。程序清单
6大凡采用了 Simple
Inheritance
库实现类的声明的例证。其中最后一句子打印输出语句是针对性 Simple
Inheritance
兑现类式继承的极端证明。

清单 6. 采用 Simple Inheritance 实现类式继承
 // 声明 Person 类
 var Person = Class.extend( { 
    _issleeping: true, 
    init: function( name ) { 
        this._name = name; 
    }, 
    isSleeping: function() { 
        return this._issleeping; 
    } 
 } ); 
 // 声明 Programmer 类,并继承 Person 
 var Programmer = Person.extend( { 
    init: function( name, issleeping ) { 
        // 调用父类构造函数
        this._super( name ); 
        // 设置自己的状态
        this._issleeping = issleeping; 
    } 
 } ); 
 var person = new Person( "张三" ); 
 var diors = new Programmer( "张江男", false ); 
 // 打印 true 
 console.log( person.isSleeping() ); 
 // 打印 false 
 console.log( diors.isSleeping() ); 
 // 此处全为 true,故打印 true 
 console.log( person instanceof Person && person instanceof Class 
    && diors instanceof Programmer && 
    diors instanceof Person && diors instanceof Class );

若是你已针对原型、函数构造器、闭包和基于上下文的 this
有了尽量的掌握,那么透亮 Simple Inheritance
的兑现原理为不用一定困难。从精神上称,var Person =
Class.extend(…)
该语句被,左边的 Person 实际上是取得了由于 Class 调用
extend 方法返回的一个构造器,也即一个 function
对象的援。顺着这思路,我们累介绍 Simple Inheritance
是何等形成及时一点,进而实现了是因为原型继承方式及类式继承方式的变换的。图
2 是
Simple Inheritance
的源码及其附带注释。为了便于清楚,用中文对代码逐行补充说明。

贪图 2.Simple Inheritance 源码解析

撇代码第二有些,整体连贯地考察第一与老三有的会见意识,extend
函数的向目的就是如结构一个装有新原型属性的新构造器。我们不由自主感慨 John
Resig
的师父真迹及其对 JS 语言本质把握的细致程度。至于 John
Resig
凡安想到这么精密的兑现方式,感兴趣的读者可以翻阅本文
(参考资源),其中起详细介绍有关最初设计
Simple Inheritance 的思维过程。

 

JavaScript 私有成员贯彻

交者结束,如果你任然对 JavaScript
面向对象持怀疑态度,那么这嫌疑一定是,JavaScript
没有落实面向对象中的消息隐藏,即私有和国有。与其他类式面向对象那样显式地声称私有公有成员的法门各异,JavaScript
的音隐藏就是赖闭包实现的。见 程序清单
7:

清单 7. 运闭包实现信息隐藏
 // 声明 User 构造器
 function User( pwd ) { 
    // 定义私有属性
    var password = pwd; 
    // 定义私有方法 
    function getPassword() { 
        // 返回了闭包中的 password 
        return password; 
    } 
    // 特权函数声明,用于该对象其他公有方法能通过该特权方法访问到私有成员
    this.passwordService = function() { 
        return getPassword(); 
    } 
 } 
 // 公有成员声明
 User.prototype.checkPassword = function( pwd ) { 
    return this.passwordService() === pwd; 
 }; 
 // 验证隐藏性
 var u = new User( "123456" ); 
 // 打印 true 
 console.log( u.checkPassword( "123456" ) ); 
 // 打印 undefined 
 console.log( u.password ); 
 // 打印 true 
 console.log( typeof u.gePassword === "undefined" );

JavaScript
必须依赖闭包实现信息隐藏,是出于该函数式语言特征所决定的。本文不会见对函数式语言与闭包这点儿单话题展开讨论,正而齐文默认你知道
JavaScript 中基于上下文的 this 一样。关于 JavaScript
中落实信息隐藏,Douglas Crockford在《 Private members in JavaScript
》(参照资源)一文被产生重贵与详尽的介绍。

 

结束语

JavaScript 被认为是世界上极其受误解的编程语言,因为它身披 c
语言家族的伪装,表现的可是 LISP
风格的函数式语言特征;没有看似,却实也彻底实现了面向对象。要针对性就宗语言来透彻的知晓,就不能不剥离其
c
语言的外衣,从新回到函数式编程的角度,同时摒弃原有类的面向对象概念去读书领会它。随着近些年来
Web 应用的普及与 JS 语言自身的敏捷发展,特别是后台 JS 引擎的面世 (
如基于 V8 的 NodeJS 等 ),可以预见,原来只是作为玩具编写页面效果的 JS
将获取更常见发展领域。这样的发展趋势,也对 JS
程序员提出了再强要求。只有干净领悟了及时宗语言,才生或以巨型的 JS
项目被表述它们的威力。