javascript 理解函数的调用——构造函数

60 篇文章 0 订阅
55 篇文章 1 订阅

函数作为构造函数调用并没有什么特别之处。构造函数的声明和其他函数类似,通过使用函数声明和函数表达式很容易得构造新的对象。唯一的例外是箭头函数。

若通过构造函数方式调用,需要在函数调用之前使用关键字new。例如,通过构造函数的方式重新调用上一节中提到的函数whatsMyContext:

function whatsMyContext() {

return this;

}

如果我们想要通过构造函数的方式调用whatsMyContext,代码需要修改为:

new whatsMyContext();

但即便可以把whatsMyContext作为构造函数调用,它也不是一个特别有用的构造函数。接下来讨论下构造函数的不同之处,之后你会明白其中的原因。

注意:

根据定义函数的一些方法,包括函数声明、函数表达式、箭头函数和生成器函数,同时也提到了函数的构造器,它可以通过字符串来构造一个新的函数。

例如:new Function('a', 'b', 'return a+b')将创建一个函数,它包含两个形参a和b,函数的返回结果是两者的和。注意不要把这些函数的构造器和构造函数混为一谈!虽然差别很小,但却至关重要。通过函数的构造器我们可以将动态创建的字符串创建为函数。而现在的主题是构造函数,是我们用来创建和初始化对象实例的函数。

构造函数的强大功能

将函数作为构造函数调用时JavaScript中一个强大特性。
console.log('--------使用构造函数来实现通用对象--------');
//构造函数创建一个对象,并在该对象也就是函数上下文上添加一个熟悉skulk。这个skulk方法再次返回函数上下文,从而能让我们在函数外部检测函数上下文。
function NinjaGeneratorTest() {
  this.skulk = function () {
    return this;
  };
}

//通过new关键字new调用构造函数从而创建两个新对象。变量ninja1和ninja2分别引用了这两个新对象。
var ninja1 = new NinjaGeneratorTest();
var ninja2 = new NinjaGeneratorTest();

//检测已创建对象中的skulk方法。每个方法都应该返回自身已创建的对象。
if(ninja1.skulk() === ninja1) {
  console.log('The 1st ninja is skulking');
}

if (ninja2.skulk() === ninja2) {
  console.log('The 2nd ninja is skulking');
}

 

 

在上述代码中我们创建了一个名为NinjaGeneratorTest的函数作为构造函数。当通过new关键字调用时会创建一个空的对象实例,并将其作为函数上下文(this参数)传递给函数。构造函数中在该对象上创建一个名为skulk的属性并赋值为一个函数,使得该函数称为新创建对象的一个方法。

一般来讲,当调用构造函数时会发生一系列特殊的操作。使用关键字new调用函数会触发以下几个动作:

1.创建一个新的空对象。

2.该对象作为this参数传递给构造函数,从而称为构造函数的函数上下文。

3.新构造的对象作为new运算符的返回值。

 

 

上图展示了使用关键字new调用函数时,会创建一个空对象实例并将其设置为构造函数上下文(this参数)。

最后两点解释了为什么new whatsMyContext()中的whatsMyContext不适合作为构造函数。构造函数的目的是创建一个新对象,并进行初始化设置,然后将其作为构造函数的返回值。任何有悖于这两点的情况都不适合作为构造函数。

对于使用构造函数的示例:NinjaGeneratorTest,该构造函数初始化skulk方法,如:

function NinjaGeneratorTest() {

this.skulk = function () {

return this;

};

}

skulk执行了返回函数上下文。

通过两次调用定义的构造函数,我们创建了两个新的NinjaGeneratorTest对象。值得注意的是,调用的返回结果存储在变量中,后续通过这些变量引用新创建的NinjaGeneratorTest对象。

var ninja1 = new NinjaGeneratorTest();

var ninja2 = new NinjaGeneratorTest();

确保每次调用该方法都对预期的对象进行操作:

if(ninja1.skulk() === ninja1) {

console.log('The 1st ninja is skulking');

}

if (ninja2.skulk() === ninja2) {

console.log('The 2nd ninja is skulking');

}

通过关键字new调用构造函数将返回新创建的对象。

构造函数返回值

构造函数的目的是初始化新创建的对象,并且新构建的对象会作为构造函数的调用结果(通过new运算符)返回。但当构造函数自身有返回值时会是什么结果?

console.log('-------------------------------------返回原始值的构造函数--------------------------------');
//定义一个叫做NinjaGeneratorReuturnTest的构造函数
function NinjaGeneratorReuturnTest() {
  this.skulk = function () {
    return true;
  };

  //构造函数返回一个确定的原始类型值,即数字1
  return 1;
}

