JavaScript小特性(7)——面向对象

简介:

面向对象编程(OOP),是目前主流的编程方式,似乎能够OOP的语言,才会被大多数人视为好语言,不能OOP的语言都是“奥特曼”。而JavaScript,则是常常被人误解成“奥特曼”的一种语言,殊不知,JavaScript有着一种更高级的OOP特性。

在传统的OOP语言中,Object是Class的一个实例,一个Class可以继承自另一个Class,我们可以理解为“基于类型(Class)”;而JavaScript的语法中并没有Class的概念,Object传承自哪里并不重要,重要的是它能做什么,我们可以理解为“基于原型(Prototype)”。下面就去看看JavaScript的OOP特性吧。

 

1、一切皆对象

在JavaScript中,一切都是对象(除了null、undefined),数字(Number)、字符串(String)、布尔值(Boolean)、函数(Function)、数组(Array)都是对象,都有属于自己的Method。不过要注意的是,Number、String、Boolean这几个基本类型对象是不可变的,即你无法添加、修改它们的方法、属性。

一个常见的误解是,Number的字面量(literal)并不是对象,因为无法直接调用它的方法(符号“.”会被解释为小数点),不过还是有很多方法可以让它看起来像一个Object:

1
2
3
4
// 2.toString();  直接调用出错:SyntaxError
2..toString(); // 第二个点号可以正常解析
2 .toString(); // 注意点号前面的空格
(2).toString(); // 2先被计算

 

2、对象的创建

2.1、原始模式

在JavaScript中创建一个对象很简单:

1
2
3
4
5
6
7
8
9
//用new关键字创建对象
var gg = new Object();
//对象字面量(literal)的方式创建对象
var mm = {};
//给对象添加属性
gg.appearance = '帅' ;
gg.character = '体贴' ;
mm.appearance = '靓' ;
mm.character = '温柔' ;

在JavaScript中,Object其实就是一个Map,属性名就是key,值就是value,通过key就可以找到对应的value,value的值没有类型限制,可是基本类型、自定义对象、函数、数组等。创建对象时可以直接给对象添加属性:

1
2
3
4
5
6
7
8
9
var father = {
     appearance : '正当壮年' ,
     character : '和蔼可亲' ,
     //GG、MM是刚才创建的对象
     son : gg,
     daughter : mm,
     //属性也可以是函数
     say : function (){alert( 'GGMM,你妈喊你回家吃饭~' );}
} //GG、MM是失散多年的兄妹呀……

 

2.2、构造函数模式

如果用上面那种原始模式创建对象,肯定非常麻烦,代码重用率低,而且容易出现一些拼写错误的低级bug,要是有个构造函数(Constructor)就好啦。这种方式就有点像我们在Java、C++中用的Class(本质是不同的):

1
2
3
4
5
6
7
8
9
10
11
//构造函数的this指向即将创建的对象
//构造函数默认return this
function Person(ap, ch){
     this .appearance = ap;
     this .character = ch;
     this .say = function (){alert( 'Hello world' );}
}
//有了构造函数的封装,创造GGMM就容易多了
//不要忘了new关键字,不然返回的是undefined
var gg2 = new Person( '老实' , '忠厚' );
var mm2 = new Person( '清纯' , '可爱' );

通过构造函数创造的对象都有一个默认的constructor属性,指向它们的构造函数;同时可以用instanceof运算符,验证构造函数与实例对象之间的关系:

1
2
alert(gg2.constructor == Person); //true
alert(mm2 instanceof Person);     //true

 

2.3、Prototype

然而单纯的构造函数模式存在一个内存浪费的问题,因为构造函数里面创建的东西都是创建出来的对象独自拥有的,都需要分配独立的内存空间,但是有些属性/方法应该是公用的(例如Person的say方法),在内存中只需生成一次。在JavaScript中,每个构造函数都有一个prototype的属性,表示它创造出来的对象的原型是什么,对这个属性进行修改,我们就可以给创建的对象一些公有的属性/方法:

