构造函数模式

JavaScript中可以自定义构造函数,从而自定义对象类型的属性和方法,构造函数本身也是函数,只不 过可以用来创建对象。

1.自定义构造函数

function Person(name, age, gender) {

this.name = name;

this.age = age;

this.gender = gender;

this.sayName = function () {

console.log(this.name);

}

}

var person1 = new Person('zhangsan', 29, 'male');

var person2 = new Person('lisi', 19, 'female');

person1.sayName(); // zhangsan

person2.sayName(); // lisi

在这个案例中,Person()构造函数代替了 createPerson()工厂函数。实际上,Person()内部的代码跟 createPerson()基本是一样的,只是有如下区别。

没有显式地创建对象。

属性和方法直接赋值给了 this。

没有 return。

另外,要注意函数名 Person 的首字母大写了。

按照惯例,构造函数名称的首字母都是要大写的, 非构造函数则以小写字母开 头。这是从面向对象编程语言那里借鉴的,有助于在 ECMAScript 中区分构造函数和普通函数。毕竟 ECMAScript 的构造函数就是 能创建对象的函数。

2.创建Person实例

要创建 Person 的实例,应使用 new 操作符。以这种方式调用构造函数会执行如下操作。

var person1 = new Person('zhangsan', 29, 'male');

var person2 = new Person('lisi', 19, 'female');

(1) 在内存中创建一个新对象。

(2) 这个新对象内部的[[Prototype]] 特性被赋值为构造函数的 prototype 属性。

(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。

(4) 执行构造函数内部的代码(给新对象添加属性)。

(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。 person1 和 person2 分别保存着 Person 的不同实例。所有对象都会从它的原型上继承一个 constructor 属性,这两个对象的constructor 属性指向 Person,如下所示:

console.log(person1.constructor === Person); // true

console.log(person2.constructor === Person); // true

3.instanceof

constructor 本来是用于标识对象类型的。不过,一般认为 instanceof 操作符是确定对象类型更可靠的方式。

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。或 者说判断一个对象是某个对象的实例。

前面案例中的每个对象都是 Object 的实例,同时也是 Person 的实例,如下面调用

//instanceof 操作符的结果所示:

console.log(person1 instanceof Object); // true

console.log(person1 instanceof Person); // true

console.log(person2 instanceof Object); // true

console.log(person2 instanceof Person); // true

定义自定义构造函数可以确保实例被标识为特定类型,相比于工厂模式,这是一个很大的好处。在这个 案例中,person1 和 person2 之所以也被认为是 Object 的实例,是因为所有自定义对象都继承自 Object。

4.使用函数表达式自定义构造函数

构造函数不一定要写成函数声明的形式。赋值给变量的函数表达式也可以表示构造函数:

var Person = function (name, age, gender) {

this.name = name;

this.age = age;

this.gender = gender;

this.sayName = function () {

console.log(this.name);

};

}

var person1 = new Person("zhangsan", 29, "male");

var person2 = new Person("lisi", 27, "female");

person1.sayName(); // zhangsan

person2.sayName(); // lisi

console.log(person1 instanceof Object); // true

console.log(person1 instanceof Person); // true

console.log(person2 instanceof Object); // true

console.log(person2 instanceof Person); // true

5.构造函数的问题

构造函数虽然有用,但也不是没有问题。构造函数的主要问题在于,其定义的方法会在每个实例上都创 建一遍。因此对前面的案例而言,person1 和 person2 都有名为 sayName()的方法,但这两个方法不是同 一个 Function 实例。我们知道,ECMAScript 中的函数是对象,因此每次定义函数时,都会初始化一个对 象。逻辑上讲,这个构造函数实际上是这样的:

function Person(name, age, gender) {

this.name = name;

this.age = age;

this.gender = gender;

this.sayName = new Function("console.log(this.name)"); // 逻辑等价

}

这样理解这个构造函数可以更清楚地知道,每个 Person 实例都会有自己的 Function 实例用于显示 name 属性。当然了,以这种方式创建函数会带来不同的作用域链和标识符解析。但创建新 Function实例的机 制是一样的。因此不同实例上的函数虽然同名却不相等,如下所示: console.log(person1.sayName === person2.sayName); // false

因为都是做一样的事,所以没必要定义两个不同的 Function 实例。况且,this 对象可以把函数与对象的 绑定推迟到运行时。 要解决这个问题,可以把函数定义转移到构造函数外部:

function Person(name, age, gender) {

this.name = name;

this.age = age;

this.gender = gender;

this.sayName = sayName;

}

function sayName() {

console.log(this.name);

}

var person1 = new Person("zhangsan", 29, "male");

var person2 = new Person("lisi", 27, "female");

person1.sayName(); // zhangsan

person2.sayName(); // lisi

在这里,sayName()被定义在了构造函数外部。在构造函数内部,sayName 属性等 于全局 sayName()函数。因为这一次 sayName 属性中包含的只是一个指向外部函 数的指针,所以 person1 和 person2共享了定义在全局作用域上的 sayName()函 数。这样虽然解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱 了,因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法,那么 就要在全局作用域中定义多个函数。这会导致自定义类型引用的代码不能很好地聚集一起。 这个新问题可以通过原型模式来解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值