资讯专栏INFORMATION COLUMN

Functions

sydMobile / 2634人阅读

摘要:如果我们把这样的定义放在全局代码中,解析器会把函数当作声明,因为它以关键字开头,在第一种情况中,我们会得到,因为我们缺少函数名。

原文

ECMA-262-3 in detail. Chapter 5. Functions.

简介

在这篇文章中,我们将讨论一个ESCMAScript对象,函数。我们将讨论不同类型的函数,每个类型是如何影响环境中的变量对象(variables object)以及内部的作用域的。我们将回答以下经常会出现的问题,“下面的函数有什么不同吗?”[译者注:当进入一个函数时,会创建一个对象,里面保存了函数运行需要的各种变量]

var foo = function () {
  ...
};
function foo() {
  ...
}
(function () {
  ...
})();
函数类型 函数声明

函数声明(Funcation Declaration),缩写FD,是一个函数:

必须拥有一个名字

在源码中的位置,要么在程序级别,要么在其他函数体的体内

在进入上下文时创建

会影响变量对象

用以下方式声明

function exampleFunc() {
  ...
}

这个类型的函数的主要特点是它影响变量对象(它们被存在环境的变量对象中)。这导致了第二个重要的点,在代码执行期间,他们已经被用了,因为当一进入环境后,函数声明就被保存在环境的变量对象中,在代码开始执行之前。例如函数可以被调用在它被声明之前,从源码的角度看过去

foo();
  
function foo() {
  console.log("foo");
}
// function can be declared:
// 1) directly in the global context
function globalFD() {
  // 2) or inside the body
  // of another function
  function innerFD() {}
}

函数声明要么出现在全局环境中,要么出现在其他函数体内。

函数表达式

函数表达式(Function Expression),缩写FE,是一个函数

在源码中可以在表达式位置被定义

可以选择不要名字

对环境的变量对象没有影响

在代码执行的阶段被创建

常见的赋值表达式

var foo = function () {
  ...
};

也可以给函数个名字

var foo = function _foo() {
  ...
};

在这值得注意的是,在函数表达式的外面通过变量foo访问函数,而在函数内部,例如递归调用,用_foo访问。函数表达式总在表达式的位置,例如以下的例子都是函数表达式

// in parentheses (grouping operator) can be only an expression
(function foo() {});
 
// in the array initialiser – also only expressions
[function bar() {}];
 
// comma also operates with expressions 
1, function baz() {}; 

函数表达式在代码执行的阶段才会创建,并不会被存在环境变量对象中

// FE is not available neither before the definition
// (because it is created at code execution phase),
  
console.log(foo); // "foo" is not defined
  
(function foo() {});
  
// nor after, because it is not in the VO
  
console.log(foo);  // "foo" is not defined

为什么需要函数表达式?是为了不污染环境变量对象,同时作为其他函数的参数。

function foo(callback) {
  callback();
}
  
foo(function bar() {
  console.log("foo.bar");
});
  
foo(function baz() {
  console.log("foo.baz");
});

函数表达式被赋值给了个变量,我们可以通过该变量来访问它

var foo = function () {
  console.log("foo");
};
  
foo();

还可以创建封闭的作用域,对外隐藏内部数据。

var foo = {};
  
(function initialize() {
  
  var x = 10;
  
  foo.bar = function () {
    console.log(x);
  };
  
})();
  
foo.bar(); // 10;
  
console.log(x); // "x" is not defined

我们可以看到foo.bar通过[[Scope]]可以访问函数initialize内部变量x,与此同时,x不能被外界直接访问。这个策略经常被用来创建私有变量和隐藏辅助实体。通常initialize函数表达式的名字是被忽略的。

(function () {
  
  // initializing scope
  
})();

这有个函数表达式,根据运行情况来创建,且不污染环境变量对象。

var foo = 10;
  
var bar = (foo % 2 == 0
  ? function () { console.log(0); }
  : function () { console.log(1); }
);
  
bar(); // 0

注意,ES5中有bind函数,锁定this的值。

