JavaScript中创建对象的7种模式和实现继承的6种方式 | GCidea's blog

前言
JavaScript是一种面向对象(OO)的语言,但由于ECMAScript没有类的概念(ES6引入关键字class),所以在js中创建对象的方法也与其他基于类的语言有所不同。本篇总结在JavaScript中创建对象的7种方法;并在此基础上,总结实现继承的6种方式。
JavaScript中对象的基本概念
创建对象的2种基本方法
创建一个Object实例
1 | var person = new Object(); |
创建的对象与上述相同,只不过使用{}的字面量方式创建。
属性与特性
关于属性和特性的细节,可以参考JavaScript中attribute(特性)和property(属性)的对比分析。对象就是由众多的属性(property)构成的,而每个属性(property)又具有多个特性(attribute)。这些特性(attribute)是为了实现JavaScript引擎使用的,在JavaScript语言使用中不能直接访问它们。为了表示是内部值,将各个特性(attribute)放在两对方括号[[]]中表示。
数据属性的特性
1.[[Configurable]]
- 名称:可配置性。
- 默认值:true
- 含义:能否通过delete删除属性并重新定义;能否修改属性的这几个特性;能否把数据属性修改为访问器属性。
2.[[Enumerable]]
- 名称:可枚举性。
- 默认值:true
- 含义:能否通过通过for-in循环返回当前属性。
3.[[Writable]]
- 名称:可写性。
- 默认值:true
- 含义:能否修改当前属性的值。
4.[[Value]]
- 名称:值。
- 默认值:undefined
- 含义:读属性值,从这个位置读;写入属性时,保存在这个位置。
访问器属性的特性
1.[[Configurable]]
- 名称:可配置性。
- 默认值:true
- 含义:能否通过delete删除属性并重新定义;能否修改属性的这几个特性;能否把访问器属性修改为数据属性。
2.[[Enumerable]]
- 名称:可枚举性。
- 默认值:true
- 含义:能否通过通过for-in循环返回当前属性。
3.[[Get]]
- 名称:值。
- 默认值:undefined
- 含义:读属性值时调用该函数。
4.[[Set]]
- 名称:值。
- 默认值:undefined
- 含义:写属性值时调用该函数。
ES5新增的关于Object的几个实用方法
1.Object.defineProperty()
使用示例:
1 | var person = {}; |
2.Object.defineProperties()
使用示例:
1 | var book = {}; |
3.Object.getOwnPropertyDescriptor()
使用示例:
1 | var book = {}; |
4.Object.getOwnPropertyNames()
5.Object.isPrototypeOf()
6.Object.create()
7.Object.keys()
8.XXX.getPrototypeOf()
9.XXX.hasOwnProperty()
创建对象的7种模式
工厂模式
示例:
1 | function createPerson(name, age, job){ |
说明:
1.函数createPerson()就是一个工厂,该工厂内部封装了生成对象的细节(新建对象实例并初始化等)
2.正如名字表示的,通过该函数可以批量创建多个同类型对象,而不必书写大量重复代码
3.工厂模式缺点:未解决对象识别问题—无法知道对象的类型
构造函数模式
示例:
1 | function Person(name, age, job){ |
说明:
1.函数Person()未显式创建对象,且无返回值
2.使用new关键字调用构造函数
3.约定构造函数首字母大写
4.关于对象类型:
1 | alert(person1.constructor == Person); //true |
5.如果构造函数不像
1 | var person1 = new Person("A", 25, "teacher"); |
这样通过new关键字来调用,而是直接
1 | Person("A", 25, "teacher"); |
也是可以使用的,只不过生成的对象会直接添加到宿主对象上,在浏览器中就是window对象上。
6.构造函数模式缺点:
由于将函数绑定到了特定的对象实例上面,每个方法都要在每个实例上重新创建一遍,浪费空间且并没有什么用处。
以下可以证明这一点:
1 | alert(person1.sayName == person2.sayName); //false |
为此,我们可以让构造函数创建的对象中的方法共享函数,也就是在方法属性处不显式声明函数,而只是写明一个函数引用,将这个函数移至构造函数外部。
1 | function Person(name, age, job){ |
可是这种方法又会有新的缺点:
- 身在全局作用于中的函数sayName()却只能用于让构造函数调用,让全局作用域名不副实
- 如果构造函数有很多方法属性,那么就要在全局作用于中写很多函数,代码结构松散,毫无封装性可言
在这种情况下,考虑使用原型模式来解决。
原型模式
1 | function Person(){ |
原型模式的缺点:
1.所有实例在默认情况下都将取得相同的值。
2.由于共享属性,一旦一个对象修改,可能引发其他变量意外地受到影响。
如下所示:
1 | function Person(){ |
组合使用构造函数模式和原型模式
核心:构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。
1 | function Person(name, age, job){ |
以上方式是目前应用最广泛、认可度最高的方式。
动态原型模式
该种方式将所有信息封装在构造函数中,通过在构造函数中“有选择”地初始化原型,来保持构造函数模式+原型模式的有点。所谓的“有选择的”,是指针对工厂模式中多次创建实例上功能相同的函数的问题,在构造函数内部通过判断实例方法的类型(即是否存在)来保证实例方法只创建一次。
1 | function Person(name, age, job){ |
寄生构造函数模式
这是一种和工厂模式非常类似的方法,只不过使用时要加上关键字new,要说明的是,在构造函数内部创建的对象与构造函数的原型没有什么关系,因此不能通过instanceof操作来判断由此创建的对象的类型。
1 | function SpecialArray(){ |
稳妥构造函数模式
与寄生模式相比,这种模式的实例方法不引用this,不用new操作符。在这种模式下,除了调用相应属性的get方法,无法访问内部变量,保证了安全性。
1 |