原文出处:http://bbs.ibeifeng.com/read.php?tid=65315
前段时间,认识的一学生,已经工作了近三年,Java,.net都有做过开发,去淘宝面试,回来后跟我聊,很受打击,说面试他的人其他的问的不多,就是对他的JavaScript语言感兴趣,但问他的即不是Jquery,也不蔭syUI,ExtJs等富客户端的东西,直接问JS最原生的特性及JS OOP相关的东西,他搞了这么多年开发,平时对jquery用的很熟,对JS基本会用,遇到效果了,网上找找,拿过来修修改改,凑合能用就行,至于JS的OOP,什么封装,闭包,原型链,JS的继承等几乎是一点也不懂,结果面试的结果可想而知了。
其实,听完他讲这些,我一点都不觉得奇怪,现在的软件开发逐渐向B/S端,移动开发方向发展,但不管哪个方向,如果要想做出一些高性能,高质量的程序,几乎对JS都会有着很高的要求,会一点JS对于普通 做网站可能就够用了,但是如果涉及到用户对体验要求很高,浏览器的兼容性要求高及一些特殊的领域如:GIS,自主开发工作流,绘制线路图等方面应用时,对JS的要求肯定会是相当高,而且这类人员的工资待遇也会相当高,做一个Web开发高手,或者是一个Web开发的老鸟的话,也一定会是要求对JS非常精通,而精通的前提就是要求理解并会使用JS OOP进行客户端技术开发。
本人有幸在10年前就从事过纯客户端产品的开发,很早接触JS及JS OOP,对这方面有一些自己的研究和心得,感觉网上这方面的资料虽然很多,但不系统,不完整,不成体系,接下来,我想快速的整理一下网上资料,让很多从来没有接触过JS OOP概念的朋友们,快速了解什么是JS OOP, 它的封装、继承、多态是怎么实现的,做一个扫盲。
一、你必须知道的
1) 字面量
2) 原型
3) 原型链
4) 构造函数
5) 稳妥对象(没有公共属性,而且其方法也不引用this的对象。稳妥对象适合用在安全的环境中和防止数据被其它程序改变的时候)
二、开始创建对象吧
<1>: 首先来看两种最基本的创建对象的方法
1> 使用Object创建对象
复制代码
- var o = new Object();
- o.sname = 'JChen___1';
- o.showName = function(){
- return this.sname;
- }
|
2> 使用对象字面量创建对象
复制代码
- var o = {
- name: 'JChen___2',
- getName: function(){
- return this.name;
- }
- }
|
但是这两个方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。
<2> 接下来看看几种创建对象的模式吧
1>工厂模式
复制代码
- function create(name){
- var o = new Object();
- o.name = name;
- o.sayName = function(){
- return this.name;
- };
- return o;
- }
- var p1 = create('JChen___');
|
工厂模式也有一个缺点:就是没有解决对象识别的问题(即怎样知道一个对象的类型)。
2> 构造函数模式
复制代码
- function create2(name){
- this.name = name;
- this.sayName = function(){
- return this.name;
- };
- //this.sayName = sayName;
- }
- //function sayName(){ return this.name};
- var p1 = new create2('JChen___4');
|
构造函数模式也有一个缺点:就是每个方法都要在每个实例上创建一遍。
当然我们可以用上面的两行注释掉了代码来屏蔽上面那个缺点。
但是……,我们又产生了一个新问题——全局变量。如果有很多方法,我们岂不是要定义很多个全局变量函数。这是个可怕的问题。
3> 原型模式
1) 普通方法
复制代码
- function create3(){}
- create3.prototype.name = 'JChen___5';
- create3.prototype.sayName = function(){
- return this.name;
- };
- var p1 = new create3();
|
2) 原型字面量方法——我姑且这么称吧
复制代码
- function create3(){}
- create3.prototype = {
- constructor: create3, //我们要设置它的constructor,如果它很重要
- name: 'JChen___5',
- sayName: function(){
- return this.name;
- }
- };
- var p1 = new create3();
|
原型的缺点:
1): 不能传参
2): 共享了变量
4> 构造+原型(模式)
复制代码
- function create4(name){
- this.name = name;
- }
- create4.prototype.sayName = function(){
- return this.name;
- }
- var p1 = new create4('JChen___6');
|
这种模式是目前使用最广泛、认同度最高的一种创建自定义类型的方法。
5> 动态原型模式
复制代码
- function create5(name){
- this.name = name;
- if(typeof this.sayName != 'function'){
- create5.prototype.sayName = function(){
- return this.name;
- }
- }
- }
- var p1 = new create5('JChen___7');
|
这种方法确实也是十分完美的一种方法。
6> 寄生构造函数模式
复制代码
- function create6(name){
- var o = new Object();
- o.name = name;
- o.sayName = function(){
- return this.name;
- }
- return o;
- }
- var p1 = new create6('JChen___8');
|
注意那个return o。构造函数在不返回值的情况下,会返回新对象实例。而通过在构造函数的末尾加入return 语句,可以重写调用构造函数时返回的值。
这个种用法可以用在,假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array的构造函数,因此可以使用这个模式。
复制代码
- function specialArray(){
- var values = new Array();
-
- values.push.apply(values, arguments);
-
- values.join2 = function(){
- return this.join('|');
- };
-
- return values;
- }
- var colors = new specialArray('red', 'blue', 'green');
- colors.join2();//returned: red|blue|green
|
7>稳妥构造函数模式
稳妥构造函数遵循与寄生构造函数类似的模式,但是有两点不同:
一是新创建对象的实现方法不引用this
二是不使用new操作符调用构造函数。
复制代码
- functioncreate7(name){
- var o = new Object();
- var age = 12; //私有变量
- o.sayName = function(){ //私有方法
- return name + ' ' + age;
- }
- returno;
- }var p1 = create7('JChen___9');
|
二、JS继承的6种方法
1> 原型链继承
原型链继承是通过创建Super的实例,并将该实例赋值给Sub.prototype来实现的。
实现的本质是:重写子类型的原型对象,代之以超类型的实例。
复制代码
- function Super(){
- this.name = 'JChen___';
- }
- Super.prototype.getSuperName = function(){
- return this.name;
- }
-
- function Sub(){
- this.subname = 'JChen___son';
- }
- Sub.prototype = new Super(); //原型继承体现在这里
- Sub.prototype.getSubName = function(){
- return this.subname;
- }
-
- var instance = new Sub();
|
注意:此时instance.constructor现在指向的是Super的,这是因为Sub.prototype指向了Super.prototype,而Super.prototype.constructor = Super。
原型链的问题:类似于利用原型创建对象,原型共享的特性也是原型链继承的最大问题。
2> 借用构造函数继承
在解决原型中包含引用类型值所带来的问题的过程中,我们开始使用一种叫做借用构造函数的技术。
这种技术的基本思想相当简单:在子类型构造函数的内部调用超类型构造函数。
这样一来,就会在新子类对象上执行超类函数中定义的所有对象初始化代码。结果,每个子类的实力都会有自己的超类中属性的副本了。
复制代码
- function Super2(name){
- this.colors = ['red', 'blue'];
- this.name = name;
- }
-
- function Sub2(){
- Super2.call(this, 'JChen___2'); //借用构造函数技术体现在这里
- this.age = 29;
- }
-
- var instance1 = new Sub2();
- instance1.colors.push('black');
- var instance2 = new Sub2();
- instance2.colors.push('green');
|
借助构造函数继承的问题:
1): 方法都在构造函数中定义,无法复用。
2): 在超类型的原型中的方法对子类是不可见的。
3> 组合继承(原型+借用构造)
组合继承指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
组合继承的思路:使用原型链实现对方法和属性的继承,通过借用构造函数实现对实例属性的继承。
复制代码
- function Super3(name){
- this.name = name;
- this.colors = ['red', 'blue'];
- }
- Super3.prototype.sayName = function(){
- return this.name;
- }
-
- function Sub3(name, age) {
- Super3.call(this, name);
- this.age = age;
- }
- Sub3.prototype = new Super3(); //解决借用构造函数技术的缺点
- Sub3.prototype.constructor = Sub3; //纠正原型继承改变了的构造函数
- Sub3.prototype.sayAge = function(){
- return this.age;
- }
|
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为JavaScript中最常用的继承模式。
组合继承的问题:两次调用超类构造函数。
4> 原型式继承
原型式继承的思路:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
复制代码
- function object(o){ //原型式继承的关键
- function F(){}
- F.prototype = o;
- return newF();
- }
- var person = {
- name: 'JChen___4',
- colors: ['blue']
- }
- var person1 = object(person);
- person1.name = 'JChen___4___2'person1.colors.push('red');
- var person2 = object(person);
- person2.name = 'JChen___4___3';
- person2.colors.push('green');
|
原型式继承的问题:同原型链一样,他也有共享的劣势。
5> 寄生式继承
寄生式继承的思路:创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后再返回该对象
复制代码
- function createAnother(origin){ //寄生式继承的关键
- var clone = object(origin);
- clone.sayHi = function(){
- return 'Hi';
- };
- return clone;
- }
- var person = {
- name: 'JChen___4',
- colors: ['blue']
- }
- var person1 = createAnother(person);
|
寄生式继承的问题:像构造函数一样,由于不能做到函数的复用而降低效率。
6> 寄生组合式继承
寄生组合式继承:通过借用构造函数来借用属性,通过原型链的混成形式来继承方法。
其背后的思想是:不必为了指定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型的一个副本而已。
复制代码
- function object(o){
- function F(){}
- F.prototype = o;
- return new F();
- }
- function inheritProto(subType, superType){ //避免第一调用构造函数的关键
- var proto = object(superType.prototype);
- proto.constructor = subType;
- subType.prototype = proto;
- }
-
- function Super6(name){
- this.name = name;
- this.colors = ['red', 'blue'];
- }
- Super6.prototype.sayName = function(){
- return this.name;
- }
-
- function Sub6(name, age){
- Super6.call(this, name);
- this.age = age;
- }
-
- inheritProto(Sub6, Super6);
-
- Sub6.prototype.sayAge = function(){
- return this.age;
- }
-
- var instance1 = new Sub6('JChen___6', '12');
- instance1.colors.push('black');
|
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
三、总结
这就是JavaScript中的6种继承方式,如果大家能够画出每个继承的原型链关系图,那么继承就是小菜一碟了
了解了这些,你基本上对OOP有了一个入门,补充说明,以上内容均来自互联网,非本人原创。不过可能对于JS OOP重来没有接触过的同学看起来还是会觉得有些吃力,我最近也在整理这方面的资料并录制相关方向的课程,
目录列下:
复制代码
- 1. part1_c05_01_01_前言-为什么你要学习JavaScript2. part1_c05_01_02_JS开发神器-WebStorm高级使用技巧
- 3. part1_c05_01_02_使用方括号([ ])引用对象的属性和方法
- 4. part1_c05_01_03_Web浏览器中JavaScript调试技巧
- 5. part1_c05_02_01_用定义函数的方式定义类
- 6. part1_c05_02_02_使用new操作符获得一个类的实例
- 7. part1_c05_02_03_动态添加、修改、删除JS对象的属性和方法
- 8. part1_c05_02_04_使用大括号({ })语法创建无类型对象
- 9. part1_c05_02_05_prototype原型对象
- 10. part1_c05_03_01_初识javascript函数对象
- 11. part1_c05_03_02_函数对象和其他内部对象的关系
- 12. part1_c05_03_03_将函数作为参数传递
- 13. part1_c05_03_04_传递给函数的隐含参数_arguments
- 14. part1_c05_03_05_函数的apply、call方法的运用
- 15. part1_c05_03_06_深入认识JavaScript中的this指针
- 16. part1_c05_04_01_理解javascript中类的实现机制
- 17. part1_c05_04_02_使用prototype对象定义类成员
- 18. part1_c05_04_03_JavaScript类的设计模式优化
- 19. part1_c05_05_01_JavaScript类的公有成员与私有成员
- 20. part1_c05_05_02_JavaScript类的静态成员
- 21. part1_c05_06_01_在JavaScript中利用for(…in…)语句实现反射
- 22. part1_c05_06_02_JS中利用反射动态设置CSS样式高级技巧
- 23. part1_c05_07_01_利用共享prototype实现继承的用法与缺陷
- 24. part1_c05_07_02_利用反射机制和prototype实现JS继承
- 25. part1_c05_07_03_参考prototype.js框架自实现JS中的类的继承
- 26. part1_c05_07_04_Prototype.js源码剖析与使用示例
- 27. part1_c05_08_01_在JavaScript中实现抽象类与虚方法
- 28. part1_c05_08_02_JavaScript中使用抽象类的示例
- 29. part1_c05_09_01_自定义实现JS中最简单的事件设计模式
- 30. part1_c05_09_02_重构自定义JavaScript事件处理程序解决事件传参问题
- 31. part1_c05_09_03_重构自定义JavaScript事件处理程序多事件绑定机制
- 32. part1_c05_10_01_JavaScript面向对象综合示例
- 33.part1_c05_11_01_JS压缩与混淆工具(JSA、JSCompressor、Google Closure Compiler )
- 34. part1_c05_11_02_JS高级调式工具(FireBugLite)
|
学完这套课程你能得到东西:
想学习JS的调试,性能优化,及一些优秀前端工具的使用
对JS的应用有更深层次了解
对JS OOP思想有一定程度的了解
自己能动手写一个基于JS OOP的简单框架
对于后继jquery,easyui,extjs等富客户端的学习打好基础
能用于Java,PHP,.NET开发,移动开发工作中
上述课程的录制正在进行中,欢迎大家多对课程提宝贵意见,以改进内容,早日出来,以飨读者。
另附:已经讲完的部分内容,大家可以下载下来看下,以加深对JS OOP的理解
附下载:
5. part1_c05_02_01_用定义函数的方式定义类
6. part1_c05_02_02_使用new操作符获得一个类的实例
7. part1_c05_02_03_动态添加、修改、删除JS对象的属性和方法
8. part1_c05_02_04_使用大括号({ })语法创建无类型对象
9. part1_c05_02_05_prototype原型对象
10. part1_c05_03_01_初识javascript函数对象
11. part1_c05_03_02_函数对象和其他内部对象的关系
12. part1_c05_03_03_将函数作为参数传递
13. part1_c05_03_04_传递给函数的隐含参数_arguments
14. part1_c05_03_05_函数的apply、call方法的运用
15. part1_c05_03_06_深入认识JavaScript中的this指针
链接:
http://pan.baidu.com/s/1pJv09ib
密码:
lo92