JS: function前面加!,引发思考

更新日期: 2023-04-26阅读: 924标签: 函数

简介

我们基本都知道,函数的声明方式有这两种

function msg(){alert('msg');}//声明式定义函数
var msg = function(){alert('msg');}//函数赋值表达式定义函数

但其实还有第三种声明方式,Function构造函数

var msg = new function(msg) {
  alert('msg')
}

等同于

function msg(msg) {
  alert('msg')
}

函数的调用方式通常是方法名()
但是,如果我们尝试为一个“定义函数”末尾加上(),解析器是无法理解的。

function msg(){
  alert('message');
}();//解析器是无法理解的

定义函数的调用方式应该是 print(); 那为什么将函数体部分用()包裹起来就可以了呢?
原来,使用括号包裹定义函数体,解析器将会以函数表达式的方式去调用定义函数。 也就是说,任何能将函数变成一个函数表达式的作法,都可以使解析器正确的调用定义函数。而 ! 就是其中一个,而 + - || ~ 都有这样的功能。

但是请注意如果用括号包裹函数体,然后立即执行。这种方式只适用一次性调用该函数,涉及到了一个作用域问题,当你想复用该函数的时候,会如下问题:


可如果你想复用该函数的话,就可按先声明函数,然后再调用函数,在同一个父级作用域下,可以复用该函数,如下:

var msg = function(msg) {}
msg();

关于这个问题,后面会进一步分析


function前面加 ! ?

自执行匿名函数:

在很多js代码中我们常常会看见这样一种写法:

(function( window, undefined ) {
    // code
})(window);

这种写法我们称之为自执行匿名函数。正如它的名字一样,它是自己执行自己的,前一个括号是一个匿名函数,后一个括号代表立即执行

前面也提到 + - || ~这些运算符也同样有这样的功能

(function () { /* code */ } ()); 
!function () { /* code */ } ();  
~function () { /* code */ } (); 
-function () { /* code */ } ();
+function () { /* code */ } ();

① ( ) 没什么实际意义,不操作返回值

② ! 对返回值的真假取反

③ 对返回值进行按位取反(所有正整数的按位取反是其本身+1的负数,所有负整数的按位取反是其本身+1的绝对值,零的按位取反是 -1。其中,按位取反也会对返回值进行强制转换,将字符串5转化为数字5,然后再按位取反。 false被转化为0,true会被转化为1。 其他非数字或不能转化为数字类型的返回值,统一当做0处理)

④ ~ +、- 是对返回值进行数学运算 ( 可见返回值不是数字类型的时候 +、- 会将返回值进行强制转换,字符串强制转换后为NaN)

先从IIFE开始介绍 (注:这个例子是参考网上)

IIFE(Imdiately Invoked Function Expression 立即执行的函数表达式)

function(){
    alert('IIFE');
}

把这个代码放在console中执行会报错


因为这是一个匿名函数,要想让它正常运行就必须给个函数名,然后通过函数名调用。
其实在匿名函数前面加上这些符号后,就把一个函数声明语句变成了一个函数表达式,是表达式就会在script标签中自动执行

所以现在很多对代码压缩和编译后,导出的js文件通常如下:

