函数的5种声明
1. 具名函数(function 命令)
function f(x,y){
return x+y
}
f.name // 'f'
具名函数的函数名也是变量名
console.log()是一个调用了的函数,它的返回值永远是undefined
函数里面如果只写return和return undefined是一个意思,另外函数返回0 '' NaN undefined null都等同于返回false
function x(val1,val2){return}等同于function x(val1,val2){return undfined}
2. 匿名函数(函数表达式)
不能单独使用,必须先声明一个变量,把他赋值给变量
var f
f = function(x,y){
return x+y
}
f.name // 'f'
小技巧:在开发者工具的console窗口里面写代码是,如果想清空之前的代码,就强刷三次
3. 具名函数赋值
采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
var f
f = function f2(x,y){ return x+y }
f.name // 'f2'
console.log(f2) //f2 is not defined
4. window.Function
var f = new Function('x','y','return x+y')
//如果你用new Function声明一个函数,那么这个函数没有名字的,也就是匿名函数
f.name // "anonymous" 匿名的
5. 箭头函数(一定是匿名函数)
//完整写法,在有大括号的情况下想得到一个明确的返回值就必须写return
var f = (x,y) => {
return x+y
}
f(1,2)//调用方法函数名(参数1,参数2) 3
//如果函数体内只有一条语句,大括号和return可以省略,也就是说在不写大括号的情况下,它默认就return
var sum = (x,y) => x+y
//如果函数只有一个参数,小括号可以省略
var n2 = n => n*n
函数的执行
var z;
function f2(){
console.log(z)
}
z=1;
f2.call()//1
上面的代码函数调用后打印出来的z是1,而不是undefined,是因为在函数f2声明的时候并未获取和打印出z这个变量,而是在f2调用的时候才去获取的,就相当于只要在函数调用的时候打印的z都是与函数调用这段代码最近的那个z。也就是说一个函数里面获取一个变量,是在函数调用之后才去获取的(获取的变量是与函数调用这个代码前面的最新一次的变量值),而不是函数声明的时候就获取。上面的代码其实你也可以看成:
var z;
z=1;
function f2(){
console.log(z)
}
f2.call()//1
将执行的函数赋值给一个变量
将一个执行的函数赋值给一个变量,它会先执行一下函数内部的语句,然后将返回值赋值给变量,之后执行这个变量操作的时候,这个变量的值都等于函数的返回值,而不会再去执行函数内部的语句。
如:
function fn(){
console.log('aaa');
return 'zzz'
}
var result = fn() //'aaa'
result //'zzz'
上面的代码将执行的fn函数赋值给result,fn函数先执行了它内部的代码,打印出了'aaa',然后将返回值'zzz'赋值给了result,所以此时var result = 'zzz',之后获取result变量得到的值都是'zzz'
如何调用函数
eval()
将字符串转为可执行的代码
函数的定义
可以执行我们写的代码的对象就是函数,另外在对象中的某一个属性是函数时,这个函数也可以叫做方法,函数和方法本质上是一个东西,你可以认为函数就是方法,而函数的调用也可以说是方法的调用
var obj = {
//这里的test就是一个方法,也可以说是函数
test: function(){
console.log('zzz')
}
}
//方法(函数)调用
obj.test()
函数传参
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
上面代码中,变量p
是一个原始类型的值,传入函数f
的方式是传值传递。因此,在函数内部,p
的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。
函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
上面代码中,传入函数f
的是参数对象obj
的地址。因此,在函数内部修改obj
的属性p
,会影响到原始值。
注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
上面代码中,在函数f
内部,参数对象obj
被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o
)的值实际是参数obj
的地址,重新对o
赋值导致o
指向另一个地址,保存在原地址上的值当然不受影响。
典型例题
function fn(obj){
obj.name = 'lifa'
obj = new Object()
obj.name = 'meinv'
}
var person = new Object()
fn(person)
console.log(person.name) //'lifa'
1.上面代码因为是将一个引用类型作为参数,所以传入的是一个引用地址(ADDR 26),也就是形参obj的值实际上就是person的引用地址
2.然后对这个ADDR 26添加一个name属性obj.name = 'lifa'
3.重新对形参obj进行赋值,指向另一个内存地址(ADDR 34)
4.在ADDR 34上添加name属性obj.name='meinv'
5.所以最后person.name也就是person的引用地址(ADDR 26)里的name,也就是‘lifa’
函数与对象的关系原型图
所有的基本类型和引用类型最终都指向了Object.prototype
f()与f.call()
function f(x,y){return x+y};
f.call(undefined,1,2) //3
//相当于 f(1,2)
典型例题:
function f1(){
function f2(){
console.log(this)
}
f2.call()
}
var obj = {name: 'obj'}
f1.call( obj )
上面代码f1函数执行了后它里面实际上执行了f2函数,而f2函数里面打印的this,在f2被调用的时候并未传值,所以是Window对象
call里面不写参数,和直接调用函数一样this都是默认的undefined,在非严格模式下也就是window
a()
//等价于this都是window
a.call()
this和arguments
上面的函数f里面写入了打印this和arguments两条语句;当执行f.call(undefined,1,2)命令时,得到this是window(chorme浏览器显示的大写的Window其实是小写的window),arguments(实参)是一个伪数组,里面有1和2两个值,这里注意为什么this不是undefined,因为默认情况下this都会变成window,所以要想得到undefined就要开启严格模式
上面的代码使用了use strict开启了严格模式,这时的this就变成了undefined,你还可以在call里面的第一个参数设置任何值,this都会返回你这个值
所以说call里面的第一个参数就是this
对象中的函数
属性方法里面直接调用一个和属性方法同名的函数,这个函数肯定不是这个属性方法,而是变量
var init=function(){
console.log('aaa')
}
var obj = {
init: function(){
console.log('zzz');
//这里的init是上面的变量init,而不是属性init,因为对象里的属性必须对象.属性,而不能直接写属性
init()
}
}
obj.init()
//zzz
//aaa
call stack调用栈
stack:先进后出
每进一次函数,就要压一次stack
递归调用
上面的代码求sum(5)需要先找到sum(4),而找sum(4)时又需要先找到sum(3),找sum(3)时又需要先找sum(2)找sum(2)时又需要找sum(1),最后在运行sum(1),所以上面代码一共进行了五步,也就是压了五次栈
stack overflow
当压栈的次数超出了就会报错
作用域
函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。
var v = 1;
function f() {
console.log(v);
}
f()// 1
上面的代码表明,函数f内部可以读取全局变量v。
在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。
function f(){
var v = 1;
}
v // ReferenceError: v is not defined
上面代码中,变量v在函数内部定义,所以是一个局部变量,函数之外就无法读取。
函数内部定义的变量,会在该作用域内覆盖同名全局变量。
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1
上面代码中,变量v同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量v覆盖了全局变量v。
注意,对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
if (true) {
var x = 5;
}console.log(x); // 5
上面代码中,变量x在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。
上面的代码函数f1的作用域里的a=3,大多数人都以为变量不写var就是在声明全局变量,其实不是,a=3主要是进行赋值,他会沿着当前作用域逐级往上找,直到找到全局里的a,然后给他赋值,如果全局里面也没有a变量(也就是沿着作用域树一直找不到当前这个变量),那么他就会声明并赋值一个全局变量a,也就只有这一种情况是声明全局变量的,其他的都是在赋值。
只要有一个函数就有一个作用域
函数作用域最要注意的就是变量提升,拿到一个函数只要里面有变量声明,就把声明提到当前作用域的最上边去:
当运行上面的代码的时候,函数f1里面的console.log(a)这句的时候打印出来的a是多少,我们要先把所有的变量声明都提升一下,变成下面的代码
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。
例题1:
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x)// ReferenceError: a is not defined
上面代码将函数x作为参数,传入函数y。但是,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。
同样的,函数体内部声明的函数,作用域绑定函数体内部。
例题2:
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;var f = foo();
f() // 1
上面代码中,函数foo内部声明了一个函数bar,bar的作用域绑定foo。当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x。
例题3:
下面函数f4里面的a的值是什么?
按照前面说的,因为函数执行时所在的作用域是函数定义时所在的作用域,而不是调用时的作用域,所以f4的作用域为全局,顾a等于1
例题4:
同样的对于对象里有回调函数的也是一个道理
var obj = {
fn(){
var a = 1
this.fn1(function(){console.log(a)})
},
fn1(f){
var a = 2
f()
}
}
obj.fn() //?
上面的代码this.fn1()括号里面的就是函数声明也就是function(){console.log(a)},而函数的作用域只与函数声明的时候有关所以这时候回调函数的作用域就在fn里,所以执行的时候打印的是1,而不是回调函数调用时fn1的作用域
例题5:
对于匿名函数的传递,就相当于将这个匿名函数通过一个变量接受然后直接把这个变量传递进去
function fn1(f){
var a = 1
f()
}
fn1(()=>{console.log(a)})
//上面的代码就相当于下面的
function f(){
console.log(a)
}
function fn1(f){
var a = 1
f()
}
fn1(f)//a is not defined
词法作用域(静态作用域)
词法树
上面的词法树,全局window下有global1变量和fn1变量,fn1下又有fn3、fn2、param2、local1和local2变量,fn3下面有local2,fn2下有local2变量;
比如:我要在fn2里面打印local1,根据右边的词法树,fn2里只有local2,所以它就会去fn2所在的那一级树里面找,然后就找到了local1
总之,就是先在你当前的作用域里找,如果当前的作用域找不到再去它的上一级找,直到找到为止,另外要注意词法树只是用来分析这个变量是不是当前对应层级的变量,不是用来分析这个变量的值是不是对应层级的变量的值,也就是说只分析语义,不分析值
问题:
var a = 1;
function b(){
console.log(a)
}
- b这个函数里的a肯定是全局里的a吗?
答:是,因为函数内没有声明a这个变量,所以他这个a肯定是全局的a
2. b函数里的a一定是1吗?
答: 不是。
比如:
var a = 1;
function b(){
console.log(a)
}
a=2;
b()
上面的代码当调用函数b的时候才会去执行函数里面的内容,也就是才回去打印出a,而这个时候a是2,这也说明了词法作用域只能确定这个a是不是全局里的a,但不能确定这个a的值是不是全局里的a的值
函数名的提升
JavaScript 引擎将函数名视同变量名,所以采用function
命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。
f();
function f() {}
上面的代码实际上等同于:
//函数提升
function f() {}
f();
如果采用赋值语句定义函数,JavaScript 就会报错。
f();
var f = function (){};
// TypeError: undefined is not a function
上面的代码等同于下面的形式:
var f;
f();
f = function () {};
上面代码第二行,调用f
的时候,f
只是被声明了,还没有被赋值,等于undefined
,所以调用他的时候会报错说undefined不是一个函数。因此,如果同时采用function
命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。
var f = function () { console.log('1');}
function f() { console.log('2');}
f() // 1
同名参数
如果有同名的参数,则取最后出现的那个值。
function f(a, a) {
console.log(a);
}
f(1, 2) // 2
上面代码中,函数f有两个参数,且参数名都是a。取值的时候,以后面的a为准,即使后面的a没有值或被省略,也是以其为准。
function f(a, a) {
console.log(a);
}
f(1) // undefined
调用函数f的时候,没有提供第二个参数,a的取值就变成了undefined。这时,如果要获得第一个a的值,可以使用arguments对象。
function f(a, a) {
console.log(arguments[0]);
}
f(1) // 1
var a = (1,2)
a //2
上面的代码a的值是2,多个值用小括号括起来,但取值的时候总是以后面的为准
设置默认参数值
function b(obj,str='z'){
return{
obj,str
}
}
上面的函数b里面有两个参数,其中第二个参数str,写成了str='z'意思是如果你不传第二个参数,它默认就相当于你传了一个'z'
//调用b函数,不传入第二个参数,str就是默认的'z'
b({love:'linlin'})
//当调用函数时传入第二个参数,那个str就会是你传入的值
b({love:'linlin'},'lifa')
另外如果函数表达式中需要传参,而你调用的时候没有传,那么这个参数就是undefined,可以通过参数是否等于undefined来判断有没有传参数
闭包
如果一个函数,使用了它范围外的变量,那么(这个函数+这个变量)就叫闭包
var a = 1;
function f4(){
console.log(a)
}
上面的代码函数f4使用了他外面的a变量所以是闭包
在另一个函数里使用当前函数的变量的场景
1.在当前函数里调用另一个函数,将当前函数的变量作为参数直接传给调用的函数
function a(){
var z = 1
b(z)
}
function b(z){
console.log(z)
}
a()//1
上面的代码在b函数里需要使用a里面的变量z,只需要在a里面调用b的时候把z变量作为形参传入即可
2.在当前函数中不调用另外一个函数,只需将另外一个函数中需要用到的变量,在当前函数中return 一下就可以,然后通过另一个函数调用这个函数将返回值赋值给一个变量即可
function a(){
var z = 1
return z
}
function b(){
//这里一定要将调用后的值赋值给一个新变量
var z = a()
console.log(z)
}
b()//1
上面的代码将a中的z变量作为返回值,然后b函数中调用这个a拿到了z的值,然后将z的值赋值给一个新的变量,因为一个变量只在一个函数里有效,所以函数的返回值得在另一个函数中通过一个变量接收
面向对象封装函数:如果其他函数里也需要用到这个变量的时候那么就需要用this,跨函数用this