本章介绍对象。
在学习Java时,对象理解为公共事物的抽象,实例为具体的个例,对象为抽象的概念,例如人为抽象的概念,具体的个例为张三,李四。
Java对象种类多,包含普通类,JavaBean,注解,枚举,接口,抽象类。每种类的特点也不同。对象之间的关系很多,泛化,实现,依赖,关联,组合,聚合。可以创建很复杂的类结构,例如IO框架,集合框架。每个对象都有强,弱,虚引用的概念,生命周期的概念。还需要考虑多线程下对象属性的安全性,总之很复杂。
而JS的对象,虽然概念相同,但它理解起来非常简单,是属性的容器,包含key-value键值对。它拥有原型对象,可以实现对象的继承,封装。构建复杂的模块(easyUI框架)等。由此可见JS对象也很强大。
本章的内容不再依照书籍原始的结构,而是将其分为两类知识点,对象容器本身和容器的元素属性。
容器:
- 操作:创建,类型判断
- 概念:引用类型,原型链,全局对象
属性:
- 操作:访问,修改,删除,判断,遍历。
- 概念:get & set属性未提及,属性的元属性未提及。
书籍的原始结构为
- 第一小节介绍通过字面量创建对象。
- 第二小节介绍访问对象的属性
- 第三小节介绍修改对象的属性
- 第四小节介绍对象是引用类型。
- 第五小节介绍对象的原型链。
- 第六小节介绍如何判断对象的具体类型
- 第七小节介绍遍历对象中的属性。
- 第八小节介绍删除对象中的属性
- 第九小节介绍如何合理的创建全局变量。
1、对象(容器)
1.1 创建
第一小节介绍对象创建的方式。JS创建对象的方式有两种,构造器和字面量,作者推荐只使用字面量方式,它的格式为{prop:value},
- prop是属性的名称,不包含特殊字符,不是关键字,如果是需要添加引号。
- value是属性值,可以是数据类型的字面量,函数,对象,数组等等。
1.2 引用类型
对象是引用类型,意味着它只是保留着访问内存中数据的链接。类似于超链接,当引用地址改变时,相当于超链接的目标地址改变。当数据改变时,相当于目标的内容发生了改变。
链接 // 当引用地址改变时,相当于href属性改变,但是还是同一个a标签。例如将baidu.com改为sina.com // 当数据发生改变时,相当于百度主页的内容发生了改变。
1.3 类型判断
JS判断具体类型的方法有以下几种
- 根据对象的class属性。自定义的对象class属性的值未必会是对象的名称。
- 根据对象构造器。必须保证在同一个上下文中。
- 根据对象构造器的名称。最优解。
- 使用typeof进行判断时,它无法定位到具体的类型,只能区分基本数据类型和引用类型。
- l 使用instanceof进行判断时,它必须保证运行在同一个上下文中,拥有相同的全局变量。而且它无法区分父类和子类
1.4 原型链
prototype是对象的原型链,是JS实现面向对象的基石。很难想象仅凭prototype属性就可以实现继承,封装,模块化等概念,但是确实是做到了。
每一个对象都有相应的原型链对象,对象可以继承原型链中的属性和方法,这是实现继承的关键。
当访问对象的属性时,会经历以下几个步骤。
- 首先在实例中查找是否存在属性,找到即返回,找不到继续步骤2
- 在实例的原型链中查找属性,找到即返回,找不到继续在步骤3
- 实例原型链也是一个对象,在该对象的原型链中查找,直到该对象为Object.prototype。Object.prototype的原型链为null。
- 如果遍历完整个原型链,还未找到属性,返回undefined。
原型链的结构与链表的结构相似,每一个原型链都有下一个原型链对象的引用。
遍历原型链的过程与遍历迭代器很相似。迭代器的终止条件时next() == null;原型链的终止条件为Object.getPrototypeOf(arg) == null,JS的所有对象都有原型链,满足这一条件的只有Object.prototype。
1.5 全局对象
全局对象可以在任何代码中可见,而且可以随意修改,一方面它很方便,只需要在全局对象上添加属性,属性值为自定义对象,这样就可以在其他地方随意使用自定义对象。另一方面它很糟糕,因为任何其他代码都可以修改全局变量,这可能导致使用全局变量的代码发生错误,或者是被覆盖,直接导致自定义对象不可用。所以在自定义全局对象时,须遵循
- 在命名时尽可能定义唯一的名称,格式为org.app.module.variable。第一级为机构,第二级为应用,第三级为应用模块,第四级为模块内的某个变量。
- 尽可能限制其他人对自定义对象的修改和属性的修改,可以通过Object的方法,属性的元属性来限制。
2、属性(元素)
2.1 访问
访问属性的方式有dot和[]两种方式,它们的区别是
- Dot方式更简洁,当属性名中存在特殊字符,或是特殊关键字时,会出错。
- []方式弥补了dot的缺点,属性名可以是任意的特殊字符,而且可以是表达式,函数,对象等等。列举以下几种情况
- 当为expression时,会计算expression的值,最常见的是++,-- 。[index++]较为常见
- 当为特殊字符时,例如属性名中存在空格,[”hello message”]
- 当为关键字时,例如[“delete”]
- 当为函数时,首先会执行函数,并将返回值作为属性名,当无返回值时,相当于[“undefined”]。这种情况下会存在安全问题,导致脚本注入。
- 当为对象时,例如ES6中的Symbol.iterator,只能通过[]访问。
2.2 修改
使用prop=value的方式修改属性
- 若属性不存在,且对象的extensible属性为true时,添加新属性,当extensible属性为false时,该表达式无任何效果。
- 若属性存在,且属性的元属性writable值为true时,使用新值替换旧值,当为false时,该表达式无任何效果
2.3 删除
使用delete关键字删除属性,只能删除自身的属性,无法删除继承来的属性。
- 当属性不存在时,无任何效果。
- 当属性存在时,且属性的元属性configurable为true时,删除成功,为false时,删除失败,返回false。
delete在删除失败时返回false,其他情况都返回true,意味着即使属性不存在也返回true。
当删除的属性在对象的原型链中存在时,删除对象的属性,导致原型链中的属性可见。
2.4 判断
使用hasOwnProperty方法判断属性是否是对象的自身属性。
根据obj.prop === undefined来判断属性是否存在于对象的原型链中。
2.5 遍历
使用for in语句遍历对象中的属性,这些属性包括继承的属性。
如果只想遍历自身的属性,可以通过hasOwnProperty方法进行过滤。
如果只想要特殊类型的属性,例如只要字符串属性,typeof obj.prop === ”value”进行判断,如果只要非函数属性,使用typeof obj.prop !== ”function”进行过滤。