cap10 函数

函数也是对象,在JS中是一等公民,并且不推荐利用Function构造函数来创建函数。这样会阻止JS优化

10.1 箭头函数

ES6中新增了箭头函数,很大程度上箭头函数实例化的函数对象与函数表达式创建的函数对象行为是相同的。任何可以使用函数的地方都可以使用箭头函数

(换成人话就是箭头函数与普通函数是相同的)

sample 1

  1. let arrowSum=(a,b)=>{
  2. return a+b;
  3. }
  4. let funSum=function(a,b){
  5. return a+b;
  6. }

sample 2 箭头函数非常适应嵌入的场景

  1. let ints=[1,2,3];
  2. console.log(ints.map(function(i){return i+1}));
  3. cosole.log(ints.map((i)=>{return i+1}));

上面两个函数一样的,都返回2 3 4

sample 3如果只有一个参数可以不用括号,没有参数或者多个参数的情况下才需要括号

  1. let ints=[2,3,5];
  2. console.log(ints.map(i=>{return i*i}));

箭头函数虽然可以省略大括号,大括号里面是函数体,但是不建议省略。一是代码可读性。二是代码安全性。箭头函数虽然简洁,但是不能使用arguments、super和new target,也不能作为构造函数。而且箭头函数也没有prototype属性

10.2函数名

函数名就是指向函数的指针,这意味着函数可以有多个名。《JS高程》

  1. function sum(){
  2. return a+b;
  3. }
  4. let suma=sum;
  5. console.log(suma(1,2)); //返回3

10.3理解函数

1、arguments对象

Function.arguments和arguments对象不一样,前者已经被web标准删除

arguments是一个对应于传递给函数的参数的类数组的对象,typeof返回的是object

  1. function sum(a,b,c){
  2. for(let i=0;i<arguments.length;i++){
  3. console.log(arguements[i]);
  4. }
  5. }
  6. fun(1,2,3); //输出 1 2 3

10.4 么有函数重载

如果JS中定义了两个同名的函数,则后定义的会覆盖原来先定义的。

  1. function sum(val){
  2. return val+100;
  3. }
  4. function sum(val){
  5. return val+200;
  6. }
  7. sum(100); //返回300

把函数名当做指针,也就明白了后面那个函数名保存了新的函数体

10.5 默认参数值

在ES5以前,实现默认的一种常用方式就是检测某个参数是否等于undefined,如果没有传入就默认一个值

sample 默认参数值

  1. //ES 5
  2. function retName(name){
  3. name=(typeof name!='undefined')?name:'木有名字';
  4. return `${木有名字}`
  5. }
  6. console.log(retName()); //返回木有名字
  7. //ES 6
  8. function retName(name="木有名字"){
  9. return `${name}`;
  10. }
  11. console.log(retName()); //返回木有名字

在使用默认参数值时,arguments并不反应参数值。arguments只以传入函数的值为准

  1. let sum=function(a,b=2,c=3){
  2. for(let i=0;i<3;i++){
  3. console.log(arguments[i]);
  4. }
  5. return a+b+c;
  6. }
  7. console.log(sum(1));
  8. //返回 1 undefined undefined 6

默认参数可以调用别的函数的返回值

  1. let square=function(a=1){
  2. return a*a;
  3. }
  4. let add=function(a=0){
  5. return a+a;
  6. }
  7. let sum=function(a,b=square(a),c=add(a)){
  8. return a+b+c;
  9. }
  10. console.log(sum(5)); //返回40 =5 + 25 +10

10.6 扩展参数与收集collect

10.6.1 扩展参数

在给函数传参时,有时候可能不需要传一个数组。而是要分别传入数组的元素

  1. let values=[1,3,5,7,9];
  2. let sum=function(){
  3. let sum=0;
  4. for(let i=0;i<arguments.length;i++){
  5. sum+=arguments[i];
  6. }
  7. return sum;
  8. }

