JS做为一种弱类型语言,在定义函数时相对于其余语言比较灵活,有多种声明函数的方式,方法不一样,天然会出现一些细微的差异,本篇内容就一块儿来探探坑。bash
// 语法格式:fn1 - 函数名, console语句 - 函数体
function fn1(name) {
console.log(`${name}的函数体`);
}
复制代码
// 语法格式:fn2 - 变量名, function - 匿名函数, console语句 - 函数体
var fn2 = function(name) {
console.log(`${name}的函数体`);
}
复制代码
// 语法格式:fn3 - 变量名, Function - 构造函数
var fn3 = new Function('name','console.log(`${name}的函数体`)');
复制代码
fn1('fn1'); // fn1的函数体
fn2('fn2'); // fn2的函数体
fn3('fn3'); // fn3的函数体
复制代码
我只是想声明一个函数啊,怎么有点懵,一会儿整出来三个,看起来都很好用彻底没问题了啊。闭包
接下来咱们放入一个简单场景,求和。app
好吃的栗子:函数
// 简单,求两个数的和
console.log(fn1(10, 10)); // 20
console.log(fn2(10, 10)); // Uncaught TypeError: fn2 is not a function
function fn1(a, b) {
return a + b;
}
var fn2 = function 函数名(a, b) {
return a + b;
}
复制代码
不对啊,明明刚才都好用...ui
console.log(typeof fn1); // function
console.log(typeof fn2); // undefined
复制代码
看一眼fn1
和fn2
是啥this
console.log(fn1); // 'ƒ fn1(a, b) {return a + b;}'
console.log(fn2); // 'ƒ (a, b) {return a + b;}'
复制代码
函数位于一个初始化语句中,不是一个函数声明,不会被提早,而只会把var fn2
提早,用typeof
操做符显示fn2
是undefined
,因此报错(fn3
同理)。spa
注意: 在fn2中出现的函数名
一样不可以被访问,存在的缘由是 - 若是该函数体内的代码报错,会提示该函数名; 递归匿名函数时你须要它。(手动滑稽)翻译
原理: 解释器会率先读取函数声明,并使其在执行以前能够访问,而使用表达式则必须等到解析器执行到它所在的代码行,才会真正被解释执行,函数声明会正常执行code
可是....既然是匿名函数,那彻底能够这么写:对象
// 声明后当即执行
var fn2 = function(a, b) {
return a + b;
}(10, 25)
console.log(fn2); // 35
复制代码
function fn1(a, b) {
return a + b;
}(10, 25)
// Uncaught SyntaxError: Unexpected token )
复制代码
补1(若是我就想让声明函数自调用怎么办呢)
我只是想声明一个函数啊,为何要搞这么多事情,那new Function()
老是没毛病的吧,你们再怎么牛,都是继承这位来的。
经过函数表达式定义的函数和经过函数声明定义的函数只会被解析一次,而Function构造函数定义的函数却不一样。也就是说,每次构造函数被调用,传递给Function构造函数的函数体字符串都要被解析一次 。虽然函数表达式每次都建立了一个闭包,但函数体不会被重复解析,所以函数表达式仍然要快于
new Function(...)
。 因此Function构造函数应尽量地避免使用。 来源:MDN
汉译汉: 简单粗暴的翻译一下,同内容下,声明函数最快,直接调用,函数表达式每次调用都会建立一个闭包,可是函数体不会再次解析,new表达式每次调用函数体都要解析一遍。
为何会每次都解析一遍函数体呢? 由于JS解释器在解析代码时是分块执行,不管声明仍是仍是函数表达式,在第一次被解析完成后,都被看作'一块代码',构造函数采用'动态解析',每次都认为是全新的字符串,从新解析。 (但但是,若是构造函数中含有
function
,那么这里的function
不会被再次解析)
好的,那么看一下这段代码 可怕的栗子:
var isSay= true;
if (isSay) {
function say() {
console.log('Hello')
}
}
say(); // Hello
复制代码
彻底没毛病,你想干啥?(声明函数能够在执行代码前声明,函数表达式要执行到才能够赋值函数体)
那么。。。
say(); // Uncaught TypeError: say is not a function
var isSay= true;
if (say) {
function say() {
console.log('Hello')
}
}
复制代码
不对啊亲,我老老实实凭实力声明的函数,怎么就很差用了啊。 固然还有。。。
play(); // Uncaught TypeError: play is not a function
for( let item of new Array(10)) {
function play() {
console.log('happy')
}
}
复制代码
函数声明很是容易(常常是意外地)转换为函数表达式。当它再也不是一个函数声明: 成为表达式的一部分,再也不是函数或者脚本自身的“源元素” (source element)。“源元素”是脚本或函数体中的非嵌套语句。 来源:MDN
有多容易转变呢,基本上除了脚本或函数体之外,嵌套进去了本身就变了。。。
上面简单而艰难的建立过程,咱们彷佛忽略了一个状况,那就是同名函数不一样声明,到底谁是老大(优先级);
眼花的栗子:
// fn1(); // fn1声明2
var fn1 = function() {
console.log('fn1 表达式1')
}
function fn1() {
console.log('fn1声明1')
}
var fn1 = function() {
console.log('fn1 表达式2')
}
function fn1() {
function fn1() {
console.log('fn1声明3')
}
console.log('fn1声明2')
}
fn1(); // fn1 表达式2
复制代码
总结:
fn1
时,函数表达式未执行,var
变量提高但未报错,说明函数声明的提高比var要高。(说了解析前就声明了,var
也只能算解析中)fn1
声明函数的函数体覆盖第一个;fn1
时,声明函数已经优先声明fn1
后被赋值,最终赋值有效;构造函数
?不是告诉你不要用了吗。(应用场景决定它不会被如此错误的使用;补2)终于,能够安心 不报错 建立函数了
补1:
()
自调用是由于解析器找不到函数名,又没有对应的函数表达式,因此能够经过将声明函数所有用()
括起来或者以+ ~ !
等特殊符号加在function
关键字前,将声明函数转为表达式便可。补2:
new Function
经过实例化一个Function原型,获得一个数据类型为function的对象,也就是一个函数,而该变量就是函数名,这原本和咱们最初的目标建立一个函数时符合的,可是new
操做符自己会改变关键字this
指向,会对建立的函数形成未知影响。new Function
时,函数体内的字符串会被解析为脚本语言并执行,也是有可能对平常开发产生影响的缘由之一,所以在平常开发中不建议使用构造函数引用: