【JavaScript】 函数 function
也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。函数参数不是必需的,JavaScript 允许省略参数,但是,没有办法只省略靠前的参数,而保留靠后的参数。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。如果函数内部修改的,不是参数对
目录
1 函数 function
- JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
- 由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
1.1 函数声明
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明
1.1.1 function 命令
function print(s) {
console.log(s);
}
1.1.2 函数表达式
- 将一个匿名函数赋值给变量
var print = function (s) {
console.log(s);
};
- 将具名匿名函数赋值给变量,该函数名只在函数体内部有效,在函数体外部无效
- 一是可以在函数体内部调用自身,
- 二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。因此,下面的形式声明函数也非常常见。
var print = function x() {
console.log(typeof x);
};
x; // ReferenceError: x is not defined
print(); // function
1.1.3 Function 构造函数
这种声明函数的方式非常不直观,几乎无人使用
var add = new Function("x", "y", "return x + y");
// 等同于
function add(x, y) {
return x + y;
}
1.1.4 箭头函数
- 箭头函数(=>):函数简写
- 无参数:
() => {}
- 单个参数:
x => {}
- 多个参数:
(x, y) => {}
- 解构参数:
({x, y}) => {}
- 嵌套使用:部署管道机制
- this 指向固定化
- 并非因为内部有绑定 this 的机制,而是根本没有自己的 this,导致内部的 this 就是外层代码块的 this
- 因为没有 this,因此不能用作构造函数
- 无参数:
箭头函数误区
-
函数体内的 this 是定义时所在的对象而不是使用时所在的对象
-
可让 this 指向固定化,这种特性很有利于封装回调函数
-
不可当作构造函数,因此箭头函数不可使用 new 命令
-
不可使用 yield 命令,因此箭头函数不能用作 Generator 函数
-
不可使用 Arguments 对象,此对象在函数体内不存在(可用 rest/spread 参数代替)
-
返回对象时必须在对象外面加上括号
-
箭头函数:
() => {}
没有自己的 this 对象, 内部的 this 就是定义时上层作用域中的 this, this 指向是固定的 -
普通函数:
fun() {}
内部的 this 指向函数运行时所在的对象, this 指向是可变的
1.2 函数提升
- JavaScript 引擎将函数名视同变量名,所以采用
function
命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。
f();
function f() {}
- 表面上,上面代码好像在声明之前就调用了函数
f
。但是实际上,由于“变量提升”,函数f
被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。
1.3 函数属性
- 函数的
name
属性返回函数的名字。 - 函数的
length
属性返回函数预期传入的参数个数,即函数定义之中的参数个数。
1.4 函数方法
- 函数的
toString()
方法返回一个字符串,内容是函数的源码。
1.5 函数作用域
作用域(scope)指的是变量存在的范围。在 ES5 的规范中,JavaScript 只有两种作用域:
-
一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;
-
一种是函数作用域,变量只在函数内部存在。在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。
-
ES6 又新增了块级作用域
-
函数内部定义的变量,会在该作用域内覆盖同名全局变量。
-
对于
var
命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。 -
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。
var
命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。 -
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
-
很容易犯错的一点是,如果函数
A
调用函数B
,却没考虑到函数B
不会引用函数A
的内部变量。
1.6 函数参数
-
函数参数不是必需的,JavaScript 允许省略参数,但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入
undefined
。 -
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
-
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
-
如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值
- 这是因为,形式参数(
o
)的值实际是参数obj
的地址,重新对o
赋值导致o
指向另一个地址,保存在原地址上的值当然不受影响
- 这是因为,形式参数(
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
- 如果有同名的参数,则取最后出现的那个值
- 调用函数
f()
的时候,没有提供第二个参数,a
的取值就变成了undefined
。这时,如果要获得第一个a
的值,可以使用arguments
对象。
1.7 函数尾调用
函数尾调用 :某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
上面代码中,函数f
的最后一步是调用函数g
,这就叫尾调用。
以下三种情况,都不属于尾调用。
// 情况一
function f(x){
let y = g(x);
return y;
}
// 情况二
function f(x){
return g(x) + 1;
}
// 情况三
function f(x){
g(x);
}
上面代码中,情况一是调用函数g
之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。
function f(x){
g(x);
return undefined;
}
- 尾调用优化:只保留内层函数的调用帧
- 尾调用
- 定义:某个函数的最后一步是调用另一个函数
- 形式:function f(x) { return g(x); }
- 尾递归
- 定义:函数尾调用自身
- 作用:只要使用尾递归就不会发生栈溢出,相对节省内存
- 实现:把所有用到的内部变量改写成函数的参数并使用参数默认值
- 尾调用
1.8 arguments 对象
arguments
对象包含了函数运行时的所有参数,arguments[0]
就是第一个参数,arguments[1]
就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。- 虽然
arguments
很像数组,但它是一个对象
1.8.1 arguments 属性
arguments.length
属性,可以判断函数调用时到底带几个参数。arguments.callee
属性,返回它所对应的原函数。
1.9 闭包
- 把闭包简单理解成 定义在一个函数内部的函数 ,能够读取其他函数内部变量的函数
- 闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc(); // 5
inc(); // 6
inc(); // 7
上面代码中,start
是函数createIncrementor
的内部变量。通过闭包,start
的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc
使得函数createIncrementor
的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。
为什么闭包能够返回外层函数的内部变量?原因是闭包(上例的inc
)用到了外层变量(start
),导致外层函数(createIncrementor
)不能从内存释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。
JS 中 return 一个函数与直接 return 一个函数变量的区别
函数的节流与防抖
function makeCounter() {
var count = 0;
function counter() {
count = count + 1;
return count;
}
return counter(); // 将嵌套函数返回
}
var doCount = makeCounter();
console.log(doCount, "--doCount1"); // 1 '--doCount1'
console.log(doCount, "--doCount2"); // 1 '--doCount2'
console.log(doCount, "--doCount3"); // 1 '--doCount3'
- 当 return counter()时,就自动调用了嵌套函数。
- 那么嵌套函数返回一个经过+1 的 count,并且 count 的值为 1.
- 所以 doCount 得到的是一个数字,并不是函数,所以无法得到闭包。
function makeCounter() {
var count = 0;
function counter() {
count = count + 1;
return count;
}
return counter; // 将嵌套函数返回,但只写函数名称
}
var doCount = makeCounter();
console.log(doCount(), "--doCount1"); // 1 '--doCount1'
console.log(doCount(), "--doCount2"); // 2 '--doCount2'
console.log(doCount(), "--doCount3"); // 3 '--doCount3'
-
return counter 返回的是整一个 cunter()函数。
-
因此执行 var doCount = makeCounter()时,doCount 将引用 counter 函数及其中的变量环境。
-
那么 counter 函数及其中的变量环境,就是闭包了
-
闭包的形成:内部函数引用了外部函数的数据(这里为 count),
-
因此在 doCount=makeCounter(),则会把这个 count 保存在 doCount 中,函数执行完,count 并不会被销毁。
-
注意: 为什么上面这段代码没有直接写的
function doCount(){…}
而是把function
赋值给了变量doCount
呢? -
我们通常会想当然的认为每次调用
doCount()
都会重走一遍doCount()
中的代码块, 但其实不然。 -
注意
makeCounter
方法中的return
不是1,2,3
这样的数值, 而是一个方法,并且把这个方法赋值给了doCount
变量。 -
那么在这个
makeCounter
自运行一遍之后, 其实最后赋值给doCount
的是count = count + 1; return count;
这段代码。 -
所以后面每次调用
doCount()
其实都是在调用count = count + 1; return count;
-
闭包会持有父方法的局部变量并且不会随父方法销毁而销毁, 所以这个
count
其实就是来自于第一次makeCounter
执行时创建的变量
1.10 立即调用的函数表达式(IIFE)
- 根据 JavaScript 的语法,圆括号
()
跟在函数名之后,表示调用该函数。比如,print()
就表示调用print
函数。
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
1.11 eval 命令
eval
命令接受一个字符串作为参数,并将这个字符串当作语句执行。
eval("var a = 1;");
a; // 1
使用严格模式 加一行 ‘use strict’;
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)