1
2
3
4
5
6
7
8
9
10
//Person共有的属性/方法
Person.prototype = {
     type : '地球人' ,
     sing : function (){alert( '唱歌' );},
     dance : function (){alert( '跳舞' );}
}
var gg3 = new Person( '肌肉猛男' , '威武霸气' );
var mm3 = new Person( '窈窕淑女' , '婀娜多姿' );
alert(gg3.type); //地球人
mm3.dance(); //跳舞

 

2.4、动态特性

或许有人会问,构造函数创建对象的方式不就很像传统的Class吗,有啥区别呢?最大的区别就在于,JavaScript的对象是动态的,不受任何Class的限制,可以随时对它进行修改,对象之间的关系仅来自于原型(prototype)的继承。

1
2
3
4
5
6
//gg3唱歌和别人不同
gg3.sing = function (){alert( '我唱歌很霸气' );}
//mm3跳舞和别人也不同
mm3.dance = function (){alert( '我跳舞很优雅' );}
//gg3除了唱歌跳舞还会写书法
gg3.write = function (){alert( '兰亭临帖 行书如行云流水' );}

每个对象实例都有一个hasOwnProperty的方法,用来判断某一个属性到底是自己的属性,还是继承自prototype的属性:

1
2
gg3.hasOwnProperty( 'sing' ); //true
mm3.hasOwnProperty( 'sing' ); //false

构造函数的prototype属性也是一个对象,我们对它进行的修改将反映到所有的实例中:

1
2
3
4
5
//地球很危险,搬家去火星
Person.prototype.type = '火星人' ;
//GG、MM很伤心的搬去了火星
alert(gg3.type); //火星人
alert(mm3.type); //火星人

 

3、继承

之前提到过,JavaScript的继承是基于原型的继承:

原型继承的含义是指,如果构造函数(Constructor)有个原型对象A,则由该构造函数创建的对象实例(Object Instance)都必然复制于A。

JavaScript中,每个对象都有一个隐性的__proto__原型(不可访问,但可通过调试工具查看),包含从上级继承过来的属性、方法,而__proto__也是一个对象,也会有隐性的原型,因此就形成了一条原型链,链的尽头是原生对象Object,这个老祖宗包含了诸如toString等天生就有的方法。

对象的原型是隐性的,而构造函数的原型是显性的,也就是它的prototype属性。给构造函数的prototype赋值一个对象A后,该构造函数创建的对象实例都将拥有A的所有属性/方法,从而实现属性/方法的共用和继承。我们上面用prototype属性添加公有属性/方法,便是利用了原型继承的特性,公有属性/方法其实是继承自某个原型对象的。

在JavaScript中有两种继承的方式,分别是原型方式(Prototypal)、伪类方式(Pseudoclassical)。

 

3.1、原型方式

原型方式显得比较古朴,但是最能体现JavaScript基于“原型链”的继承原理。

这种方式也就是我们之前给Person添加公有属性/方法的办法,直接对prototype进行赋值:

1
2
3
4
5
6
7
8
9
function Chinese(){
     this .country = '中国' ;
};
//此处创建一个Person对象作为Chinese的原型
Chinese.prototype = new Person( '黄皮肤黑眼睛' , '勤劳善良' );
Chinese.prototype.say = function (){alert( '你好, 世界' );}
var me = new Chinese();
alert(me.type); //地球人
me.say(); //你好, 世界

对于一些没有构造函数的对象(例如直接通过对象字面量创建的对象),我们可以通过一个空函数来实现它的继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.prototype.create = function (parentObj){
     var F = function (){};
     F.prototype = parentObj;
     return new F();
}
var robot = {
     type: '机器人' ,
     sing: function (){alert( '不会' );},
     dance: function (){alert( '不会' );}
}
var transformor = Object.create(robot);
transformor.type = '变形金刚' ;
transformor.transform = function (){alert( '我会变身' )};

 

3.2、伪类方式

这种继承的方式看起来更像传统Class的继承,或许有点掩盖了JavaScript基于原型继承的特性,但对于习惯Java、C++的开发者来说这种方式比较好理解。这也可以理解为用JavaScript去模拟Java、C++的继承方式/语法。

1
2
3
4
5
6
7
8
//GG和MM应该是有点不同的吧,通过继承来给他们区别一下
function Male(ap, ch){
     //调用父类的构造函数(不需要也可去掉)
     Person.apply( this , [ap,ch]);
     this .sex = '男人' ; //性别不同
}
//继承父类的公有属性/方法(此处是有问题的)
Male.prototype = Person.prototype;

但是上面的代码是有问题的,直接将父类的prototype赋值给了子类,则子类和父类保持同一个prototype的引用,子类对prototype的修改也会反应到父类中,这不是我们希望见到的。因此,我们可以用一个空函数作为中介来解决这个问题:

1
2
3
4
5
6
7
8
//用空函数作为中介, 对prototype的修改
//将反应到空函数的实例中, 不会影响父类
var F = function (){};
F.prototype = Person.prototype;
Male.prototype = new F();
 
//Male的constructor变成了F, 需要修正它
Male.prototype.constructor = Male;

对这种思路进行进一步的封装,作为一个公用的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function inherits(Child,Parent){
     var Inherit = function (){},
         proto = Child.prototype,
         newProto, key;
 
     Inherit.prototype = Parent.prototype;
     newProto = new Inherit();
     for (key in proto){
         newProto[key] = proto[key];
     }
     newProto.parent = Parent;
     newProto.constructor = Child;
     Child.prototype = newProto;
}

使用的时候,方法如下(逻辑上是不是有点像传统OOP的继承了):

1
2
3
4
5
6
7
8
9
10
11
12
13
//注意,需要用匿名函数赋值方法创建函数
var Female = function (ap, ch){
     //调用父类构造函数
     Person.apply( this , [ap,ch]);
     //定义子类属性
     this .sex = '女人' ;
}
//定义子类方法
Female.prototype = {
     makeup: function (){alert( '我会化妆' )}
}
//实现继承
inherits(Female,Person);

 

3.3、拷贝继承

除了上面两种继承方法,还有一种称之为“拷贝继承”,即将原型对象的所有属性都拷贝到新的对象当中,个人认为这种方式实际上破坏了JavaScript原有的继承机制,只能称为是一种“克隆”,而不是继承了,这里就不详细介绍了(具体可参考jQuery的extend方法)。

 

4、一些个人看法

一个面向对象的语言必须具备三个特性:封装、继承、多态。封装和继承上面都说到了,而JavaScript作为一种弱类型语言,多态特性更是不在话下,可见JavaScript是一个非常纯粹的面向对象语言。

如果真要分出“基于原型”和“基于类型”的优劣,我会更喜欢JavaScript的方式。

JavaScript更能体现出对象之间的差异,对象可以不断拓展、递增;不像“基于类型”方式那样,从模子里出来就不变了,为了对象的多样化需要创造很多的类。JavaScript的方式更加的灵活,也更加贴近真实世界的行为方式,原型继承模型通过提供一个有代表性的对象为基础来产生各种新的对象,并由此继续产生更符合实际应用的对象,一切只与对象有关,不受Class模板的限制。


本文转自艾伦 Aaron博客园博客,原文链接:http://www.cnblogs.com/aaronjs/archive/2012/08/29/2661583.html,如需转载请自行联系原作者

相关文章
|
23天前
|
JavaScript 前端开发 容器
盘点JavaScript中所有声明变量的方式及特性
本文详细介绍了JavaScript中变量定义的多种方式,包括传统的`var`、`let`和`const`,以及通过`this`、`window`、`top`等对象定义变量的方法。每种方式都有其独特的语法和特性,并附有代码示例说明。推荐使用`let`和`const`以避免作用域和提升问题,谨慎使用`window`和`top`定义全局变量,不建议使用隐式全局变量。掌握这些定义方式有助于编写更健壮的JS代码。
36 11
|
3月前
|
JavaScript 前端开发 安全
JavaScript与TypeScript的对比,分析了两者的特性及在实际项目中的应用选择
本文深入探讨了JavaScript与TypeScript的对比,分析了两者的特性及在实际项目中的应用选择。JavaScript以其灵活性和广泛的生态支持著称,而TypeScript通过引入静态类型系统,提高了代码的可靠性和可维护性,特别适合大型项目。文章还讨论了结合使用两种语言的优势,以及如何根据项目需求和技术背景做出最佳选择。
100 4
|
3月前
|
JavaScript 前端开发 安全
ECMAScript 6(以下简称 ES6)的出现为 JavaScript 带来了许多新的特性和改进,其中 let 和 const 是两个非常重要的关键字。
ES6 引入了 `let` 和 `const` 关键字,为 JavaScript 的变量管理带来了革新。`let` 提供了块级作用域和暂存死区特性,避免变量污染,增强代码可读性和安全性;`const` 用于声明不可重新赋值的常量,但允许对象和数组的内部修改。两者在循环、函数内部及复杂项目中广泛应用,有助于实现不可变数据结构,提升代码质量。
47 5
|
3月前
|
自然语言处理 JavaScript 前端开发
ECMAScript 6 的出现为 JavaScript 带来了许多新的特性和改进
这些只是ES6的一些主要特性,它们极大地增强了JavaScript的功能和表现力,使得JavaScript在大型应用开发、前端框架等领域能够更加高效地编写复杂的应用程序。
|
4月前
|
JavaScript 前端开发 索引
JavaScript ES6及后续版本:新增的常用特性与亮点解析
JavaScript ES6及后续版本:新增的常用特性与亮点解析
140 4
|
4月前
|
JavaScript 前端开发 编译器
掌握现代化JavaScript:ECMAScript提案与特性
【10月更文挑战第13天】本文介绍了ECMAScript(ES)的最新提案与特性,包括可选链、空值合并运算符、类字段和顶层Await等。通过跟踪TC39提案、使用Babel或TypeScript、测试兼容性以及逐步迁移,开发者可以高效地采用这些新特性,简化代码、提高开发效率并增强应用功能。文章还提供了实战技巧,帮助开发者在现代Web开发中充分利用这些现代化的特性。
|
3月前
|
前端开发 JavaScript
JavaScript新纪元:ES6+特性深度解析与实战应用
【10月更文挑战第29天】本文深入解析ES6+的核心特性,包括箭头函数、模板字符串、解构赋值、Promise、模块化和类等,结合实战应用,展示如何利用这些新特性编写更加高效和优雅的代码。
84 0
|
5月前
|
JavaScript 前端开发 Oracle
软件工程师,学习下JavaScript ES6新特性吧
软件工程师,学习下JavaScript ES6新特性吧
58 9
|
6月前
|
Rust JavaScript 前端开发
Rust! 无VDom! 尤雨溪解析 Vue.js 2024 新特性
Rust! 无VDom! 尤雨溪解析 Vue.js 2024 新特性
|
6月前
|
Web App开发 前端开发 JavaScript
[译] JavaScript ES2021 中激动人心的特性
[译] JavaScript ES2021 中激动人心的特性

热门文章

最新文章

  • 1
    当面试官再问我JS闭包时,我能答出来的都在这里了。
    40
  • 2
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    27
  • 3
    Node.js 中实现多任务下载的并发控制策略
    32
  • 4
    【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
    26
  • 5
    【JavaScript】深入理解 let、var 和 const
    49
  • 6
    【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
    47
  • 7
    【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
    54
  • 8
    【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
    57
  • 9
    如何通过pm2以cluster模式多进程部署next.js(包括docker下的部署)
    72
  • 10
    【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
    55