构造函数具有两个特点,
- 第一首字母大写,
- 第二内部使用this关键字,调用时必须通过new命令调用生成实例。
new的作用
调用构造函数生成一个实例对象,有点类似于工厂模式,每个实例拥有构造函数的方法与属性。
使用new时可以不加"()",
new Foo;
不过阅读方便这里推荐加上,构造函数可以接受参数,与普通函数一样。
function Foo(name) { this.name = name; } var a = new Foo(); var a = new Foo;
new 调用构造函数生成实例步骤分为四步
- 生成一个对象,代表要返回的实例对象;
- 将生成对象的原型指向构建函数的prototype;
- 将内部this指向生成的对象;
- 执行函数内部的代码。
如果忘记加上new命令会怎么样呢?
function Foo() { this.name = 10; } Foo(); //undefined name; //10;
因为Foo()未指定函数返回值,这里默认为返回undefined,
this在全局环境下调用默认为全局对象,这里是浏览器环境this指向window,生成了一个window.name并赋值为10。
如何避免调用忘记不加new方法调用?
- 在函数内部使用严格模式,严格模式下,this的默认指向为undefined,为undefined添加属性会报错。
function Foo() { 'use strict'; this.name = 10; } Foo(); //annot set property 'name' of undefined
- 在函数体内通过判断
function Foo(name) { if (!(this instanceof Foo)) { return new Foo(name); } this.name = name; } var a = Foo(123);
在全局下this指向全局对象,而使用newthis指向实例对象,
通过instanceof来判断构造函数的原型链上是否存在实例对象。
- 通过new.target属性判断
如果是使用new调用构造函数,那么newtarget属性指向构造函数,否则返回undefined。
function Foo(name) { if (!new.target) { return new Foo(name); } this.name = name; } var a = Foo(123);
构造函数也可以自己指定返回值, 但是返回值必须是一个对象,如果不是则忽略返回值,返回this对象,通过指定返回值返回的对象,内部this执行的语句会被忽略。
function Foo(name ,age) { this.name = name; this.age = age; return 123; } var a = new Foo('zhangsan' ,18); //Foo {name: "zhangsan", age: 18} function Foo(name ,age) { this.name = name; this.age = age; return {obj : 123}; } var a = new Foo('zhangsan' ,18); //{obj: 123}
可以看到上面两个例子有一个共同点,总是返回一个对象,
但是如果使用new命令,而函数内部没有this会怎么样呢?
function Foo() { return 123; } var a = new Foo(); console.log(a); //Foo{}
这里返回一个空对象,原型指向构造函数Foo.prototype,之所以这样是因为this指向实例对象a,在Foo内部并没有this,所以就是一个空对象。
构建函数的缺点
比如下面和这个例子,
假设一个构造函数内部age是输出一个特定的值,在多个实例中重复出现,这样的话不仅浪费内存,而且因为实例对象不同,每个age方法的内存地址也不同,修改age的方法不能做到全部实例更新。
function Foo(name , value) { this.name = name; this.value = value; this.age = function() { console.log('重复的一个调用'); } } var a = new Foo('yang' ,20); var b = new Foo('liu' ,11); console.log(a.age === b.age);//false
每个函数都有一个prototype(原型)属性,对于构造函数来说,实例对象的原型就指向他。
我们可以在构造函数的prototype对象上定义一些共有的方法或者属性,来实现实例对象共享属性或者方法。
function Foo(name , value) { this.name = name; this.value = value; }; Foo.prototype.age = function() { console.log('重复的一个调用'); }; var a = new Foo('yang' ,20); var b = new Foo('liu' ,11); console.log(a.age === b.age);//true
因为原型链查找规则,我们也可以在实例自身上定制跟构造函数相同的方法或者属性。
function Foo(name) { this.name = name; } Foo.prototype.color = 'yellow'; var a = new Foo('yang'); var b = new Foo('liu'); console.log(a.color ,b.color);//yellow yellow a.color = 'bolor'; console.log(a.color ,b.color);//bolor yellow Foo.prototype.color = 'a'; console.log(a.color ,b.color);//bolor a
这样就做到了自身和构造函数不同行为。