var boundFn = function () {
  return this.x;
}.bind({x: 10});
 
boundFn(); // 10
boundFn.call({x: 20}); // still 10

这通常在事件监听,或延迟函数(setTimeout)中被使用。

括号问题

根据规范,表达式声明(expression statement),不能以{开始,这会被当作块,也不能以关键字function开始,会被当作函数声明。所以,如果我们想定义一个函数,用下面的方式(以function关键字开头)立马调用。

function () {
  ...
}();
 
// or even with a name
 
function foo() {
  ...
}();

我们处理函数声明,同时会产生解析错误。如果我们把这样的定义放在全局代码中,解析器会把函数当作声明,因为它以function关键字开头,在第一种情况中,我们会得到SyntaxError,因为我们缺少函数名。在第二个情况中,我们确实有了函数名,函数声明应该被正常的创建。但是我们有另一个语法错误,一个组操作符中没有表达式。所以在这个场合下,括号只是函数声明后面的组操作符,而不是函数调用。

// "foo" is a function declaration
// and is created on entering the context
 
console.log(foo); // function
 
function foo(x) {
  console.log(x);
}(1); // and this is just a grouping operator, not a call!
 
foo(10); // and this is already a call, 10
// function declaration
function foo(x) {
  console.log(x);
}
 
// a grouping operator
// with the expression
(1);
 
// another grouping operator with
// another (function) expression
(function () {});
 
// also - the expression inside
("foo");
 
// etc

如果我们在语句中有函数声明,也会报错

if (true) function foo() {console.log(1)}

我们如何创建一个函数立刻调用它?它应该是个函数表达式,创建函数表达式最简单的操作就是组操作符。如此,一个函数会在执行时创建,调用,移除,如果没有引用指向它。

(function foo(x) {
  console.log(x);
})(1); // OK, it"s a call, not a grouping operator, 1

注意在下面的例子中,括号已经不需要了,因为函数已经在表达式的位置了,解析器知道把它当作函数表达式,在执行的时候被创建。

var foo = {
  
  bar: function (x) {
    return x % 2 != 0 ? "yes" : "no";
  }(1)
  
};
  
console.log(foo.bar); // "yes"

正如我们所见,foo.bar是一个字符串而不是函数,函数在初始化属性时就被调用了。括号是需要的,如果我们想立刻调用函数在创建它后,而函数并不在表达式的位置,如果函数已经在表达式的位置,括号就不需要了。除了括号,还有其他转换函数表达式的方法

1, function () {
  console.log("anonymous function is called");
}();
 
// or this one
!function () {
  console.log("ECMAScript");
}();
 
// and any other manual
// transformation
(function () {})();
(function () {}());
实现扩展: Function Statement
if (true) {
  
  function foo() {
    console.log(0);
  }
  
} else {
  
  function foo() {
    console.log(1);
  }
  
}
  
foo(); // 1 or 0 ? test in different implementations

这里要说的是,根据规范,这种语法构造是不正确的。因为函数声明不能出现在代码块中,而这里有if/else的代码块,函数声明只能出现在程序级别(program level)或其他函数体内。然而在规范的错误处理中,允许了这种实现扩展。但是有各自不同的实现方式。if/else是希望我们能根据运行时的情况,来创建函数,这暗示了把它们当作函数表达式,实际上,主要的实现中,在进入上下文时,就会创建函数声明,因为它们同名,所以最后一个会被调用,所以打印1。然后spidermonkey(一种js引擎)以不同的方式实现这个。

有名字的函数表达式的特点 (NFE)

如果函数表达式有名字(named function expression),缩写NFE。由定义可知,函数表达式并不影响环境的变量对象。然后,函数表达式有时候需要在递归中调用自己。

(function foo(bar) {
  
  if (bar) {
    return;
  }
  
  foo(true); // "foo" name is available
  
})();
  
// but from the outside, correctly, is not
  
foo(); // "foo" is not defined

当解释器在代码执行的过程中遇到有名字的函数表达式,在创建函数表达式之前,解释器创建了一个辅助特殊的对象,把它加在当前的作用域链前面。然后它创建函数表达式,函数获得[[Scope]]属性。在那之后,有名字的函数表达式被作为一个属性,添加到了特殊的对象上,对象的值是函数表达式的引用。最后一步是从父作用域链中移除特殊的对象。

specialObject = {};
  
Scope = specialObject + Scope;
  
foo = new FunctionExpression;
foo.[[Scope]] = Scope;
specialObject.foo = foo; // {DontDelete}, {ReadOnly}
  
delete Scope[0]; // remove specialObject from the front of scope chain
NFE and SpiderMonkey

[译者注:讲述SpiderMonkey(Mozilla火狐c/c++)引擎如何处理有名函数表达式,不译了]
[译者注:讲述Rhino(Mozilla java)引擎如何处理有名函数表达式,不译了]

NFE and JScript

[译者注:讲述JScript(微软)引擎如何处理有名函数表达式,不译了]

通过函数构造器创建函数

由函数构造器构造的函数,它的[[Scope]]只包括全局对象。

var x = 10;
  
function foo() {
  
  var x = 20;
  var y = 30;
  
  var bar = new Function("console.log(x); console.log(y);");
  
  bar(); // 10, "y" is not defined
  
}
函数创建算法
F = new NativeObject();
  
// property [[Class]] is "Function"
F.[[Class]] = "Function"
  
// a prototype of a function object
F.[[Prototype]] = Function.prototype
  
// reference to function itself
// [[Call]] is activated by call expression F()
// and creates a new execution context
F.[[Call]] = 
  
// built in general constructor of objects
// [[Construct]] is activated via "new" keyword
// and it is the one who allocates memory for new
// objects; then it calls F.[[Call]]
// to initialize created objects passing as
// "this" value newly created object 
F.[[Construct]] = internalConstructor
  
// scope chain of the current context
// i.e. context which creates function F
F.[[Scope]] = activeContext.Scope
// if this functions is created 
// via new Function(...), then
F.[[Scope]] = globalContext.Scope
  
// number of formal parameters
F.length = countParameters
  
// a prototype of created by F objects
__objectPrototype = new Object();
__objectPrototype.constructor = F // {DontEnum}, is not enumerable in loops
F.prototype = __objectPrototype
  
return F

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

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

相关文章

  • Javascript anonymous functions

    Javascript anonymous functions Anonymous functions are functions that are dynamically declared at runtime. They’re called anonymous functions because they aren’t given a name in the same way as no...

    刘福 评论0 收藏0
  • Study Blazor .NET(四)数据绑定

    摘要:下面是支持的事件参数方法方法这里是组件间双向绑定的另一种方法,我们可以基于任何事件方法的执行手动触发一个,在中,用内置函数手动触发属性,同时回调父组件中相应的方法,在父组件中,通过通知状态已经改变。翻译自:Study Blazor .NET,转载请注明。数据绑定单向绑定在blazor中单向绑定简单而直接,无需UI刷新或渲染。下面示例展示了单向数据绑定://Counter.razor@page...

    incredible 评论0 收藏0
  • Flask注册视图函数

    摘要:键是函数名,值是函数对象,函数名也用于生成。注册一个视图函数,用装饰器。获取储存视图函数字典中的函数对象视图函数类中的字典储存了注册的视图函数名和视图函数对象。输出视图函数视图函数名重复修改解决 那天莫名其妙出了个错。。就顺便看了看Flask路由 在flask存储路由函数是以函数名为键,函数对象为值 class Flask: def __init__(self, *args, ...

    2bdenny 评论0 收藏0
  • 【underscore 源码解读】Array Functions 相关源码拾遗 & 小结

    摘要:最近开始看源码,并将源码解读放在了我的计划中。将转为数组同时去掉第一个元素之后便可以调用方法总结数组的扩展方法就解读到这里了,相关源码可以参考这部分。放个预告,下一篇会暂缓下,讲下相关的东西,敬请期待。 Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中。 阅读一些著名框架类库的源码,就好...

    SimpleTriangle 评论0 收藏0

发表评论

0条评论

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