对function函数的解析

函数

概念

  • 把一个相对独立的完整功能的代码块封装起来

  • 使程序变得更剪短清晰

  • 有利于程序维护

  • 可以提高程序开发效率

  • 提高代码复用性

匿名函数

  • 函数名部分可以不写,被当做匿名函数,函数为匿名函数的时候可以自调用


<script>
  (function () {
    console.log("自调用成功")
  })
</script>

形参实参

  • 在函数声明时候写的参数,形式参数,简称形参。位于函数内部相当于局部变量。

  • 在函数调用的时候,往里面传入的参数,实际参数,简称实参。

  • 一一对应接收,可以有多个

  • arguments:天然存在于函数里,当前函数执行时,传入实参的集合列表,是一个类数组(伪数组)。具有数组的长度和下标,但是没有数组的方法。适合在参数个数不确定的情况下使用。

函数定义

函数声明方式


<script>
  function fn1(a,b) {
    return a+b;
  }
</script>

函数表达式方式


<script>
  var fn2=function(a,b){
    return a+b;
  }
</script>

全局变量和局部变量

  • 函数内部或外部,赋值表达式(没有var),默认在全局生成一个隐式的全局变量

    扫描二维码关注公众号,回复: 1485497 查看本文章
  • 防止两个函数相互影响,各自加各自的形参,此时形参相当于局部变量。

  • 全局变量在函数外部能用,在函数内部也可以调用

  • 局部变量在外部使用会报错,有自己的局部作用域

递归

  • 函数在自身中调用自身,一定要加条件在某一刻停止递归,否则会变成死递归


<script>
  var i=0;
  function fn1() {
    console.log("fn1");
    i++;
    if(i<10) {
      fn1();
    }
  }
  fn1();
</script>

回调

  • 把一个函数当做参数传入另一个函数内部 这个参数函数就是回调函数


<script>
  var num=10;
  var fn=function() {
    console.log("fn");
  }
  function fn1(a) {
    a();
  }
  fn1(fn);
</script>

三个重要内容

arguments

  • 是函数调用的时候实参的集合,伪数组

  • 实际应用于参数个数不确定的时候

return

  • 可以返回任何数据类型;不明写的默认返回undefined;返回返回值之后,后续代码不执行,直接终止函数。

  • 返回值需要接收

this

  • 指向很重要

函数进阶

函数的定义方式

函数的声明

  • 函数的声明function foo ( ) {}

  • 预解析的时候,同名函数和变量以函数为准


<script>
  console.log(typeof fn) // function
  var fn = 123
  function fn() {
    console.log('hello')
  }
</script>
  • 执行的时候对 fn 重新赋值,因为函数 fn 已经被解析过,所以这里不再执行,所以最终输出结果是 number


<script>
  var fn=123
  function fn() {
    console.log('hello')
  }
  console.log(typeof fn)
</script>

函数的表达式

  • 函数的表达式 var foo=function ( ) {}

  • 函数表达式类似于普通变量赋值,函数


<script>
  console.log(typeof fn)
  var fn =function() {
    console.log('hello')
  }
</script>

函数声明与函数表达式的异同

  • 函数声明必须有名字

  • 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用

  • 函数表达式类似于变量赋值

  • 函数表达式可以没有名字,例如匿名函数

  • 函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用

  • 拓展;在 JavaScript 中没有块作用域,花括号就是块,if 块中声明的变量成员都存在于执行 if 语句所处的作用域环境,这里的代码一些比较老的浏览器会先进行函数提升,下面的语句,只要后面调用 fn1() fn2() 在 IE10 及以下的浏览器中两个函数都会执行


<script>
  var condition=true
  if(condition) {
    function fn1 () {
      console.log('hello')
    }
  }else {
    function fn2 () {
      console.log('world')
    }
  }
</script>
  • 对于上面的方式,使用函数表达式来处理就可以了,因为函数表达式没有函数提升

new Function方式

  • 函数表达式或者函数声明,都是 Function 的实例,类似于{}和 new Object()

  • 这种写法很少用,因为它的执行效率比较低,因为它是在执行阶段根据传递的字符串来创建函数,所以效率很低

  • 函数的调用方式

    • 普通函数

    • 构造函数

    • 对象方法

  • 函数也是对象

    • 所有函数都是Function的实例

    • Function的实例是Function自己

call、apply、bind

call

  • fun.call(thisArg[,arg1[,arg2[,...]]])

    • thisArg在fun函数运行时指定的this值

    • 如果制定了null或者undefined则内部this指向window

    • arg1,arg2指定的参数列表

  • 函数的call方法可以实现改变内部this的指向,然后调用函数

  • 实例

    • 定义一个数组

    • 定义一个函数handle,传入参数i

    • 功能是打印输出键值对

    • 循环遍历对象数组

    • 循环中自调用这个函数

    • 用call指定this指向animal的第i项

    • 把i当参数的原因是i会时时改变,如果不传入当参数,只会得出一个最终结果

    
    <script>
      var animals = [
        { species: 'Lion', name: 'King' },
        { species: 'Whale', name: 'Fail' }
      ];
      var handle = function (i) {
        // this = animals[i]
        this.print = function () {
        console.log('#' + i + ' ' + this.species + ': ' + this.name);
        }
      }
      for (var i = 0; i < animals.length; i++) {
        ;(function (i) {
          this.print = function () {
            console.log('#' + i + ' ' + this.species + ': ' + this.name);
          }
        }).call(animals[i], i)
      }
      console.log(i)
    </script>
  • call方法的其他使用场景

    • 这是一个伪数组,伪数组不能用forEach,但是可以借用call

    
    <script>
      var obj={
        0:'a',
        1:'b',
        2:'c',
        length:3
      }
      Array.prototype.forEach.call(obj,function(item,index) {
        console.log(item,index)
      })
    </script>
    • 这里获取的divs是一个伪数组对象,要调用forEach的方法,可以先用一个空数组调用,然后指向这个伪数组就可以了

    • getElementsByTagName获取的没有forEach方法

    • querySelectorAll 的方法原型中有forEach方法

    
    <script>
      var divs=document.getElementsByTagName('div')
      ;[].forEach.call(divs,function(item) {
        console.log(item)
      })
    </script>

apply

  • fun.apply(thisArg,[argsArray])

    • thisArg在fun函数运行时指定的this值,如果制定了null或者undefined则内部this指向window

    • argsArray包含多个参数的数组

  • apply必须传数组,会把数组给展开,然后一一对应地传递给fn方法,只能处理一个数组参数,其他的不处理

bind

  • bind( )函数会创建一个新的函数(绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体。

  • 当目标函数被调用的时候this值绑定到bind( )的第一个参数,该参数不能被重写。

  • 绑定函数被调用时,bind( )也接收预设的参数提供给原函数。

  • 一个绑定的函数也能使用new操作符创建对象,这种行为就像把原函数当成构造器。

  • 提供的this值被忽略,同时调用的参数被提供给模拟函数

  • fun.bind(thisArg[,arg1[,arg2[,...]]])

  • 返回值:由指定的this值和初始化参数改造的原函数拷贝

  • call和apply的方法是不绑定的,直接调用

  • bind方法不调用,而是返回一个指定this环境的新的函数

总结

  • call和apply特性一样

    • 都是用来调用函数,而且是立即调用。可以在调用函数的同时,通过第一个参数指定函数内部this的指向。如果第一个参数指定了null或者undefined则内部this指向window。

    • call( )方法接收的是若干个参数的列表,参数之间用逗号隔开。

    • apply( )方法接收的事一个包含多个参数的数组

  • bind

    • 可以用来指定内部this的指向,然后生成了一个改变了this指向的新的函数。不会调用。

    • 支持传递参数,bind的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部

  • 函数的其他成员

    • arguments 实参集合

    • caller 函数的调用者

    • length 形参的个数

    • name 函数的名称

高阶函数

函数可以作为参数


<script>
  function eat (callback) {
    setTimeout(function () {
      console.log('吃完了')
      callback()
    },100)
  }
  eat(function () {
    console.log('去唱歌')
  })
</script>

函数可以作为返回值


<script>
  var isArray = generateCheckTypeFn('[object Array]')
  var isObject = generateCheckTypeFn('[object Object]')
  var isString = generateCheckTypeFn('[object String]')
  var isNumber = generateCheckTypeFn('[object Number]')
  function generateCheckTypeFn(type) {
    // 没调用一次 generateCheckTypeFn 内部就要创建一个函数作为返回值
    var checkType = function (obj) {
      return Object.prototype.toString.call(obj) === type
    }
    return checkType
  }
</script>

函数闭包

作用域链

  • 定义三个变量abc在不同的位置,fn1中找a,先在自己身上找,找不到,跳到上一级,fn中找,找不到,跳到全局,找到了。找b和c同理,fn2中找c,自己身上没有,父级fn中没有,全局中也没有,所以会报错。fn2不能跑到fn1中找fn1的变量,这就是作用域链


<script>
  var a=10
  function fn() {
    var b=20
    function fn1() {
      var c=30
      console.log(a+b+c)//60
    }
    function fn2() {
      var d=40
      console.log(c+d)//报错
    }
    fn1()
    fn2()
  }
  fn()
</script>
  • 函数的作用域链以函数定义时的位置为准,不是调用时

闭包

  • 就是能够读取其他函数内部变量的函数,由于在JavaScript语言中,只有函数内部的子函数才能读取变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”,所以在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

  • 在getCount中获取外部的count,之后在setCount中进行设置,之后再输出的时候,count已经发生了变化


<script>
  function fn () {
    var count=0
    return {
      getCount:function () {
        console.log(count)
      },
      setCount:function () {
        count++
      }
    }
  }
  var fns=fn()
  fns.getCount()//0
  fns.setCount()
  fns.getCount()//1
</script>
  • 在函数内部,getFoo就是一个闭包环境(闭包函数)


<script>
  var getFoo=function() {
    return foo
  }
</script>
  • 闭包函数的核心就是作用域链

实例

  • 定义一个数组,循环遍历这个数组,for循环中i数全局变量,自执行匿名函数中包含一个闭包函数


<script>
  var arr = [10, 20, 30]
  for (var i = 0; i < arr.length; i++) {
    arr[i] = (function (i) {
      return function () {
        console.log(i)//0 1 2
      }
    })(i)
  }
  arr.forEach(function (item, index) {
    item()
  })
</script>

实例

  • 定时器永远会放到普通代码之后才执行

  • for循环的时候不会等定时器,直接进行下一次循环


<script>
  console.log(111)
  for (var i = 0; i < 3; i++) {
    setTimeout((function (i) {
      return function () {
        console.log(i)
      }
    })(i), 0)
  }
  console.log(222)
</script>

沙箱

  • 利用匿名函数自执行保护内部成员不会被外部修改或访问,但是在函数内部可以访问修改


<script>
  ;(function () {
    var age = 3
    function F () {}
    F.prototype.getAge = function () {
      return age
    }
    F.prototype.setAge = function (val) {
      if (val < 18) {
        return console.error('age 不能小于18岁')
      }
      age = val
    }
    window.F = F
  })()
  var f = new F()
  console.log(f.getAge())
</script>

思考

  • 函数内部定义的函数,this指向window

  • inner是在函数内部定义的,有自己的指向,是window,所以不走作用域链


<script>
  var name = "The Window";
  var object = {
    name: "My Object",
    getNameFunc: function () {
      var inner = function () {
        console.log(this.name)//The Window
      }
      return inner
      return function () {
        return this.name;
      };
    }
  };
  console.log(object.getNameFunc()())//undefined
</script>

思考


<script>
  var name = "The Window";
  var object = {
    name: "My Object",
    getNameFunc: function () {
      var that = this;
        // 除了 this
        // 其它成员,按照定义时所处的作用域环境沿着作用域链查找
      return function () {
        return that.name;
      };
    }
  };
  console.log(object.getNameFunc()())//My Object
</script>

递归

DOM树


<body>
<div>
  132
  <div>
    456
    <p>123</p>
    <p>123</p>
    <p>123</p>
    <span>dsadsa</span>
  </div>
  <div>
    <div>
      <div>
        <p>1</p>
        <p>2</p>
        <p>3</p>
        <p>4</p>
      </div>
    </div>
  </div>
</div>
<script>
  // 把当前网页中所有的 DOM 节点元素获取到放到一个数组中
  // 1. 先获取根节点
  // 2. 然后拿到根节点的子节点
  var rootNode = document.getElementsByTagName('html')[0]
  var domTree = []
  function getDomTree(children) {
    for (var i = 0; i < children.length; i++) {
      var element = children[i]
      domTree.push(element)
      if (element.children) {
        getDomTree(element.children)
      }
      // 如果 element 还有 children 则继续遍历 element.children
    }
  }
  getDomTree(rootNode.children)
  console.log(domTree)
</script>
</body>

尾调用

  • 尾调用:在一个函数执行的最后一步return调用另一个函数


<script>
  function f(x) {
    return g(x)
  }
  function g(x) {
    return x
  }
  console.log(f(10))
</script>
  • f(x)执行到最后一步,f(x)弹栈,g(x)进来,占用f(x)刚才的位置,相当于替换

  • 下面三种都不是尾调用

    • 第一个,在调用g之后还有赋值操作,不属于尾调用

    • 第二个,调用后还有加操作,不属于尾调用

    • 第三个,没有返回,不属于尾调用,相当于下图,返回一个undefined

    
    <script>
      function f(x) {
        let y=g(x);
        return y;
      }
      function f(x) {
        return g(x)+1;
      }
      function f(x) {
        g(x)
      }
    </script>
  • 尾调用和函数调用

    • 函数调用会在内存形成一个调用记录,又称调用帧,保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用就形成一个调用帧。

    • 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

try-catch

  • try-catch用来捕获异常

  • 发生错误之后,try 后面的语句不执行,catch 后面的语句执行,finally 后面的语句执行

  • 没有发生错误,try 后面的语句执行,catch 后面的语句不执行,finally 后面的语句执行

  • catch中传入的参数就是错误e,e.message表示错误短语,e.stack是具体错误堆栈信息,包含具体的消息,文件,行号等

  • Error是内置函数

    • EvalError 创建一个error实例,表示错误的原因,与eval()有关

    • InternalError 创建一个代表 JavaScript 引擎内部错误的异常跑出实例。如:递归太多

    • RangeError 创建一个 error 实例,表示错误的原因:数值变量或参数超出其有效范围

    • ReferenceError 创建一个error实例,表示错误的原因:无效引用

    • SyntaxError 创建一个error实例,表示错误的原因:eval()在解析代码的过程中发生语法错误

    • TypeError 创建一个error实例,表示错误的原因:变量或参数不属于有效类型

    • URIError 创建一个error实例,表示错误的原因,给encodeURI()或decodeURI()传递的参数无效

猜你喜欢

转载自blog.csdn.net/weixin_42154189/article/details/80583057
今日推荐