上面的sum函数希望将所有加数逐个传进来,然后通过迭代arguments对象来实现累加。如果不使用扩展操作符,想定义在这个函数的数组拆分,那么就得运用apply()方法

  1. console.log(sum.apply(null,values));

在ES6中,可以通过扩展操作符极为简洁的实现这种操作,对可迭代对象应用扩展操作符。并作为一个参数传入。

  1. console.log(sum(...values)); //返回25
  2. console.log(sum(-1,-2,...values)); //返回22
  3. console.log(sum(-1,-2,...values,2)); //返回24

对于arguments而言,它并不知道扩展操作符的存在,而是按照调用函数时传入的参数接收每一个值

10.6.2 收集参数 对象方法reduce()

在构思函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组。有点类似于arguments,只不过收集参数的结果会得到一个Array对象的实例

sample 1 利用reduce()方法来进行累加

  1. let sum=[1,3,5,7,9].reduce(function(x,y){return x+y});
  2. //简写
  3. let sum=[1,3,5,7,9].reduce((x,y)=>x+y);
  4. // 返回25,

10.7 函数声明与函数表达式

  1. let sum=function(a,b){
  2. return a+b;
  3. }
  4. function sum(a,b){
  5. return a+b;
  6. }

10.8 函数作为值

因为在JS中函数名就是变量,所以函数可以用在任何使用变量的地方。这意味着可以把函数当做参数传递给另一个函数,也可以在函数中返回一个函数。

1、把函数作为参数

  1. let f1=function(){
  2. return 1;
  3. }
  4. let f2=function(){
  5. return 2;
  6. }
  7. let fun=function(f1,f2){
  8. for(let i=0;i<arguments.length;i++){
  9. console.log(arguments[i]);
  10. }
  11. }
  12. fun(f1,f2); //返回了 f1 和 f2

2、返回一个函数

把函数返回

  1. function retsum(){
  2. return function(){
  3. let sum=0;
  4. for(let i=0;i<arguments;i++){
  5. sum+=arguments[i];
  6. }
  7. return sum;
  8. }
  9. }
  10. let allsum=retsum();
  11. let vals=[1,3,5,7,9];
  12. console.log(allsum(...vals)); //返回 25

10.9 函数内部

10.9.1 arguments 略过

10.9.2 this

  1. const obj={
  2. pro:42,
  3. func:function(val){
  4. return this.pro;
  5. }
  6. }
  7. console.log(obj.func()); //返回42

MDN:

全局上下文

无论是在严格模式下还是全局模式下,this都指向window

  1. var a=10010; //这里var 才能定义全局作用域,let只能定义局部作用域
  2. c=10086;
  3. const b={
  4. a:11111;
  5. }
  6. console.log(this.a); //返回10010
  7. console.log(window.a); //返回10010
  8. console.log(this.b); //undefined
  9. console.log(this.c); //返回10086

只有var 能定义全局作用域下的变量,let和const只能定义局部作用域

函数上下文

在函数内部,this的值取决于函数被调用的方式

  1. let f1=function(){
  2. return this;
  3. }
  4. console.log(f1===window); //返回 true

在严格模式下

  1. "use strict"; //在严格模式下
  2. let f1=function(){
  3. return this;
  4. }
  5. console.log(f1()===window); //返回false

如果想要把this的值从一个环境传入另一个环境,就需要用到call和apply方法

对象方法call

call()方法是使用一个指定的this值和单独给出一个或多个参数来调用一个函数

这个方法和apply类似,apply接收的是一个或者多个参数,而call()接收的是一个参数列表

  1. function Product(name,price){
  2. this.name=name;
  3. this.price=price;
  4. }
  5. function Food(name,price){
  6. Product.call(this,name,price);
  7. this.type="小吃";
  8. }
  9. let cdf=new Food("臭豆腐",15);
  10. console.log(cdf.name); //返回臭豆腐
  11. console.log(cdf); //返回 Food{ name:"臭豆腐",price:"15",type:"小吃"}

