资讯专栏INFORMATION COLUMN

function作为构造函数和非构造函数调用的区别

didikee / 2704人阅读

摘要:把这个执行上下文压入调用栈的顶部,即设置成运行执行上下文。函数作为构造函数调用没有继承关系有继承关系我们把一个函数被当作构造函数,使用操作符调用时发生的主要步骤新建一个普通对象,把其原型指向构造函数的属性的值。把当前执行上下文弹出调用栈。

var currentTime = Date() 能生成一个当前时间的日期对象,var currentTime = new Date() 也能生成一个同样的对象。如果你看过一些框架,那么你会发现有的框架生成对象写法是 new ClassName(),有的框架是 className()。 那么两种方式有什么区别呢?

普通函数/方法调用

假设我们定义了一个函数:

function normalFunc() {
  console.log( this );
}
// 第一种调法
normalFunc();
// 第二种调法
normalFunc.call( null );
// 第三种调法
var obj = {
  method: normalFunc
}
obj.method();

我们把一个函数被当作一个普通函数或者方法调用归为一类,其被调用时发生的主要步骤:

生成一个新的执行上下文和对应的作用域。(如果对执行上下文是什么不了解的话,可以参考我上一篇《什么是作用域和执行上下文》)

把当前函数和这个新的执行上下文和作用域关联起来。
2.1. 如果当前函数是箭头函数,那么把作用域中的 environment record 对象的内部属性 [[thisBindingStatus]] 设置成 lexical。

把这个执行上下文压入调用栈的顶部,即设置成运行执行上下文(running execution context)。

接下来处理当前函数的属性 this 的取值:
4.1. 如果当前函数是箭头函数,那么这步就不做任何处理(因为已经在步骤2.1中做了标志位)。

4.2. 如果不是箭头函数,那么先查看当前函数是否处在严格模式下。

4.2.1. 严格模式:this 的取值取决于如何调用当前函数,譬如上例代码中第一种调法,取值为 undefined,第二种调法取值为 `normalFunc.call(` 的第一个参数,第三种调法取值为 obj。
4.2.2. 非严格模式:先按4.2.1的分类获得 this 的取值,如果是 null 或者 undefined,用全局对象代替 null 或者 undefined。如果 this 的取值是非空值那么把 this 指向这个非空值(注1)。 

4.3. 把 this 的取值保存在作用域中的 environment record 对象的内部属性 [[thisValue]] 中(步骤4中并非把 this 直接指向这些取值,而是把值保存在作用域特定内部属性中,this 的寻值过程还有额外一步,下面会说明)。

执行函数体。

把当前执行上下文弹出调用栈。

如果步骤5有返回,则返回这个结果。如果步骤5没有返回,则返回 undefined。

注1:这里非空值还要判断是原始类型(primitive value),还是对象类型。如果是原始类型,取值还要再把原始类型包装成对象才能作为 this 的取值。步骤中避免太繁琐,省略了细节顾特地加上注释。

函数中 this 的取值过程(ResolveThisBinding)

结合上面描述的步骤,我们来看看当你在函数中使用 this 时(上面的步骤5中 this 已经可用),程序时如何寻找 this 的:

根据当前执行上下文查找到对应的 enviroment record(execution context -> scope -> environment record)。

判断当前这个 record 是否存储过[[thisValue]],如果没有的就沿着作用域链向上查找,以全局作用域为终点。

如果找到了,则返回。

如上所述,箭头函数本身的作用域并没有存储[[thisValue]],所以其内部使用 this 会去定义箭头函数的地方(函数)去取 this,如果取不到继续向上查找。

函数作为构造函数调用
// 没有继承关系
function normalFuncAsContructor() {
  // return a new object?
  // return {}
  // or not
  // [return]
}
var o = new normalFuncAsContructor();
// 有继承关系
function Parent(){}
function Child(){}
Child.prototype = new Parent();
var c = new Child();

我们把一个函数被当作构造函数,使用 new 操作符调用时发生的主要步骤:

新建一个普通对象,把其原型 [[Prototype]] 指向构造函数的 prototype 属性的值。

如普通函数调用的步骤1一样,生成一个新的执行上下文和对应的作用域,并把当前构造函数和两者关联起来。

把这个执行上下文压入调用栈的顶部。

把第一步生成的对象当作 this 的取值保存到作用域中的 environment record 对象的内部属性 [[thisValue]] 中。

执行函数体。

把当前执行上下文弹出调用栈。

处理函数执行的结果,即 new 了之后返回啥:
7.1 如果步骤5返回一个对象,那么就把这个对象作为此次 new 操作的返回值。

7.2 如果返回的不是对象,而且这个函数不是 generator 函数,那么返回第一步生成的对象(generator 就先不在这里讨论了)。

知道了两者的区别,我们就能在函数体里面搞文章了,你可以通过如下代码检测用户怎么调用你的函数。如果你知道了用户怎么调用,你自然可以根据你想要的结果限制用户的使用方法。

function myFunc() {
  if ( this && myFunc.prototype.isPrototypeOf( this ) ) {
    console.log( "called by new operator" );
  } else {
    console.log( "commonly invoked" );
  }
}
myFunc(); // commonly invoked
new myFunc(); // called by new operator
var obj = {
  method: myFunc
}
obj.method(); // commonly invoked

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83196.html

相关文章

  • JavaScript中this

    摘要:作为构造函数何为构造函数所谓构造函数就是用来对象的函数,像等都是全局定义的构造函数。正在跑步正在说话正在跑步正在说话如上,如果函数作为构造函数用,那么其中的就代表它即将出来的对象。 前言 总括:详解JavaScript中的this的一篇总结,不懂this这个难点,很多时候会造成一些困扰,写出一些bug不知如何收场,所以一起来写bug吧,不对,一起来写代码吧。 原文地址:JavaScr...

    SimpleTriangle 评论0 收藏0
  • 箭头函数

    摘要:带有块体的箭头函数不会自动返回值。使用箭头函数创建普通对象时有一点需要注意。箭头函数内部的值始终从封闭范围继承。对于将使用语法调用的方法,请使用非箭头函数。 箭头函数的格式 // ES5 var selected = allJobs.filter(function (job) { return job.isSelected(); }); // ES6 箭头函数 var selec...

    bingchen 评论0 收藏0
  • 你应该要知道JS中this

    摘要:在用创建对象时,指向发生改变是在第二步创建一个对象实例将构造函数中的指向这个对象执行构造函数中的代码返回这个新创建的对象箭头函数中的箭头函数内部是不会绑定的,它会捕获外层作用域中的,作为自己的值。参考你不知道的上卷 前言 this 是 JavaScript 中不可不谈的一个知识点,它非常重要但又不容易理解。因为 JavaScript 中的 this 不同于其他语言。不同场景下的 thi...

    edgardeng 评论0 收藏0
  • js函数this理解?手写apply、call、bind就够了

    摘要:一是什么函数的内部属性,引用的是函数据以执行的环境对象。函数做为节点事件调用时指向节点本身做为构造函数实力化方法时指向实例对象箭头函数里的普通函数,由于闭包函数是执行的,所以指向箭头函数的指向函数创建时的作用域。 一、this是什么? 函数的内部属性,this引用的是函数据以执行的环境对象。也就是说函数的this会指向调用函数的执行环境。 function a(){ retur...

    Cciradih 评论0 收藏0

发表评论

0条评论

didikee

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<