From:https://www.runoob.com/js/js-function-definition.html
JavaScript "创建函数" 的几种方法
方法 1:函数声明
JavaScript 使用关键字 function 定义函数。"箭头函数" 不需要使用 function 关键字
"分号" 是用来分隔可执行 JavaScript 语句。
由于函数声明不是一个可执行语句,所以不以分号结束。
示例:函数声明
// 函数声明和定义
function myFunction(a, b) {
return a * b;
}
var result = myFunction(10, 20); // 函数调用
console.log(result);
箭头函数
((a) => {
console.log("箭头函数");
console.log(a);
})("参数");
function func_1(n, m) {
m(n);
}
let a = 10;
func_1(a, (b)=>{console.log(b)});
方法 2:匿名函数
函数在定义的时候,可以指定函数名,也可以不用指定函数名,如果不指定函数名,则函数就是 "匿名函数",即 函数没有名称。通常将 匿名函数存储在变量中,通过变量来调用函数。
// 定义一个数组, 数组中元素是 函数表达式(匿名函数)
let arr = [
function (a, b) {console.log(a+b);},
function (a, b) {console.log(a-b);},
function (a, b) {console.log(a*b);},
]
arr[0](1, 2);
arr[1](3, 4);
arr[2](5, 6);
// 遍历数组, 并调用函数
arr.forEach((element) => {
element(7, 8);
})
方法 3:匿名函数 赋值给 变量 (相当于C++函数指针)
因为函数名其实就是指向函数的内存地址空间,所以可以直接把 "匿名函数" 赋值给一个变量来保存,通过变量来调用匿名函数。 "匿名函数" 又叫 "函数表达式"
var x = function (a, b) {return a * b};
var z = x(4, 3); // 以分号结尾,因为它是一个执行语句。
方法 4:使用 Function 创建函数
在 JavaScript 中,很多时候需要避免使用 new 关键字。
JavaScript 中函数除了通过关键字 function 定义。还可以通过内置的 函数构造器 Function() 定义。这种方式允许在运行时动态地构建函数,虽然它不是最常见的创建函数的方式,但在某些特定情况下可能会很有用,特别是当你需要根据不同的条件来动态地生成函数时。
Function 构造函数的语法如下:const newFunction = new Function([arg1[, arg2[, ...argN]],] functionBody);
- arg1, arg2, ... argN:是新函数的参数名,是可选的。
- functionBody:是新函数的函数体,是一个包含JavaScript语句的字符串。
示例:
var myFunction = new Function("a", "b", "return a * b");
var x = myFunction(4, 3);
Function("a", "b", "let sum = a + b; console.log(sum)")(2, 5);
特点
- 动态性:
Function
构造函数可以动态地创建函数。你可以根据条件动态地构建函数体,并创建新的函数实例。 - 全局作用域:用
Function
构造函数创建的函数不会闭包任何创建它们时所在作用域的变量。这意味着,它们总是在全局作用域中创建的。 - 性能:不推荐过度使用
Function
构造函数,因为每次使用它都需要解析函数体字符串,这可能会比直接使用函数声明或函数表达式更慢。
使用场景
- 考虑到性能和作用域的特性,
Function
构造函数并不适合常规的函数创建。它更多的是在一个需要根据不同条件动态构建函数内容的情况下使用,或者在执行由用户输入或者其他来源以字符串形式提供的代码时使用。
安全注意事项
- 因为
Function
构造函数允许运行字符串中的代码,所以使用它时要特别注意安全问题。没有经过适当清理和验证的用户输入直接用于构造函数可以导致安全漏洞,例如代码注入攻击。因此,除非绝对必要,否则应避免使用Function
构造函数,特别是在处理不可信的输入时。
方法 5:箭头函数
ES6 新增了箭头函数。
箭头函数表达式的语法比普通函数表达式更简洁。
(参数1, 参数2, …, 参数N) => { 函数声明 }
(参数1, 参数2, …, 参数N) => 表达式(单一)
// 相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }
当只有一个参数时,圆括号是可选的:
(单一参数) => {函数声明}
单一参数 => {函数声明}
没有参数的函数应该写成一对圆括号:
() => {函数声明}
示例:
// ES5
var x = function(x, y) {
return x * y;
}
// ES6
const x = (x, y) => x * y;
var fn = function(){};
var fn = () => {};
var fn = function(name){}
var fn = name => {}
var fn = (name) => {}
var fn = function(name, age){}
var fn = (name, age) => {}
有的箭头函数都没有自己的 this。 不适合定义一个 对象的方法。
当我们使用箭头函数的时候,箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的。
箭头函数是不能提升的,所以需要在使用之前定义。
使用 const 比使用 var 更安全,因为 函数表达式始终是一个常量。
如果函数部分只是一个语句,则可以省略 return 关键字和大括号 {},这样做是一个比较好的习惯:
const x = (x, y) => { return x * y };
console.log(x(5, 5));
const saySomething = () => {
console.log('Something');
};
返回值
如果箭头函数的函数体只有一个表达式,那么该表达式的值将自动作为返回值。
const arr_1 = [
{id: 1, url: "https://i1.huishahe.com/uploads/tu/201911/10045/57a9f00c39_33.jpg"},
{id: 2, url: "https://i1.huishahe.com/uploads/tu/201909/10387/2e80dc74f2_44.jpg"},
{id: 3, url: "https://i1.huishahe.com/uploads/tu/201909/10340/2.jpg"}
];
console.log("原数组array为", arr_1);
const arr_2 = arr_1.map(v=>v.url);
console.log("处理之后先产生的数组map", arr_2);
const multiply = (a, b) => a * b;
console.log(multiply);
如果箭头函数的函数体包含多条语句,那么需要使用 return
关键字来明确返回值。
const addAndMultiply = (a, b) => {
const sum = a + b;
return sum * 2;
};
console.log(addAndMultiply);
方法 6:自调用 (自执行) 函数
函数调用时候,必须是函数名加上括号,但是匿名函数没有函数名,所以就在匿名函数的内存地址空间上加上括号,就代表一个函数。
// 数组里面定义了两个匿名函数,数组元素都是保存的函数内存空间的地址
let arr = [
function (a,b) {
return a + b;
},
function () {
console.log("测试");
}
]
console.log(arr[0](1, 2));
arr[1]();
// 由此可以引申出自执行函数
// 匿名函数没有函数名,所以就在匿名函数的内存地址空间上加上括号,就代表一个函数。
(function () {
console.log("自执行函数");
})();
- 函数表达式 ( 即 匿名函数 ) 可以 "自调用"。
- 自调用表达式 会 自动调用自己。
- 如果 函数表达式 后面紧跟 () 同时加上 分号,则会自动调用。
- 声明的函数 不能 自调用(即 不能自动调用自己)。
语法:( 函数表达式,即匿名函数 ) ( ); // 最后的 分号 不能少,表示是一个语句
(function () {
var x = "Hello!!"; // 我将调用自己
})();
以上函数实际上是一个 匿名自我调用的函数 (没有函数名)。这种函数在函数定义的地方就直接执行了。
通常的函数声明和调用分开的写法如下:
function foo() {/*...*/} // 这是定义,定义只是让解释器知道其存在,但是不会运行。
foo(); // 这是语句,解释器遇到语句是会运行它的。
IIFE ( 立即调用的函数表达式,immediately invoked function expression ) 在引入更标准化的解决方案(如 CommonJS 和 ES 模块)之前,它作为一种支持模块化编程的方法在 JavaScript 中很流行。立即调用的函数表达式可用于避免从块内提升变量,防止污染全球环境,同时允许公众访问方法,同时保留函数中定义的变量的隐私。理解 IIFE 等设计模式的关键是要认识到,在 ES6 之前,JavaScript 只具有函数作用域(因此缺少块作用域),在闭包内通过引用传递值。现在情况已不再如此,因为 JavaScript 的 ES6 版本使用 new let 和 const 关键字实现了块范围。
立即调用的函数表达式可以用多种不同的方式编写。
- 一个常见的约定是 "将函数表达式,用括号括起来" 从而以显式方式告诉解析器是表达式而不是函数。否则,在大多数情况下,当解析器遇到function关键字时,它会将其视为函数声明(语句),而不是函数表达式。
(function () { /* ... */ })();
(function () { /* ... */ }());
(() => { /* ... */ })(); // ES6 箭头函数(虽然括号只允许在外面) - 还有其他方法可以强制执行函数表达式:
!function () { /* ... */ }();
~function () { /* ... */ }();
-function () { /* ... */ }();
+function () { /* ... */ }();
void function () { /* ... */ }();
delete function () { /* ... */ }();
typeof function () { /* ... */ }();
await function () { /* ... */ }(); - 在需要表达式的上下文场景中,就不需要用括号括起来:
void function(){/*...*/}(); void 声明了不需要返回值
let f = function () { /* ... */ }(); 将IIFE函数的返回值赋给 f
var foo = function(){/*...*/}(); 将IIFE函数的返回值赋给 foo
true && function () { /*...*/ }(); 在表达式中使用
0, function () { /*...*/ }(); 在表达式中使用 - 将变量传递到作用域中的过程如下:(function(a, b) { /* ... */ })("hello", "world");
示例:(function($){...})(jQuery); 是将实参jQuery传入函数function($){},通过形参$接收。 - 防御性分号:在自执行函数中,最开始的那个括号,可能会由于 "js自动插入分号" 而引发问题。例如:
a = b + c
;(function () {
// code
})();
如果没有第二行的分号,那么该处有可能被解析为c()而开始执行。所以有的时候,可能会看到这样的写法:;(function foo(){/*...*/}()),前边的分号可以认为是防御型分号。
$(function(){});
$(function(){/*...*/});是$(document).ready(function(){/*...*/})的简写形式,是在DOM加载完成后执行的回调函数,并且只会执行一次。
$( document ).ready(function() {
console.log( "ready!" );
});
等价于
$(function() {
console.log( "ready!" );
});
在一个页面中不同的js中写的$(function(){/*...*/});函数,会根据js的排列顺序依次执行。
例如:test.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<span>This page is shown to understand '$(document).ready()' in jQuery. <br /><span>
<p>
If you see this line, it means DOM hierarchy has been loaded. NOW loading jQuery from server and execute JS...<br /><br />
</p>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="test1.js"></script>
<script src="test2.js"></script>
</body>
</html>
test1.js
$(function(){
$(document.body).append("$(document).ready()1 is now been executed!!!<br /><br />");
});
test2.js
$(function(){
$(document.body).append("$(document).ready()2 is now been executed!!!<br /><br />");
});
页面输出如下:
This page is shown to understand '$(document).ready()' in jQuery.
If you see this line, it means DOM hierarchy has been loaded. NOW loading jQuery from server and execute JS...
$(document).ready()1 is now been executed!!!
$(document).ready()2 is now been executed!!!
函数提升(Hoisting)
- 提升(Hoisting)是 JavaScript 默认将当前作用域提升到前面去的的行为。
- 提升(Hoisting)应用在:变量的声明 与 函数的声明。
因此,函数可以在声明之前调用:
myFunction(5);
function myFunction(y) {
return y * y;
}
使用 表达式定义函数 时 无法提升。
函数是对象
- 在 JavaScript 中使用 typeof 操作符判断 函数类型 将返回 "function" 。
- 但是把 JavaScript 函数描述为一个对象更加准确。
- JavaScript 函数有 属性 和 方法。
- arguments.length 属性返回函数调用过程接收到的参数个数:
function myFunction(a, b) {
return arguments.length;
}
toString() 方法将函数作为一个字符串返回:
function myFunction(a, b) {
return a * b;
}
var txt = myFunction.toString();
console.log(txt);
/*
输出结果:
function myFunction(a, b) {
return a * b;
}
* */
// 扩展,ES5, 定义一个对象
function Person(name, age) {
this.name = name;
this.age = age
}
obj = new Person("对象构造器", 123)
注意 函数 和 方法 的区别:
- 函数:不属于对象的函数,即普通函数,叫做 函数
- 方法:如果一个函数属于对象,则这个函数 叫做 方法
JavaScript 函数参数
函数的参数
JavaScript 函数对参数的值,不会进行任何的检查。
参数规则
- 形参:JavaScript 函数定义显式参数 ( 形参 ) 时 不指定数据类型。( 即 不指定 形参 的 数据类型,啥类型都可以 )
- 实参:JavaScript 函数对隐式参数 ( 实参 ) 不进行类型检测。 ( 即 不检测 实参 的 数据类型 ,想传递什么类型就传递什么类型)
- 参数个数:JavaScript 函数对隐式参数 ( 实参 ) 的个数不进行检测。( 即 不检查 传递的 实参个数,想传递多少个实参都可以。 )
显式参数(Parameters)
function funcName(parameter1, parameter2, parameter3) {
// 要执行的代码……
}
隐式参数(Arguments)
JavaScript 函数有个内置的对象 arguments 对象。argument 对象包含了函数调用的参数数组。
function sign() {
//arguments是一个数组,内部存储的是函数传递过来所有的参数
console.log(arguments)
}
sign()
sign(11, 22, 33)
sign(1, 2, 3, 4, 5);
示例:找到最大的一个参数的值:
x = findMax(1, 123, 500, 115, 44, 88);
function findMax() {
var i, max = arguments[0];
if (arguments.length < 2) return max;
for (i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
x = findMax(1, 123, 500, 115, 44, 88);
console.log(x)
或者创建一个函数用来统计所有数值的和:
x = sumAll(1, 123, 500, 115, 44, 88);
function sumAll() {
var i, sum = 0;
for (i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
console.log(x)
dsfa
默认参数
ES5中,如果函数在调用时未提供隐式参数,参数会默认设置为: undefined
有时这是可以接受的,但是建议最好为参数设置一个默认值:
function myFunction(x, y) {
if (y === undefined) {
y = 0;
}
}
或者,更简单的方式:( 如果 y 已经定义 , y || 返回 y, 因为 y 是 true, 否则返回 0, 因为 undefined 为 false。 )
function myFunction(x, y) {
y = y || 0;
}
如果函数调用时设置了过多的参数,参数将无法被引用,因为无法找到对应的参数名。 只能使用 arguments 对象来调用。
ES6 函数可以自带参数
ES6 支持函数带有默认参数,就判断 undefined 和 || 的操作:
function myFunction(x, y = 10) {
// y is 10 if not passed or undefined
return x + y;
}
myFunction(0, 2) // 输出 2
myFunction(5); // 输出 15, y 参数的默认值
参数 的 值传递
调用函数时,传递给函数的参数叫做 实参,即 隐式参数
JavaScript 隐式参数通过值来传递:函数仅仅只是获取值。
如果函数修改参数的值,不会修改显式参数的初始值(在函数外定义)。
隐式参数的改变在函数外是不可见的。
参数 的 对象传递
在JavaScript中,可以引用对象的值。
因此我们在函数内部修改对象的属性就会修改其初始的值。
修改对象属性可作用于函数外部(全局变量)。
修改对象属性在函数外是可见的。
示例:
var person = {
'name': 'king',
'age': 100,
'sex': '男'
}
console.log("111111111111111111111111111")
console.log(person)
function myFunction(argPerson) {
argPerson.name = '皇帝';
argPerson.age = 123;
console.log("22222222222222222222222222222")
console.log(argPerson);
}
myFunction(person);
console.log("33333333333333333333333333333")
console.log(person);
JavaScript 函数调用
函数调用 就是 执行函数中的代码。
JavaScript 函数有 4 种调用方式。每种方式的不同在于 this 的初始化。
this 关键字
this 是 JavaScript 语言的一个关键字。它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。一般而言,在 Javascript 中,this 指向函数执行时的当前对象。
function test() {
this.x = 1;
console.log(this);
console.log(this === global); // true
}
test();
随着函数使用场合的不同,this 的值会发生变化。但是有一个总原则:this 总是指向调用函数的那个对象。
<script>
var myObject, myArray;
myObject={
name: "hahaha ",
hsk: "en"
};
function myFunction(a, b) {
alert(this);
return this.name +this.hsk;
}
myArray = [10, 2]
myObject = myFunction.apply(myObject, myArray);
document.getElementById("demo").innerHTML = myObject;
</script>
call 机制
call 函数有(call的调用者表示函数的参数个数+1)个参数
- 参数1:会被赋值给call的调用者,在函数内部 this 执行第一个参数
- 参数 2,3,4 ...
a.call(n, i) 等同于:a(i), 同时 a函数的内部 this 指向 n
// JavaScript 中一切皆对象,所以函数也是一个对象
// 所以每个函数内部都会有一个this指针,该this就好比是python中的self
function sign(n) {
//this = 123
console.log(`n ---> ${n}`)
console.log(`this ---> ${this}`)
}
//基于call机制的函数调用
//参数123会被赋值给sign函数内部的this,456会被赋值给sing的参数n。
sign.call(123, 456)
/*
n ---> 456
this ---> 123
*/
执行函数
方法 1:直接执行
//函数定义
function sign(n, a) {
//this的指向没有发生改变
console.log(n + a)
}
//函数调用
sign('name:', 'bobo')
sign 函数不属于任何对象。但是在 JavaScript 中它始终是默认的全局对象。
在 HTML 中默认的全局对象是 HTML 页面本身,所以函数是属于 HTML 页面。
在浏览器中的页面对象是浏览器窗口(window 对象)。以上函数会自动变为 window 对象的函数。
下面代码中 myFunction() 和 window.myFunction() 是一样的:
function myFunction(a, b) {
return a * b;
}
window.myFunction(10, 2); // window.myFunction(10, 2) 返回 20
上面代码中是调用 JavaScript 函数常用的方法, 但不是良好的编程习惯。全局变量,方法或函数容易造成命名冲突的bug。
方法 2:call 机制 执行函数
call 函数有(call的调用者表示函数的参数个数+1)个参数
- 参数1:会被赋值给call的调用者,在函数内部 this 执行第一个参数
- 参数 2,3,4 ...
a.call(n, i) 等同于:a(i), 同时 a函数的内部 this 指向 n
// JavaScript 中一切皆对象,所以函数也是一个对象
// 所以每个函数内部都会有一个this指针,该this就好比是python中的self
function sign(n) {
//this = 123
console.log(`n ---> ${n}`)
console.log(`this ---> ${this}`)
}
//基于call机制的函数调用
//参数123会被赋值给sign函数内部的this,456会被赋值给sing的参数n。
sign.call(123, 456)
/*
n ---> 456
this ---> 123
*/
全局对象
当函数没有被自身的对象调用时 this 的值就会变成全局对象。在 web 浏览器中全局对象是浏览器窗口(window 对象)。
该实例返回 this 的值是 window 对象:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>在 HTML 中 <b>this</b> 的值, 在全局函数是一个 window 对象。</p>
<p id="demo"></p>
<script>
function myFunction() {
return this;
}
document.getElementById("demo").innerHTML = myFunction();
</script>
</body>
</html>
函数作为全局对象调用,会使 this 的值成为全局对象。 使用 window 对象作为一个变量容易造成程序崩溃。
函数作为方法调用
在 JavaScript 中你可以将函数定义为对象的方法。
以下实例创建了一个对象 (myObject), 对象有两个属性 (firstName 和 lastName), 及一个方法 (fullName):
var myObject = {
firstName:"John",
lastName: "Doe",
fullName: function () {
return this.firstName + " " + this.lastName;
}
}
myObject.fullName(); // 返回 "John Doe"
fullName 方法是一个函数。函数属于对象。 myObject 是函数的所有者。
this 对象,拥有 JavaScript 代码。实例中 this 的值为 myObject 对象。
测试以下!修改 fullName 方法并返回 this 值:
var myObject = {
firstName:"John",
lastName: "Doe",
fullName: function () {
return this;
}
}
console.log(myObject.fullName()); // 返回 [object Object] (所有者对象)
函数作为对象方法调用,会使得 this 的值成为对象本身。
使用构造函数调用函数
如果函数调用前使用了 new 关键字, 则是调用了构造函数。
这看起来就像创建了新的函数,但实际上 JavaScript 函数是重新创建的对象:
// 构造函数:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var x = new myFunction("John", "Doe");
console.log(x.firstName); // 返回 "John"
构造函数的调用会创建一个新的对象。新对象会继承构造函数的属性和方法。
构造函数中 this 关键字没有任何的值。
this 的值在函数调用实例化对象(new object)时创建。
作为函数方法调用函数
在 JavaScript 中, 函数是对象。JavaScript 函数有它的属性和方法。
call() 和 apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身。
function myFunction(a, b) {
return a * b;
}
myObject = myFunction.call(myObject, 10, 2); // 返回 20
function myFunction(a, b) {
return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray); // 返回 20
两个方法都使用了对象本身作为第一个参数。 两者的区别在于第二个参数: apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。
在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。
通过 call() 或 apply() 方法你可以设置 this 的值, 且作为已存在对象的新方法调用。
改变 this 指向:call、 apply、bind
使用 call() 和 apply()方法时,就会改变 this 的指向。
func_a.call(obj_a, arg_1, arg_2)
等价于
func_a(arg_1, arg_2), 同时在func_a中, this 指向 obj_a
先看明白下面:例 1
var name = '小王', age = 17;
var obj = {
name: '小张',
objAge:this.age,
myFun:function () {
console.log(this.name + "年龄" + this.age);
}
}
var obj_age = obj.objAge;
console.log(obj_age);
obj.myFun();
例 2
var tempStr = "扫地僧";
function showStr(){
console.log(this.tempStr);
}
showStr();
比较一下这两者 this 的差别,第一个打印里面的 this 指向 obj,第二个全局声明的 shows() 函数 this 是 window ;
1,call()、apply()、bind() 都是用来重定义 this 这个对象的!
var name = '小王', age = 17;
var obj = {
name: '小张',
objAge:this.age,
myFun:function () {
console.log(this.name + "年龄" + this.age);
}
}
var db = {
name: '德玛',
age: 99
}
obj.myFun.call(db); // 德玛年龄 99
obj.myFun.apply(db); // 德玛年龄 99
obj.myFun.bind(db)(); // 德玛年龄 99
注意:bind 方法后面多了个 () 外 ,结果返回都一致!bind 返回的是一个新的函数,你必须调用它才会被执行。
2,对比 call 、bind 、 apply 传参情况下
var name = '小王', age = 17;
var obj = {
name: '小张',
objAge:this.age,
myFun:function () {
console.log(this.name + "年龄" + this.age);
}
}
var db = {
name: '德玛',
age: 99
}
obj.myFun.call(db,'成都','上海'); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.apply(db,['成都','上海']); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.bind(db,'成都','上海')(); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.bind(db,['成都','上海'])(); // 德玛 年龄 99 来自 成都, 上海去往 undefined
call 、bind 、 apply 这三个函数的参数:
- 第一个参数:都是 this 的指向对象,
- 第二个参数差别就来了:
call 的参数是直接放进去的,参数之间全都用逗号分隔。 obj.myFun.call(db,'成都', ... ,'string' )。
apply 的所有参数都必须放在一个数组里面。 obj.myFun.apply(db, ['成都', ..., 'string' ])。
bind 除了返回是函数以外,它的参数和 call 一样。
当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等!
JS 中的 call() 方法和 apply() 方法用法总结:JS中的call()方法和apply()方法用法总结_js的apply-CSDN博客
JavaScript 的 闭包
JavaScript 变量可以是 局部变量 或 全局变量。闭包可以返回函数内部的私有变量。
闭包,就是在一个封闭的环境内部,自己和自己玩,避免了对该模块内部的冲击和改动以及变量之间的冲突问题。
闭包的特点:
- 1. 内层函数对外层函数变量的使用.
- 2. 会让变量常驻与内存.
函数外部访问函数内部私有变量的方法:
- 方法 1:通过函数返回值,访问函数内部私有变量
- 方法 2:把函数内部的私有变量挂载到 全局变量 window ( nodejs 中是 global) 上
window = global;
(function (w) {
let key = "12345";
let jia_mi = function (d) {
return `加密${key}_${d}`;
}
w['jia_mi'] = {
md5: function (d) {
let result = jia_mi(d)
return result;
}
};
})(window)
局部变量
在函数内声明的变量作用域是局部的(函数内):
var globalVar; // 全局变量
// 这里不能使用 carName 变量
function myFunction() {
var carName = "Volvo"; // 函数内的局部变量
// 这里可以使用 carName 变量
}
// 这里不能使用 carName 变量
函数内
局部变量:在函数内部声明,并且只能在函数内部起作用,这些变量就是局部变量。局部变量作用域是局部性的。函数的参数也是局部性的,只在函数内部起作用。
- 全局变量:在函数外部声明得变量就叫全局变量。全局变量作用域是全局性的,即在整个JavaScript程序中,处处可以访问。
全局变量 和 局部变量 即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。
全局变量
在函数内部,可以访问在函数外部定义的全局变量,如:
var global_a = 4;
function myFunction() {
return global_a * global_a ;
}
在 web 页面中全局变量属于 window 对象。全局变量可应用于页面上的所有脚本。
计数器困境
设想下如果你想统计一些数值,且该计数器在所有函数中都是可用的。
你可以使用全局变量,函数设置计数器递增:
var counter = 0;
function add() {
return counter += 1;
}
add();
add();
add();
// 计数器现在为 3
计数器数值在执行 add() 函数时发生变化。
但问题来了,页面上的任何脚本都能改变计数器,即便没有调用 add() 函数。
如果我在函数内声明计数器,如果没有调用函数将无法修改计数器的值:
function add() {
var counter = 0;
return counter += 1;
}
add();
add();
add();
// 本意是想输出 3, 但事与愿违,输出的都是 1 !
以上代码将无法正确输出,每次我调用 add() 函数,计数器都会设置为 1。
JavaScript 内嵌函数可以解决该问题。
JavaScript 内嵌函数
所有函数都能访问全局变量。
实际上,在 JavaScript 中,所有函数都能访问它们上一层的作用域。
JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。
该实例中,内嵌函数 plus() 可以访问父函数的 counter 变量:
function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;
}
如果我们能在外部访问 plus() 函数,这样就能解决计数器的困境。
我们同样需要确保 counter = 0 只执行一次。
我们需要闭包。
JavaScript 闭包
还记得函数自我调用吗?该函数会做什么?
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
add();
add();
add();
// 计数器为 3
实例解析
变量 add 指定了函数自我调用的返回字值。
自我调用函数只执行一次。设置计数器为 0。并返回函数表达式。
add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。
这个叫作 JavaScript 闭包。它使得函数拥有私有变量变成可能。
计数器受匿名函数的作用域保护,只能通过 add 方法修改。
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。
直观的说:就是形成一个不销毁的栈环境。