sample 使用call 方法调用父构造函数

  1. function Product(name,price){
  2. this.name=name;
  3. this.price=price;
  4. }
  5. function Food(name,price){
  6. Product.call(this,name,price);
  7. this.type="食物";
  8. }
  9. function Toy(name,price){
  10. Product.call(this,name,price);
  11. this.type="玩具";
  12. }
  13. let cdf=new Food("臭豆腐",15);
  14. let fun=new Toy("变形金刚",40);

10.10 函数属性和方法

前面提到过,ECMAScript中的函数是对象,因此有属性和方法。每个函数都有两个属性length和prototype,其中,length属性保存函数定义的命名参数个数。

prototype属性

prototype属性是ECMAScript中最有趣的部分。prototype是保存引用类型所有实例方法的地方。

意味着,toString()、valueOf()等方法实际上都保存在prototype上

function还有两个函数,一个是apply()和call()这两个方法都会指定this值来调用函数。

sample\apply()函数

  1. let nums=[1,3,5,7,9];
  2. function sum(num1,num2){
  3. return num1+num2;
  4. }
  5. function callSum1(num1,num2){
  6. return sum.apply(this,arguments); //传入 arguments对象
  7. }
  8. function callSum2(num1,num2){
  9. return sum.apply(this,[num1,num2]);
  10. }
  11. console.log(callSum1(10,10)); //返回 20
  12. console.log(callSum2(100,200)); //返回 300
  13. function plu(num){
  14. let ret=0;
  15. for(let=0;i<num.length;i++){
  16. ret+=num[i];
  17. }
  18. return ret;
  19. }
  20. nums.push(55);
  21. function callSum3(num){
  22. return plu.apply(this,arguments);
  23. }
  24. function callSum4(num){
  25. return plu.apply(this,[num]);
  26. }
  27. console.log(callSum3(nums)); //返回 80
  28. console.log(callSum4(nums)); //返回 80
  29. console.log(Math.max.apply(null,nums)); //返回 55
  30. console.log(Math.min.apply(null,nums)); //返回 1
  31. console.log(plu.apply(null,[nums])); //返回 80

注意,apply(this,[nums]) [nums]为一个类数组的参数,可以是arguments可以是数组

apply()和call()真正强大的地方并不是给函数传参。而是控制函数调用上下文,即函数体内this值得能力

Sample控制函数的参数调用

  1. window.color="red";
  2. let p={
  3. color:"pink",
  4. }
  5. let b={
  6. color:'black'
  7. }
  8. function sayColor(){
  9. return this.color;
  10. }
  11. sayColor(); //返回red
  12. sayColor.call(this); //返回 red
  13. sayColor.call(window); //返回 red
  14. sayColor.call(p); //返回 pink
  15. sayColor.call(b); //返回 black

bind方法

bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数作为新函数的参数供调用。

  1. const test={
  2. name:"JackMa",
  3. getName:function(){
  4. return this.name;
  5. }
  6. }
  7. const notGetX=test.getName;
  8. console.log(notGetX()); //返回 undefined
  9. const GetX=notGetX.bind(test);
  10. console.log(GetX()); //返回42

bind()最简单的用法是创建一个函数,无论怎样调用,这个函数都有同样的this值。JavaScript新手经常犯的错误是将一个方法从对象中拿出来,然后再调用。期望方法中的this还是原来的对象。如果不做特殊处理,则会丢失

10.11 函数表达式

函数表达式包括函数声明和函数表达式

MDN:函数在JS中是头等公民,它和其它对象一样具有属性和方法,简而言之函数是Function对象的实例

sample 在FireFox中打开

  1. let fun=function(x,y){
  2. return x+y;
  3. }
  4. console.log(fun.constructor); //fun的constructor指向fun的构造函数,返回的是Function

形参和实参

函数定义

语法:

  1. function FUNC([parms,[parms]]){statements};

FUNC 函数名,parms参数,statements组成函数体的声明语句

函数表达式