首页 > 其他分享 >第六篇 引用类型 - 函数 - Function

第六篇 引用类型 - 函数 - Function

时间:2023-03-28 14:26:04浏览次数:39  
标签:Function function log 函数 作用域 参数 引用 console 第六篇

函数 — javascript的第一等公民

函数的多变来源于参数的灵活多变和返回值的多变

普通函数 — 如果参数是一般的数据类型或一般对象,这样的函数就是 通函数

高级函数 — 如果函数的参数时函数,我们称之为 高级函数

便函数 — 如果创建的函数调用另外一部分 (变量和参数已经预置)这样的函数就是便函数

函数的创建

函数申明
 function getApi(params){}
 
 函数申明  
 
    function 是申明中的第一个词;
    
    不可以省略函数名;
    
    函数名提升 => 被定义之前就可以被调用
    
    在主代码流中申明为单独的语句的函数;
    
    重复申明 => 如果一个函数多次申明,后面申明会覆盖前面的申明
    
    函数申明的名称标识符会绑定到自身的作用域中
    
        function getParams(){}
        
        console.log(getParams) => function getParams(){}
函数表达式
 let getApi = function(params){}
 
 (function(params){})
 
 setTimeout(function timer(){},200)
 
 函数表达式
 
     函数表达式可以是匿名的
     
     不能提升 => 定义之后才能调用
     
     函数表达式的名称标识符会绑定在表达式自身的函数中,而不是所在的作用域中
     
         let getParams = function getData(){}
         
         console.log(getData) => ReferenceError: bar is not defined
         
         (function bar(){})
         
         console.log(bar) => ReferenceError: bar is not defined
函数申明和函数表达式主要区别
  1、函数申明提升到顶部
  
      函数申明 可在申明前面调用
      
      函数表达式 只能在创建后调用
  
  2、函数表达式可以立即执行,但函数申明不可以
  
      函数表达式
      var func = function () {console.log('func')} () =>'func'
      
      函数声明
      function func () {console.log('func')} () => 语法错误
      
  3、作用域区别
  
      函数声明:在函数内外均可通过函数名称访问函数
      
      函数表达式: 函数外无法通过函数名称访问函数,只能通过变量名访问。 但函数内部可以通过函数名称访问。
构造函数

这种声明函数的方式非常不直观,内部字符串有安全隐患,几乎无人使用

   下面代码中,Function构造函数接受三个参数(字符串形式),除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数

   const add = new Function(
       'x',
       'y',
       'return x + y'
   );
   
   等价于
   
   function add(x,y){
      return x + y
   }
   
注意:

   你可以传递任意数量的参数给Function构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体
   
   Function构造函数可以不使用new命令,返回结果完全一样

函数的属性

length
  length 属性表示函数希望接收的命名参数的个数
  
     function sum(n1,n2){
        return n1 + n2
     }
     
     console.log(sum.length) => 2
     
     注意: length 统计的是函数的命名参数的数量,不定参数的加入不会影响 length 属性的值
     
     function (n1,n1,...rest){
        return n1 + n2 + rest.reduce((per,next) => pre + next)
     }
     
     console.log(sum.length) => 2
name
  name 属性返回函数实例的名称
  
     function sum(){} 
     
     console.log(sum.name) => sum
     
     let sum = function(){}
     
     console.log(sum.name) => sum
     
     函数申明的 name 对应申明时的 函数名称
     
     匿名函数表达式的 name 对应被赋值该匿名函数的变量的名称
     
    注意: Fucntion.bind() 所创建的函数将会在函数的名称前加上 ‘bound’
     
     function sum() {}
     
     sum.bind({}).name  => "bound foo"
prototype
  Function.prototype 属性存储了 Function 的原型对象
  
  用来给 Function 实例添加公共方法和属性
元属性 new.target
  元属性是指非对象的属性,其可以提供非对象目标的补充信息 如: new

函数的实例方法

apply() call() 是 Function构造函数原型对象上的方法,所有函数 (包括call)都可以调用 call() 和 apply()

使用 apply() 和 call() 调用函数被称为 函数应用。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值

构造函数通常借助 call()、apply() 来完成继承

call() 和 apply() 干的事从本质上讲都是一样的动态的改变this上下文

apply()
  const obj = {
     name:"caixin"
  }
  
  var name = "caiqiran"
  
  function fn() {
      console.log(this.name)
  }
  
  fn() => 'caiqiran'
  
  fn.apply(obj) => 'caixin'
  
  fn.apply(obj) 调用时等价于 直接调用 obj.fn() 如下:
  
     const obj = {
        name: 'caixin'
        fn(){
           console.log(this.name)
        }
     }
call()
 作用 和 效果 同 apply()
 
 call 和 aplly 的第一个参数都是要改变上下文的对象
 
 区别:
 
   call() 方法从第二参数开始接受的是一个参数列表 (可传递引用类型 和 传递值)
   
      Person.call(this,name,sex)
      
   apply() 第二个参数只能传递数组 (只能传递数组)  
      
      let arr = [1,2,3]
      
      Person.apply(this,arr)
bind()
 fn = sum.bind(this,args)

 call() 和 apply() 改变了函数的 this 上下文后便执行该函数
 
 bind() 则是返回改变了上下文后的一个函数。
 
 bind() 的第一个参数为新绑定的上下文,其余参数将作为新函数的参数,供调用时使用, 所以 bind() 不会直接调用,需要手动调用。
 
 let fn = Person.bind(this,params)
 
 fn()
 
 所以 bind() 使用的场景就是当我们想要某一个事件等待某个时刻在开始执行的时候,就可以使用我们的 bind() 方法
 
 如果要立即调用 则:
 
   person.showName.bind(animal)();
应用场景
    1、判断数据类型
    
      Object.prototype.toString.apply([])  //"[object Array]"
    
    2、类数组借助数组方法
    
       如 arguments
    
        function fn(){
           arguments.shift(); => 报错
           Array.prototype.shift.apply(arguments)
        }
      
      fn(1,2,3)
      
     3、继承  
     
       构造函数通常借助 call() 和 apply() 来完成继承
       
       function Person(name,sex){
           this.name = name
           this.sex = sex
           this.permission = ['user','salary']
       }
       
       Person.prototype.say = function() {
           console.log(`{this.name} 说话了`)
       }
       
       function Staff(name,age){
          Person.call(this,name);
          this.age = age;
       }
       
       Staff.prototype.eat = function(){
           console.log("学习啦~~~")
       }
       
       const Caixin = new Staff("蔡鑫",35)
       
       console.log(Caixin)
       
    4、求数组中的最大值和最小值 
    
       var arr = [34,5,3,6,54,6,-67,5,7,6,-8,687];
       
       Math.max.apply(Math, arr); 
       Math.max.call(Math, 34,5,3,6,54,6,-67,5,7,6,-8,687);
       
       Math.min.apply(Math, arr);
       Math.min.call(Math, 34,5,3,6,54,6,-67,5,7,6,-8,687);
toString() 方法

函数的toString方法返回一个字符串,内容是函数的源码

    函数内部的注释也可以返回
    
    function add(a,b){
      // let c = 10
      return a + b
    }
    
    console.log(add.toString) 

函数相关常见概念

函数的多重用途 (Call 和 Construct)
 JavaScript 函数有两种不同的内部方法: [[Call]] 和 [[Construct]]
 
 当通过 new 关键字调用函数时,执行的是 [[Construct]] 函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将 this 绑定到实例上
 
 如果不通过 new 关键字调用函数,则执行 [[Call]] 函数,从而直接执行代码中的函数体
 
 具有 [[Construct]] 方法的函数被统称为构造函数
 
 不是所有函数都有 [[Construct]] 方法,因此不是所有函数都可以通过 new 来调用,例如 箭头函数 就没有 [[Construct]] 方法。
默认参数 (ES6 新增特性)

对于函数的命名参数,如果不显式传值,则其默认值为 undefined

ES6 新增特性可以为参数传入值提供一个默认的初始值

 默认参数对 arguments 对象的影响
 
   如果一个函数使用了默认参数值,则 arguments 对象保持与命名参数分离,改变命名参数不会影响 arguments 对象
   
 默认参数表达式
 
    可以通过函数执行来得到默认参数的值
    
    function getValue(){
       return 5
    }
    
    function add(num1,num2 = getValue()){
       return num1 + num2
    }
    
    console.log(add(1,1)) => 2
    console.log(add(1)) => 6
不定参数 (ES6 新增特性)

在函数的命名参数前添加三个点 (…)就表明这是一个不定参数,该参数为一个数组,包含自它之后传入的所有参数,通过这个数组名即可逐一访问里面的参数。

 function sum(num,...keys){
 
    console.log(keys) => [2,3,4,5,6]
    
    console.log(arguments) => [1,2,3,4,5,6]  
 }
 
 sum(1,2,3,4,5,6)
 console.log(sum.length) => 1
 
 注意: 
 
    函数的 length 属性统计的是函数命名参数的数量,不定参数的加入不会影响 length 属性的值
    
    另外无论是否使用不定参数,arguments 总是包含了所有传入函数的参数
    
    每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾
函数作用域与执行环境
函数作用域
 函数作用域是最常见的作用域单元
 
 函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)
 
 每个函数都是对象,它同其他对象一样,拥有可编程访问的属性,和一系列不能通过代码访问的仅供 JavaScript 引擎存取的内部属性
 
 其中一个内部属性是 [[Scope]],它包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定哪些数据能被函数访问
 
 可以像理解对象的原型链 [[Prototype]]一样去理解函数的作用域链 [[Scope]]
 
 原型链的尽头是 Object.prototype,作用域链的尽头是全局作用域
函数执行环境
 执行此函数时会创建一个称为执行环境(execution context,也称为执行上下文)的内部对象
 
 一个执行环境定义了一个函数执行时的环境
 
 函数每次执行时对应的执行环境都是独一无二的,所以多次调用同一个函数就会导致创建多个执行环境
 
 当函数执行完毕,执行环境就被销毁
 
 每个执行环境都有自己的作用域链,用于解析标识符
 
 当执行环境被创建时,他的作用域链初始化为当前运行函数的 [[Scope]] 属性中的对象
 
 这些值按照它们出现的顺序被复制到执行环境的作用域链中
 
 这个过程一旦完成,一个被称为“活动对象(activation object)”的新对象就为执行环境创建好了
 
 活动对象作为函数运行时的变量对象,包含了所有局部变量、命名参数、参数集合以及 this
 
 然后此对象被推入作用域链的最前端。当执行环境被销毁,活动对象也随之销毁

闭包

函数执行完毕后仍能保持对函数内部作用域的引用的行为,就是闭包

使用闭包可以在JS中模仿块级作用域

闭包可以用于对象中创建私有变量

闭包有权访问包含函数内部的所有变量

作用域闭包
  function sum(){
     let id = 1
     alert(id)
  }
  
  function bar(){
     let id = 2
     document.getElementById('btn').onclick = function handle(){
      alert(id)
     }
  }
  
   函数 bar 执行完毕后,由于 onclick 事件绑定的函数 handle 保持着对变量 id 的引用,导致 bar() 的活动对象(内部作用域)并未随 bar() 的执行环境的销毁一起被销毁
   
   function foo(){
     let id = 3
     return function bar(){
        console.log(id)
     }
   }
   
   let baz = foo()
   
   baz() => 3
   
     这里 foo 执行完毕后,我们仍然可以在外部通过 foo() 的返回值 bar 来访问 foo() 的内部作用域,这样就形成了一个闭包
     
     拜 bar 所声明的位置所赐, 它拥有涵盖 foo() 内部作用域的闭包, 使得该作用域能够一直存活, 以供 bar() 在之后任何时间进行引用
     
     bar() 依然持有对该作用域的引用, 而这个引用就叫作闭包
     
 说明:
 
    本质上无论何时何地, 如果将函数(访问它们各自的词法作用域) 当作第一级的值类型并到处传递, 你就会看到闭包在这些函数中的应用
    
    在定时器、 事件监听器、Ajax 请求、 跨窗口通信、 Web Workers 或者任何其他的异步(或者同步) 任务中, 只要使用了回调函数, 实际上就是在使用闭包!
    
   for(var i = 1;i <= 3; i++) {
      console.log('i:'+i)
      setTimeout(() => console.log(i), i*1000)
   }
模块

模块是一个提供接口却隐藏状态与实现的函数或对象

我们可以用函数和闭包来构造模块

 普通词法查找
 
       function foo(){
          console.log(a) => 2
       }
       
       function bar(){
          var a = 3
          foo()
       }
       
       var a = 2
       
       bar()
       
  闭包查找
  
       function bar(){
          var a = 3
          return function foo(){
            console.log(a) => 3
          }
       } 
       
       var a = 2
       
       bar()

模块的两个主要特征

    为创建内部作用域而创建了一个包装函数
    
    包装函数的返回值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包

this 词法

this 提供了一种更优雅的方式来隐式“传递” 一个对象引用, 因此可以将 API 设计得更加简洁并且易于复用

this 是在运行时进行绑定的, 并不是在编写时绑定,它的上下文取决于函数调用时的各种条件

this 的绑定和函数声明的位置没有任何关系, 只取决于函数的调用方式

当一个函数被执行时会创建一个称为执行环境的对象。每个环境都有自己的作用域链。每个环境也都有自己的 this

此外这个执行环境还包含函函数在哪里调用(调用栈)、函数的调用方法、传入的参数等信息

this 的四种绑定规则
1、默认绑定

  独立函数调用时在 非 strict mode 下默认绑定到全局对象,strict mode 则为 undefined
  
2、隐式绑定

  当函数引用有上下文对象时, 隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。隐式绑定可能发生绑定丢失
  
3、显示绑定

   通过 call()、apply() 或者 bind() 绑定
   
4、new 绑定

   使用 new 来调用函数, 或者说发生构造函数调用时,会自动执行下面的操作 ( new 发生了什么? )
   
     创建(或者说构造) 一个全新的对象
   
     这个新对象会被执行 [[ 原型 ]] 连接。(即将新对象的 [[ProtoType]] 链关联至构造函数的 prototype 属性指向的对象,这个对象通常被称为原型)
     
     这个新对象会绑定到函数调用的 this
     
     如果函数没有返回其他对象, 那么 new 表达式中的函数调用会自动返回这个新对象
     
 注意:
 
       new 绑定会修改显示绑定的 this
     
       显式绑定时 传入 null 或 undefined 会使用默认绑定绑定到全局对象
     
       所以最好传入个空对象,例如 someFunc.call(Object.creat(null))
     
       间接引用函数(最容易发生在赋值时,尤其注意函数作为参数赋值时),此时使用的是目标函数的引用,没有上下文对象,会应用默认绑定
     
       箭头函数的 this 会使用词法作用域规则在当前作用域查找 this,不会应用 this 绑定规则

函数的常用分类

普通函数
 有函数名,参数,返回值,同名覆盖有函数名,参数,返回值,同名覆盖

 function add(a,b){
    return a + b
 }
匿名函数 (函数表达式 拉姆达函数)

没有函数名,可以把函数赋值给变量和函数,或者作为回调函数使用

非常特殊的就是 立即执行函数 和 闭包

立即执行函数
 (function(){
    console.log(1)
 })()
闭包函数
 var func = (fucntion(){
    var i = 1;
    return function(){
       console.log(i)
    }
 })()
高级函数
 高级函数就是可以把函数作为参数和返回值的函数。如上面的闭包
 
 ECMAScript中也提供大量的高级函数如forEach(), every(), some(), reduce()等等
便函数
箭头函数 (ES6 新增特性)

没有 this、super、arguments 和 new.target 绑定 箭头函数内部的这些值直接取自定义时的外围非箭头函数

所以它不适合做方法函数,构造函数,也不适合用 call(), apply() 改变this

不能通过 new 关键字调用 箭头函数没有 [[Construct]] 方法,所以不能被用作构造函数,如果通过 new 关键字调用箭头函数,程序会抛出错误

没有原型 由于不可以通过 new 关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在 prototype 这个属性

不可以改变 this 的绑定 函数内部的 this 值不可被改变,在函数声明周期内始终保持一致

不支持 arguments 对象 箭头函数没有 arguments 绑定,所以你必须通过命名参数和剩余参数这两种形式访问函数的参数

不支持重复的命名参数 无论是在严格还是非严格模式下,箭头函数都不支持重复的命名参数;而在传统函数的规定中,只有在严格模式下才不能有重复的命名参数

箭头函数 特点就是更短,和解决匿名函数中this指向全局作用域的问题

包括回调函数在内的所有使用匿名函数表达式的地方都适合使用箭头函数来改写

 var name = "caixin"
 setTimeout(() => {
    console.log(this.name) => "caixin"
 })
 
 const arr = [1,4,2,3,7,5]
 
 cosnt result = arr.sort((a,b) => a - b)
 
 console.log(result) => [1,2,3,4,5,7]
构造函数与类
 const arr = new Array(2,3,4)
函数柯里化

柯里化,也常译为局部套用,是把多参函数转换为一系列单参函数并进行调用的技术

 柯里化允许我们把函数与传递给它的参数相结合,产生出一个新的函数
递归函数

递归可以把复杂的算法变的简单

  递归算法实现阶乘函数
  
  function sum(n){
    return n == 0 ? 1 : n * sum(n-1)
  }
尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归

尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身

做到这一点的方法,就是把所有用到的内部变量改写成函数的参数

在ECMAScript 6中,如果没有停止条件尾递归代码代码可以一直执行下去。所以具有停止递归的边界条件非常重要

 function fibonacci(n, ac1 = 1, ac2 = 1) {
 
     if(n < 2){return ac2}
     
     return fibonacci(n - 1, ac2, ac1 + ac2)
  }
  
  fibonacci(100)  => 573147844013817200000
防抖函数

函数去抖背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行

  函数防抖的应用场景
  
     1、每次 resize/scroll 触发统计事件
     
     2、文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)
     
     function debounce(fn,delay){
        var timer = null;
        return runction(...args){
           var context = this
           clearTimout(timer);
           timer = setTimeout(function(){
              fn.apply(context,args)
           },delay)
        }
     }
     
     var foo = function(){
        cosnole.log('run')
     }
     
     window.onscroll = debounce(foo,150)
节流函数

函数节流能使得连续的函数执行,变为固定时间段间断地执行

例如给 onscroll 这种短时间连续执行的事件的执行函数加了 100ms 的函数节流,则用户滚动每 100ms,执行函数都会执行1次

函数节流一般作为函数去抖的增强功能

   函数节流的应用场景:
    
        DOM 元素的拖拽功能实现(mousemove)
        
        射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
        
        计算鼠标移动的距离(mousemove)
        
        Canvas 模拟画板功能(mousemove)
        
        搜索联想(keyup)
        
        监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次
防抖函数 和 节流函数 的区别
防抖:

    防抖的主要作用时为了防止在少量时间内,每一次相同的行为都执行事情,即防止无意的抖动行为,其主要的特点是在一个相同连续的行为中,只执行最后一次行为所监听的事件。
    
    如提交表单的按钮,如果在同一时间大量点击按钮,我们使用防抖技术,只执行最后一次的点击事件,这个可以节约大量的api请求。
    
节流:    
    
    节流的主要作用时为了防止在多中重复的行为中,每一次相同的行为都执行事情,虽然这点和防抖很像,但是节流的主要特点是在一个相同连续的行为中,每隔一个时间,只执行一次行为所监听的事件。
    
    如鼠标移动小球的行为,如果没有使用节流,每次移动都会触发鼠标移动事件,而使用节流,每隔20ms才触发事件,能够达到差不多的效果,但是触发事件的数量少了很多

标签:Function,function,log,函数,作用域,参数,引用,console,第六篇
From: https://www.cnblogs.com/caix-1987/p/17264977.html

相关文章

  • 对象引用对于非静态的字段、方法或属性“HttpContext.User”是必需的
    控制器内的HttpContext是从ControllerBase继承的属性,如果在控制器外使用HttpContext只是一个类,因此只能访问它的静态成员。为了访问它,您必须通过或沿线路注入HttpContext......
  • 引用和指针的区别
    1、定义和性质不同1、指针是一个变量,存储的地址,指向内存单元2、引用是变量的别名,跟原始变量是同一块内存inta=10;int*p=&a;//p是指针,&在此是取地址运算in......
  • 引用
    一、引用的基本概念引用变量是C++新增的符合类型。引用时已定义的变量名。引用的主要用途是作用函数的形参和返回值。声明/创建引用的语法:数据类型&......
  • 引用的: 本篇主要和大家分享有关NGS检测体细胞突变时判断DNA污染的方法。
     来自https://mp.weixin.qq.com/s?src=11&timestamp=1679887356&ver=4431&signature=FISeM4PGVyZ-AGQmG7Aib6zDiSV7B1TvaBgc41rLNE-ofmYD1pw2Nu46xLzgY5AsBI261SsCp*MlRJ......
  • 密码学SAT入门文献03——Encoding Cryptographic Functions to SAT Using TRANSALG Sy
    AlgebraicandLogicSolvingMethodsforCryptanalysis AbstractInthispaperweproposethetechnologyforconstructingpropositionalencodingsofdiscr......
  • 方法引用
    方法引用1、概述顾名思义:方法就是我们之前学习的方法引用就是把已经有的方法拿过来用,把他当做函数式接口中抽象方法的方法体(引用的方法,可以是Java已经写好的,也可以是一......
  • js中的按值和按引用
    先定义两种数据类型组合1.简单类型:包含数值,boolen,string等2.复杂类型:包含对象(object),数组等对于给函数传递参数1为按值传递2为传递共享引用(与按引用传递不同)对于赋......
  • SAP入门技术分享七:Field Symbol和数据引用
    FieldSymbol和数据引用1.概要2.定义FieldSymbol(1)定义-GenericTYPE(泛型)(2)定义-FullyTYPE(全类型)3.分配FieldSymbol(1)Assign语句的基本结构(2)将结构体字段分配到字段符号......
  • AndroidStudio引用第三方so库的正确姿势
    以项目名称app1为例:1、把so文件复制到\app1\app\libs\文件夹下,但是要注意,so文件是放在对应的平台文件夹之下(如arm64-v8a,armeabi-v7a,x86,x86_64),这点非常重要,否则不能成功......
  • flink -udf函数(AggregateFunction)报错
    编写自定义函数AggregateFunction时,报错如下: 最终发现是因为导包错误:之后上网查了,发现这两个算子的应用场景不同:......