JS函数
Posted Chris_在IT道路上前行
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS函数相关的知识,希望对你有一定的参考价值。
函数定义和调用
-
调用函数
由于javascript允许传入任意个参数而不影响调用,因此传入的参数个数和定义的参数个数不一致也没有问题,虽然函数内部并不需要这些参数:
var abs = function (x) { if (x >= 0) { return x; } else { return -x; } }; abs(10); // 返回10 abs(-9); // 返回9 abs(10, \'blablabla\'); // 返回10 abs(-9, \'haha\', \'hehe\', null); // 返回9 abs(); // 返回NaN
要避免收到undefined
,可以对参数进行检查:
function abs(x) { if (typeof x !== \'number\') { throw \'Not a number\'; } if (x >= 0) { return x; } else { return -x; } }
-
arguments
它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
实际上arguments
最常用于判断传入参数的个数
function foo(a, b, c) { if (arguments.length === 2) { console.log(2); } if(arguments.length ===3){ console.log(3); } } foo(1,2); foo(1,2,3);
-
rest
ES6标准引入了rest参数,来获取额外的参数。
rest参数只能写在最后,前面用...
标识(扩展运算符将一个数组,变为参数序列),从运行结果可知,传入的参数先绑定a
、b
,多余的参数以数组形式交给变量rest
function foo(a, b, ...rest) { console.log(\'a = \' + a); console.log(\'b = \' + b); console.log(rest); } foo(1, 2, 3, 4, 5); // 结果: // a = 1 // b = 2 // Array [ 3, 4, 5 ] foo(1); // 结果: // a = 1 // b = undefined // Array []
-
变量提升
\'use strict\'; function foo() { var x = \'Hello, \' + y; console.log(x); var y = \'Bob\'; } foo(); 结果:Hello, undefined
JavaScript引擎自动提升了变量y
的声明,但不会提升变量y
的赋值。
-
全局作用域
window
JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError
错误。
-
名字空间
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = \'myapp\'; MYAPP.version = 1.0; // 其他函数: MYAPP.foo = function () { return \'foo\'; };
把自己的代码全部放入唯一的名字空间MYAPP
中,会大大减少全局变量冲突的可能。
-
局部作用域
function foo() { for (var i=0; i<100; i++) { // } i += 100; // 仍然可以引用变量i console.log(i); } foo(); 结果: 200
为了解决块级作用域,ES6引入了新的关键字let
,用let
替代var
可以申明一个块级作用域的变量:
function foo() { for (let i=0; i<100; i++) { // } i += 100; console.log(i); } foo(); 结果:Uncaught ReferenceError: i is not defined
-
解构赋值
-
方法
var xiaoming = { name: \'小明\', birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; } }; xiaoming.age; // function xiaoming.age() xiaoming.age(); // 今年调用是25,明年调用就变成26了
以上代码,在一个方法内部,this
是一个特殊变量,它始终指向当前对象,也就是xiaoming
这个变量。
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: \'小明\', birth: 1990, age: getAge }; xiaoming.age(); // 25, 正常结果 getAge(); // NaN
以上方法,如果单独调用函数,比如getAge()
,此时,该函数的this
指向全局对象,也就是window
。在strict模式下让函数的this
指向undefined
注意,要保证this
指向正确,必须用obj.xxx()
的形式调用!
apply
要指定函数的this
指向哪个对象,可以用函数本身的apply
方法,
它接收两个参数,第一个参数就是这个对象将代替Function类里this对象,第二个参数是Array
,表示函数本身的参数。
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: \'小明\', birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
apply()
类似的方法是call()
,唯一区别是:参数列表不同
apply()
把参数打包成Array
再传入;
call()
把参数按顺序传入。
Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5
-
高阶函数
高阶函数英文叫Higher-order function:一个函数就可以接收另一个函数作为参数
function add(x, y, f) { return f(x) + f(y); } var x = add(-5, 6, Math.abs); // 11 console.log(x); 11
1.map/reduce
由于map()
方法定义在JavaScript的Array
中,我们调用Array
的map()
方法,传入我们自己的函数,就得到了一个新的Array
作为结果:
function pow(x) { return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
注意:map()
传入的参数是pow
,即函数对象本身。不是pow()。
Array的reduce()
把一个函数作用在这个Array
的[x1, x2, x3...]
上,这个函数必须接收两个参数,reduce()
把结果继续和序列的下一个元素做累积计算
var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; }); // 25 第一步:(1,3)第二步:(4,5)第三步:(9,7)第四步:(16,9)
要把[1, 3, 5, 7, 9]
变换成整数13579,reduce()
也能派上用场:
var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x * 10 + y; }); // 13579filter
2.filter
filter也是一个常用的操作,它用于把Array
的某些元素过滤掉,然后返回剩下的元素。
Array
的filter()
也接收一个函数。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是true
还是false
决定保留还是丢弃该元素。
例如,在一个Array
中,删掉偶数,只保留奇数,可以这么写:
var arr = [1, 2, 4, 5, 6, 9, 10, 15]; var r = arr.filter(function (x) { return x % 2 !== 0; }); r; // [1, 5, 9, 15]
3.sort
// 看上去正常的结果: [\'Google\', \'Apple\', \'Microsoft\'].sort(); // [\'Apple\', \'Google\', \'Microsoft\']; // apple排在了最后: [\'Google\', \'apple\', \'Microsoft\'].sort(); // [\'Google\', \'Microsoft", \'apple\'] // 无法理解的结果: [10, 20, 1, 2].sort(); // [1, 10, 2, 20]
第二个排序把apple
排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a
的ASCII码在大写字母之后。(A十进制表示65,a十进制表示97)
第三个排序结果是什么鬼?简单的数字排序都能错?
这是因为Array
的sort()
方法默认把所有元素先转换为String再排序,结果\'10\'
排在了\'2\'
的前面,因为字符\'1\'
比字符\'2\'
的ASCII码小。(0十进制表示48)
要按数字大小排序,我们可以这么写:
通常规定,对于两个元素x
和y
,如果认为x < y
,则返回-1
,如果认为x == y
,则返回0
,如果认为x > y
,则返回1
var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); console.log(arr); // [1, 2, 10, 20]
最后友情提示,sort()
方法会直接对Array
进行修改,它返回的结果仍是当前Array
:
var a1 = [\'B\', \'A\', \'C\']; var a2 = a1.sort(); a1; // [\'A\', \'B\', \'C\'] a2; // [\'A\', \'B\', \'C\'] a1 === a2; // true, a1和a2是同一对象
-
闭包
函数声明和函数表达式
1、Function Declaration(函数声明)必须以“function”开头。
ECMA 5(13.0)定义语法:
function Identifier ( FormalParameterList[opt] ) { FunctionBody }
函数名在自身作用域和父作用域内是可获取的
function bar() { return 3; } bar() //3 bar //function
2、Function Expression(函数表达式)将函数定义为表达式语句(通常是变量赋值)的一部分。
函数名(如果有的话)在作用域外是不可获取的
//anonymous function expression var a = function() { return 3; } //named function expression var a = function bar() { return 3; } //self invoking function expression (function sayHello() { alert("hello!"); })();
小测试题目:
function foo(){ function bar() { return 3; } return bar(); function bar() { return 8; } } alert(foo());//8 //Function declaration和function variable(函数变量)通常会被 JavaScript 解释器移(‘hoisted\')到当前作用域顶部 => function foo(){ //define bar once function bar() { return 3; } //redefine it function bar() { return 8; } //return its invocation return bar(); //8 } alert(foo());
function foo(){ var bar = function() { return 3; }; return bar(); var bar = function() { return 8; }; } alert(foo());// 3 //等号左边的Variable Declaration 会被提升,但是等号右边的 Assignment Expression(赋值表达式)不会 => //**Simulated processing sequence for Question 2** function foo(){ //a declaration for each function expression var bar = undefined; var bar = undefined; //first Function Expression is executed bar = function() { return 3; }; // Function created by first Function Expression is invoked return bar(); // second Function Expression unreachable bar = function() { return 8; }; } alert(foo()); //3
alert(foo()); //3 function foo(){ var bar = function() { return 3; }; return bar(); var bar = function() { return 8; }; } //函数声明foo()被提升了,函数表达式var bar = function() {return 8;};没有被提升 => function foo(){ var bar = function() { return 3; }; return bar(); var bar = function() { return 8; }; } alert(foo()); //3
function foo(){ return bar(); var bar = function() { return 3; }; var bar = function() { return 8; }; } alert(foo());//Uncaught TypeError: bar is not a function //函数没有提升,变量有提升 => //**Simulated processing sequence for Question 4** function foo(){ //a declaration for each function expression var bar = undefined; var bar = undefined; return bar(); //TypeError: "bar not defined" //neither Function Expression is reached } alert(foo());
函数声明和函数表达式的区别:
函数声明和函数表达式不同之处在于:
一、Javascript引擎在解析javascript代码时会‘函数声明提升’(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,
而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式;
fnName(); function fnName(){ ... } //正常,因为‘提升\'了函数声明,函数调用可在函数声明之前 fnName(); var fnName=function(){ ... } //报错,变量fnName还未保存对函数的引用,函数调用必须在函数表达式之后
二、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用 。
(function(a){ console.log(a); })(123); //function外面加括号表示函数表达式
函数作为返回值
1.实现一个对Array
的求和
function sum(arr) { return arr.reduce(function (x, y) { return x + y; }); } sum([1, 2, 3, 4, 5]); // 15
2.如果不需要立刻求和,而是在后面的代码中。可以不返回求和的结果,而是返回求和的函数!
function lazy_sum(arr) { var sum = function () { return arr.reduce(function (x, y) { return x + y; }); } return sum; }
3.当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数:
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
//返回的函数并没有立刻执行,而是直到调用了f()
才执行
4.调用函数f
时,才真正计算求和的结果:
f(); // 15
闭包的定义:在这个例子中,我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中。
注意:
当我们调用lazy_sum()
时,即使传入相同的参数,每次调用都会返回一个新的函数:
f1()
和f2()
的调用结果互不影响
var f1 = lazy_sum([1, 2, 3, 4, 5]); var f2 = lazy_sum([1, 2, 3, 4, 5]); f1 === f2; // false
闭包
闭包最大用处有两个:
1、是可以读取函数内部的变量
//闭包概念:函数嵌套函数 function t2() { var b=100; function t3() { console.log(b); } return t3(); } t2(); //100 在这段代码中,在函数t2内部声明的变量b本来是一个局部变量,为什么在调用时t3函数能打印出b变量的值呢:
在上面的代码中,函数t3就被包括在函数t2内部,这时t2内部的所有局部变量,对t3都是可见的。
但是反过来就不行,t3内部的局部变量,对t2就是不可见的。
这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。
所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
这就是闭包的其中一个作用,可以读取函数内部的一个变量。
2、借助闭包,同样可以封装一个私有变量
function create_counter(initial) { var x = initial || 0; return { inc: function () { x += 1; return x; } } } var c1 = create_counter(); c1.inc(); // 1 c1.inc(); // 2 c1.inc(); // 3 var c2 = create_counter(10); c2.inc(); // 11 c2.inc(); // 12 c2.inc(); // 13 //在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。
3、让这些变量的值始终保持在内存中
返回的函数在其定义内部引用了局部变量arr
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。
function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push(function () { return i * i; }); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; //结果不是1,4,9 f1(); // 16 f2(); // 16 f3(); // 16 //全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量(以上,arr中不要引用i)。
箭头函数
x => x * x 上面的箭头函数相当于: function (x) { return x * x; }
箭头函数相当于匿名函数,并且简化了函数定义。
箭头函数包含多条语句,这时候就不能省略{ ... }
和return
:
x => { if (x > 0) { return x * x; } else { return - x * x; } }
如果参数不是一个,就需要用括号()
括起来:
// 两个参数: (x, y) => x * x + y * y // 无参数: () => 3.14 // 可变参数: (x, y, ...rest) => { var i, sum = x + y; for (i=0; i<rest.length; i++) { sum += rest[i]; } return sum; }
如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错:
x => { foo: x }// SyntaxError: 因为和函数体的{ ... }有语法冲突,所以要改为: x => ({ foo: x })// ok:
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this
是词法作用域,由上下文确定。
由于JavaScript函数对this
绑定的错误处理,下面的例子无法得到预期结果:
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = function () { return new Date().getFullYear() - this.birth; // this指向window或undefined }; return fn(); } }; obj.getAge(); //NaN //原因是this指针只在getAge方法的函数内指向obj。 在函数内部定义的函数,this又指向undefined了!(在非strict模式下,它重新指向全局对象window!)
现在,箭头函数完全修复了this
的指向,箭头函数中的this
总是指向词法作用域,也就是外层调用者obj
:
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); } }; obj.getAge(); // 25
由于this
在箭头函数中已经按照词法作用域绑定了,所以,用call()
或者apply()
调用箭头函数时,无法对this
进行绑定,即传入的第一个参数被忽略:
var obj = { birth: 1990, getAge: function (year) { var b = this.birth; // 1990 var fn = (y) => y - this.birth; // this.birth仍是1990 return fn.call({birth:2000}, year);//birth:2000被忽略 } }; obj.getAge(2015); // 25
以上是关于JS函数的主要内容,如果未能解决你的问题,请参考以下文章