JavaScript面向对象(1)——谈谈对象

来源:互联网 发布:我的淘宝没有我要开店 编辑:程序博客网 时间:2024/05/29 17:24

       很多同学甚至在相当长的时间里,都忽略了JavaScript也可以进行面向对象编程这个事实。一方面是因为,在入门阶段我们所实现的各种页面交互功能,都非常顺理成章地使用过程式程序设计解决了,我们只需要写一些方法,然后将事件绑定在页面中的DOM节点上便可以完成。尤其像我这类一开始C++这类语言没好好学,第一门主力语言就是JavaScript的同学来说,过程化程序设计的思维似乎更加根深蒂固。另一方面,就算是对于Java、C++等语言的程序员来说,JavaScript的面向对象也是一个异类:JavaScript中没有class的概念(在ES5及之前版本中没有,ES6会单独介绍),其基于prototype的继承模式也与传统面向对象语言不同,而JavaScript的弱类型特性更会令这里面的很多人抓狂。当然,在熟悉了之后,这种灵活性也会带来很多好处。总之,封装、继承、多态、聚合这些面向对象的基本特性JavaScript都有其自己的实现方式,这些知识的学习是从入门级JS程序员进阶的必经之路。

JavaScript面向对象(1)——谈谈对象

JavaScript面向对象(2)——谈谈函数(函数、对象、闭包)

JavaScript面向对象(3)——原型与基于构造函数的继承模式(原型链)

JavaScript面向对象(4)——最佳继承模式(深拷贝、多重继承、构造器借用、组合寄生式继承)



       很久没有更新博客了,这段时间里方向发生了很大的变化,现在在准备出国读研的事情,技术学习少了很多很多。这段时间一直想把方向的转变过程中所做的思考、面对的问题发出来~  工作室送老刚过去不久,每一级同学都有着各自面临着的问题,或许我的思考过程可以带来启示? 当然,假如日后证明我选错了,这些思虑就可以当作经验教训来复盘了哈哈哈哈。 

       毫无疑问的,JavaScript是一种面向对象的编程语言,它的面向对象的实现机制又是那么的特殊。最近想把这一整块的知识整理一下,先从JavaScript的一些语言特性说起,谈谈变量,谈谈对象,谈谈函数,谈谈JavaScript面向对象的实现。


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


       要来谈谈JavaScript中的对象,我们要从JavaScript的数据类型来说起。在JavaScript中,除了字符串、数字、true、false、null、undefined之外,所有的值都是对象,对象在JavaScript中可是一等公民。对象自身,则是一系列属性的无序集合,也就是键值对的集合,这样的数据结构在其他地方被叫做“散列”、“字典”、“关联数组”等~ 其中键是字符串,而值则可以是JS中的任意数据类型。

       JS对象是引用类型的,是可变的,也是动态的。我们将对象赋值给别的变量,并不会创建一个新的对象副本,只是赋了一个对象的引用;而对象可变,在语句修改了对象的内容之后,所有引用了这个对象的变量都会变化。而JS对象的动态性可以让我们对属性进行操作,新增、删除等等都可以随时进行。这个特性在之后的面向对象实现中很重要,在传统面向对象语言中我们必须预先设计好类,将方法和行为提前设计好;在JS中,我们可以在使用对象的时候直接对它进行扩充,就是利用了它的动态性。在从几个方面讨论对象之前,关于对象与属性,还有一些需要知道的概念:

               对象特性:除了属性之外,对象拥有的一些特性,其中包括:

                                 对象的原型:指向另一个对象,相当于父对象。

                                 对象的类:一个标识对象类型的字符串。

                                 对象的扩展标记:指明了是否可以向该对象添加新属性。

               对象分类:按照对象的来源,共有三类JavaScript对象:

                                内置对象:由ECMAScript规范所定义的对象和类,包括数组、函数、日期、正则表达式、错误等,输入JavaScript语言核心的范畴。

                                宿主对象:由JavaScript解释器所嵌入的宿主环境所定义的。比如文档对象就是由浏览器所定义的。(还记得刚接触NodeJs第一次使用repl的时候,直接敲了个alert偷笑~ alert是浏览器所定义的Window对象提供的,在NodeJs环境中并不存在这个对象。)

                                自定义对象:由运行中的JavaScript语句所创建的对象

               属性特性:除了名字和值之外,每个属性还有几个与之相关的特性值:可写、可枚举、可配置。它们决定了该属性是否可写、是否可以通过for/in循环返回、是否何以删除或修改。

               属性分类:自有属性和继承属性。 自由属性是直接在对象中定义的属性,而继承属性是在对象的原型对象中定义的属性。


