JavaScript 探索函数范围和不同类型的JavaScript函数的概念
JavaScript基于函数式编程,因此函数是JavaScript的基本构建块。因此,在JavaScript中函数被称为 头等公民 。
语法:
- 定义一个函数
function functionName(parameters) {
// SET OF STATEMENTS
}
- 调用一个函数
functionName(arguments);
当所有语句都执行完毕或者遇到return语句时,函数的执行会停止。 “return语句”停止函数的执行并返回在return关键字后面写的值。
例如: 让我们来了解一下javascript中函数如何工作
Javascript
<script>
// Define
function addAtLeastThree(a, b, ...numbers) {
// Console.log(a+b+c); give error c not defined
// because it is as rest parameter
var sum=a+b;
for(var i=0; i<numbers.length; ++i) {
sum += numbers[i];
}
return sum;
}
// Call
console.log( addAtLeastThree(10, 20, 30, 40, 50) );
</script>
输出:
范围:
代码中变量可以访问的区域,在该区域内可以使用该变量。它取决于变量的定义位置。
基本上有两个范围:
- 全局范围:
变量在整个程序中都可以访问。
- 函数范围:
也称为词法范围。当变量或函数在特定函数内定义时,只能在该函数内部访问。因此,它的范围仅限于定义变量或函数的那个函数。
1. 函数中的变量范围
示例:
Javascript
<script>
var i = 0; // Global scope
// Function define
function gfg() {
var j = 1; // Local scope
console.log(i); // This will print 0
console.log(j); // This will print 1
}
// Function call
gfg()
// This statement will throw an error
// as scope of j is only within
// Function abc. The scope of j is not
// global means it cannot access in
// current scope.
console.log(j);
</script>
输出:
现在让我们来尝试理解不同作用域中具有相同名称的不同变量的范围。
示例:
JavaScript
<script>
var name = "global";
function scope() {
var name = "lexical";
console.log(name);
}
scope();
console.log(name);
</script>
输出:
当解释器遇到函数时,它会查找当前作用域中是否存在var name,如果找不到,则会查找全局作用域,但在这里,name再次在函数内部定义,所以它输出的是词法作用域。外部的print语句根据外部作用域输出结果。
2. 函数内部的函数作用域(嵌套函数): 嵌套函数只能在父函数内部调用。嵌套函数可以访问父函数的变量以及全局变量。但父函数无法访问内部函数的变量。当需要创建一个传递给函数的变量时,这是非常有用的。使用全局变量创建函数是不好的,因为其他函数可能会修改它。因此,对于这里来说,嵌套函数是有用的。它可以防止其他函数使用这个变量。嵌套函数的作用域仅限于词法作用域。
示例: 嵌套函数中变量的作用域。
Javascript
<script>
var i = 0;
function gfg() {
var j = 1;
console.log(i); // This will print 0
console.log(j); // This will print 1
var article = "Not publish";
const result = publish(article);
console.log(result);
}
function publish(article) {
console.log("gfg article");
return "published";
}
gfg();
console.log(j);
</script>
输出:
示例: 在这个示例中,我们将创建一个嵌套的函数调用。
Javascript
<script>
function totalCount() {
var count = 0;
// Nested function definition
function increaseCount() {
count++;
}
// Nested function invocation
increaseCount();
increaseCount();
increaseCount();
return count;
}
console.log(totalCount());
</script>
输出:
在这里,count变量只能由内部函数访问和修改。就作用域的术语而言,与执行环境和执行堆栈有几个术语相关。
执行上下文: 它是称为环境的东西。在JavaScript中,每个代码都与一个执行上下文相关联,这意味着运行该代码的环境。执行上下文在运行特定的代码时创建。
现在让我们举个例子来理解。
所以这里描述了变量和函数的执行上下文,在这个执行上下文中有哪些参数,比如函数var,this函数,对外部执行上下文的引用,只有在调用该函数或name变量执行打印时才会创建这个执行上下文,所以每次调用函数都会创建相应的执行上下文。
执行堆栈: 它通常与其他编程语言(如C、CPP、Java等)中使用的调用堆栈相同。它创建一个堆栈,维护当前活动的所有执行上下文的列表。在每个上下文执行完成后,它从堆栈中弹出,然后运行下一个执行上下文。全局上下文是每次创建的第一个执行上下文。
例子: 在这个例子中,我们将看到执行堆栈的工作。
Javascript
<script>
function c() {
console.log("Inside c");
}
function b() {
c();
console.log("Inside b");
}
function a() {
b();
console.log("Inside a");
}
a();
console.log("global context");
// Call stack
/* sequence in which these function executed
|c() | pop1st
|b() | pop2nd
|a() | pop3rd
|global| pop4th
*/
</script>
输出:
说明: 首先是全局上下文,然后调用函数a,所以会创建一个a的上下文,执行上下文将包含函数中所有变量的信息和对外部上下文的引用,所以a的外部上下文是全局上下文。然后会创建函数b的执行上下文,它将包含外部上下文即函数a和其中的所有变量的信息,以此类推,整个过程就是这样运作的。执行完成后逐个弹出。
让我们来了解JavaScript解释器如何查找变量。
作用域链: 当一个变量在函数内部使用时,解释器会在函数内部查找该变量。如果在函数内部仍然未找到该变量,那么它会在外部作用域中即父函数中查找。如果在父函数中仍然未找到,它将移动到父级的外部作用域并进行检查;以此类推,直到找到该变量或者在全局作用域中未找到变量时,解释器将抛出错误。
例子:
Javascript
<script>
function a() {
var i = 20;
function b() {
function c() {
console.log(i);
// Scope of i is whichin function
// a so inner function b and c
// can access it
var j = 30;
console.log(j)
}
// console.log(j)
// Gives error because scope is
// within c so outer env i.e,
// b and a can't access it.
c();
}
b();
}
a();
</script>
输出:
JavaScript函数的类型: 这基本上是指定义函数的方式。
JavaScript中的函数可以通过两种方式定义:
- 函数定义
语法:
function functionName(parameters) {
// SET OF STATEMENTS
}
- 函数表达式
语法:
var variableName = function (parameters) {
// SET OF STATEMENTS
} ;
根据这两种不同的函数类型,有:
普通函数(函数声明): 使用关键字function和函数名来创建函数。它具有一些预解析属性。
示例: 在此示例中,我们将看到传递的原始值。
Javascript
<script>
// Value passed as an argument is primitive
function abc(b) {
b = 20;
console.log(b);
}
var a = 10;
abc(a);
console.log(a);
</script>
输出:
示例: 在这个示例中,我们将看到非基本类型的值被传递。
JavaScript
<script>
// Value passed as an argument
// is non-primitive
function abc(arr1, arr2) {
arr1[1] = 50;
console.log(arr1);
console.log(arr2["2"]);
}
var arr1 = [10, 20, 30];
var arr2 = {
"1": "a",
"2": "b",
"3": "c"
}
abc(arr1, arr2);
console.log(arr1);
console.log(arr2);
</script>
输出:
递归函数: 如果基本情况不满足,则调用函数自身,直到达到基本情况,否则会发生堆栈溢出。
例子:
JavaScript
<script>
// Recursive
function factorial(n) {
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
factorial(6);
</script>
输出:
命名函数表达式 - 将函数作为表达式创建并存储在变量中。不能通过函数名调用函数,因为它只是变量的函数名称,所以只能通过变量名来调用。它解决了提升的问题,因为对于声明而言,同时需要定义。
示例:
Javascript
<script>
// Function expression
var factorial1 = function fact(n) {
var ans = 1;
for (var i = 1; i <= n; i++)
ans = ans * i;
return ans;
}
console.log(factorial1);
console.log(factorial1(6));
</script>
输出:
匿名函数表达式: 与函数表达式相同,唯一的区别是不给函数命名,但在需要堆栈跟踪时需要函数名。如果名称不存在,则堆栈会将变量名作为函数名。
例子:
JavaScript
<script>
// Anonymous function
var factorial = function (n) {
var ans = 1;
for (var i = 1; i <= n; i++)
ans = ans * i;
return ans;
}
console.log(factorial);
console.log(factorial1(6));
</script>
输出结果:
回调函数(作为参数的函数): 函数可以作为参数传递给其他函数,意味着我会稍后回来。
两种方法:
- 将函数名作为参数传递给另一个函数
- 将函数定义为另一个函数的参数
示例:
JavaScript
<script>
var factorial = function fact(n) {
var ans = 1;
for (var i = 1; i <= n; i++)
ans = ans * i;
return ans;
}
// Passing function as arguments
function ncr(n, r, factorial) {
return factorial(n) / (factorial(r) * factorial(n - r));
}
console.log(ncr(5, 2, factorial))
</script>
输出:
闭包函数: 内部函数可以访问外部(封闭)函数的变量 – 一个作用域链(自己的作用域、外部函数的变量、全局变量)。当内部函数从创建它的函数外部可访问时,即外部函数返回一个内部函数时,就会创建闭包。内部函数会记住当时所有在作用域中的变量(及其值)。
示例:
JavaScript
<script>
var i = 10;
function outer() {
var j = 20;
console.log(i, j);
var inner = function () {
var k = 30;
console.log(i, j, k);
// Increment after second call of inner
i++;
// j increment after second call of inner
j++;
// k not increment after second call of inner
k++;
}
// Outer function return inner function
return inner;
}
// Inner preserved the scope of outer
// function and variables but each
// time new execution context is
// created inner function
var inner = outer();
// Each time execution call complete
// then no more exist execution
// context of inner function
inner();
inner();
</script>
输出:
箭头函数: 它在Es6中引入,是函数表达式的一种版本。它使函数的使用更加简便。它没有与此函数绑定。
例子:
Javascript
<script>
//Arrow function
var double = x => 2 * x;
console.log(double(3));
function Person(name) {
this.name = name;
console.log(this);
setTimeout(() =>
console.log(this),
// Arrow function does not have
// own binding so picked from
// outer scope
100)
};
var p = new Person("gfg")
</script>
输出:
7. 构造函数(函数创建对象):
构造函数提供了对象的蓝图/结构。使用相同的结构创建多个对象。通过使用“new”关键字调用构造函数可以创建对象。
例子:
Javascript
<script>
function Person(name) {
this.name = name;
setTimeout(function () {
console.log(this);
}, 100);
}
//object is created and stored it in p
var p = new Person("gfg");
var q = new Person("gfg1");
</script>
输出: