JS 我只是想建立一个函数(声明函数 函数表达式 构造函数)

JS做为一种弱类型语言,在定义函数时相对于其余语言比较灵活,有多种声明函数的方式,方法不一样,天然会出现一些细微的差异,本篇内容就一块儿来探探坑。bash

1、 经常使用的function语句(声明函数)

// 语法格式:fn1 - 函数名, console语句 - 函数体
    function fn1(name) {
        console.log(`${name}的函数体`);
    }
复制代码

2、 函数表达式(函数字面量)

// 语法格式:fn2 - 变量名, function - 匿名函数, console语句 - 函数体
    var fn2 = function(name) {
        console.log(`${name}的函数体`);
    }

复制代码

3、 Function() 构造函数

// 语法格式:fn3 - 变量名, Function - 构造函数
    var fn3 = new Function('name','console.log(`${name}的函数体`)');
复制代码

4、执行结果

fn1('fn1'); // fn1的函数体
    fn2('fn2'); // fn2的函数体
    fn3('fn3'); // fn3的函数体
复制代码

5、 你已经达到目的,成功的声明了函数

我只是想声明一个函数啊,怎么有点懵,一会儿整出来三个,看起来都很好用彻底没问题了啊。闭包

接下来咱们放入一个简单场景,求和。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
复制代码

看一眼fn1fn2是啥this

console.log(fn1); // 'ƒ fn1(a, b) {return a + b;}'
    console.log(fn2); // 'ƒ (a, b) {return a + b;}'
复制代码

函数位于一个初始化语句中,不是一个函数声明,不会被提早,而只会把var fn2提早,用typeof操做符显示fn2undefined,因此报错(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不会被再次解析)

6、道理我都懂了,别问,问就声明函数建立起来

好的,那么看一下这段代码 可怕的栗子:

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

有多容易转变呢,基本上除了脚本或函数体之外,嵌套进去了本身就变了。。。

7、最后一个坑了,填完就建立

上面简单而艰难的建立过程,咱们彷佛忽略了一个状况,那就是同名函数不一样声明,到底谁是老大(优先级);

眼花的栗子:

// 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时,函数体内的字符串会被解析为脚本语言并执行,也是有可能对平常开发产生影响的缘由之一,所以在平常开发中不建议使用构造函数

引用:

  1. MDN
相关文章
相关标签/搜索