一、创建对象的三种方法

               1、通过对象直接量创建:大括号内是一个对象,键-值用冒号分隔,键-值间用逗号分隔。嗯,没错,就是JavaScript对象表示法(JSON)了。

    

var object = {"name" : "zhuwq","age" : 21,"callHim" : function(){//call me},"girlFriend" : {"name" : undefined,"age" : undefined}}

               2、通过new关键字创建对象:与其他面向对象语言一样,可以使用new关键字调用构造函数初始化一个对象。JavaScript内置对象都内置构造函数:

var object = new Object(),array = new Array(),date = new Date(),regExp = new RegExp();

                     使用自定义构造函数来初始化新对象更加重要,会在之后详述。     

               3、使用Object.create()方法:对于这种对象创建方法,原型的概念非常重要:

                     Object.create()方法是ES5开始才提供的,它可以通过将原型对象(原型会在之后单独讨论)作为参数传入的方式创建一个新对象:

var obj = { "name" : "obj"};var o1 = Object.create(obj);obj.a;         //  "obj"
var o2 = Object.create(null);             // 创建一个没有原型的新对象var o3 = Object.create(Object.prorotype); // 创建一个普通的空对象 与var o3 = {}; 以及var o3 = new Object();相同

二、属性的操作

               1、属性的访问和修改:可以通过点(.)和方括号([])运算符来访问对象的属性:
var obj = { "name" : "obj"};obj.name;   //"obj"obj["name"]; //"obj"
                                             对于点来说,右边必须是一个以属性命名的简单标识符;而对方括号来说,方括号中必须是一个返回字符串或者可以转换为字符串的表达式。这就使得在某些情况下,通过方括号这种类似于关联数组的访问方式可以完成更加灵活的工作:
var addr = {};for(var i = 0;i < 5;i++){addr["name"+i] = i;}addr; //{ name0: 0, name1: 1, name2: 2, name3: 3, name4: 4 }
                                            在很多无法预知属性名的场景下,只有使用数组写法才能完成程序。
                                            还可以使用for/in循环遍历对象属性:
var object = {"name" : "zhuwq","age" : 21,"girlFriend" : {"name" : undefined,"age" : undefined}}var message = '';for(pro in object){message += pro + ":" + object[pro] + ",";}message; //name:zhuwq,age:21,girlFriend:[object Object],
               2、属性的添加:在访问属性的时候,直接向一个不存在的属性赋值就会直接添加该属性:
var obj ;= {};obj.a = "a";obj["b"] = "b";obj; // { "a": "a", "b": "b" }
               3、属性的删除:直接使用delete运算符即可
var obj = {"a":"a","b":"b"};delete obj.a; //trueobj; // {"b" : "b"}
               4、存取器属性:我们通常所使用的对象属性叫做数据属性,而从ES5开始,存取器属性被添加了进来。它使得对象的属性值可以被一两个方法所替代:当程序设置一个存取器属性的值时调用setter方法,当程序查询一个存取器属性的值时调用getter方法。同样的,可以只添加getter方法或只添加setter方法使这个属性成为一个只读或只写属性。定义存取器方法时,不使用functon关键字而是使用get或set关键字定义与属性名同名的方法,函数体和属性名之间无需冒号分隔,但一个函数体与下一项之间依然需要逗号分隔:
var rectangle = {    width: 1.0,    height: 2.0,    name: undefined,    get area(){        return this.width * this.height;    },    set nameValue(newName){        this.name = newName;    },    get nameValue(){        return "This object's name is " + this.name;    }}rectangle.area;  // 2rectangle.nameValue = "zhuwq";rectangle.nameValue;  // This object's name is zhuwq
                 在使用存取器属性的时候,要注意存取器属性和数据属性不能同名,这样会报RangeError的错误。刚接触存储器属性的时候有可能会因为一些思维惯性将存取器属性作为操作同名数据属性的方法来使用,需要注意。  同样的,存取器属性也可以继承。

三、关于属性的特性

               在文章的最开始已经介绍过,JavaScript对象中的属性除了其名字和值以外,还有三个特性,分别标识了这些属性是否可写、可枚举、可配置。在ES3的时代,这些特性是无法修改的,并且所有通过程序创建的属性都是可写可枚举可配置的。从ES5开始,JavaScript包含了配置属性特性的API。数据属性的特性包括值、可写性、可枚举性、可配置性;而存取器属性的特性包括读取性、写入性、可枚举性、可配置性。
               
               1、检测属性:在某些时候我们需要检测对象中是否存在某个属性,以及属性是自有属性还是继承属性。这里有几种方法:
//构建对象var object1 = {    x : 1}var object2 = Object.create(object1);object2.y = 2;//检测方法1:  使用in运算符 不区分自由属性和继承属性"x" in object2; // true"y" in object2; // true"z" in object2; // false//检测方法2:  使用hasOwnProperty()方法 检测是否为自有属性object2.hasOwnProperty("x"); // false 继承自object1object2.hasOwnProperty("y"); // trueobject2.hasOwnProperty("z"); // false 无该属性//检测方法3:  使用propertyIsEnumerable()方法 检测是否为可枚举自有属性object2.propertyIsEnumerable("x");        //false 继承自object1object2.propertyIsEnumerable("y");        //trueobject2.propertyIsEnumerable("z");        //false 无该属性object2.propertyIsEnumerable("toString"); //false 不可枚举
                 还有一种情况需要注意区分:在对象没有该属性或者该属性值为undefined时,访问该属性都会返回undefined。这时候需要灵活使用in运算符和!==运算符按需区分这两种情况。

               2、属性特性的查询:ES5中定义了一个“属性描述符”对象,通过将对象和属性名作为参数传入Object.getOwnPropertyDescriptor()方法可以获得该对象该属性的属性描述符。需要注意的是,这里只能获得自有属性的属性描述符,要查询继承属性的特性,需要使用Object.getPrototypeOf()等方法获取到原型对象再进行查询。:
var rectangle = {    width: 1.0,    set nameValue(newName){        this.name = newName;    },    get nameValue(){        return "This object's name is " + this.name;    }}Object.getOwnPropertyDescriptor(rectangle,"width"); // { value: 1, writable: true, enumerable: true, configurable: true }Object.getOwnPropertyDescriptor(rectangle,"nameValue");// { get: [Function: get nameValue], set: [Function: set nameValue], enumerable: true, configurable: true }Object.getOwnPropertyDescriptor(rectangle,"aaa"); // undefinedObject.getOwnPropertyDescriptor(rectangle,"toString"); // undefined
               3、属性特性的创建和修改:有Object.defineProperty()和Object.defineProperties()两个方法,分别用来创建或修改单个属性特性和多个属性特性:
//通过将对象、属性、属性描述符对象传入Object.defineProperty()对象来配置单个属性的属性特性var obj = {};Object.defineProperty(obj,"b",{    value: 1,    writable: true,    enumerable: true,    configurable: true});obj.b; //1Object.defineProperty(obj,"b",{    value: 1,    writable: false,    enumerable: true,    configurable: true});obj.b = 2; obj.b; //1   这里仅展示了可写性的配置
//通过将对象、属性及其描述符的集合对象传入Object.defineProperties()对象来配置多个属性的属性特性var obj = {};Object.defineProperties(obj,{    b: {value: 1,writable: false, enumerable: true, configurable: true},    c: {get: function(){ return "get c"}, set: undefined,enumerable: true, configurable: true}});obj.b = 2; obj.b; //1obj.c; // get c
               当然了,这两个操作的成功与否,与对象的可扩展性、该属性的可配置性、可写性都有关联。操作失败是则会抛出类型错误异常。

四、对象的三个特性

               1、原型属性:原型与原型链是JavaScript面向对象中的核心部分,会在之后有单独一篇讲述。对象的原型属性是在对象创建的时候就已经设置好了的,根据三种创建对象的方式,会有三种原型的设置。在ES5中,我们可以通过Object.getPropertyOf()方法来查询一个对象的原型。
               2、类属性: 类属性是一个标识对象类型的字符串,到ES5中也没有提供修改这个属性的方法。我们可以通过调用对象的toString方法来获取对象的class属性。因为很多对象继承的toString方法重写了,这里提供一个《JavaScript权威指南》中提供的方法,各种数据类型都可以直接传入:
function classOf(obj){    if (obj === null) return "Null";    if (obj === undefined) return "undefined";    return Object.prototype.toString.call(obj).slice(8,-1);}
               3、可扩展性:对象的可扩展性决定了是否可以给对象添加新属性。在JavaScript中所有的内置对象和自定义对象都是可扩展的。有一些方法可以将对象转为不可扩展的,需要注意的是,一旦转为不可扩展对象,就无法再转变回来了。进行将对象封闭,可以避免外界的干扰。有三种方法可以从不同程度将对象进行锁定,这里按程度递增介绍:
//通过Object.esExtensible()方法查询对象是否可扩展var obj = {};Object.esExtensible(obj); // true//通过Object.preventExtensions()方法将对象转为不可扩展Object.preventExtensions(obj);Object.esExtensible(obj); // false

var obj = {a:1};//通过Object.seal()方法将对象转为不可扩展并将所有自有属性设为不可配置//可以使用Object.isSealed()方法判断是否已经封闭Object.isSealed(obj); // falseObject.seal(obj);Object.isSealed(obj); // trueObject.esExtensible(obj); //falseObject.defineProperty(obj,"b",{    value: 1,    writable: false,    enumerable: true,    configurable: true}); // TypeError: Cannot define property:b, object is not extensible.

var obj = {a:1};//通过Object.freeze()方法将对象转为不可扩展、将所有自有属性设为不可配置、将所有数据属性设为只读//可以使用Object.isFrozen()方法判断是否已经封闭Object.isFrozen(obj); // falseObject.freeze(obj);Object.isFrozen(obj); // trueObject.esExtensible(obj); //falseObject.defineProperty(obj,"b",{    value: 1,    writable: false,    enumerable: true,    configurable: true});  // TypeError: Cannot define property:b, object is not extensible.b.a = 2;b.a; // 1


五、对象方法

               所有的JavaScript对象都从Object.prototype继承属性,大部分是方法。上文中已经提到过一些:hasOwnProperty(),propertuIsEnumerable(),isPrototypeOf(),Object.create(),Object.getPrototypeOf()等等。和它们相同的从Object.prototype中继承的方法还有toString(),toLocalString,valueOf()等,都非常有用。但很多对象会对这些方法进行重写,例如如果对Array对象调用自身的toString方法,将会得到一个数组元素列表,每个元素都转换成了字符串;若是调用原始的toString方法,则只会得到"[array Array]"字符串。  关于这些方法,这里不做赘述。





原创粉丝点击