//该函数以函数的形式被调用,正如预期,其返回值为数字
if (NinjaGeneratorReuturnTest() === 1) {
  console.log('Return value honored when not called as a constructor.');
}

//该函数通过New关键字以构造函数的形式被调用
var ninjaReturn = new NinjaGeneratorReuturnTest();

//测试表明,返回值1被忽略了,一个新的被初始化的对象被通过关键字new所返回。
if (typeof ninjaReturn === 'object') {
  console.log('Object returned when called as a constructor.');
}

if(typeof ninjaReturn.skulk === 'function') {
  console.log('ninjaReturn object has a skulk method.');
}

 

如果执行这段代码,会发现一切正常。事实上,这个NinjaGeneratorReuturnTest函数虽然返回简单的数字1,但对代码的行为没有显著影响。如果将NinjaGeneratorReuturnTest作为一个函数调用,的确返回1,但如果通过new关键字将其作为构造函数调用,会构造并返回一个新的ninja对象。

但是如果做一些改变,一个构造函数返回另一个对象。

console.log('---------------------------显式返回对象值的构造函数------------------------------------');
//创建一个全局对象,该对象的rules属性设置为false。
var puppet = {
  rules: false
};

//尽管初始化了传入的this对象,返回该全局对象
function Emperor() {
  this.rules = true;
  return puppet;
}

//作为构造函数调用该函数
var emperor = new Emperor();
//测试表明,变量emperor的值为由构造函数返回的对象,而不是new表达式所返回的对象。
if (emperor === puppet) {
  console.log('The emperor is merely a puppet!');
}
if (emperor.rules === false) {
  console.log('The puppet does not know hhow to rule!');
}

 

 

这个示例中采用的方式略有不同。首先创建了一个全局对象,通过puppet引用它,并将其包含的rules属性设置为false:

var puppet = {

rules: false

};

然后定义了一个Emperor函数,它会为新构造的对象添加一个rules属性并设置为true。此外,Emperor函数还有一个特殊点,它返回了全局的puppet对象:

function Emperor() {

this.rules = true;

return puppet;

}

之后通过关键字new 将Emperor作为构造函数调用:

var emperor = new Emperor();

这里设置了一种模棱两可的情况:新生成的对象会传递给构造函数作为函数上下文this,同时被初始化。当我们显示地返回一个完全不同的puppet对象时,哪个对象会最终作为构造函数的返回值?

if (emperor === puppet) {

console.log('The emperor is merely a puppet!');

}

if (emperor.rules === false) {

console.log('The puppet does not know hhow to rule!');

}

测试结果表明:puppet对象最终作为构造函数调用的返回值,而且在构造函数中对上下文的操作都是无效的。最终返回的将是puppet。

总结:

1.如果构造函数返回一个对象,则该对象作为整个表达式的值的返回,而传入构造函数的this将被丢弃。

2.但是,如果构造函数返回的是非对象类型,则忽略返回值,返回新创建的对象。

正因为上述特性,构造函数的写法一般不同于其他函数。

 

编写构造函数的注意事项

构造函数的目的是根据初始条件对函数调用创建的新对象进行初始化。虽然这些函数也可以被“正常”调用,或者被赋值为对象属性从而作为方法调用,但这样并没有太大的意义。

function NinjaGenerator() {

this.skulk = function () {

return this;

};

}

var whatever = NinjaGenerator();

我们可以将NinjaGenerator作为一个简单函数调用,如果在非严格模式下调用的话,skulk属性将创建在window对象上——这并非一个十分有效的操作。严格模式下情况下会更糟,因为在严格模式下this并未定义。因此JavaScript应用将会崩溃。但这是好事情,如果在非严格模式下犯这样的错误,很可能被忽略(除非有很好的测试),但在严格模式下则暴露无遗。这也是推荐使用严格模式的一个很好示例。

因为构造函数通常以不同于普通函数的方式编码和使用,并且只有作为构造函数调用时才有意义,因此出现了命名约定来区分构造函数和普通的函数及方法。

函数和方法的命名通常以描述其行为(skulk、creep、sneak、doSomethingWonderful等)的动词开头,且第一个字母小写。而构造函数则通常以描述所构造对象的名词命名,并以大写字母开头:Ninja、Samurai、Emperor、Ronin等。

很显然,通过构造函数我们可以更优雅地创建多个遵循相同模式的对象,而无需一次次重复相同的代码。通用代码只需要作为构造函数的主体写一次即可。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值