JavaScript对象的创建初学
javascript最初被称之为一门玩具性语言,主要的功能用于表单提交时的验证。时至今日,仍然有一些人认为javascript非常简单,有过其他语言的人学起来似乎不用几小时看下文档就行。其实不然,javascript虽然是一门脚本语言,但是他有着自己特色,如果不去了解他与众不同的地方,那遇到的坑将数不胜数。而javascript中难点之一就是原型链,他也是javascript中继承实现的重要基础。
函数
在开始讲原型链之前,我们先来聊一聊javascript中的函数。
javascript有5种基本类型以及一种复杂数据类型object,object本质上由一组无序的名值对组成。
object其实是一种引用类型,在javascript中引用类型和其他如c#语言中的类有点像。我们可以把javascript中的引用类型假想成类,引用类型的值就是类的一个实例,即对象。
引用类型:
object array date regexp function
我们可以看到,其实函数也是一种引用类型,即每一个函数实例可以称之为一个对象。
创建对象
前面已经说到,在javascript中,对象本质就是一组名值对。那如何来创建对象呢?
方式一:使用object构造函数创建
var person = new object(); person.name = 'jack'; person.age = 18; person.sayname = function () { console.log(this.name); };
早期的开发人员经常使用这种模式创建对象,不过后来又兴起来一种更为简单直接的方式。
方式二:使用对象字面量的方式创建
这种方式你可能经常在使用,但是不知道这种创建方式的名字——对象字面量。
var person = { name: 'jack', age: 18, sayname: function () { console.log(this.name) } };
当你使用上面两种方式创建单个对象时,无疑是很方便简单的,但是现实中往往不是那么的简单,很多时候,我们都会面临一些复杂的场景。如果我们需要创建多个对象,那么采用上述方法就会产生大量重复代码。
方式三:使用工厂模式创建
工厂模式是软件工程领域中一种广为人知的设计模式,通过这种模式我们可以避免在创建多个对象时产生大量重复代码。
function createperson (name, age) { var obj = new object(); obj.name = name; obj.age = age; obj.sayname = function () { console.log(this.name); }; return obj; } var person1 = createperson('jack', 20); var person2 = createperson('tony', 22);
方式四:使用构造函数模式创建
虽然工厂模式解决了创建多个相似对象的问题,但是这里却存在另一个问题。我们知道在程序中,当我们要对某个参数进行处理时,往往会先判断其类型或检查其值是否符合我们的要求。
通过工厂创建的方式,显然无法对其创建的对象进行识别。随着javascript的发展,另一种方式出现了——构造函数模式。
在本文开始讲函数的时候提到,每个函数都是function类型的一个实例,即函数是对象。函数不仅可以使用函数声明语法和函数表达式去定义,而且还可以使用function构造函数定义。
在javascript中,构造函数可以用来创建特点类型的对象,比如像object、array以及function这样的原生构造函数,当然我们亦可以创建自定义构造函数。
function person (name, age) { this.name = name; this.age = age; this.sayname = function () { console.log(this.name); }; } var person1 = new person('jack', 20); var person2 = new person('tony', 22);
观察上面的代码,我们可以发现这种写法有点像其他oop语言。
class person { private $name; private $age; public function __construct(name, age) { $this->name = name; $this->age = age; } public function sayname() { echo $this->name; } }
在前面的例子当中,person1和person2两个对象都有一个constructor(构造函数)属性,该属性指向person。如此一来,便可以解决对象类型的问题。我们可以使用如下方式进行判断:
console.log(person1.constructor == person); // true
方式五:使用原型模式创建
构造函数虽然好用,但是并非没有缺点。构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。
让我们再看一下前面的例子:
function person (name, age) { this.name= name; this.age = age; this.sayname = new function('console.log(this.name)'); }
前面提供,每个函数其实就是一个对象,上面定义函数方法和之前是等价的。
面对这种情况,我们可以通过原型模式来解决。
首先我们要牢记三个概念:
构造函数(类似于oop中的类) 实例或对象,通过new 构造函数()创建(类似于oop中的对象) 原型对象,可以想象成所有通过new操作符产生的实例的原始对象,只要我们创建一个构造函数,那么就会生成一个原型对象。在构造函数内部也会创建一个prototype属性,该属性指向原型对象。
下面我们看看如何使用原型模式来重写上面的例子:
function person () {} person.prototype.name = 'jack'; person.prototype.age = 18; person.prototype.sayname = function () { console.log(this.name); }; var person1 = new person();
首先我们创建了一个自定义构造函数,里面是空的,什么也没加。然后,我们通过其包含的属性prototype,为其原型对象添加属性和方法。最后我们通过new操作符,创建了一个person实例。这个时候,person1也拥有了name属性、age属性以及sayname方法。
看到这里,如果你是新手,肯定觉得一脸蒙蔽。产生这种现象的原因其实就是javascript的原型链,也就是本文的主题。
当我们访问person1的name属性时,javascript会现在person1实例中查找,如果找不到,就根据person1实例内部的一个指针[[prototype]](该指针指向原型对象),去寻找其原型对象,如果原型对象里面有,那么就会取这个值。
在这我们来回顾一下方式四中说道person1和person2两个对象都有一个constructor,该属性指向person构造函数。其实从这里可以看出,不是这两个实例中有constructor,而是在它们的原型对象中有这个指向构造函数的constructor属性。所以实例和构造函数是没有直接联系的。
方式六:组合使用构造函数模式和原型模式创建
如果仅仅使用原型模式去创建对象,这样又会造成一个问题。我们希望像name、age这样的属性,能够通过参数进行传递,所以大多数情况都是结合两种方式来创建对象的。
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性
function person (name, age) { this.name = name; this.age = age; } person.prototype.sayname = function () { console.log(this.name); }; var person1 = new person('jack', 18);
推荐阅读