@(javascript)[js函数]javascript
[toc]java
JavaScript中的函数能够分为两类:有名函数
与匿名函数
。而定义函数的方式有两种:函数声明
与函数表达式
。算法
目标:定义一个函数 fn ==> 有名函数数组
// 使用函数声明
function fn(){
// 函数执行体
}
// 使用函数表达式
var fn = function(){
// 函数执行体
}
复制代码
使用函数声明的重要特征就是函数声明提高
,即在读取代码前会先读取函数声明。函数名()
表示执行函数 看看下面的代码没有任何问题。闭包
// 定义一个有名函数 fn1 使用函数声明
function fn(){
console.log("fn1")
}
// 调用函数 fn1
fn1(); // fn1
// 定义一个有名函数 fn2 使用函数表达式
var fn2 = function(){
console.log("fn2")
}
// 调用函数 fn2
fn2(); // fn2
复制代码
可是若是是把调用放在定义函数前面,使用函数表达式
的就会报错(Uncaught ReferenceError: fn1 is not defined)app
// 调用函数 fn1
fn1(); // fn1
// 定义一个有名函数 fn1 使用函数声明
function fn(){
console.log("fn1")
}
// 调用函数 fn2
fn2(); // Uncaught ReferenceError: fn1 is not defined
// 定义一个有名函数 fn2 使用函数表达式
var fn2 = function(){
console.log("fn2")
}
复制代码
这就是使用两种的区别。函数
每个函数在调用的时候都会默认返回一个undefined
。性能
function fn(){
console.log(1)
}
fn(); // 1
console.log(fn); // console出一个函数 即 fn
console.log(fn()); // undefined
复制代码
这里须要注意的地方就是关于函数执行过程
与函数执行结果
。ui
fn()
表示调用函数。那就会执行函数体。并默认返回一个undefined
。只不过这个值undefined
没有变量接收或者说是咱们没有用这个值。this
console.log(fn)
就只是console出一个变量fn
的值。只不过这个值是一个函数。
console.log(fn())
与第一个的区别就是函数执行了并返回了一个结果。这个结果呢与上面不一样的就是如今这个结果咱们用上了(放在了console)里面。再因为值是undefined
,因此console了一个undefined
。
既然函数是能够有返回值的,而且这个值默认是一个undefined
。那咱们能够能够修改呢?答案是能够的。 咱们使用return
语句可让函数返回一个值
function fn(){
console.log(1)
return "哈哈"
}
fn(); // 1
console.log(fn); // 函数 fn
console.log(fn()); // 哈哈
复制代码
能够看一下第一个与第二个的结果与以前的是相同的。可是第三个的结果就不一样了,他是字符串哈哈
。由于咱们修改了函数的默认返回值。
因此,能够把默认函数理解成这样的
function fn(){
return undefined;
}
复制代码
而咱们修改返回值就是吧这个undefined给变成其余的值了。 注意:
函数的返回值能够是任意的数据类型。
函数是能够接收参数的,在定义函数的时候放的参数叫形式参数
,简称形参
。在调用函数的时候传递的参数叫实际参数
,简称实参
。一个函数能够拥有任意个参数
function fn(a,b){
console.log(a)
console.log(b)
console.log(a+b)
}
// 调用函数并传递参数
fn(3,5); // 3,5,8
fn(3); // 3,undefined,NaN
fn(3,5,10) // 3,5,8
复制代码
能够看看上面的例子。定义函数的时候有两个形参。调用的时候分为了三种状况。
第一种,传递两个参数,在console时候a=3,b=5,a+b=8
。老铁,没问题。
第二种,传递一个参数,在console的时候a=3,b=undefined,a+b=NaN
。哈哈,你不行。
第三种,传递3个。在console的时候a=3,b=5,a+b=8
。握草,你牛逼。对第三个参数视而不见了。
以上就是三种状况。一句话:参数一一对应,实参少了,那么没有对应的就是undefined,实参多了,多出来的就是没有用的
在不肯定参数(或者定义函数的时候没有形参)的时候,调用函数你传递参数了,可是你没有使用新参去接收,就没法使用。把此时就有一个arguments
对象能够获取到实参的个数以及具体的值。
function fn(){
console.log(arguments)
}
fn(1,2,3,4,5,6,7) // Arguments(7) [1, 2, 3, 4, 5, 6, 7, callee: ƒ, Symbol(Symbol.iterator): ƒ]
复制代码
arguments
在严格模式下没法使用。
递归:就是函数本身调用本身。好比下面经典的阶层递归函数
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * stratum(n - 1);
}
}
stratum(5) // 120 = 5 * (4 * (3 * (2 * 1) ) )
复制代码
能够看出实现的阶层的功能。 不过须要注意一下每个的执行顺序。不是5 * 4 * 3 * 2 * 1
。而是5 * (4 * (3 * (2 * 1) ) )
的顺序。为了证实这一点。能够将*
换为-
function fn(n){
if (n <= 1){
return 1;
} else {
return n - fn(n - 1);
}
}
fn(5) // 3
复制代码
若是是按照不带括号的5-4-3-2-1 = -5
。可是结果倒是3
。那3
是怎么来的呢?5 - (4 - (3 - (2 - 1) ) ) = 5 - (4 - (3 - 1)) = 5 - (4 - 2) = 5 - 2 = 3
还能够使用arguments.callee
方法调用自身。这个方法就指向当前运行的函数
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * arguments.callee(n - 1);
}
}
stratum(5) // 120
复制代码
递归虽然可让代码更简洁,可是能不使用递归的时候就不要使用,递归会影响性能(由于过多的调用本身会一直保存每一次的返回值与变量,致使内存占用过多甚至内存泄露)。
console.time(1);
function stratum(n){
if (n <= 1){
return 1;
} else {
return n * arguments.callee(n - 1);
}
}
console.log(stratum(5))
console.timeEnd(1) // 1: 4.470947265625ms
console.time(2)
var a = 1;
for (var i = 1; i <= 5; i++) {
a *= i;
}
console.log(a);
console.timeEnd(2) // 2: 0.2373046875ms
复制代码
两个阶层,一看。for循环快太多了。具体的性能问题能够看看爱情小傻蛋关于递归的算法改进。
闭包是指有权访问另外一个函数做用域中的变量的函数。 两个条件:
function fn(){
var a = 1;
return function(){
console.log(a);
a++;
}
}
fn()(); // 1
fn()(); // 1
var a = fn();
a(); // 1
a(); // 2
复制代码
上面的例子中的函数就是一个闭包。注意上面的直接调用返回值与先保存返回值在调用的区别。
闭包只能取得包含函数中任何变量的最后一个值。this
是没法在闭包函数中调用的。由于每个函数都有一个this
。
闭包函数中使用的变量是不会进行销毁的,像上面的var a = fn()
,这个函数a中使用了函数fn中的变量,且a
是一直存在的,因此函数fn
里面的变量a
是不会销毁的。若是是直接调用函数fn()()
只是至关于调用一次fn
函数的返回值。调用完函数返回值就销毁了。因此变量a
不会一直保存。
由于闭包函数的变量会一直保存不会
三个方法都是改变this指向
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
var name = "嘻嘻"
var obj = {
"name": "哈哈"
}
// 执行函数fn
fn(1,2) // 1,2,嘻嘻
复制代码
直接调用函数fn(1,2)
,this.name
的值是嘻嘻
若是使用call:
fn.call(obj,1,2) // 1,2,哈哈
复制代码
call
方法的第一个参数是改变this
指向的东西,能够是任何的数据类型。只不过若是是null
或者是undefined
就会指向window
。其余的参数依次对应函数的每个形参。
若是使用apply
fn.apply(obj,[1,2])
复制代码
apply
的使用与call的使用的惟一的区别就是它对应函数每一项形参是一个数组而不是单独的每个。
call与applu都是在函数调用的时候去使用
bind
则是在函数定义的时候使用
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
var name = "嘻嘻"
var obj = {
"name": "哈哈"
}
// 执行函数fn
fn(1,2) // 1,2,嘻嘻
复制代码
若是使用bind
能够是一下几种方式
// 使用函数表达式 + 匿名函数
var fn = function(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}.bind(obj)
fn(1,2)
// 使用有名函数
function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}
fn.bind(obj)(1,2)
// 函数在自执行
(function fn(a,b){
console.log(a)
console.log(b)
console.log(this.name)
}.bind(obj)(1,2))
(function fn(){
console.log(a)
console.log(b)
console.log(this.name)
}.bind(obj))(1,2);
(function fn(){
console.log(a)
console.log(b)
console.log(this.name)
}).bind(obj)(1,2);
复制代码
使用bind
的时候也是能够传递参数的,可是不要这样使用,由于使用bind
后你不调用函数那么参数仍是没有做用。既然仍是要调用函数,咱们通常就把函数的实参传递到调用的括号里面。