1. 简介 下面是一个常见的js函数:
1 2 3 function a ( ) {console .log ("a" )}function b ( ) {console .log ("a" )}function c ( ) {console .log ("a" )}
而我们在网站中,常看到:
1 2 3 4 5 6 7 8 9 10 !function (i ) { function n (t ) { return i[t].call (a, b, c, d) } }([ function (t, e ) {}, function (t, e, n ) {}, function (t, e, r ) {}, function (t, e, o ) {} ]);
上面的代码,我们对其进行简化:
1 2 3 4 5 6 7 8 9 10 !function (allModule ) { function useModule (whichModule ) { allModule[whichModule].call (null , "hello world!" ); } useModule (0 ) }([ function module0 (param ) {console .log ("module0: " + param)}, function module1 (param ) {console .log ("module1: " + param)}, function module2 (param ) {console .log ("module2: " + param)}, ]);
运行上面代码,输出:moddul0:hello world!
上面主要用到了!function(){}()
和function.call()
2. 函数的声明和表达式 创建函数,有两种方法:
2.1 函数声明 函数声明是通过使用 function
关键字后跟函数名称的方式来创建函数。函数声明会被提升到所在作用域的顶部,可以在声明之前调用。
1 2 3 4 5 6 test ("Hello World!" ) function test (arg ) { console .log (arg) }
2.2 函数表达式 函数表达式: 函数表达式是将函数赋值给变量或属性的方式来创建函数。函数表达式不会被提升,只能在赋值之后 调用。
1 2 3 4 5 var test = function (arg ) { console .log (arg) } test ("Hello World!" )
函数表达式也可以使用箭头函数来定义:
1 2 3 var myFunction = ( ) => { };
函数表达式还可以作为立即执行函数(Immediately Invoked Function Expression,IIFE )使用:
需要注意的是,在使用函数表达式时 ,需要记住使用分号作为语句的结束符 ,以避免可能的错误。
2.3 立即执行函数(IIEF) 函数表达式创建以后立即执行,当函数变成立即执行表达式时,表达式中的变量不能从外部访问。IIFE主要用来隔离作用域,避免污染。
2.3.1 常见格式 说明: 我们将函数简写为SH,即
1 2 3 function ( ) { console .log ("I AM IIFE" ) }
匿名函数前面加上一元操作符,后面加上():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 !function ( ) { console .log ("I AM IIFE" ) }(); -function ( ) { console .log ("I AM IIFE" ) }(); +function ( ) { console .log ("I AM IIFE" ) }(); ~function ( ) { console .log ("I AM IIFE" ) }();
简写:
匿名函数后面加上(),然后再用()将整个括起来:
1 2 3 (function ( ) { console .log ("I AM IIFE" ) }());
简写:
先用 ()
将匿名函数括起来,再在后面加上 ()
:
1 2 3 (function ( ) { console .log ("I AM IIFE" ) })();
简写:
使用箭头函数表达式,先用()将箭头函数表达式括起来,再在后面加上():
1 2 3 (() => { console .log ("I AM IIFE" ) })()
简写:
匿名函数前面加上 void
关键字,后面加上 ()
, void
指定要计算或运行一个表达式,但是不返回值:
1 2 3 void function ( ) { console .log ("I AM IIFE" ) }();
有时候,还能看到立即执行函数前后分号的情况,如:
1 2 3 4 5 6 7 ;(function ( ) { console .log ("I AM IIFE" ) }()) ;!function ( ) { console .log ("I AM IIFE" ) }()
有效于前面的代码隔离,否则可能会出现意外的情况。
2.3.2 参数传递 将参数放在末尾即可实现参数传递:
eg1:
1 2 3 4 5 6 7 var text = "I AM IIFE" ;(function (param ) { console .log (param) })(text);
eg2:
1 2 3 4 5 6 7 var dict = {name : "Bob" , age : "20" };(function ( ) { console .log (dict.name ); })(dict);
eg3:
1 2 3 4 5 6 7 8 9 10 11 var list = [1 , 2 , 3 , 4 , 5 ];(function ( ) { var sum = 0 ; for (var i = 0 ; i < list.length ; i++) { sum += list[i]; } console .log (sum); })(list);
2.4 call() / apply() / bind() 他们都是比较常用的方法,作用一模一样,即,改变函数中的this指向 ,区别:
call() 方法会立即执行这个函数,接受一个或多个参数,参数之间用逗号隔开;
apply()方法会立即执行这个函数,接受一个包含多个参数的数组;
bind()方法不会 立即执行这个函数,返回一个修改后的函数,便于稍后调用,接受的参数和call一样。
2.4.1 call() call()
方法接受多个参数,第一个参数 thisArg 指定了函数体内 this 对象的指向,如果这个函数处于非严格模式下,指定为 null 或 undefined 时会自动替换为指向全局对象(浏览器中就是 window 对象),在严格模式下,函数体内的 this 还是为 null。从第二个参数开始往后,每个参数被依次传入函数,基本语法如下:
1 function .call (thisArg, arg1, arg2, ...)
eg1:
1 2 3 4 5 function test (a, b, c ) { console .log (a + b + c) } test.call (null , 1 , 2 , 3 )
eg2:
1 2 3 4 5 6 function test ( ) { console .log (this .firstName + " " + this .lastName ) } var data = {firstName : "John" , lastName : "Doe" }test.call (data)
2.4.2 apply() apply()
方法接受两个参数,第一个参数 thisArg 与 call()
方法一致,第二个参数为一个带下标的集合,从 ECMAScript 第5版开始,这个集合可以为数组,也可以为类数组,apply()
方法把这个集合中的元素作为参数传递给被调用的函数,基本语法如下:
1 function .apply (thisArg, [arg1, arg2, ...])
eg1:
1 2 3 4 5 function test (a, b, c ) { console .log (a + b + c) } test.apply (null , [1 , 2 , 3 ])
eg2:
1 2 3 4 5 6 function test ( ) { console .log (this .firstName + " " + this .lastName ) } var data = {firstName : "John" , lastName : "Doe" }test.apply (data)
2.4.3 bind() bind()
方法和 call()
接受的参数是相同的,只不过 bind()
返回的是一个函数 ,基本语法如下:
1 function .bind (thisArg, arg1, arg2, ...)
eg1:
1 2 3 4 5 function test (a, b, c ) { console .log (a + b + c) } test.bind (null , 1 , 2 , 3 )()
eg2:
1 2 3 4 5 6 function test ( ) { console .log (this .firstName + " " + this .lastName ) } var data = {firstName : "John" , lastName : "Doe" }test.bind (data)()
2.5 重新认识webpack 我们重新看前面webpack中的函数的写法:
1 2 3 4 5 6 7 8 9 10 !function (allModule ) { function useModule (whichModule ) { allModule[whichModule].call (null , "hello world!" ); } useModule (0 ) }([ function module0 (param ) {console .log ("module0: " + param)}, function module1 (param ) {console .log ("module1: " + param)}, function module2 (param ) {console .log ("module2: " + param)}, ]);
整体是一个IIFE立即执行函数,传递一个数组,里面包含三个方法,视为三个模块,IIFE里面包含一个useModule()
函数,将其视为模块加载器 ,即需要用哪个模块,即调用哪个,例中表示调用第一个模块,函数里面使用call()
方法改变函数中的this
指向并传递参数,调用相应的模块,并输出。
2.6 动手做 下面是一段加密参数的webpack模块化写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 CryptoJS = require ("crypto-js" )!function (func ) { function acvs ( ) { var kk = func[1 ].call (null , 1e3 ); var data = { r : "I LOVE PYTHON" , e : kk, i : "62bs819idl00oac2" , k : "0123456789abcdef" } return func[0 ].call (data); } console .log ("加密文本:" + acvs ()) function odsc (account ) { var cr = false ; var regExp = /(^\d{7,8}$)|(^0\d{10,12}$)/ ; if (regExp.test (account)) { cr = true ; } return cr; } function mkle (account ) { var cr = false ; var regExp = /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/ ; if (regExp.test (account)) { cr = true ; } return cr; } }([ function ( ) { for (var n = "" , t = 0 ; t < this .r .length ; t++) { var o = this .e ^ this .r .charCodeAt (t); n += String .fromCharCode (o) } return encodeURIComponent (n) }, function (x ) { return Math .ceil (x * Math .random ()) }, function (e ) { var a = CryptoJS .MD5 (this .k ); var c = CryptoJS .enc .Utf8 .parse (a); var d = CryptoJS .AES .encrypt (e, c, { iv : this .i }); return d + "" }, function (e ) { var b = CryptoJS .MD5 (this .k ); var d = CryptoJS .enc .Utf8 .parse (b); var a = CryptoJS .AES .decrypt (e, d, { iv : this .i }).toString (CryptoJS .enc .Utf8 ); return a } ]);
有了前面的基础,直接将其中的函数传递进行,改写如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function a (r, e ) { for (var n = "" , t = 0 ; t < r.length ; t++) { var o = e ^ r.charCodeAt (t); n += String .fromCharCode (o) } return encodeURIComponent (n) } function b (x ) { return Math .ceil (x * Math .random ()) } function acvs ( ) { var kk = b (1e3 ); var r = "I LOVE PYTHON" ; return a (r, kk); } console .log ("加密文本:" + acvs ())
参考 [1] 爬虫逆向基础,理解 JavaScript 模块化编程 webpack - 掘金 (juejin.cn)