JS函数声明方法
今天看到了一个自己关注了的大神给我回了私信,觉得自己仿佛摸到了大神的裤腿,哈哈,而且人还特别好,居然会给小菜鸟回私信,特别开心呀,一个菜鸟的小激动,言归正传啦
1.最为常见的函数声明的方式,function+函数名称标识符
function func1([参数]){/*函数体*/}
2.将匿名函数赋给一个变量,调用方式:func2(参数)
var func2 = function([参数]){/*函数体*/}
3.定义一个函数func4,将它赋给变量func3,调用方法为:func3([参数]);或者func4([参数])
var func3 = function func4([参数]){/*函数体*/}
4.声明func5为一个对象
var func5 = new Function();
1.函数声明:
function sum1(n1,n2){
return n1 + n2;
}
2.函数表达式(函数字面量)
var sum2 = function(n1,n2){
return n1 + n2;
二者存在区别:
解析器会先解读函数声明,并使其在执行代码之前可访问,而函数表达式则必须等到解析器执行到它所在的代码才能被真正解释执行
例如:
//函数预解析,使其在执行代码前可以访问
func1()
function func(a){
alert(a);
}
</script>
<script>
//会提示func1未定义
func1(1);
var func = function(a){
alert(a);
}
之所以会出现上面的状况,是因为在JavaScript中,存在“预解析”行为,深入理解预解析(看了一篇文章深有感悟)
一.变量和函数在内存中的展示
JavaScript中的变量和其他语言一样,有基本数据类型和引用数据类型。基本数据类型:undefined,null,boolean,string,Number,引用数据类型主要是对象(包括{},[],/^$/,Date,Function)
var num = 24;
var obj = {name:'iceam',age:24};
function func(){
console.log('hello world');
}
当浏览器加载html界面时,首先会提供一个全局JavaScript代码执行的环境,称之为全局作用域。
基本数据类型按照值来操作,引用数据类型按照地址来操作
基本类型是直接存储在栈的内存当中,而对象是存储在堆内存中,变量只持有该对象的地址。所以obj持有一个对象地址oxff44,而函数func持有一个地址0xff66
console.log(func);
console.log(func());
第一行输出的是整个函数定义的部分(也就是指函数本身)
function func(){
cnsole.log('hello world');
}
func存储的是一个地址,改地址指向一块堆内存,该堆就保留了函数的定义
第二行输出的是函数返回的结果
undefined
二.预解析
含义:在当前作用域中,JavaScript执行代码之前,浏览器回默认把所有带var和function声明的变量进行提前声明或者定义
var num = 24;
1.声明:var num;告诉浏览器,在全局作用域中有num变量了,如果一个变量只是声明了,但是没有赋值,默认值是undefined
2.定义:num = 12;定义即给变量赋值
2.2. var声明的变量和function声明的函数在预解析的区别
var声明的变量和function声明的函数在预解析的时候有区别,var声明的变量在预解析的时候只是提前的声明,function声明的函数在预解析的时候会提前声明并且会同时定义。也就是说var声明的变量和function声明的函数的区别是在声明的同时有没同时进行定义。
2.3. 预解析只发生在当前的作用域下
程序最开始的时候,只对window下的变量和函数进行预解析,只有函数执行的时候才会对函数中的变量函数进行预解析。
以例题作为分析:
console.log(num);
var num = 24;
console.log(num);
func(100,200);
function func(num1,num2){
var total = num1 + num2;
console.log(total);
}
输出结果:
第一次输出num,由于预解析的原因,把变量num的声明提前了,定义(赋值)不会提前,所以,第一次输出num是undefined,代码执行到第二次输出num时,num已经定义了,直接输出num的值
由于函数的声明和定义是同时进行的,所以func()虽然是在func函数定义声明之前调用的,但是依然可以正常调用
三、 作用域链
先理解以下三个概念:
函数里面的作用域成为私有作用域,window所在的作用域称为全局作用域;
在全局作用域下声明的变量是全局变量;
在“私有作用域中声明的变量”和“函数的形参”都是私有变量;
在私有作用域中,代码执行的时候,遇到了一个变量,首先需要确定它是否为私有变量,如果是私有变量,那么和外面的任何东西都没有关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直查找到window为止,这就是作用域链。
当函数执行的时候,首先会形成一个新的私有作用域,然后按照如下的步骤执行:
如果有形参,先给形参赋值;
进行私有作用域中的预解析;
私有作用域中的代码从上到下执行
函数形成一个新的私有的作用域,保护了里面的私有变量不受外界的干扰(外面修改不了私有的,私有的也修改不了外面的),这也就是闭包的概念。
console.log(total);
var total = 0;
function func(num1,num2){
console.log(total);
var total = num1 + num2;
console.log(total);
}
func(100,200);
console.log(total);
以上代码执行的时候,第一次输出total的时候会输出undefined(因为预解析),当执行func(100,200)的时候,会执行函数体里的内容,此时func函数会形成一个新的私有作用域,按照之前描述的步骤:
先给形参num1、num2赋值,分别为100、200;
func中的代码进行预解析;
执行func中的代码
因为在func函数内进行了预解析,所以func函数里面的total变量会被预解析,在函数内第一次输出total的时候,会输出undefined,接着为total赋值了,第二次输出total的时候就输出300。 因为函数体内有var声明的变量total,函数体内的输出total并不是全局作用域中的total。
最后一次输出total的时候,输出0,这里输出的是全局作用域中的total。
在全局作用域中声明变量带var可以进行预解析,所以在赋值的前面执行不会报错;声明变量的时候不带var的时候,不能进行预解析,所以在赋值的前面执行会报错。
console.log(num1);
var num1 = 12;
console.log(num2);
num2 = 12;
num2 = 12;相当于给window增加了一个num2的属性名,属性值是12;
var num1 = 12;相当于给全局作用域增加了一个全局变量num1,但是不仅如此,它也相当于给window增加了一个属性名num,属性值是12
注意:JS中,如果在不进行任何特殊处理的情况下,上面的代码报错,下面的代码不再执行
五、 预解析中的一些变态机制
5.1 不管条件是否成立,都要把带var的进行提前的声明
if (!('num' in window)) {
var num = 12;
}
console.log(num); // undefined
JavaScript进行预解析的时候,会忽略所有if条件,因为在ES6之前并没有块级作用域的概念。本例中会先将num预解析,而预解析会将该变量添加到window中,作为window的一个属性。那么 ‘num’ in window 就返回true,取反之后为false,这时代码执行不会进入if块里面,num也就没有被赋值,最后console.log(num)输出为undefined。
5.2 只预解析“=”左边的,右边的是指,不参与预解析
fn(); // -> undefined(); // Uncaught TypeError: fn is not a function
var fn = function () {
console.log('ok');
}
fn(); -> 'ok'
function fn() {
console.log('ok');
}
fn(); -> 'ok'
5.3 自执行函数:定义和执行一起完成
(function (num) {
console.log(num);
})(100);
自治性函数定义的那个function在全局作用域下不进行预解析,当代码执行到这个位置的时候,定义和执行一起完成了。
补充:其他定义自执行函数的方式
~ function (num) {}(100)
+ function (num) {}(100)
- function (num) {}(100)
! function (num) {}(100)
5.4 return下的代码依然会进行预解析
function fn() {
console.log(num); // -> undefined
return function () {
};
var num = 100;
}
fn();
函数体中return下面的代码,虽然不再执行了,但是需要进行预解析,return中的代码,都是我们的返回值,所以不进行预解析。
5.5 名字已经声明过了,不需要重新的声明,但是需要重新的赋值
var fn = 13;
function fn() {
console.log('ok');
}
fn(); // Uncaught TypeError: fn is not a function
解决方法:
var fn = 13;
var a = function fn(){
console.log('ok');
}
a();//输出ok