当javaScript从入门到提高前需要注意的细节:对象部分

简介:

最近看了一个帖子,楼主抱怨说javaScript不是标准的面向对象语言,最多勉强算是基于面向对象的语言。这样的说法也是非常有现实的市场的,主要是基于class的编程语言影响力太大了,C++、java、C#哪个不是名门之后,搞得大家现在一说面向对象就是必须有class关键字了。

面向对象的开发编程只是一种编程的思想,和对编程的指导意见(设计模式更是一种经验的归纳和名称,绝对不是圣经来的)。面向对象的思想主要是建议开发人员注意几个事项:尽可能的实现代码重用(过程性的函数也是代码重用来的),尽可能的为特定的个体设计独特的数据类型,在这个独特的类型中包含了这个类型所有自我完成的运算,但是仅仅将需要调用者关心的数据和函数公开,其他都屏蔽掉。

具体一个语言是怎么实现面向对象的,和面向对象的开发准则并没有具体的约定。由于历史原因,我们大部分的面向对象编程选择了C++风格的静态CLASS方式,所以我们很多人就习惯上了基于CLASS的面向对象编程方式而已。而且CLASS从C++开始到java再到C#,编译器厂商为了讨好开发人员再语法糖上面下了极大的功夫,推陈出新了众多用于描述对象封装,继承,多态应用的关键字,让我们开发效率极大的提高,自然我们也就喜欢上了CLASS的编程,也自然的认可了CLASS作为面向对象开发的标准或代言人。

既然我们开始理解了面向对象的编程不仅仅有基于CLASS的方式,还有其他的各种方式,比如VB是基于IMPLEMENTS的实现方式(具体记不清了,太晚了,懒得bing了),而javaScript是基于PROTOTYPE来实现的。

javaScript的特殊在于,其没有类,所有的一切都是实例(这里我特别的用了实例而不是对象,就是担心说都是对象其实是不严谨的),从类型来观察,javaScript提供了6种我们可以访问的基本数据类型:undefined、null、boolean、string、number、object,以下的代码在null的时候有点另人疑惑,其他都蛮正常,原因是在javaScript中null被定义为了Null。

 
  1. alert(typeof undefined);  
  2. alert(typeof null);  
  3. alert(typeof 123);  
  4. alert(typeof 123.45);  
  5. alert(typeof true);  
  6. alert(typeof false);  
  7. alert(typeof "Hello");  
  8. alert(typeof {}); 

说到Null,我们知道在javaScript中还有String、Date、Number、Boolean、Object、Function,在java或C#中,我们可以判断出这些应该是对象,而且是对基本数据类型的隐射,但是在javaScript中,那就完全不同

 
  1. alert(typeof Number);  
  2. alert(typeof String);  
  3. alert(typeof Date);  
  4. alert(typeof Boolean);  
  5. alert(typeof Function);  
  6. alert(typeof Object); 

以上得到的结果全是function。这点就告诉我们两个基本事实:1 javaScript中只有object是对象数据类型(就这么唯一一个);2 对象和函数直接的确有着非常暧昧的关系。

如果对以上的函数采用new运算符,我们得到的结果是

 
  1. alert(typeof new Number());  
  2. alert(typeof new String());  
  3. alert(typeof new Date());  
  4. alert(typeof new Boolean());  
  5. alert(typeof new Function());  
  6. alert(typeof new Object()); 

除了Function之外,其他都返回对象。现在我们开始进入主题。

 

在javaScript中最简单的对象定义就是使用字面量进行定义

 
  1. var obj = {};  
  2.  
  3. var Poker = {  
  4.     Title: "K",  
  5.     Style: "spade",  
  6.     Value: 13  
  7. }; 

上面这段代码,我们可以得到两个对象。要记住在javaScript中,不采用CLASS来抽象对象,所有的一切值都是实例的。我们不需要任何CLASS就可以得到一个对象,使用{}就是声明了一个对象实例。在这样的一个直接对象中所定义的值默认都是公开的,试图采用var在字面量的对象中声明一个私有属性是不可以的。需要说明下,javaScript对象是一个key-value的集合,我们说的属性其实是一个key,正确的将应该是我们在Poker中定义了3个可便利的Key:Title,Style和Value。讲属性那是方便使用C#、Java的程序员了解。 javaScript对面向对象的封装和CLASS的不同,他没有提供public和private等访问修饰符。如果我们使用现代的浏览器,比如IE9,Chrome等支持ECMAScript5 的浏览器,那么我们可以定义不可遍历的key,并且可以为key来定义set和get,并且可以约定一个key只读,好像已经接近CLASS的习惯了,如下是一个demo

 
  1. var obj = {};  
  2.  
  3. var Poker = {  
  4.     Title: "K",  
  5.     Style: "spade",  
  6.     Value: 13,  
  7.     State: 1  
  8. };  
  9.  
  10.  
  11. Object.defineProperties(  
  12. Poker,  
  13. {  
  14.     "backgroundImg": {  
  15.         value: "images\\common\\hide.png",  
  16.         enumerable: false//不可以for 遍历  
  17.         writable: false//只读  
  18.     },  
  19.     "forgroundImg": {  
  20.         value: "images\\spade\\K.png",  
  21.         enumerable: false//不可以for 遍历  
  22.         writable: false//只读  
  23.     },  
  24.     Img: {  
  25.         get: function() {  
  26.             return this.State == 0 ? this.backgroundImg : this.forgroundImg;  
  27.         },  
  28.         enumerable: true 
  29.  
  30.     }  
  31. }  
  32. );  
  33.  
  34. alert(Poker.Img); //images\\spade\\K.png  
  35.  
  36. for (var key in Poker) {  
  37.     alert(key); //backgroundImg 和  forgroundImg无法被遍历到  
  38. }  
  39.  
  40. alert(Poker.backgroundImg); //依然可以访问  
  41. Poker.backgroundImg = "XXX";  
  42. alert(Poker.backgroundImg); //依然是images\\common\\hide.png 

但使用字面量直接定义对象的话,至少还有两个麻烦,1是重用比较麻烦,2是对私有变量的封装还是不给力。虽然说字面量创建的对象也是可以继承的比如

 
  1. var Poker = {  
  2.     State: 1  
  3. };  
  4.  
  5. var PokerA = {  
  6.     Title: "A",  
  7.     Style: "spade",  
  8.     Value: 14,  
  9.     __proto__: Poker  
  10. };  
  11.  
  12. var Poker10 = {  
  13.     Title: "10",  
  14.     Style: "club",  
  15.     Value: 10,  
  16.     __proto__: Poker  
  17. };  
  18.  
  19.  
  20. alert(PokerA.Title); //A  
  21. alert(PokerA.State); //1  
  22. alert(Poker10.Title); //10  
  23. alert(Poker10.State); //1  
  24. //在IE8-访问  
  25. alert(PokerA.__proto__.State);  
  26. alert(Poker10.__proto__.State); 

因为javaScript中字面量的对象都是实例,我们不管这样的继承是不是比较恶心难看,但至少我们可以认识到,Poker对象(实例是一直存在的),每一个继承者都需要一个Poker的实例空间

 
  1. Poker.State = 3;  
  2. PokerA.State = 0;  
  3. alert(PokerA.State); //0  
  4. alert(Poker10.State); //3  
  5. alert(Poker.State); //3 

以下的代码更是明确的告诉我们,Poker的实例中this指向的是子类(当然你承认这个是子类的话)

 
  1. var Poker = {  
  2.     State: 1,  
  3.     toString: function() {  
  4.         alert(this.Style + this.Title + ":" + this.Value);  
  5.     }  
  6. };  
  7.  
  8. var PokerA = {  
  9.     Title: "A",  
  10.     Style: "spade",  
  11.     Value: 14,  
  12.     __proto__: Poker  
  13. };  
  14.  
  15. var Poker10 = {  
  16.     Title: "10",  
  17.     Style: "club",  
  18.     Value: 10,  
  19.     __proto__: Poker  
  20. };  
  21.  
  22.  
  23. PokerA.toString(); // spadeA: 14  
  24. Poker10.toString(); //club10:10 

同时,我们可以了解到一个继承链的事情是:当一个被调用的key在当前对象没有定义的时候,会顺着继承链向上去找,直到找到第一个key存在为止(object自身的toString在这个demo中被无视了),如果都没有就是undefined了。你可以认为是CLASS中的重写来实现多态吧,我想可以这么说。

 

一般来讲,我不建议在字面量(我用这个词非常不舒服,我喜欢叫直接对象)用来继承,直接对象最有价值的用法就是作为参数或什么的用一次,简单高效,看的明白。如果要合理的使用内存,灵活的创建对象,那必须用function才比较爽。

 
  1. var Poker = function(style, title, value, state) {  
  2.     this.Title = title;  
  3.     this.Style = style;  
  4.     this.Value = value;  
  5.     this.State = arguments[3] === undefined ? 0 : state;  
  6.     var backgroundImg = "images\\common\\hide.png";  
  7.     var forgroundImg = "images\\spade\\" + title + ".png";  
  8.     this.Img = (function(x) {  
  9.         return x == 0 ? backgroundImg : forgroundImg;  
  10.     } (this.State));  
  11.     this.toString = function() {  
  12.         return this.Style + this.Title + ":" + this.Value  
  13.     }  
  14. }  
  15.  
  16. var Poker10 = new Poker("spade""10""10");  
  17. var PokerA = new Poker("spade""A""14", 1);  
  18.  
  19. alert(PokerA.toString()); // spadeA: 14  
  20. alert(Poker10.toString()); //club10:10  
  21. alert(PokerA.Title); //A  
  22. alert(PokerA.State); //1  
  23. alert(Poker10.Title); //10  
  24. alert(Poker10.State); //0  
  25. alert(PokerA.Img); //images\spade\A.png  
  26. alert(Poker10.Img); //images\common\hide.png 

上面的代码显然舒适多了,而且容易理解。我不反对吧Poker这个函数叫构造函数,事实上他也的确帮助我们构造了一个Poker对象,不过由于javaScript没有CLASS的概念,所以我更喜欢倾向于说:Poker函数帮助我们构造了一个以Poker为原型的对象。叫起来非常的绕口,那就还是说是构造函数吧。

无论是直接对象还是函数构造得到的对象,对于javaScript来讲都是动态的,这个实例可以动态的添加key。

看看以下代码,在构造完成了Pkoner10和PokerA之后,我们在Poker的原型上创建了一个新的函数createDom,这个函数依然对Pkoner10和PokerA是有效的。

 
  1. var Poker10 = new Poker("spade""10""10");  
  2. var PokerA = new Poker("spade""A""14", 1);  
  3.  
  4. Poker.prototype.createDom = function() {  
  5.     return $("<img id=" + this.Style + this.Title + ">");  
  6. }  
  7.  
  8.  
  9. $(  
  10. function() {  
  11.     PokerA.createDom().appendTo("body");  
  12.     Poker10.createDom().appendTo("body");  
  13. }  
  14. ); 

不过你要记得,function新加的key必须加在prototype上,以下代码就是错误的

 
  1. Poker.insert = function(box, poker) {  
  2.     poker.appentTo(box);  
  3. }  
  4.  
  5. PokerA.insert("body", PokerA.createDom()); //错误了 

原因我估计是这样的,function当函数用的时候就是函数,当用new构造的时候,编译器将this指向的key防止到了function的prototype(原型)上,以下算是一个证明吧

 
  1. alert(Poker.prototype.constructor === Poker); //true  
  2. alert(PokerA.constructor === Poker); //true  
  3. alert(PokerA.createDom === PokerA.__proto__.createDom); //true  
  4. alert(PokerA.__proto__.constructor === Poker.prototype.constructor); //true 

说到key了,正好说下javaScript中的数组,数组一般在静态语言的概念中式连续分配的一段内存,大小是固定的。不过javaScript中的数组我看其实是对象的变形

 
  1. var arr = [];  
  2. for (var i = 0; i < 5; i++) {  
  3.     arr[i] = i;  
  4. }  
  5. arr.push(99);  
  6.  
  7. for (var index in arr) {  
  8.     alert(index); // 0 1 2 3 4 5  

最后的for得到的不是数组的值,而是数组的index,所以我们可以这样求值

 
  1. var arr = [];  
  2. for (var i = 0; i < 5; i++) {  
  3.     arr[i] = i;  
  4. }  
  5. arr.push(99);  
  6.  
  7. for (var index in arr) {  
  8.     alert(arr[index]);  

所以,估计arr[i] = i是动态的添加了一个属性,并赋值而已啦。

还是回到构造函数来说,一般看来,为了符合面向对象的一个编程意思:对象是自己的一组数据的集合,所以我们一般在构造函数中定义对象的属性(也就是数据),数据由构造函数的参数提供,而对象的方法在外部用函数编写,并且指向这个构造的原型。原因是在javaScript中字面量的对象没有property……

这个property就是function构造出的实例,如果把方法直接写在function上,那就是这个function的静态成员了,估计可以这么说,一下是一个demo

 
  1. function Poker(style, title, value) {  
  2.     this.Style = style;  
  3.     this.Title = title;  
  4.     this.Value = value;  
  5. }  
  6.  
  7. Poker.max = function(Poker1, Poker2) {  
  8.     var p;  
  9.     for (var i = 0; i < arguments.length; i++) {  
  10.         if (arguments[i] instanceof Poker) {  
  11.             if (typeof p == "undefined") {  
  12.                 p = arguments[i];  
  13.             }  
  14.             else {  
  15.                 p = p.Value > arguments[i].Value ? p : arguments[i];  
  16.             }  
  17.         }  
  18.     }  
  19.     return p;  
  20. }  
  21.  
  22.  
  23. var p = Poker.max(new Poker("club""K", 13),  
  24.             new Poker("diamond""A", 14),  
  25.             "",  
  26.             new Poker("diamond""10", 10));  
  27.  
  28. alert(p.Style + p.Title + "[" + p.Value + "]"); // diamondA[14] 

那么函数,构造函数,对象之间的原型关系到底怎么样的呢?下面的代码描述了这些关系

 
  1. alert(Poker.constructor); //function Function() { [native code] }  
  2. alert(new Poker().constructor); //function Poker(style, title, value) {  
  3.                                 //            this.Style = style;  
  4.                                 //            this.Title = title;  
  5.                                 //            this.Value = value;  
  6.                                 //        }  
  7. alert(Poker.constructor.prototype); //function Empty() {}  
  8. alert(Poker.prototype == new Poker().constructor.prototype); // true  
  9. alert(Poker.constructor.prototype == new Poker().constructor.prototype); // false  
  10. alert(new Poker().propertye); //undefined 

函数的构造函数是Function对象;

 

函数的对象化(函数创建的对象)的构造函数是function的定义;

函数的构造函数的原型是一个空函数;

函数的原型和函数创建的对象的构造函数的原型相等(所以对象是函数构造出来的);

函数的构造函数的原型和函数的对象化的构造函数的原型不同;

对象没有原型可以直接访问(这个和字面量的对象一样的);

上面的文字看上去和绕口令一样的,但多念念就明白了……

所以证明前面的说法:如果需要函数的成员让实例访问,要么加到函数代码里面,要么在外面加到函数的原型上去


本文转自shyleoking 51CTO博客,原文链接:http://blog.51cto.com/shyleoking/803072


相关文章
|
11月前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
2914 2
|
10月前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
5月前
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
140 23
|
10月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
6月前
|
JSON JavaScript 前端开发
菜鸟之路Day23一一JavaScript 入门
本文介绍了 JavaScript 的基础内容,包括 JS 的引入方式、基础语法、数据类型、运算符、类型转换、函数、对象(如 Array、String、自定义对象、JSON、BOM 和 DOM)、事件监听,以及 Vue 框架的初步使用。重点讲解了内部和外部脚本的引入、变量声明(var、let、const)、常见输出语句、数组与字符串的操作方法、DOM 操作及事件绑定,并通过实例展示了 Vue 的双向数据绑定和常用指令(v-bind、v-model、v-on、v-if、v-for 等)。
158 7
|
9月前
|
JavaScript 前端开发
【JavaScript】——JS基础入门常见操作(大量举例)
JS引入方式,JS基础语法,JS增删查改,JS函数,JS对象
|
10月前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
114 1
JavaScript中对象的数据拷贝
|
10月前
|
监控 前端开发 JavaScript
React 静态网站生成工具 Next.js 入门指南
【10月更文挑战第20天】Next.js 是一个基于 React 的服务器端渲染框架,由 Vercel 开发。本文从基础概念出发,逐步探讨 Next.js 的常见问题、易错点及解决方法,并通过具体代码示例进行说明,帮助开发者快速构建高性能的 Web 应用。
446 10
|
10月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
1329 1
|
10月前
|
JavaScript 前端开发 图形学
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。