(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i=">replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do 

运算符

也许这里有人会疑惑,运算符为何能将声明式函数,转译成函数表达式,这里就涉及到了一个概念解析器

程序在运行之前需要经过编译或解释的过程,把源程序翻译成为字节码,但是在翻译之前,需要把字符串形式的程序源码解析为语法树或者抽象语法树等数据结构,这就需要用到解析器

那么什么是解析器?

所谓解析器(Parser),一般是指把某种格式的文本(字符串)转换成某种数据结构的过程。最常见的解析器(Parser),是把程序文本转换成编译器内部的一种叫做抽象语法树(AST)的数据结构,此时也叫做语法分析器(Parser)。也有一些简单的解析器(Parser),用于处理CSV、JSON,XML之类的格式

JS解析器在执行第一步预解析的时候,会从代码的开始搜索直到结尾,只去查找var、function和参数等内容。一般把第一步称之为“JavaScript的预解析”。而且,当找到这些内容时,所有的变量,在正式运行代码之前,都提前赋了一个值:未定义;所有的函数,在正式运行代码之前,都是整个函数块。让解析器识别到是一个表达式,那就得加上特殊符号来让其解析器识别出来,比如刚才提到的特殊运算符。

解析过程大致如下:

1、“找一些东西”: var、 function、 参数;(也被称之为预解析)

备注:如果遇到重名分为以下两种情况:遇到变量和函数重名了,只留下函数;遇到函数重名了,根据代码的上下文顺序,留下最后一个。

2、逐行解读代码。

备注:表达式可以修改预解析的值 (可以自行查阅文档,这就是后面说到的内容)

函数声明与函数定义

函数声明 一般相对规范的声明形式为:fucntion msg(void) 注意是有分号

function msg() 

函数定义 function msg()注意没有分号

{
    alert('IIFE');
}

函数调用

这样是一个函数调用

msg();

函数声明加一个()就可以调用函数了

function msg(){
    alert('IIFE');
}()

就这样
但是我们按上面在console中执行发现出错了


因为这样的代码混淆了函数声明和函数调用,以这种方式声明的函数 `msg`,就应该以 `msg()` 的方式调用。

若改成(function msg())()就是这样的一个结构体: (函数体)(IIFE),能被Javascript的解析器识别并正常执行

从Js解析器的预解析过程了解到:

解析器都能识别一种模式:使用括号封装函数。对于解析器来说,这几乎总是一个积极的信号,即函数需要立即执行。如果解析器看到一个左括号,紧接着是一个函数声明,它将立即解析这个函数。可以通过显式地声明立即执行的函数来帮助解析器加快解析速度

那么也就是说,括号的作用,就是将一个函数声明,让解析器识别为一个表达式,最后由程序执行这个函数


总结

任何消除函数声明和函数表达式间歧义的方法,都可以被Javascript解析器正确识别

赋值,逻辑,甚至是逗号,各种操作符,只要是解析器支持且用来识别的特殊符号都可以用作消除歧义的方式方法,而!function() 和(function()), 都是其中转换成表达式的一种方式。

测试

至于优先使用哪一个,推荐(), 而其他运算符,相对于多了一步执行步骤,比如+(表达式),那就是,立即执行+运算符运算, 大致测了一下:


结论

从测试结果的截图中我们能大致的看到,(IIFE)方式,比运算符快的是一个级别(进一位数的速度),如果说立即执行()的时间复杂度是O(n),那么运算符就是O(10n),当然这也只是粗略的测试,而且在现有的浏览器解析速度,时间基数小到可以忽略不计,所以看个人需求,写法就是萝卜白菜,大家各有所好,看个人

作者:糖墨夕
链接:https://juejin.cn/post/7203734711780081722

链接: https://fly63.com/article/detial/12453

JavaScript 函数式编程

我理解的 JavaScript 函数式编程,都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

Js函数式编程,给你的代码增加一点点函数式编程的特性

给你的代码增加一点点函数式编程的特性,最近我对函数式编程非常感兴趣。这个概念让我着迷:应用数学来增强抽象性和强制纯粹性,以避免副作用,并实现代码的良好可复用性。同时,函数式编程非常复杂。

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

JavaScript函数创建的细节

如果你曾经了解或编写过JavaScript,你可能已经注意到定义函数的方法有两种。即便是对编程语言有更多经验的人也很难理解这些差异。在这篇博客的第一部分,我们将深入探讨函数声明和函数表达式之间的差异。

编写小而美函数的艺术

随着软件应用的复杂度不断上升,为了确保应用稳定且易拓展,代码质量就变的越来越重要。不幸的是,包括我在内的几乎每个开发者在职业生涯中都会面对质量很差的代码。这些代码通常有以下特征:

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

JavaScript中函数的三种定义方法

函数的三种定义方法分别是:函数定义语句、函数直接量表达式和Function()构造函数的方法,下面依次介绍这几种方法具体怎么实现,在实际编程中,Function()构造函数很少用到,前两中定义方法使用比较普遍。

js在excel的编写_excel支持使用JavaScript自定义函数编写

微软 称excel就实现面向开发者的功能,也就是说我们不仅可以全新定义的公式,还可以重新定义excel的内置函数,现在Excel自定义函数增加了使用 JavaScript 编写的支持,下面就简单介绍下如何使用js来编写excel自定义函数。

js中的立即执行函数的写法,立即执行函数作用是什么?

这篇文章主要讲解:js立即执行函数是什么?js使用立即执行函数有什么作用呢?js立即执行函数的写法有哪些?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!