Home  >  Article  >  Web Front-end  >  Detailed explanation of Javascript function declaration and recursive call

Detailed explanation of Javascript function declaration and recursive call

高洛峰
高洛峰Original
2016-12-09 13:21:521359browse

The way Javascript functions are declared and called is already a tired cliché, but some things are like this. You say it once and I will say it again. Every time I see that there are four ways to call a Javascript function written in a book or blog, I will think of Kong Yiji: There are four ways to write the word "fennel". Did you make it up?

Despite its flaws, Javascript is fascinating. At the heart of Javascript's many beautiful features are functions as first-class objects. Functions are created like other normal objects, assigned to variables, passed as arguments, return values, and hold properties and methods. Functions, as top-level objects, give Javascript powerful functional programming capabilities and also bring flexibility that is not easy to control.

1. Function declaration

Variable declaration first creates an anonymous function, and then assigns it to a specified variable:

var f = function () { // function body };

Usually we don’t have to care whether the scope of the expression on the right side of the equal sign is global or certain Within a closure, because it can only be referenced through the variable f on the left side of the equal sign, what should be concerned about is the scope of the variable f. If the reference of f to the function is destroyed (f = null), and the function is not assigned to any other variable or object property, the anonymous function will be destroyed by the garbage collection mechanism because it loses all references.

You can also use function expressions to create functions:

function f() { // function body }

Different from variable expressions, this declaration method assigns a value to a built-in attribute name of the function. At the same time, assign the function to a variable with the same name in the current scope. (The name attribute of the function, configurable, enumerable and writable are all false)

function f() { // function body }
console.log(f.name); // "f"
console.log(f); // f()


Javascript variables have a special feature, that is, the declaration of the variable will be advanced, and the function declaration of the expression will also be The definition of the entire function is prepended, so you can use it before the function is defined:

console.log(f.name); // "f"
console.log(f); // f()
function f() { // function body }

The declaration of the function expression will be hoisted to the top level of the scope, try the following code, they are not the focus of this article:

var a = 0;
console.log(a); // 0 or a()?
function a () {}

Crockford recommends always using the first way to declare functions. He believes that the second way relaxes the requirement that functions must be declared first and then used, which can lead to confusion. (Crockford is a "conscience programmer" similar to the "conscience artist" that Russell used to compare Wittgenstein. This sentence is very difficult to pronounce)

Functional declaration

function f() {}

Look Get up is the abbreviation of

var f = function f(){};

. As for the expression of

var a = function b(){};


, create a function and assign the built-in name attribute to "b", and then assign this function to the variable a. You can use a() to call it externally, but you cannot Using b(), because the function has been assigned to a, a variable b will not be automatically created unless you declare a variable b using var b = a. Of course the name of this function is "b" not "a".

Using the Function constructor can also be used to create functions:

var f = new Function("a,b,c","return a+b+c;");


This method actually generates an anonymous function in the global scope and assigns it to the variable f.

2. Recursive calls

Recursion is used to simplify many problems, which requires calling itself in a function body:

// 一个简单的阶乘函数
var f = function (x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};


The huge flexibility of functions in Javascript leads to the use of recursion Difficulty with function names. For the variable declaration above, f is a variable, so its value can be easily replaced:

var fn = f;
f = function () {};


The function is a value, it is assigned to fn, and we expect to use fn( 5) A value can be calculated, but since the variable f is still referenced inside the function, it cannot work properly.

The functional declaration looks better, but it’s a pity:

function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
}
var fn = f;
f = function () {}; // may been warning by browser
fn(5); // NaN


It seems that once we define a recursive function, we must be careful not to change the name of the variable easily.

What we talked about above are all functional calls. There are other ways to call functions, such as calling them as object methods.

We often declare objects like this:

var obj1 = {
  num : 5,
  fac : function (x) {
    // function body
  }
};


Declare an anonymous function and assign it to the object’s attribute (fac).

If we want to write a recursion here, we have to reference the property itself:

var obj1 = {
  num : 5,
  fac : function (x) {
    if (x === 1) {
      return 1;
    } else {
      return x * obj1.fac(x - 1);
    }
  }
};


Of course, it will also suffer from the same problem as the function call method:

var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // Sadness


method After being assigned to the fac attribute of obj2, obj1.fac still needs to be referenced internally, so... it failed.

Another way will be improved:

var obj1 = {
   num : 5,
   fac : function (x) {
    if (x === 1) {
      return 1;
    } else {
      return x * this.fac(x - 1);
    }
  }
};
var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // ok


Get the attributes in the context when the function is executed through this keyword, so that when obj2.fac is executed, the fac attribute of obj2 will be referenced inside the function .

But the function can also be called by modifying the context arbitrarily, that is, the universal call and apply:

obj3 = {};
obj1.fac.call(obj3, 5); // dead again


So the recursive function cannot work properly again.

We should try to solve this problem. Do you remember the function declaration method mentioned earlier?

var a = function b(){};

   


这种声明方式叫做内联函数(inline function),虽然在函数外没有声明变量b,但是在函数内部,是可以使用b()来调用自己的,于是

var fn = function f(x) {
  // try if you write "var f = 0;" here
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};
var fn2 = fn;
fn = null;
fn2(5); // OK

    

// here show the difference between "var f = function f() {}" and "function f() {}"
var f = function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * f(x - 1);
  }
};
var fn2 = f;
f = null;
fn2(5); // OK

    

var obj1 = {
  num : 5,
  fac : function f(x) {
    if (x === 1) {
      return 1;
    } else {
      return x * f(x - 1);
    }
  }
};
var obj2 = {fac: obj1.fac};
obj1 = {};
obj2.fac(5); // ok
  
var obj3 = {};
obj1.fac.call(obj3, 5); // ok

   


就这样,我们有了一个可以在内部使用的名字,而不用担心递归函数被赋值给谁以及以何种方式被调用。

Javascript函数内部的arguments对象,有一个callee属性,指向的是函数本身。因此也可以使用arguments.callee在内部调用函数:

function f(x) {
  if (x === 1) {
    return 1;
  } else {
    return x * arguments.callee(x - 1);
  }
}

   


但arguments.callee是一个已经准备被弃用的属性,很可能会在未来的ECMAscript版本中消失,在ECMAscript 5中"use strict"时,不能使用arguments.callee。

最后一个建议是:如果要声明一个递归函数,请慎用new Function这种方式,Function构造函数创建的函数在每次被调用时,都会重新编译出一个函数,递归调用会引发性能问题——你会发现你的内存很快就被耗光了。


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn